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:
parent
1613418f9b
commit
3960f4de64
@ -31,7 +31,6 @@
|
|||||||
"@babel/core": "^7.2.0",
|
"@babel/core": "^7.2.0",
|
||||||
"@babel/eslint-shared-fixtures": "*",
|
"@babel/eslint-shared-fixtures": "*",
|
||||||
"eslint": "^6.0.1",
|
"eslint": "^6.0.1",
|
||||||
"espree": "^6.0.0",
|
|
||||||
"lodash.clonedeep": "^4.5.0"
|
"lodash.clonedeep": "^4.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import { types as t, traverse } from "@babel/core";
|
import { types as t, traverse } from "@babel/core";
|
||||||
|
|
||||||
function convertNodes(ast, code) {
|
function convertNodes(ast, code) {
|
||||||
const state = { source: code };
|
|
||||||
const astTransformVisitor = {
|
const astTransformVisitor = {
|
||||||
noScope: true,
|
noScope: true,
|
||||||
enter(path) {
|
enter(path) {
|
||||||
const node = path.node;
|
const { node } = path;
|
||||||
|
|
||||||
// private var to track original node type
|
|
||||||
node._babelType = node.type;
|
|
||||||
|
|
||||||
if (node.innerComments) {
|
if (node.innerComments) {
|
||||||
delete node.innerComments;
|
delete node.innerComments;
|
||||||
@ -23,7 +19,16 @@ function convertNodes(ast, code) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
exit(path) {
|
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()) {
|
if (path.isTypeParameter()) {
|
||||||
node.type = "Identifier";
|
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
|
// 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.Property = t.VISITOR_KEYS.ObjectProperty;
|
||||||
@ -95,19 +101,14 @@ function convertNodes(ast, code) {
|
|||||||
function convertProgramNode(ast) {
|
function convertProgramNode(ast) {
|
||||||
ast.type = "Program";
|
ast.type = "Program";
|
||||||
ast.sourceType = ast.program.sourceType;
|
ast.sourceType = ast.program.sourceType;
|
||||||
ast.directives = ast.program.directives;
|
|
||||||
ast.body = ast.program.body;
|
ast.body = ast.program.body;
|
||||||
delete ast.program;
|
delete ast.program;
|
||||||
|
delete ast.errors;
|
||||||
|
|
||||||
if (ast.comments.length) {
|
if (ast.comments.length) {
|
||||||
const lastComment = ast.comments[ast.comments.length - 1];
|
const lastComment = ast.comments[ast.comments.length - 1];
|
||||||
|
|
||||||
if (!ast.tokens.length) {
|
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 {
|
|
||||||
const lastToken = ast.tokens[ast.tokens.length - 1];
|
const lastToken = ast.tokens[ast.tokens.length - 1];
|
||||||
|
|
||||||
if (lastComment.end > lastToken.end) {
|
if (lastComment.end > lastToken.end) {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
export default function convertComments(comments) {
|
export default function convertComments(comments) {
|
||||||
for (let i = 0; i < comments.length; i++) {
|
for (const comment of comments) {
|
||||||
const comment = comments[i];
|
|
||||||
if (comment.type === "CommentBlock") {
|
if (comment.type === "CommentBlock") {
|
||||||
comment.type = "Block";
|
comment.type = "Block";
|
||||||
} else if (comment.type === "CommentLine") {
|
} else if (comment.type === "CommentLine") {
|
||||||
|
|||||||
@ -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)})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +1,88 @@
|
|||||||
import assert from "assert";
|
import path from "path";
|
||||||
import espree from "espree";
|
|
||||||
import escope from "eslint-scope";
|
import escope from "eslint-scope";
|
||||||
import unpad from "dedent";
|
import unpad from "dedent";
|
||||||
import { parseForESLint } from "../src";
|
import { parseForESLint } from "../src";
|
||||||
import assertImplementsAST from "./helpers/assert-implements-ast";
|
|
||||||
|
|
||||||
const babelOptions = {
|
const BABEL_OPTIONS = {
|
||||||
configFile: require.resolve(
|
configFile: require.resolve(
|
||||||
"@babel/eslint-shared-fixtures/config/babel.config.js",
|
"@babel/eslint-shared-fixtures/config/babel.config.js",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
const ALLOWED_PROPERTIES = [
|
||||||
|
"importKind",
|
||||||
|
"exportKind",
|
||||||
|
"variance",
|
||||||
|
"typeArguments",
|
||||||
|
];
|
||||||
|
|
||||||
function parseAndAssertSame(code) {
|
function deeplyRemoveProperties(obj, props) {
|
||||||
code = unpad(code);
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
const esAST = espree.parse(code, {
|
if (typeof v === "object") {
|
||||||
ecmaFeatures: {
|
if (Array.isArray(v)) {
|
||||||
// enable JSX parsing
|
for (const el of v) {
|
||||||
jsx: true,
|
if (el != null) deeplyRemoveProperties(el, props);
|
||||||
// enable return in global scope
|
}
|
||||||
globalReturn: true,
|
}
|
||||||
// enable implied strict mode (if ecmaVersion >= 5)
|
|
||||||
impliedStrict: true,
|
if (props.includes(k)) delete obj[k];
|
||||||
// allow experimental object rest/spread
|
else if (v != null) deeplyRemoveProperties(v, props);
|
||||||
experimentalObjectRestSpread: true,
|
continue;
|
||||||
},
|
}
|
||||||
tokens: true,
|
|
||||||
loc: true,
|
if (props.includes(k)) delete obj[k];
|
||||||
range: true,
|
}
|
||||||
comment: true,
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
sourceType: "module",
|
|
||||||
});
|
|
||||||
const babylonAST = parseForESLint(code, {
|
|
||||||
eslintVisitorKeys: true,
|
|
||||||
eslintScopeManager: true,
|
|
||||||
babelOptions,
|
|
||||||
}).ast;
|
|
||||||
assertImplementsAST(esAST, babylonAST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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", () => {
|
describe("compatibility", () => {
|
||||||
it("should allow ast.analyze to be called without options", function() {
|
it("should allow ast.analyze to be called without options", function() {
|
||||||
const esAST = parseForESLint("`test`", {
|
const ast = parseForESLint("`test`", {
|
||||||
eslintScopeManager: true,
|
eslintScopeManager: true,
|
||||||
eslintVisitorKeys: true,
|
eslintVisitorKeys: true,
|
||||||
babelOptions,
|
babelOptions: BABEL_OPTIONS,
|
||||||
}).ast;
|
}).ast;
|
||||||
expect(() => {
|
expect(() => {
|
||||||
escope.analyze(esAST);
|
escope.analyze(ast);
|
||||||
}).not.toThrow(new TypeError("Should allow no options argument."));
|
}).not.toThrow(new TypeError("Should allow no options argument."));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -244,9 +278,9 @@ describe("babylon-to-espree", () => {
|
|||||||
const babylonAST = parseForESLint(code, {
|
const babylonAST = parseForESLint(code, {
|
||||||
eslintVisitorKeys: true,
|
eslintVisitorKeys: true,
|
||||||
eslintScopeManager: true,
|
eslintScopeManager: true,
|
||||||
babelOptions,
|
babelOptions: BABEL_OPTIONS,
|
||||||
}).ast;
|
}).ast;
|
||||||
assert.strictEqual(babylonAST.tokens[1].type, "Punctuator");
|
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Espree doesn't support the nullish coalescing operator yet
|
// Espree doesn't support the nullish coalescing operator yet
|
||||||
@ -255,9 +289,9 @@ describe("babylon-to-espree", () => {
|
|||||||
const babylonAST = parseForESLint(code, {
|
const babylonAST = parseForESLint(code, {
|
||||||
eslintVisitorKeys: true,
|
eslintVisitorKeys: true,
|
||||||
eslintScopeManager: true,
|
eslintScopeManager: true,
|
||||||
babelOptions,
|
babelOptions: BABEL_OPTIONS,
|
||||||
}).ast;
|
}).ast;
|
||||||
assert.strictEqual(babylonAST.tokens[1].type, "Punctuator");
|
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Espree doesn't support the pipeline operator yet
|
// Espree doesn't support the pipeline operator yet
|
||||||
@ -266,9 +300,9 @@ describe("babylon-to-espree", () => {
|
|||||||
const babylonAST = parseForESLint(code, {
|
const babylonAST = parseForESLint(code, {
|
||||||
eslintVisitorKeys: true,
|
eslintVisitorKeys: true,
|
||||||
eslintScopeManager: true,
|
eslintScopeManager: true,
|
||||||
babelOptions,
|
babelOptions: BABEL_OPTIONS,
|
||||||
}).ast;
|
}).ast;
|
||||||
assert.strictEqual(babylonAST.tokens[1].type, "Punctuator");
|
expect(babylonAST.tokens[1].type).toEqual("Punctuator");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Espree doesn't support private fields yet
|
// Espree doesn't support private fields yet
|
||||||
@ -277,19 +311,17 @@ describe("babylon-to-espree", () => {
|
|||||||
const babylonAST = parseForESLint(code, {
|
const babylonAST = parseForESLint(code, {
|
||||||
eslintVisitorKeys: true,
|
eslintVisitorKeys: true,
|
||||||
eslintScopeManager: true,
|
eslintScopeManager: true,
|
||||||
babelOptions,
|
babelOptions: BABEL_OPTIONS,
|
||||||
}).ast;
|
}).ast;
|
||||||
assert.strictEqual(babylonAST.tokens[3].type, "Punctuator");
|
expect(babylonAST.tokens[3].type).toEqual("Punctuator");
|
||||||
assert.strictEqual(babylonAST.tokens[3].value, "#");
|
expect(babylonAST.tokens[3].value).toEqual("#");
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-disabled-tests
|
it("empty program with line comment", () => {
|
||||||
it.skip("empty program with line comment", () => {
|
|
||||||
parseAndAssertSame("// single comment");
|
parseAndAssertSame("// single comment");
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-disabled-tests
|
it("empty program with block comment", () => {
|
||||||
it.skip("empty program with block comment", () => {
|
|
||||||
parseAndAssertSame(" /* multiline\n * comment\n*/");
|
parseAndAssertSame(" /* multiline\n * comment\n*/");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -448,9 +480,13 @@ describe("babylon-to-espree", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("do not allow import export everywhere", () => {
|
it("do not allow import export everywhere", () => {
|
||||||
assert.throws(() => {
|
expect(() => {
|
||||||
parseAndAssertSame('function F() { import a from "a"; }');
|
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", () => {
|
it("return outside function", () => {
|
||||||
@ -458,9 +494,9 @@ describe("babylon-to-espree", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("super outside method", () => {
|
it("super outside method", () => {
|
||||||
assert.throws(() => {
|
expect(() => {
|
||||||
parseAndAssertSame("function F() { super(); }");
|
parseAndAssertSame("function F() { super(); }");
|
||||||
}, /SyntaxError: 'super' keyword outside a method/);
|
}).toThrow(new SyntaxError("'super' keyword outside a method"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("StringLiteral", () => {
|
it("StringLiteral", () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user