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

File tree

61 files changed

+1996
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1996
-20
lines changed
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",
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy