Skip to content

Commit

Permalink
Parse destructuring private fields (#13931)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JLHwung committed Nov 25, 2021
1 parent 55f020e commit c87850c
Show file tree
Hide file tree
Showing 61 changed files with 1,982 additions and 18 deletions.
@@ -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
// and record the position of the first private name
parsePropertyName(
prop: N.ObjectOrClassMember | N.ClassMember | N.TsNamedTypeElementBase,
refExpressionErrors?: ?ExpressionErrors,
): 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, */
/*:: 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

0 comments on commit c87850c

Please sign in to comment.