Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse destructuring private fields #13931

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,7 @@
class C {
#x;
m() {
#x in (#x in this);
var { #x: x } = this;
}
}
@@ -0,0 +1,3 @@
{
"plugins": ["destructuringPrivate"]
}
11 changes: 11 additions & 0 deletions packages/babel-generator/test/fixtures/types/PrivateName/output.js
@@ -0,0 +1,11 @@
class C {
#x;

m() {
#x in (#x in this);
var {
#x: x
} = this;
}

}
35 changes: 27 additions & 8 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -73,6 +73,7 @@ import { cloneIdentifier } from "./node";

/*::
import type { SourceType } from "../options";
declare var invariant;
*/

const invalidHackPipeBodies = new Map([
Expand Down Expand Up @@ -316,6 +317,10 @@ export default class ExpressionParser extends LValParser {
if (refExpressionErrors.shorthandAssign >= startPos) {
refExpressionErrors.shorthandAssign = -1; // reset because shorthand default was used correctly
}
if (refExpressionErrors.privateKey >= startPos) {
this.checkDestructuringPrivate(refExpressionErrors);
refExpressionErrors.privateKey = -1; // reset because `({ #x: x })` is an assignable pattern
}
} else {
node.left = left;
}
Expand Down Expand Up @@ -830,13 +835,14 @@ export default class ExpressionParser extends LValParser {

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

if (state.maybeAsyncArrow) {
if (maybeAsyncArrow) {
this.expressionScope.enter(newAsyncArrowScope());
refExpressionErrors = new ExpressionErrors();
}

if (state.optionalChainMember) {
if (optionalChainMember) {
node.optional = optional;
}

Expand All @@ -851,18 +857,20 @@ export default class ExpressionParser extends LValParser {
refExpressionErrors,
);
}
this.finishCallExpression(node, state.optionalChainMember);
this.finishCallExpression(node, optionalChainMember);

if (state.maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) {
if (maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) {
/*:: invariant(refExpressionErrors != null) */
state.stop = true;
this.checkDestructuringPrivate(refExpressionErrors);
this.expressionScope.validateAsPattern();
this.expressionScope.exit();
node = this.parseAsyncArrowFromCallExpression(
this.startNodeAt(startPos, startLoc),
node,
);
} else {
if (state.maybeAsyncArrow) {
if (maybeAsyncArrow) {
this.checkExpressionErrors(refExpressionErrors, true);
this.expressionScope.exit();
}
Expand Down Expand Up @@ -1719,6 +1727,7 @@ export default class ExpressionParser extends LValParser {
this.shouldParseArrow(exprList) &&
(arrowNode = this.parseArrow(arrowNode))
) {
this.checkDestructuringPrivate(refExpressionErrors);
this.expressionScope.validateAsPattern();
this.expressionScope.exit();
this.parseArrowExpression(arrowNode, exprList, false);
Expand Down Expand Up @@ -1993,7 +2002,7 @@ export default class ExpressionParser extends LValParser {
let isGenerator = this.eat(tt.star);
this.parsePropertyNamePrefixOperator(prop);
const containsEsc = this.state.containsEsc;
const key = this.parsePropertyName(prop);
const key = this.parsePropertyName(prop, refExpressionErrors);

if (!isGenerator && !containsEsc && this.maybeAsyncOrAccessorProp(prop)) {
const keyName = key.name;
Expand Down Expand Up @@ -2195,8 +2204,12 @@ export default class ExpressionParser extends LValParser {
return node;
}

// https://tc39.es/ecma262/#prod-PropertyName
// when refExpressionErrors presents, it will parse private name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
// when refExpressionErrors presents, it will parse private name
// when refExpressionErrors is present, it will parse private name

// and record the position of the first private name
parsePropertyName(
prop: N.ObjectOrClassMember | N.ClassMember | N.TsNamedTypeElementBase,
refExpressionErrors?: ?ExpressionErrors,
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
): N.Expression | N.Identifier {
if (this.eat(tt.bracketL)) {
(prop: $FlowSubtype<N.ObjectOrClassMember>).computed = true;
Expand Down Expand Up @@ -2225,8 +2238,14 @@ export default class ExpressionParser extends LValParser {
break;
case tt.privateName: {
// the class private key has been handled in parseClassElementName
const privateKeyPos = this.state.start + 1;
this.raise(privateKeyPos, Errors.UnexpectedPrivateField);
const privateKeyPos = this.state.start;
if (refExpressionErrors != null) {
if (refExpressionErrors.privateKey === -1) {
refExpressionErrors.privateKey = privateKeyPos;
}
} else {
this.raise(privateKeyPos, Errors.UnexpectedPrivateField);
}
key = this.parsePrivateName();
break;
}
Expand Down
15 changes: 13 additions & 2 deletions packages/babel-parser/src/parser/lval.js
Expand Up @@ -16,6 +16,7 @@ import type {
/*:: ObjectMember, */
/*:: TsNamedTypeElementBase, */
/*:: Identifier, */
/*:: PrivateName, */
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
/*:: ObjectExpression, */
/*:: ObjectPattern, */
} from "../types";
Expand Down Expand Up @@ -63,6 +64,7 @@ export default class LValParser extends NodeUtils {
+parsePropertyName: (
prop: ObjectOrClassMember | ClassMember | TsNamedTypeElementBase,
) => Expression | Identifier;
+parsePrivateName: () => PrivateName
*/
// Forward-declaration: defined in statement.js
/*::
Expand Down Expand Up @@ -141,9 +143,14 @@ export default class LValParser extends NodeUtils {
}
break;

case "ObjectProperty":
this.toAssignable(node.value, isLHS);
case "ObjectProperty": {
const { key, value } = node;
if (this.isPrivateName(key)) {
this.classScope.usePrivateName(this.getPrivateNameSV(key), key.start);
}
this.toAssignable(value, isLHS);
break;
}

case "SpreadElement": {
this.checkToRestConversion(node);
Expand Down Expand Up @@ -418,6 +425,10 @@ export default class LValParser extends NodeUtils {
const { type, start: startPos, startLoc } = this.state;
if (type === tt.ellipsis) {
return this.parseBindingRestProperty(prop);
} else if (type === tt.privateName) {
this.expectPlugin("destructuringPrivate", startPos);
this.classScope.usePrivateName(this.state.value, startPos);
prop.key = this.parsePrivateName();
} else {
this.parsePropertyName(prop);
}
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -636,6 +636,7 @@ export default class StatementParser extends ExpressionParser {
}
}
if (isForOf || this.match(tt._in)) {
this.checkDestructuringPrivate(refExpressionErrors);
this.toAssignable(init, /* isLHS */ true);
const description = isForOf ? "for-of statement" : "for-in statement";
this.checkLVal(init, description);
Expand Down
19 changes: 16 additions & 3 deletions packages/babel-parser/src/parser/util.js
Expand Up @@ -262,10 +262,11 @@ export default class UtilParser extends Tokenizer {
andThrow: boolean,
) {
if (!refExpressionErrors) return false;
const { shorthandAssign, doubleProto, optionalParameters } =
const { shorthandAssign, doubleProto, privateKey, optionalParameters } =
refExpressionErrors;
// shorthandAssign >= 0 || doubleProto >= 0 || optionalParameters >= 0
const hasErrors = shorthandAssign + doubleProto + optionalParameters > -3;
// shorthandAssign >= 0 || doubleProto >= 0 || privateKey >= 0 || optionalParameters >= 0
const hasErrors =
shorthandAssign + doubleProto + privateKey + optionalParameters > -4;
if (!andThrow) {
return hasErrors;
} else if (hasErrors) {
Expand All @@ -275,6 +276,9 @@ export default class UtilParser extends Tokenizer {
if (doubleProto >= 0) {
this.raise(doubleProto, Errors.DuplicateProto);
}
if (privateKey >= 0) {
this.raise(privateKey, Errors.UnexpectedPrivateField);
}
if (optionalParameters >= 0) {
this.unexpected(optionalParameters);
}
Expand Down Expand Up @@ -388,6 +392,13 @@ export default class UtilParser extends Tokenizer {
this.scope.enter(SCOPE_PROGRAM);
this.prodParam.enter(paramFlags);
}

checkDestructuringPrivate(refExpressionErrors: ExpressionErrors) {
const { privateKey } = refExpressionErrors;
if (privateKey !== -1) {
this.expectPlugin("destructuringPrivate", privateKey);
}
}
}

/**
Expand All @@ -399,11 +410,13 @@ export default class UtilParser extends Tokenizer {
*
* - **shorthandAssign**: track initializer `=` position
* - **doubleProto**: track the duplicate `__proto__` key position
* - **privateKey**: track private key `#p` position
* - **optionalParameters**: track the optional paramter (`?`).
* It's only used by typescript and flow plugins
*/
export class ExpressionErrors {
shorthandAssign = -1;
doubleProto = -1;
privateKey = -1;
optionalParameters = -1;
}
7 changes: 5 additions & 2 deletions packages/babel-parser/src/plugins/estree.js
Expand Up @@ -336,8 +336,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>

toAssignable(node: N.Node, isLHS: boolean = false): N.Node {
if (node != null && this.isObjectProperty(node)) {
this.toAssignable(node.value, isLHS);

const { key, value } = node;
if (this.isPrivateName(key)) {
this.classScope.usePrivateName(this.getPrivateNameSV(key), key.start);
}
this.toAssignable(value, isLHS);
return node;
}

Expand Down
Expand Up @@ -2,7 +2,7 @@
"type": "File",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: Unexpected private name. (2:11)"
"SyntaxError: Unexpected private name. (2:10)"
],
"program": {
"type": "Program",
Expand Down
@@ -0,0 +1,7 @@
(class {
#x;
m = {
#x: x,
y: y = m
}
})
@@ -0,0 +1,115 @@
{
"type": "File",
"start":0,"end":53,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":2}},
"errors": [
"SyntaxError: Unexpected private name. (4:4)"
],
"program": {
"type": "Program",
"start":0,"end":53,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":2}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":53,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":2}},
"expression": {
"type": "ClassExpression",
"start":1,"end":52,"loc":{"start":{"line":1,"column":1},"end":{"line":7,"column":1}},
"id": null,
"superClass": null,
"body": {
"type": "ClassBody",
"start":7,"end":52,"loc":{"start":{"line":1,"column":7},"end":{"line":7,"column":1}},
"body": [
{
"type": "ClassPrivateProperty",
"start":11,"end":14,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5}},
"static": false,
"key": {
"type": "PrivateName",
"start":11,"end":13,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":4}},
"id": {
"type": "Identifier",
"start":12,"end":13,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"x"},
"name": "x"
}
},
"value": null
},
{
"type": "ClassProperty",
"start":17,"end":50,"loc":{"start":{"line":3,"column":2},"end":{"line":6,"column":3}},
"static": false,
"key": {
"type": "Identifier",
"start":17,"end":18,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":3},"identifierName":"m"},
"name": "m"
},
"computed": false,
"value": {
"type": "ObjectExpression",
"start":21,"end":50,"loc":{"start":{"line":3,"column":6},"end":{"line":6,"column":3}},
"properties": [
{
"type": "ObjectProperty",
"start":27,"end":32,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":9}},
"method": false,
"key": {
"type": "PrivateName",
"start":27,"end":29,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":6}},
"id": {
"type": "Identifier",
"start":28,"end":29,"loc":{"start":{"line":4,"column":5},"end":{"line":4,"column":6},"identifierName":"x"},
"name": "x"
}
},
"shorthand": false,
"value": {
"type": "Identifier",
"start":31,"end":32,"loc":{"start":{"line":4,"column":8},"end":{"line":4,"column":9},"identifierName":"x"},
"name": "x"
}
},
{
"type": "ObjectProperty",
"start":38,"end":46,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":12}},
"method": false,
"key": {
"type": "Identifier",
"start":38,"end":39,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":5},"identifierName":"y"},
"name": "y"
},
"computed": false,
"shorthand": false,
"value": {
"type": "AssignmentExpression",
"start":41,"end":46,"loc":{"start":{"line":5,"column":7},"end":{"line":5,"column":12}},
"operator": "=",
"left": {
"type": "Identifier",
"start":41,"end":42,"loc":{"start":{"line":5,"column":7},"end":{"line":5,"column":8},"identifierName":"y"},
"name": "y"
},
"right": {
"type": "Identifier",
"start":45,"end":46,"loc":{"start":{"line":5,"column":11},"end":{"line":5,"column":12},"identifierName":"m"},
"name": "m"
}
}
}
]
}
}
]
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
}
}
],
"directives": []
}
}
Expand Up @@ -2,7 +2,7 @@
"type": "File",
"start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: Unexpected private name. (2:11)"
"SyntaxError: Unexpected private name. (2:10)"
],
"program": {
"type": "Program",
Expand Down