Breaking: align babel-eslint-parser & espree (#11137)

* Chore: align babel-eslint-parser & espree

* Start program at beginning of comment when no tokens exist

* Import correct version of Espree for tests

* Remove hasOwnProperty guard
This commit is contained in:
Kai Cataldo 2020-02-20 11:31:57 -05:00 committed by GitHub
parent 1613418f9b
commit 3960f4de64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 107 deletions

View File

@ -31,7 +31,6 @@
"@babel/core": "^7.2.0",
"@babel/eslint-shared-fixtures": "*",
"eslint": "^6.0.1",
"espree": "^6.0.0",
"lodash.clonedeep": "^4.5.0"
}
}

View File

@ -1,14 +1,10 @@
import { types as t, traverse } from "@babel/core";
function convertNodes(ast, code) {
const state = { source: code };
const astTransformVisitor = {
noScope: true,
enter(path) {
const node = path.node;
// private var to track original node type
node._babelType = node.type;
const { node } = path;
if (node.innerComments) {
delete node.innerComments;
@ -23,7 +19,16 @@ function convertNodes(ast, code) {
}
},
exit(path) {
const node = path.node;
const { node } = path;
// Used internally by @babel/parser.
if (node.extra) {
delete node.extra;
}
if (node?.loc.identifierName) {
delete node.loc.identifierName;
}
if (path.isTypeParameter()) {
node.type = "Identifier";
@ -74,6 +79,7 @@ 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;
@ -95,19 +101,14 @@ function convertNodes(ast, code) {
function convertProgramNode(ast) {
ast.type = "Program";
ast.sourceType = ast.program.sourceType;
ast.directives = ast.program.directives;
ast.body = ast.program.body;
delete ast.program;
delete ast.errors;
if (ast.comments.length) {
const lastComment = ast.comments[ast.comments.length - 1];
if (!ast.tokens.length) {
// if no tokens, the program starts at the end of the last comment
ast.start = lastComment.end;
ast.loc.start.line = lastComment.loc.end.line;
ast.loc.start.column = lastComment.loc.end.column;
} else {
if (ast.tokens.length) {
const lastToken = ast.tokens[ast.tokens.length - 1];
if (lastComment.end > lastToken.end) {

View File

@ -1,6 +1,5 @@
export default function convertComments(comments) {
for (let i = 0; i < comments.length; i++) {
const comment = comments[i];
for (const comment of comments) {
if (comment.type === "CommentBlock") {
comment.type = "Block";
} else if (comment.type === "CommentLine") {

View File

@ -1,40 +0,0 @@
// Checks if the source ast implements the target ast. Ignores extra keys on source ast
export default function assertImplementsAST(target, source, path) {
if (!path) {
path = [];
}
function error(text) {
const err = new Error(`At ${path.join(".")}: ${text}:`);
err.depth = path.length + 1;
throw err;
}
const typeA = target === null ? "null" : typeof target;
const typeB = source === null ? "null" : typeof source;
if (typeA !== typeB) {
error(
`have different types (${typeA} !== ${typeB}) (${target} !== ${source})`,
);
} else if (
typeA === "object" &&
["RegExp"].indexOf(target.constructor.name) !== -1 &&
target.constructor.name !== source.constructor.name
) {
error(
`object have different constructors (${target.constructor.name} !== ${source.constructor.name}`,
);
} else if (typeA === "object") {
const keysTarget = Object.keys(target);
for (const i in keysTarget) {
const key = keysTarget[i];
path.push(key);
assertImplementsAST(target[key], source[key], path);
path.pop();
}
} else if (target !== source) {
error(
`are different (${JSON.stringify(target)} !== ${JSON.stringify(source)})`,
);
}
}

View File

@ -1,54 +1,88 @@
import assert from "assert";
import espree from "espree";
import path from "path";
import escope from "eslint-scope";
import unpad from "dedent";
import { parseForESLint } from "../src";
import assertImplementsAST from "./helpers/assert-implements-ast";
const babelOptions = {
const BABEL_OPTIONS = {
configFile: require.resolve(
"@babel/eslint-shared-fixtures/config/babel.config.js",
),
};
const ALLOWED_PROPERTIES = [
"importKind",
"exportKind",
"variance",
"typeArguments",
];
function parseAndAssertSame(code) {
code = unpad(code);
const esAST = espree.parse(code, {
ecmaFeatures: {
// enable JSX parsing
jsx: true,
// enable return in global scope
globalReturn: true,
// enable implied strict mode (if ecmaVersion >= 5)
impliedStrict: true,
// allow experimental object rest/spread
experimentalObjectRestSpread: true,
},
tokens: true,
loc: true,
range: true,
comment: true,
ecmaVersion: 2020,
sourceType: "module",
});
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions,
}).ast;
assertImplementsAST(esAST, babylonAST);
function deeplyRemoveProperties(obj, props) {
for (const [k, v] of Object.entries(obj)) {
if (typeof v === "object") {
if (Array.isArray(v)) {
for (const el of v) {
if (el != null) deeplyRemoveProperties(el, props);
}
}
if (props.includes(k)) delete obj[k];
else if (v != null) deeplyRemoveProperties(v, props);
continue;
}
if (props.includes(k)) delete obj[k];
}
}
describe("babylon-to-espree", () => {
describe("Babel and Espree", () => {
let espree;
function parseAndAssertSame(code) {
code = unpad(code);
const espreeAST = espree.parse(code, {
ecmaFeatures: {
// enable JSX parsing
jsx: true,
// enable return in global scope
globalReturn: true,
// enable implied strict mode (if ecmaVersion >= 5)
impliedStrict: true,
// allow experimental object rest/spread
experimentalObjectRestSpread: true,
},
tokens: true,
loc: true,
range: true,
comment: true,
ecmaVersion: 2020,
sourceType: "module",
});
const babelAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions: BABEL_OPTIONS,
}).ast;
deeplyRemoveProperties(babelAST, ALLOWED_PROPERTIES);
expect(babelAST).toEqual(espreeAST);
}
beforeAll(async () => {
// Use the version of Espree that is a dependency of
// the version of ESLint we are testing against.
const espreePath = require.resolve("espree", {
paths: [path.dirname(require.resolve("eslint"))],
});
espree = await import(espreePath);
});
describe("compatibility", () => {
it("should allow ast.analyze to be called without options", function() {
const esAST = parseForESLint("`test`", {
const ast = parseForESLint("`test`", {
eslintScopeManager: true,
eslintVisitorKeys: true,
babelOptions,
babelOptions: BABEL_OPTIONS,
}).ast;
expect(() => {
escope.analyze(esAST);
escope.analyze(ast);
}).not.toThrow(new TypeError("Should allow no options argument."));
});
});
@ -244,9 +278,9 @@ describe("babylon-to-espree", () => {
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions,
babelOptions: BABEL_OPTIONS,
}).ast;
assert.strictEqual(babylonAST.tokens[1].type, "Punctuator");
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
});
// Espree doesn't support the nullish coalescing operator yet
@ -255,9 +289,9 @@ describe("babylon-to-espree", () => {
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions,
babelOptions: BABEL_OPTIONS,
}).ast;
assert.strictEqual(babylonAST.tokens[1].type, "Punctuator");
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
});
// Espree doesn't support the pipeline operator yet
@ -266,9 +300,9 @@ describe("babylon-to-espree", () => {
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions,
babelOptions: BABEL_OPTIONS,
}).ast;
assert.strictEqual(babylonAST.tokens[1].type, "Punctuator");
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
});
// Espree doesn't support private fields yet
@ -277,19 +311,17 @@ describe("babylon-to-espree", () => {
const babylonAST = parseForESLint(code, {
eslintVisitorKeys: true,
eslintScopeManager: true,
babelOptions,
babelOptions: BABEL_OPTIONS,
}).ast;
assert.strictEqual(babylonAST.tokens[3].type, "Punctuator");
assert.strictEqual(babylonAST.tokens[3].value, "#");
expect(babylonAST.tokens[3].type).toEqual("Punctuator");
expect(babylonAST.tokens[3].value).toEqual("#");
});
// eslint-disable-next-line jest/no-disabled-tests
it.skip("empty program with line comment", () => {
it("empty program with line comment", () => {
parseAndAssertSame("// single comment");
});
// eslint-disable-next-line jest/no-disabled-tests
it.skip("empty program with block comment", () => {
it("empty program with block comment", () => {
parseAndAssertSame(" /* multiline\n * comment\n*/");
});
@ -448,9 +480,13 @@ describe("babylon-to-espree", () => {
});
it("do not allow import export everywhere", () => {
assert.throws(() => {
expect(() => {
parseAndAssertSame('function F() { import a from "a"; }');
}, /SyntaxError: 'import' and 'export' may only appear at the top level/);
}).toThrow(
new SyntaxError(
"'import' and 'export' may only appear at the top level",
),
);
});
it("return outside function", () => {
@ -458,9 +494,9 @@ describe("babylon-to-espree", () => {
});
it("super outside method", () => {
assert.throws(() => {
expect(() => {
parseAndAssertSame("function F() { super(); }");
}, /SyntaxError: 'super' keyword outside a method/);
}).toThrow(new SyntaxError("'super' keyword outside a method"));
});
it("StringLiteral", () => {