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

Refactor parsing object members #9607

Merged
merged 5 commits into from
Feb 28, 2019
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
201 changes: 107 additions & 94 deletions packages/babel-parser/src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,18 @@ export default class ExpressionParser extends LValParser {
// strict mode, init properties are also not allowed to be repeated.

checkPropClash(
prop: N.ObjectMember,
prop: N.ObjectMember | N.SpreadElement,
propHash: { [key: string]: boolean },
): void {
if (prop.computed || prop.kind) return;
if (
prop.type === "SpreadElement" ||
prop.computed ||
prop.kind ||
// $FlowIgnore
prop.shorthand
) {
return;
}

const key = prop.key;
// It is either an Identifier or a String/NumericLiteral
Expand Down Expand Up @@ -197,13 +205,10 @@ export default class ExpressionParser extends LValParser {
this.checkLVal(left, undefined, undefined, "assignment expression");

let patternErrorMsg;
let elementName;
if (left.type === "ObjectPattern") {
patternErrorMsg = "`({a}) = 0` use `({a} = 0)`";
elementName = "property";
} else if (left.type === "ArrayPattern") {
patternErrorMsg = "`([a]) = 0` use `([a] = 0)`";
elementName = "element";
}

if (patternErrorMsg && left.extra && left.extra.parenthesized) {
Expand All @@ -213,7 +218,7 @@ export default class ExpressionParser extends LValParser {
);
}

if (elementName) this.checkCommaAfterRestFromSpread(elementName);
if (patternErrorMsg) this.checkCommaAfterRestFromSpread();
this.state.commaAfterSpreadAt = oldCommaAfterSpreadAt;

this.next();
Expand Down Expand Up @@ -639,7 +644,7 @@ export default class ExpressionParser extends LValParser {
if (possibleAsync && this.shouldParseAsyncArrow()) {
state.stop = true;

this.checkCommaAfterRestFromSpread("parameter");
this.checkCommaAfterRestFromSpread();

node = this.parseAsyncArrowFromCallExpression(
this.startNodeAt(startPos, startLoc),
Expand Down Expand Up @@ -1207,13 +1212,13 @@ export default class ExpressionParser extends LValParser {
spreadStart = this.state.start;
exprList.push(
this.parseParenItem(
this.parseRest(),
this.parseRestBinding(),
spreadNodeStartPos,
spreadNodeStartLoc,
),
);

this.checkCommaAfterRest(tt.parenR, "parameter");
this.checkCommaAfterRest();

break;
} else {
Expand Down Expand Up @@ -1409,7 +1414,6 @@ export default class ExpressionParser extends LValParser {
isPattern: boolean,
refShorthandDefaultPos?: ?Pos,
): T {
let decorators = [];
const propHash: any = Object.create(null);
let first = true;
const node = this.startNode();
Expand All @@ -1425,108 +1429,117 @@ export default class ExpressionParser extends LValParser {
if (this.eat(tt.braceR)) break;
}

if (this.match(tt.at)) {
if (this.hasPlugin("decorators")) {
this.raise(
this.state.start,
"Stage 2 decorators disallow object literal property decorators",
);
} else {
// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
// the call to this.parseDecorator
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
}
}
const prop = this.parseObjectMember(isPattern, refShorthandDefaultPos);
// $FlowIgnore RestElement will never be returned if !isPattern
if (!isPattern) this.checkPropClash(prop, propHash);

let prop = this.startNode(),
isGenerator = false,
isAsync = false,
startPos,
startLoc;
if (decorators.length) {
prop.decorators = decorators;
decorators = [];
}

if (this.match(tt.ellipsis)) {
prop = this.parseSpread(isPattern ? { start: 0 } : undefined);
node.properties.push(prop);
if (isPattern) {
this.toAssignable(prop, true, "object pattern");
this.checkCommaAfterRest(tt.braceR, "property");
this.expect(tt.braceR);
break;
}
continue;
}

prop.method = false;

if (isPattern || refShorthandDefaultPos) {
startPos = this.state.start;
startLoc = this.state.startLoc;
// $FlowIgnore
if (prop.shorthand) {
this.addExtra(prop, "shorthand", true);
}

if (!isPattern) {
isGenerator = this.eat(tt.star);
}
node.properties.push(prop);
}

const containsEsc = this.state.containsEsc;
return this.finishNode(
node,
isPattern ? "ObjectPattern" : "ObjectExpression",
);
}

if (!isPattern && this.isContextual("async")) {
if (isGenerator) this.unexpected();
isAsyncProp(prop: N.ObjectProperty): boolean {
return (
!prop.computed &&
prop.key.type === "Identifier" &&
prop.key.name === "async" &&
(this.match(tt.name) ||
this.match(tt.num) ||
this.match(tt.string) ||
this.match(tt.bracketL) ||
this.state.type.keyword ||
this.match(tt.star)) &&
!this.hasPrecedingLineBreak()
);
}

const asyncId = this.parseIdentifier();
if (
this.match(tt.colon) ||
this.match(tt.parenL) ||
this.match(tt.braceR) ||
this.match(tt.eq) ||
this.match(tt.comma)
) {
prop.key = asyncId;
prop.computed = false;
} else {
isAsync = true;
isGenerator = this.eat(tt.star);
this.parsePropertyName(prop);
}
parseObjectMember(
isPattern: boolean,
refShorthandDefaultPos: ?Pos,
): N.ObjectMember | N.SpreadElement | N.RestElement {
let decorators = [];
if (this.match(tt.at)) {
if (this.hasPlugin("decorators")) {
this.raise(
this.state.start,
"Stage 2 decorators disallow object literal property decorators",
);
} else {
this.parsePropertyName(prop);
// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
// the call to this.parseDecorator
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
}
}

this.parseObjPropValue(
prop,
startPos,
startLoc,
isGenerator,
isAsync,
isPattern,
refShorthandDefaultPos,
containsEsc,
);
this.checkPropClash(prop, propHash);
const prop = this.startNode();
let isGenerator = false;
let isAsync = false;
let startPos;
let startLoc;

if (prop.shorthand) {
this.addExtra(prop, "shorthand", true);
if (this.match(tt.ellipsis)) {
if (decorators.length) this.unexpected();
if (isPattern) {
this.next();
// Don't use parseRestBinding() as we only allow Identifier here.
prop.argument = this.parseIdentifier();
this.checkCommaAfterRest();
return this.finishNode(prop, "RestElement");
}

node.properties.push(prop);
return this.parseSpread();
}

if (decorators.length) {
this.raise(
this.state.start,
"You have trailing decorators with no property",
);
prop.decorators = decorators;
decorators = [];
}

return this.finishNode(
node,
isPattern ? "ObjectPattern" : "ObjectExpression",
prop.method = false;

if (isPattern || refShorthandDefaultPos) {
startPos = this.state.start;
startLoc = this.state.startLoc;
}

if (!isPattern) {
isGenerator = this.eat(tt.star);
}

const containsEsc = this.state.containsEsc;
this.parsePropertyName(prop);

if (!isPattern && !containsEsc && !isGenerator && this.isAsyncProp(prop)) {
isAsync = true;
isGenerator = this.eat(tt.star);
this.parsePropertyName(prop);
} else {
isAsync = false;
}

this.parseObjPropValue(
prop,
startPos,
startLoc,
isGenerator,
isAsync,
isPattern,
refShorthandDefaultPos,
containsEsc,
);

return prop;
}

isGetterOrSetterMethod(prop: N.ObjectMethod, isPattern: boolean): boolean {
Expand Down
35 changes: 11 additions & 24 deletions packages/babel-parser/src/parser/lval.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default class LValParser extends NodeUtils {

this.raise(prop.key.start, error);
} else if (prop.type === "SpreadElement" && !isLast) {
this.raiseRestNotLast(prop.start, "property");
this.raiseRestNotLast(prop.start);
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
Expand Down Expand Up @@ -160,7 +160,7 @@ export default class LValParser extends NodeUtils {
if (elt) {
this.toAssignable(elt, isBinding, contextDescription);
if (elt.type === "RestElement") {
this.raiseRestNotLast(elt.start, "element");
this.raiseRestNotLast(elt.start);
}
}
}
Expand Down Expand Up @@ -213,7 +213,7 @@ export default class LValParser extends NodeUtils {
return this.finishNode(node, "SpreadElement");
}

parseRest(): RestElement {
parseRestBinding(): RestElement {
const node = this.startNode();
this.next();
node.argument = this.parseBindingAtom();
Expand Down Expand Up @@ -260,13 +260,8 @@ export default class LValParser extends NodeUtils {
} else if (this.eat(close)) {
break;
} else if (this.match(tt.ellipsis)) {
elts.push(this.parseAssignableListItemTypes(this.parseRest()));
this.checkCommaAfterRest(
close,
this.scope.inFunction && this.state.inParameters
? "parameter"
: "element",
);
elts.push(this.parseAssignableListItemTypes(this.parseRestBinding()));
this.checkCommaAfterRest();
this.expect(close);
break;
} else {
Expand Down Expand Up @@ -441,27 +436,19 @@ export default class LValParser extends NodeUtils {
}
}

checkCommaAfterRest(close: TokenType, kind: string): void {
checkCommaAfterRest(): void {
if (this.match(tt.comma)) {
if (this.lookahead().type === close) {
this.raiseCommaAfterRest(this.state.start, kind);
} else {
this.raiseRestNotLast(this.state.start, kind);
}
this.raiseRestNotLast(this.state.start);
}
}

checkCommaAfterRestFromSpread(kind: string): void {
checkCommaAfterRestFromSpread(): void {
if (this.state.commaAfterSpreadAt > -1) {
this.raiseCommaAfterRest(this.state.commaAfterSpreadAt, kind);
this.raiseRestNotLast(this.state.commaAfterSpreadAt);
}
}

raiseCommaAfterRest(pos: number, kind: string) {
this.raise(pos, `A trailing comma is not permitted after the rest ${kind}`);
}

raiseRestNotLast(pos: number, kind: string) {
this.raise(pos, `The rest ${kind} must be the last ${kind}`);
raiseRestNotLast(pos: number) {
this.raise(pos, `Rest element must be last element`);
}
}
14 changes: 11 additions & 3 deletions packages/babel-parser/src/plugins/estree.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,24 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

checkPropClash(
prop: N.ObjectMember,
prop: N.ObjectMember | N.SpreadElement,
propHash: { [key: string]: boolean },
): void {
if (prop.computed || !isSimpleProperty(prop)) return;
if (
prop.type === "SpreadElement" ||
prop.computed ||
prop.method ||
// $FlowIgnore
prop.shorthand
) {
return;
}

const key = prop.key;
// It is either an Identifier or a String/NumericLiteral
const name = key.type === "Identifier" ? key.name : String(key.value);

if (name === "__proto__") {
if (name === "__proto__" && prop.kind === "init") {
if (propHash.proto) {
this.raise(key.start, "Redefinition of __proto__ property");
}
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/plugins/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const restNode: N.TsRestType = this.startNode();
this.next(); // skips ellipsis
restNode.typeAnnotation = this.tsParseType();
this.checkCommaAfterRest(tt.bracketR, "type");
this.checkCommaAfterRest();
return this.finishNode(restNode, "TSRestType");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"throws": "The rest parameter must be the last parameter (1:18)"
"throws": "Rest element must be last element (1:18)"
}