Skip to content

Commit de5c7b1

Browse files
JLHwungnicolo-ribaudo
authored andcommitted
Parse destructuring private fields (#13931)
* feat: parse destructuring private * add test cases * fix: register private name on destructuring * add typings * add syntax plugin * add generator test case * add syntax plugin to preset-stage-2 * fix flow errors * address review comments * fix: use private name in toAssignable * test: add case with undefined refExpressionErrors
1 parent 27c0137 commit de5c7b1

61 files changed

Lines changed: 1996 additions & 20 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class C {
2+
#x;
3+
m() {
4+
#x in (#x in this);
5+
var { #x: x } = this;
6+
}
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"plugins": ["destructuringPrivate"]
3+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class C {
2+
#x;
3+
4+
m() {
5+
#x in (#x in this);
6+
var {
7+
#x: x
8+
} = this;
9+
}
10+
11+
}

packages/babel-parser/src/parser/expression.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import { cloneIdentifier } from "./node";
7878

7979
/*::
8080
import type { SourceType } from "../options";
81+
declare var invariant;
8182
*/
8283

8384
const invalidHackPipeBodies = new Map([
@@ -329,6 +330,14 @@ export default class ExpressionParser extends LValParser {
329330
) {
330331
refExpressionErrors.shorthandAssignLoc = null; // reset because shorthand default was used correctly
331332
}
333+
if (
334+
refExpressionErrors.privateKeyLoc != null &&
335+
// $FlowIgnore[incompatible-type] We know this exists, so it can't be undefined.
336+
indexes.get(refExpressionErrors.privateKeyLoc) >= startPos
337+
) {
338+
this.checkDestructuringPrivate(refExpressionErrors);
339+
refExpressionErrors.privateKeyLoc = null; // reset because `({ #x: x })` is an assignable pattern
340+
}
332341
} else {
333342
node.left = left;
334343
}
@@ -843,13 +852,14 @@ export default class ExpressionParser extends LValParser {
843852

844853
let node = this.startNodeAt(startPos, startLoc);
845854
node.callee = base;
855+
const { maybeAsyncArrow, optionalChainMember } = state;
846856

847-
if (state.maybeAsyncArrow) {
857+
if (maybeAsyncArrow) {
848858
this.expressionScope.enter(newAsyncArrowScope());
849859
refExpressionErrors = new ExpressionErrors();
850860
}
851861

852-
if (state.optionalChainMember) {
862+
if (optionalChainMember) {
853863
node.optional = optional;
854864
}
855865

@@ -864,18 +874,20 @@ export default class ExpressionParser extends LValParser {
864874
refExpressionErrors,
865875
);
866876
}
867-
this.finishCallExpression(node, state.optionalChainMember);
877+
this.finishCallExpression(node, optionalChainMember);
868878

869-
if (state.maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) {
879+
if (maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) {
880+
/*:: invariant(refExpressionErrors != null) */
870881
state.stop = true;
882+
this.checkDestructuringPrivate(refExpressionErrors);
871883
this.expressionScope.validateAsPattern();
872884
this.expressionScope.exit();
873885
node = this.parseAsyncArrowFromCallExpression(
874886
this.startNodeAt(startPos, startLoc),
875887
node,
876888
);
877889
} else {
878-
if (state.maybeAsyncArrow) {
890+
if (maybeAsyncArrow) {
879891
this.checkExpressionErrors(refExpressionErrors, true);
880892
this.expressionScope.exit();
881893
}
@@ -1738,6 +1750,7 @@ export default class ExpressionParser extends LValParser {
17381750
this.shouldParseArrow(exprList) &&
17391751
(arrowNode = this.parseArrow(arrowNode))
17401752
) {
1753+
this.checkDestructuringPrivate(refExpressionErrors);
17411754
this.expressionScope.validateAsPattern();
17421755
this.expressionScope.exit();
17431756
this.parseArrowExpression(arrowNode, exprList, false);
@@ -2040,7 +2053,7 @@ export default class ExpressionParser extends LValParser {
20402053
let isGenerator = this.eat(tt.star);
20412054
this.parsePropertyNamePrefixOperator(prop);
20422055
const containsEsc = this.state.containsEsc;
2043-
const key = this.parsePropertyName(prop);
2056+
const key = this.parsePropertyName(prop, refExpressionErrors);
20442057

20452058
if (!isGenerator && !containsEsc && this.maybeAsyncOrAccessorProp(prop)) {
20462059
const keyName = key.name;
@@ -2245,8 +2258,12 @@ export default class ExpressionParser extends LValParser {
22452258
return node;
22462259
}
22472260

2261+
// https://tc39.es/ecma262/#prod-PropertyName
2262+
// when refExpressionErrors presents, it will parse private name
2263+
// and record the position of the first private name
22482264
parsePropertyName(
22492265
prop: N.ObjectOrClassMember | N.ClassMember | N.TsNamedTypeElementBase,
2266+
refExpressionErrors?: ?ExpressionErrors,
22502267
): N.Expression | N.Identifier {
22512268
if (this.eat(tt.bracketL)) {
22522269
(prop: $FlowSubtype<N.ObjectOrClassMember>).computed = true;
@@ -2275,10 +2292,16 @@ export default class ExpressionParser extends LValParser {
22752292
break;
22762293
case tt.privateName: {
22772294
// the class private key has been handled in parseClassElementName
2278-
this.raise(Errors.UnexpectedPrivateField, {
2279-
// FIXME: explain
2280-
at: createPositionWithColumnOffset(this.state.startLoc, 1),
2281-
});
2295+
const privateKeyLoc = this.state.startLoc;
2296+
if (refExpressionErrors != null) {
2297+
if (refExpressionErrors.privateKeyLoc === null) {
2298+
refExpressionErrors.privateKeyLoc = privateKeyLoc;
2299+
}
2300+
} else {
2301+
this.raise(Errors.UnexpectedPrivateField, {
2302+
at: privateKeyLoc,
2303+
});
2304+
}
22822305
key = this.parsePrivateName();
22832306
break;
22842307
}

packages/babel-parser/src/parser/lval.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
/*:: ObjectMember, */
1717
/*:: TsNamedTypeElementBase, */
1818
/*:: Identifier, */
19+
/*:: PrivateName, */
1920
/*:: ObjectExpression, */
2021
/*:: ObjectPattern, */
2122
} from "../types";
@@ -63,6 +64,7 @@ export default class LValParser extends NodeUtils {
6364
+parsePropertyName: (
6465
prop: ObjectOrClassMember | ClassMember | TsNamedTypeElementBase,
6566
) => Expression | Identifier;
67+
+parsePrivateName: () => PrivateName
6668
*/
6769
// Forward-declaration: defined in statement.js
6870
/*::
@@ -143,9 +145,14 @@ export default class LValParser extends NodeUtils {
143145
}
144146
break;
145147

146-
case "ObjectProperty":
147-
this.toAssignable(node.value, isLHS);
148+
case "ObjectProperty": {
149+
const { key, value } = node;
150+
if (this.isPrivateName(key)) {
151+
this.classScope.usePrivateName(this.getPrivateNameSV(key), key.start);
152+
}
153+
this.toAssignable(value, isLHS);
148154
break;
155+
}
149156

150157
case "SpreadElement": {
151158
this.checkToRestConversion(node);
@@ -427,6 +434,10 @@ export default class LValParser extends NodeUtils {
427434
const { type, start: startPos, startLoc } = this.state;
428435
if (type === tt.ellipsis) {
429436
return this.parseBindingRestProperty(prop);
437+
} else if (type === tt.privateName) {
438+
this.expectPlugin("destructuringPrivate", startLoc);
439+
this.classScope.usePrivateName(this.state.value, startPos);
440+
prop.key = this.parsePrivateName();
430441
} else {
431442
this.parsePropertyName(prop);
432443
}

packages/babel-parser/src/parser/statement.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ export default class StatementParser extends ExpressionParser {
726726
}
727727
}
728728
if (isForOf || this.match(tt._in)) {
729+
this.checkDestructuringPrivate(refExpressionErrors);
729730
this.toAssignable(init, /* isLHS */ true);
730731
const description = isForOf ? "for-of statement" : "for-in statement";
731732
this.checkLVal(init, description);

packages/babel-parser/src/parser/util.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,18 @@ export default class UtilParser extends Tokenizer {
285285
andThrow: boolean,
286286
) {
287287
if (!refExpressionErrors) return false;
288-
const { shorthandAssignLoc, doubleProtoLoc, optionalParametersLoc } =
289-
refExpressionErrors;
288+
const {
289+
shorthandAssignLoc,
290+
doubleProtoLoc,
291+
privateKeyLoc,
292+
optionalParametersLoc,
293+
} = refExpressionErrors;
290294

291295
const hasErrors =
292-
!!shorthandAssignLoc || !!doubleProtoLoc || !!optionalParametersLoc;
296+
!!shorthandAssignLoc ||
297+
!!doubleProtoLoc ||
298+
!!optionalParametersLoc ||
299+
!!privateKeyLoc;
293300

294301
if (!andThrow) {
295302
return hasErrors;
@@ -305,6 +312,10 @@ export default class UtilParser extends Tokenizer {
305312
this.raise(Errors.DuplicateProto, { at: doubleProtoLoc });
306313
}
307314

315+
if (privateKeyLoc != null) {
316+
this.raise(Errors.UnexpectedPrivateField, { at: privateKeyLoc });
317+
}
318+
308319
if (optionalParametersLoc != null) {
309320
this.unexpected(optionalParametersLoc);
310321
}
@@ -417,6 +428,13 @@ export default class UtilParser extends Tokenizer {
417428
this.scope.enter(SCOPE_PROGRAM);
418429
this.prodParam.enter(paramFlags);
419430
}
431+
432+
checkDestructuringPrivate(refExpressionErrors: ExpressionErrors) {
433+
const { privateKeyLoc } = refExpressionErrors;
434+
if (privateKeyLoc !== null) {
435+
this.expectPlugin("destructuringPrivate", privateKeyLoc);
436+
}
437+
}
420438
}
421439

422440
/**
@@ -428,11 +446,13 @@ export default class UtilParser extends Tokenizer {
428446
*
429447
* - **shorthandAssignLoc**: track initializer `=` position
430448
* - **doubleProtoLoc**: track the duplicate `__proto__` key position
449+
* - **privateKey**: track private key `#p` position
431450
* - **optionalParametersLoc**: track the optional paramter (`?`).
432451
* It's only used by typescript and flow plugins
433452
*/
434453
export class ExpressionErrors {
435454
shorthandAssignLoc: ?Position = null;
436455
doubleProtoLoc: ?Position = null;
456+
privateKeyLoc: ?Position = null;
437457
optionalParametersLoc: ?Position = null;
438458
}

packages/babel-parser/src/plugins/estree.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
330330

331331
toAssignable(node: N.Node, isLHS: boolean = false): N.Node {
332332
if (node != null && this.isObjectProperty(node)) {
333-
this.toAssignable(node.value, isLHS);
334-
333+
const { key, value } = node;
334+
if (this.isPrivateName(key)) {
335+
this.classScope.usePrivateName(this.getPrivateNameSV(key), key.start);
336+
}
337+
this.toAssignable(value, isLHS);
335338
return node;
336339
}
337340

packages/babel-parser/test/fixtures/es2022/class-private-properties/invalid-object-method/output.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"type": "File",
33
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
44
"errors": [
5-
"SyntaxError: Unexpected private name. (2:11)"
5+
"SyntaxError: Unexpected private name. (2:10)"
66
],
77
"program": {
88
"type": "Program",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(class {
2+
#x;
3+
m = {
4+
#x: x,
5+
y: y = m
6+
}
7+
})

0 commit comments

Comments
 (0)