eslint-parser: ES2020 features (#11815)

* 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>
This commit is contained in:
Huáng Jùnliàng
2020-07-29 16:46:29 -04:00
committed by GitHub
parent 059e9124ff
commit d7347fb8bd
36 changed files with 690 additions and 60 deletions

View File

@@ -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": {

View File

@@ -1,4 +1,5 @@
import { types as t, traverse } from "@babel/core";
import VISITOR_KEYS from "../visitor-keys";
function convertNodes(ast, code) {
const astTransformVisitor = {
@@ -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",
];
const oldExportAllDeclarationKeys = t.VISITOR_KEYS.ExportAllDeclaration;
traverse(ast, astTransformVisitor, null, state);
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;
// 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;
// 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) {

View File

@@ -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 ||
@@ -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;

View File

@@ -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,

View File

@@ -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 {}");
});
@@ -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", () => {
@@ -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

View File

@@ -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"
);
}