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
Breaking: align babel-eslint-parser & espree #11137
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was wondering why we had this in the first place, but a quick search on GitHub (search) shows that it's only used in snapshot tests output: it can be safely removed. |
||
const { node } = path; | ||
|
||
if (node.innerComments) { | ||
delete node.innerComments; | ||
|
@@ -23,7 +19,15 @@ function convertNodes(ast, code) { | |
} | ||
}, | ||
exit(path) { | ||
const node = path.node; | ||
const { node } = path; | ||
|
||
if (Object.hasOwnProperty.call(node, "extra")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems unlikely ESLint would need this, but please let me know if I'm wrong. I also don't see this referenced anywhere in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK I think we can even remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, thanks! |
||
delete node.extra; | ||
} | ||
|
||
if (node.loc && Object.hasOwnProperty.call(node.loc, "identifierName")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems unlikely ESLint would need this, but please let me know if I'm wrong. I also don't see this referenced anywhere in |
||
delete node.loc.identifierName; | ||
} | ||
|
||
if (path.isTypeParameter()) { | ||
node.type = "Identifier"; | ||
|
@@ -74,6 +78,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 +100,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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra property that ESLint doesn't use (though we are planning to implement a top-level |
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This behavior doesn't match Espree's (it probably did at one point and has since changed). |
||
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) { | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,85 @@ | ||
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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming we want to leave There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to add tests for third-party ESLint plugins in the e2e Circle CI workflow. |
||
"exportKind", | ||
"variance", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is used in |
||
"typeArguments", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not used in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we should keep it: if espree doesn't support flow, we don't need to align in this case. |
||
]; | ||
|
||
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); | ||
} | ||
} | ||
|
||
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); | ||
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`", { | ||
eslintScopeManager: true, | ||
eslintVisitorKeys: true, | ||
babelOptions, | ||
babelOptions: BABEL_OPTIONS, | ||
}).ast; | ||
expect(() => { | ||
escope.analyze(esAST); | ||
|
@@ -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", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now aligned! 🎉 |
||
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,19 +480,23 @@ 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", () => { | ||
parseAndAssertSame("return;"); | ||
}); | ||
|
||
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", () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on this? Seems like we should ensure we're using the version of Espree that's included with the version of ESLint we're testing against.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think it should be a combo peer dep and dep, that way
npm ls
will fail (andnpm install
in npm 7+) when the peer dep isn't met.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolo-ribaudo @JLHwung @existentialism Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we use
require.resolve
to get ESLint's dep, this shouldn't be needed anymore.