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 Feb 1, 2022
1 parent 7d32f49 commit d8c7a8a
Show file tree
Hide file tree
Showing 61 changed files with 1,995 additions and 20 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;
}

}
43 changes: 33 additions & 10 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -78,6 +78,7 @@ import { cloneIdentifier } from "./node";

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

const invalidHackPipeBodies = new Map([
Expand Down Expand Up @@ -329,6 +330,14 @@ export default class ExpressionParser extends LValParser {
) {
refExpressionErrors.shorthandAssignLoc = null; // reset because shorthand default was used correctly
}
if (
refExpressionErrors.privateKeyLoc != null &&
// $FlowIgnore[incompatible-type] We know this exists, so it can't be undefined.
indexes.get(refExpressionErrors.privateKeyLoc) >= startPos
) {
this.checkDestructuringPrivate(refExpressionErrors);
refExpressionErrors.privateKeyLoc = null; // reset because `({ #x: x })` is an assignable pattern
}
} else {
node.left = left;
}
Expand Down Expand Up @@ -843,13 +852,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 @@ -864,18 +874,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 @@ -1738,6 +1750,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 @@ -2040,7 +2053,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 @@ -2245,8 +2258,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 @@ -2275,10 +2292,16 @@ export default class ExpressionParser extends LValParser {
break;
case tt.privateName: {
// the class private key has been handled in parseClassElementName
this.raise(Errors.UnexpectedPrivateField, {
// FIXME: explain
at: createPositionWithColumnOffset(this.state.startLoc, 1),
});
const privateKeyLoc = this.state.startLoc;
if (refExpressionErrors != null) {
if (refExpressionErrors.privateKeyLoc === null) {
refExpressionErrors.privateKeyLoc = privateKeyLoc;
}
} else {
this.raise(Errors.UnexpectedPrivateField, {
at: privateKeyLoc,
});
}
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 @@ -143,9 +145,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 @@ -427,6 +434,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", startLoc);
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 @@ -726,6 +726,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
26 changes: 23 additions & 3 deletions packages/babel-parser/src/parser/util.js
Expand Up @@ -285,11 +285,18 @@ export default class UtilParser extends Tokenizer {
andThrow: boolean,
) {
if (!refExpressionErrors) return false;
const { shorthandAssignLoc, doubleProtoLoc, optionalParametersLoc } =
refExpressionErrors;
const {
shorthandAssignLoc,
doubleProtoLoc,
privateKeyLoc,
optionalParametersLoc,
} = refExpressionErrors;

const hasErrors =
!!shorthandAssignLoc || !!doubleProtoLoc || !!optionalParametersLoc;
!!shorthandAssignLoc ||
!!doubleProtoLoc ||
!!optionalParametersLoc ||
!!privateKeyLoc;

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

if (privateKeyLoc != null) {
this.raise(Errors.UnexpectedPrivateField, { at: privateKeyLoc });
}

if (optionalParametersLoc != null) {
this.unexpected(optionalParametersLoc);
}
Expand Down Expand Up @@ -417,6 +428,13 @@ export default class UtilParser extends Tokenizer {
this.scope.enter(SCOPE_PROGRAM);
this.prodParam.enter(paramFlags);
}

checkDestructuringPrivate(refExpressionErrors: ExpressionErrors) {
const { privateKeyLoc } = refExpressionErrors;
if (privateKeyLoc !== null) {
this.expectPlugin("destructuringPrivate", privateKeyLoc);
}
}
}

/**
Expand All @@ -428,11 +446,13 @@ export default class UtilParser extends Tokenizer {
*
* - **shorthandAssignLoc**: track initializer `=` position
* - **doubleProtoLoc**: track the duplicate `__proto__` key position
* - **privateKey**: track private key `#p` position
* - **optionalParametersLoc**: track the optional paramter (`?`).
* It's only used by typescript and flow plugins
*/
export class ExpressionErrors {
shorthandAssignLoc: ?Position = null;
doubleProtoLoc: ?Position = null;
privateKeyLoc: ?Position = null;
optionalParametersLoc: ?Position = null;
}
7 changes: 5 additions & 2 deletions packages/babel-parser/src/plugins/estree.js
Expand Up @@ -330,8 +330,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": []
}
}

0 comments on commit d8c7a8a

Please sign in to comment.