Skip to content

Commit

Permalink
eslint-parser: ES2020 features (#11815)
Browse files Browse the repository at this point in the history
* chore: update espree test on nullish coalescing

* feat: add optional chaining support

* fix: adapt to estree AST shape

* chore: update lockfile

* add estree optional-chaining test fixtures

* address review comments

* chore: simplify smoke test

* export * support

Co-authored-by: Brian Ng <bng412@gmail.com>
  • Loading branch information
JLHwung and existentialism committed Jul 29, 2020
1 parent 059e912 commit d7347fb
Show file tree
Hide file tree
Showing 36 changed files with 692 additions and 62 deletions.
4 changes: 2 additions & 2 deletions eslint/babel-eslint-parser/package.json
Expand Up @@ -23,8 +23,8 @@
"eslint": ">=6.0.0"
},
"dependencies": {
"eslint-scope": "5.0.0",
"eslint-visitor-keys": "^1.1.0",
"eslint-scope": "5.1.0",
"eslint-visitor-keys": "^1.3.0",
"semver": "^6.3.0"
},
"devDependencies": {
Expand Down
38 changes: 23 additions & 15 deletions eslint/babel-eslint-parser/src/convert/convertAST.js
@@ -1,4 +1,5 @@
import { types as t, traverse } from "@babel/core";
import VISITOR_KEYS from "../visitor-keys";

function convertNodes(ast, code) {
const astTransformVisitor = {
Expand Down Expand Up @@ -81,21 +82,28 @@ function convertNodes(ast, code) {
};
const state = { source: code };

// Monkey patch visitor keys in order to be able to traverse the estree nodes
t.VISITOR_KEYS.Property = t.VISITOR_KEYS.ObjectProperty;
t.VISITOR_KEYS.MethodDefinition = [
"key",
"value",
"decorators",
"returnType",
"typeParameters",
];

traverse(ast, astTransformVisitor, null, state);

// These can be safely deleted because they are not defined in the original visitor keys.
delete t.VISITOR_KEYS.Property;
delete t.VISITOR_KEYS.MethodDefinition;
const oldExportAllDeclarationKeys = t.VISITOR_KEYS.ExportAllDeclaration;

try {
// Monkey patch visitor keys in order to be able to traverse the estree nodes
t.VISITOR_KEYS.ChainExpression = VISITOR_KEYS.ChainExpression;
t.VISITOR_KEYS.Property = VISITOR_KEYS.Property;
t.VISITOR_KEYS.MethodDefinition = VISITOR_KEYS.MethodDefinition;

// Make sure we visit `exported` key to remove `identifierName` from loc node
t.VISITOR_KEYS.ExportAllDeclaration = t.VISITOR_KEYS.ExportAllDeclaration.concat(
"exported",
);

traverse(ast, astTransformVisitor, null, state);
} finally {
// These can be safely deleted because they are not defined in the original visitor keys.
delete t.VISITOR_KEYS.ChainExpression;
delete t.VISITOR_KEYS.MethodDefinition;
delete t.VISITOR_KEYS.Property;

t.VISITOR_KEYS.ExportAllDeclaration = oldExportAllDeclarationKeys;
}
}

function convertProgramNode(ast) {
Expand Down
7 changes: 6 additions & 1 deletion eslint/babel-eslint-parser/src/convert/convertTokens.js
Expand Up @@ -115,7 +115,6 @@ function convertToken(token, source) {
type === tt.incDec ||
type === tt.colon ||
type === tt.question ||
type === tt.questionDot ||
type === tt.template ||
type === tt.backQuote ||
type === tt.dollarBraceL ||
Expand Down Expand Up @@ -173,6 +172,12 @@ function convertToken(token, source) {
} else if (type === tt.bigint) {
token.type = "Numeric";
token.value = `${token.value}n`;
} else if (type === tt.questionDot) {
token.value = type.label;
}
if (typeof token.type !== "string") {
// Acorn does not have rightAssociative
delete token.type.rightAssociative;
}

return token;
Expand Down
5 changes: 4 additions & 1 deletion eslint/babel-eslint-parser/src/visitor-keys.js
@@ -1,10 +1,13 @@
import { types as t } from "@babel/core";
import { KEYS as ESLINT_VISITOR_KEYS } from "eslint-visitor-keys";

const { VISITOR_KEYS: BABEL_VISITOR_KEYS } = t;
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
const { ExportAllDeclaration, ...BABEL_VISITOR_KEYS } = t.VISITOR_KEYS;

export default Object.assign(
{
ChainExpression: ESLINT_VISITOR_KEYS.ChainExpression,
ExportAllDeclaration: ESLINT_VISITOR_KEYS.ExportAllDeclaration,
Literal: ESLINT_VISITOR_KEYS.Literal,
MethodDefinition: ["decorators"].concat(
ESLINT_VISITOR_KEYS.MethodDefinition,
Expand Down
35 changes: 9 additions & 26 deletions eslint/babel-eslint-parser/test/index.js
Expand Up @@ -253,6 +253,10 @@ describe("Babel and Espree", () => {
parseAndAssertSame('import "foo";');
});

it("import meta", () => {
parseAndAssertSame("const url = import.meta.url");
});

it("export default class declaration", () => {
parseAndAssertSame("export default class Foo {}");
});
Expand All @@ -273,15 +277,8 @@ describe("Babel and Espree", () => {
parseAndAssertSame('export * from "foo";');
});

// Espree doesn't support `export * as ns` yet
it("export * as ns", () => {
const code = 'export * as Foo from "foo";';
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: BABEL_OPTIONS,
}).ast;
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
parseAndAssertSame('export * as Foo from "foo";');
});

it("export named", () => {
Expand All @@ -292,26 +289,12 @@ describe("Babel and Espree", () => {
parseAndAssertSame("var foo = 1;export { foo as bar };");
});

// Espree doesn't support the optional chaining operator yet
it("optional chaining operator (token)", () => {
const code = "foo?.bar";
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: BABEL_OPTIONS,
}).ast;
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
it("optional chaining operator", () => {
parseAndAssertSame("foo?.bar?.().qux()");
});

// Espree doesn't support the nullish coalescing operator yet
it("nullish coalescing operator (token)", () => {
const code = "foo ?? bar";
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: BABEL_OPTIONS,
}).ast;
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
it("nullish coalescing operator", () => {
parseAndAssertSame("foo ?? bar");
});

// Espree doesn't support the pipeline operator yet
Expand Down
Expand Up @@ -53,7 +53,8 @@ function isOptionalCallExpression(node) {
return (
!!node &&
node.type === "ExpressionStatement" &&
node.expression.type === "OptionalCallExpression"
node.expression.type === "ChainExpression" &&
node.expression.expression.type === "CallExpression"
);
}

Expand Down
36 changes: 31 additions & 5 deletions packages/babel-parser/src/plugins/estree.js
Expand Up @@ -388,8 +388,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
delete node.arguments;
// $FlowIgnore - callee isn't optional in the type definition
delete node.callee;
} else if (node.type === "CallExpression") {
(node: N.Node).optional = false;
}
return node;
Expand Down Expand Up @@ -431,10 +429,38 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return node;
}
parseSubscript(...args) {
const node = super.parseSubscript(...args);
parseSubscript(
base: N.Expression,
startPos: number,
startLoc: Position,
noCalls: ?boolean,
state: N.ParseSubscriptState,
) {
const node = super.parseSubscript(
base,
startPos,
startLoc,
noCalls,
state,
);
if (node.type === "MemberExpression") {
if (state.optionalChainMember) {
// https://github.com/estree/estree/blob/master/es2020.md#chainexpression
if (
node.type === "OptionalMemberExpression" ||
node.type === "OptionalCallExpression"
) {
node.type = node.type.substring(8); // strip Optional prefix
}
if (state.stop) {
const chain = this.startNodeAtNode(node);
chain.expression = node;
return this.finishNode(chain, "ChainExpression");
}
} else if (
node.type === "MemberExpression" ||
node.type === "CallExpression"
) {
node.optional = false;
}

Expand Down
@@ -0,0 +1 @@
(foo?.())()
@@ -0,0 +1,41 @@
{
"type": "File",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"program": {
"type": "Program",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"expression": {
"type": "CallExpression",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"callee": {
"type": "ChainExpression",
"start":1,"end":8,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":8}},
"expression": {
"type": "CallExpression",
"start":1,"end":8,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":8}},
"callee": {
"type": "Identifier",
"start":1,"end":4,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":4},"identifierName":"foo"},
"name": "foo"
},
"optional": true,
"arguments": []
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
},
"arguments": [],
"optional": false
}
}
]
}
}
@@ -0,0 +1 @@
foo?.()()
@@ -0,0 +1,37 @@
{
"type": "File",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"program": {
"type": "Program",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"expression": {
"type": "ChainExpression",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"expression": {
"type": "CallExpression",
"start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}},
"callee": {
"type": "CallExpression",
"start":0,"end":7,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":7}},
"callee": {
"type": "Identifier",
"start":0,"end":3,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":3},"identifierName":"foo"},
"name": "foo"
},
"optional": true,
"arguments": []
},
"optional": false,
"arguments": []
}
}
}
]
}
}
@@ -0,0 +1 @@
foo?.().bar
@@ -0,0 +1,42 @@
{
"type": "File",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"program": {
"type": "Program",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"expression": {
"type": "ChainExpression",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"expression": {
"type": "MemberExpression",
"start":0,"end":11,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":11}},
"object": {
"type": "CallExpression",
"start":0,"end":7,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":7}},
"callee": {
"type": "Identifier",
"start":0,"end":3,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":3},"identifierName":"foo"},
"name": "foo"
},
"optional": true,
"arguments": []
},
"property": {
"type": "Identifier",
"start":8,"end":11,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":11},"identifierName":"bar"},
"name": "bar"
},
"computed": false,
"optional": false
}
}
}
]
}
}
@@ -0,0 +1 @@
(foo?.()).bar
@@ -0,0 +1,46 @@
{
"type": "File",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}},
"program": {
"type": "Program",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}},
"expression": {
"type": "MemberExpression",
"start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}},
"object": {
"type": "ChainExpression",
"start":1,"end":8,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":8}},
"expression": {
"type": "CallExpression",
"start":1,"end":8,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":8}},
"callee": {
"type": "Identifier",
"start":1,"end":4,"loc":{"start":{"line":1,"column":1},"end":{"line":1,"column":4},"identifierName":"foo"},
"name": "foo"
},
"optional": true,
"arguments": []
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
},
"property": {
"type": "Identifier",
"start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"bar"},
"name": "bar"
},
"computed": false,
"optional": false
}
}
]
}
}

0 comments on commit d7347fb

Please sign in to comment.