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

eslint-parser: ES2020 features #11815

Merged
merged 8 commits into from Jul 29, 2020
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
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 @@ -374,8 +374,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 @@ -417,10 +415,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
}
}
]
}
}