[babel 8] Move ESLint parsing to a Worker (#13199)
This commit is contained in:
parent
c2181343f1
commit
9d620c2d42
@ -37,9 +37,9 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"packages/*/src/**/*.{js,ts}",
|
||||
"codemods/*/src/**/*.{js,ts}",
|
||||
"eslint/*/src/**/*.{js,ts}",
|
||||
"packages/*/src/**/*.{js,ts,cjs}",
|
||||
"codemods/*/src/**/*.{js,ts,cjs}",
|
||||
"eslint/*/src/**/*.{js,ts,cjs}",
|
||||
],
|
||||
rules: {
|
||||
"@babel/development/no-undefined-identifier": "error",
|
||||
@ -130,6 +130,12 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["eslint/babel-eslint-parser/src/**/*.js"],
|
||||
rules: {
|
||||
"no-restricted-imports": ["error", "@babel/core"],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/babel-plugin-transform-runtime/scripts/**/*.js"],
|
||||
rules: {
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -196,6 +196,12 @@ jobs:
|
||||
BABEL_ENV: test
|
||||
BABEL_8_BREAKING: true
|
||||
STRIP_BABEL_8_FLAG: true
|
||||
- name: Lint
|
||||
run: make lint
|
||||
env:
|
||||
BABEL_ENV: test
|
||||
BABEL_8_BREAKING: true
|
||||
BABEL_TYPES_8_BREAKING: true
|
||||
- name: Test
|
||||
# Hack: --color has supports-color@5 returned true for GitHub CI
|
||||
# Remove once `chalk` is bumped to 4.0.
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
"printWidth": 80,
|
||||
"overrides": [{
|
||||
"files": [
|
||||
"**/{codemods,eslint,packages}/*/{src,test}/**/*.{js,ts}"
|
||||
"**/{codemods,eslint,packages}/*/{src,test}/**/*.{js,ts,cjs}"
|
||||
],
|
||||
"excludeFiles": ["**/packages/babel-helpers/src/helpers/**/*.js"],
|
||||
"options": {
|
||||
|
||||
@ -19,10 +19,10 @@
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || >=14.0.0"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"main": "./lib/index.cjs",
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
".": "./lib/index.cjs",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -1,31 +1,37 @@
|
||||
import { types as t } from "@babel/core";
|
||||
import escope from "eslint-scope";
|
||||
import { Definition } from "eslint-scope/lib/definition";
|
||||
import OriginalPatternVisitor from "eslint-scope/lib/pattern-visitor";
|
||||
import OriginalReferencer from "eslint-scope/lib/referencer";
|
||||
import { getKeys as fallback } from "eslint-visitor-keys";
|
||||
import childVisitorKeys from "./visitor-keys";
|
||||
const escope = require("eslint-scope");
|
||||
const { Definition } = require("eslint-scope/lib/definition");
|
||||
const OriginalPatternVisitor = require("eslint-scope/lib/pattern-visitor");
|
||||
const OriginalReferencer = require("eslint-scope/lib/referencer");
|
||||
const { getKeys: fallback } = require("eslint-visitor-keys");
|
||||
|
||||
const flowFlippedAliasKeys = t.FLIPPED_ALIAS_KEYS.Flow.concat([
|
||||
"ArrayPattern",
|
||||
"ClassDeclaration",
|
||||
"ClassExpression",
|
||||
"FunctionDeclaration",
|
||||
"FunctionExpression",
|
||||
"Identifier",
|
||||
"ObjectPattern",
|
||||
"RestElement",
|
||||
]);
|
||||
const { getTypesInfo, getVisitorKeys } = require("./client.cjs");
|
||||
|
||||
const visitorKeysMap = Object.entries(t.VISITOR_KEYS).reduce(
|
||||
(acc, [key, value]) => {
|
||||
let visitorKeysMap;
|
||||
function getVisitorValues(nodeType) {
|
||||
if (visitorKeysMap) return visitorKeysMap[nodeType];
|
||||
|
||||
const { FLOW_FLIPPED_ALIAS_KEYS, VISITOR_KEYS } = getTypesInfo();
|
||||
|
||||
const flowFlippedAliasKeys = FLOW_FLIPPED_ALIAS_KEYS.concat([
|
||||
"ArrayPattern",
|
||||
"ClassDeclaration",
|
||||
"ClassExpression",
|
||||
"FunctionDeclaration",
|
||||
"FunctionExpression",
|
||||
"Identifier",
|
||||
"ObjectPattern",
|
||||
"RestElement",
|
||||
]);
|
||||
|
||||
visitorKeysMap = Object.entries(VISITOR_KEYS).reduce((acc, [key, value]) => {
|
||||
if (!flowFlippedAliasKeys.includes(value)) {
|
||||
acc[key] = value;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
}, {});
|
||||
|
||||
return visitorKeysMap[nodeType];
|
||||
}
|
||||
|
||||
const propertyTypes = {
|
||||
// loops
|
||||
@ -65,7 +71,7 @@ class Referencer extends OriginalReferencer {
|
||||
|
||||
// Visit type annotations.
|
||||
this._checkIdentifierOrVisit(node.typeAnnotation);
|
||||
if (t.isAssignmentPattern(node)) {
|
||||
if (node.type === "AssignmentPattern") {
|
||||
this._checkIdentifierOrVisit(node.left.typeAnnotation);
|
||||
}
|
||||
|
||||
@ -258,7 +264,7 @@ class Referencer extends OriginalReferencer {
|
||||
}
|
||||
|
||||
// get property to check (params, id, etc...)
|
||||
const visitorValues = visitorKeysMap[node.type];
|
||||
const visitorValues = getVisitorValues(node.type);
|
||||
if (!visitorValues) {
|
||||
return;
|
||||
}
|
||||
@ -322,7 +328,7 @@ class Referencer extends OriginalReferencer {
|
||||
}
|
||||
}
|
||||
|
||||
export default function analyzeScope(ast, parserOptions) {
|
||||
module.exports = function analyzeScope(ast, parserOptions) {
|
||||
const options = {
|
||||
ignoreEval: true,
|
||||
optimistic: false,
|
||||
@ -337,7 +343,7 @@ export default function analyzeScope(ast, parserOptions) {
|
||||
fallback,
|
||||
};
|
||||
|
||||
options.childVisitorKeys = childVisitorKeys;
|
||||
options.childVisitorKeys = getVisitorKeys();
|
||||
|
||||
const scopeManager = new escope.ScopeManager(options);
|
||||
const referencer = new Referencer(options, scopeManager);
|
||||
@ -345,4 +351,4 @@ export default function analyzeScope(ast, parserOptions) {
|
||||
referencer.visit(ast);
|
||||
|
||||
return scopeManager;
|
||||
}
|
||||
};
|
||||
67
eslint/babel-eslint-parser/src/client.cjs
Normal file
67
eslint/babel-eslint-parser/src/client.cjs
Normal file
@ -0,0 +1,67 @@
|
||||
const path = require("path");
|
||||
|
||||
let send;
|
||||
|
||||
exports.getVersion = sendCached("GET_VERSION");
|
||||
|
||||
exports.getTypesInfo = sendCached("GET_TYPES_INFO");
|
||||
|
||||
exports.getVisitorKeys = sendCached("GET_VISITOR_KEYS");
|
||||
|
||||
exports.getTokLabels = sendCached("GET_TOKEN_LABELS");
|
||||
|
||||
exports.maybeParse = (code, options) => send("MAYBE_PARSE", { code, options });
|
||||
|
||||
function sendCached(action) {
|
||||
let cache = null;
|
||||
|
||||
return () => {
|
||||
if (!cache) cache = send(action, undefined);
|
||||
return cache;
|
||||
};
|
||||
}
|
||||
|
||||
if (process.env.BABEL_8_BREAKING) {
|
||||
const {
|
||||
Worker,
|
||||
receiveMessageOnPort,
|
||||
MessageChannel,
|
||||
SHARE_ENV,
|
||||
} = require("worker_threads");
|
||||
|
||||
// We need to run Babel in a worker for two reasons:
|
||||
// 1. ESLint workers must be CJS files, and this is a problem
|
||||
// since Babel 8+ uses native ESM
|
||||
// 2. ESLint parsers must run synchronously, but many steps
|
||||
// of Babel's config loading (which is done for each file)
|
||||
// can be asynchronous
|
||||
// If ESLint starts supporting async parsers, we can move
|
||||
// everything back to the main thread.
|
||||
const worker = new Worker(
|
||||
path.resolve(__dirname, "../lib/worker/index.cjs"),
|
||||
{ env: SHARE_ENV },
|
||||
);
|
||||
|
||||
// The worker will never exit by itself. Prevent it from keeping
|
||||
// the main process alive.
|
||||
worker.unref();
|
||||
|
||||
const signal = new Int32Array(new SharedArrayBuffer(4));
|
||||
|
||||
send = (action, payload) => {
|
||||
signal[0] = 0;
|
||||
const subChannel = new MessageChannel();
|
||||
|
||||
worker.postMessage({ signal, port: subChannel.port1, action, payload }, [
|
||||
subChannel.port1,
|
||||
]);
|
||||
|
||||
Atomics.wait(signal, 0, 0);
|
||||
const { message } = receiveMessageOnPort(subChannel.port2);
|
||||
|
||||
if (message.error) throw Object.assign(message.error, message.errorData);
|
||||
else return message.result;
|
||||
};
|
||||
} else {
|
||||
send = require("./worker/index.cjs");
|
||||
}
|
||||
20
eslint/babel-eslint-parser/src/configuration.cjs
Normal file
20
eslint/babel-eslint-parser/src/configuration.cjs
Normal file
@ -0,0 +1,20 @@
|
||||
exports.normalizeESLintConfig = function (options) {
|
||||
const {
|
||||
babelOptions = {},
|
||||
// ESLint sets ecmaVersion: undefined when ecmaVersion is not set in the config.
|
||||
ecmaVersion = 2020,
|
||||
sourceType = "module",
|
||||
allowImportExportEverywhere = false,
|
||||
requireConfigFile = true,
|
||||
...otherOptions
|
||||
} = options;
|
||||
|
||||
return {
|
||||
babelOptions,
|
||||
ecmaVersion,
|
||||
sourceType,
|
||||
allowImportExportEverywhere,
|
||||
requireConfigFile,
|
||||
...otherOptions,
|
||||
};
|
||||
};
|
||||
138
eslint/babel-eslint-parser/src/convert/convertAST.cjs
Normal file
138
eslint/babel-eslint-parser/src/convert/convertAST.cjs
Normal file
@ -0,0 +1,138 @@
|
||||
function* it(children) {
|
||||
if (Array.isArray(children)) yield* children;
|
||||
else yield children;
|
||||
}
|
||||
|
||||
function traverse(node, visitorKeys, visitor) {
|
||||
const { type } = node;
|
||||
if (!type) return;
|
||||
const keys = visitorKeys[type];
|
||||
if (!keys) return;
|
||||
|
||||
for (const key of keys) {
|
||||
for (const child of it(node[key])) {
|
||||
if (child && typeof child === "object") {
|
||||
visitor.enter(child);
|
||||
traverse(child, visitorKeys, visitor);
|
||||
visitor.exit(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const convertNodesVisitor = {
|
||||
enter(node) {
|
||||
if (node.innerComments) {
|
||||
delete node.innerComments;
|
||||
}
|
||||
|
||||
if (node.trailingComments) {
|
||||
delete node.trailingComments;
|
||||
}
|
||||
|
||||
if (node.leadingComments) {
|
||||
delete node.leadingComments;
|
||||
}
|
||||
},
|
||||
exit(node) {
|
||||
// Used internally by @babel/parser.
|
||||
if (node.extra) {
|
||||
delete node.extra;
|
||||
}
|
||||
|
||||
if (node?.loc.identifierName) {
|
||||
delete node.loc.identifierName;
|
||||
}
|
||||
|
||||
if (node.type === "TypeParameter") {
|
||||
node.type = "Identifier";
|
||||
node.typeAnnotation = node.bound;
|
||||
delete node.bound;
|
||||
}
|
||||
|
||||
// flow: prevent "no-undef"
|
||||
// for "Component" in: "let x: React.Component"
|
||||
if (node.type === "QualifiedTypeIdentifier") {
|
||||
delete node.id;
|
||||
}
|
||||
// for "b" in: "var a: { b: Foo }"
|
||||
if (node.type === "ObjectTypeProperty") {
|
||||
delete node.key;
|
||||
}
|
||||
// for "indexer" in: "var a: {[indexer: string]: number}"
|
||||
if (node.type === "ObjectTypeIndexer") {
|
||||
delete node.id;
|
||||
}
|
||||
// for "param" in: "var a: { func(param: Foo): Bar };"
|
||||
if (node.type === "FunctionTypeParam") {
|
||||
delete node.name;
|
||||
}
|
||||
|
||||
// modules
|
||||
if (node.type === "ImportDeclaration") {
|
||||
delete node.isType;
|
||||
}
|
||||
|
||||
// template string range fixes
|
||||
if (node.type === "TemplateLiteral") {
|
||||
for (let i = 0; i < node.quasis.length; i++) {
|
||||
const q = node.quasis[i];
|
||||
q.range[0] -= 1;
|
||||
if (q.tail) {
|
||||
q.range[1] += 1;
|
||||
} else {
|
||||
q.range[1] += 2;
|
||||
}
|
||||
q.loc.start.column -= 1;
|
||||
if (q.tail) {
|
||||
q.loc.end.column += 1;
|
||||
} else {
|
||||
q.loc.end.column += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function convertNodes(ast, visitorKeys) {
|
||||
traverse(ast, visitorKeys, convertNodesVisitor);
|
||||
}
|
||||
|
||||
function convertProgramNode(ast) {
|
||||
ast.type = "Program";
|
||||
ast.sourceType = ast.program.sourceType;
|
||||
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) {
|
||||
const lastToken = ast.tokens[ast.tokens.length - 1];
|
||||
|
||||
if (lastComment.end > lastToken.end) {
|
||||
// If there is a comment after the last token, the program ends at the
|
||||
// last token and not the comment
|
||||
ast.range[1] = lastToken.end;
|
||||
ast.loc.end.line = lastToken.loc.end.line;
|
||||
ast.loc.end.column = lastToken.loc.end.column;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!ast.tokens.length) {
|
||||
ast.loc.start.line = 1;
|
||||
ast.loc.end.line = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.body && ast.body.length > 0) {
|
||||
ast.loc.start.line = ast.body[0].loc.start.line;
|
||||
ast.range[0] = ast.body[0].start;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function convertAST(ast, visitorKeys) {
|
||||
convertNodes(ast, visitorKeys);
|
||||
convertProgramNode(ast);
|
||||
};
|
||||
@ -1,148 +0,0 @@
|
||||
import { types as t, traverse } from "@babel/core";
|
||||
import { newTypes, conflictTypes } from "../visitor-keys";
|
||||
|
||||
function convertNodes(ast, code) {
|
||||
const astTransformVisitor = {
|
||||
noScope: true,
|
||||
enter(path) {
|
||||
const { node } = path;
|
||||
|
||||
if (node.innerComments) {
|
||||
delete node.innerComments;
|
||||
}
|
||||
|
||||
if (node.trailingComments) {
|
||||
delete node.trailingComments;
|
||||
}
|
||||
|
||||
if (node.leadingComments) {
|
||||
delete node.leadingComments;
|
||||
}
|
||||
},
|
||||
exit(path) {
|
||||
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";
|
||||
node.typeAnnotation = node.bound;
|
||||
delete node.bound;
|
||||
}
|
||||
|
||||
// flow: prevent "no-undef"
|
||||
// for "Component" in: "let x: React.Component"
|
||||
if (path.isQualifiedTypeIdentifier()) {
|
||||
delete node.id;
|
||||
}
|
||||
// for "b" in: "var a: { b: Foo }"
|
||||
if (path.isObjectTypeProperty()) {
|
||||
delete node.key;
|
||||
}
|
||||
// for "indexer" in: "var a: {[indexer: string]: number}"
|
||||
if (path.isObjectTypeIndexer()) {
|
||||
delete node.id;
|
||||
}
|
||||
// for "param" in: "var a: { func(param: Foo): Bar };"
|
||||
if (path.isFunctionTypeParam()) {
|
||||
delete node.name;
|
||||
}
|
||||
|
||||
// modules
|
||||
if (path.isImportDeclaration()) {
|
||||
delete node.isType;
|
||||
}
|
||||
|
||||
// template string range fixes
|
||||
if (path.isTemplateLiteral()) {
|
||||
for (let i = 0; i < node.quasis.length; i++) {
|
||||
const q = node.quasis[i];
|
||||
q.range[0] -= 1;
|
||||
if (q.tail) {
|
||||
q.range[1] += 1;
|
||||
} else {
|
||||
q.range[1] += 2;
|
||||
}
|
||||
q.loc.start.column -= 1;
|
||||
if (q.tail) {
|
||||
q.loc.end.column += 1;
|
||||
} else {
|
||||
q.loc.end.column += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
const state = { source: code };
|
||||
const oldVisitorKeys = new Map();
|
||||
|
||||
try {
|
||||
for (const [type, visitorKey] of Object.entries(conflictTypes)) {
|
||||
// backup conflicted visitor keys
|
||||
oldVisitorKeys.set(type, t.VISITOR_KEYS[type]);
|
||||
|
||||
t.VISITOR_KEYS[type] = visitorKey;
|
||||
}
|
||||
for (const [type, visitorKey] of Object.entries(newTypes)) {
|
||||
t.VISITOR_KEYS[type] = visitorKey;
|
||||
}
|
||||
|
||||
traverse(ast, astTransformVisitor, null, state);
|
||||
} finally {
|
||||
// These can be safely deleted because they are not defined in the original visitor keys.
|
||||
for (const type of Object.keys(newTypes)) {
|
||||
delete t.VISITOR_KEYS[type];
|
||||
}
|
||||
|
||||
// These should be restored
|
||||
for (const type of Object.keys(conflictTypes)) {
|
||||
t.VISITOR_KEYS[type] = oldVisitorKeys.get(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertProgramNode(ast) {
|
||||
ast.type = "Program";
|
||||
ast.sourceType = ast.program.sourceType;
|
||||
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) {
|
||||
const lastToken = ast.tokens[ast.tokens.length - 1];
|
||||
|
||||
if (lastComment.end > lastToken.end) {
|
||||
// If there is a comment after the last token, the program ends at the
|
||||
// last token and not the comment
|
||||
ast.range[1] = lastToken.end;
|
||||
ast.loc.end.line = lastToken.loc.end.line;
|
||||
ast.loc.end.column = lastToken.loc.end.column;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!ast.tokens.length) {
|
||||
ast.loc.start.line = 1;
|
||||
ast.loc.end.line = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.body && ast.body.length > 0) {
|
||||
ast.loc.start.line = ast.body[0].loc.start.line;
|
||||
ast.range[0] = ast.body[0].start;
|
||||
}
|
||||
}
|
||||
|
||||
export default function convertAST(ast, code) {
|
||||
convertNodes(ast, code);
|
||||
convertProgramNode(ast);
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
export default function convertComments(comments) {
|
||||
module.exports = function convertComments(comments) {
|
||||
for (const comment of comments) {
|
||||
if (comment.type === "CommentBlock") {
|
||||
comment.type = "Block";
|
||||
@ -11,4 +11,4 @@ export default function convertComments(comments) {
|
||||
comment.range = [comment.start, comment.end];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,12 +1,4 @@
|
||||
import { tokTypes } from "@babel/core";
|
||||
|
||||
const tl = (
|
||||
process.env.BABEL_8_BREAKING
|
||||
? Object.fromEntries
|
||||
: p => p.reduce((o, [k, v]) => ({ ...o, [k]: v }), {})
|
||||
)(Object.keys(tokTypes).map(key => [key, tokTypes[key].label]));
|
||||
|
||||
function convertTemplateType(tokens) {
|
||||
function convertTemplateType(tokens, tl) {
|
||||
let curlyBrace = null;
|
||||
let templateTokens = [];
|
||||
const result = [];
|
||||
@ -97,7 +89,7 @@ function convertTemplateType(tokens) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertToken(token, source) {
|
||||
function convertToken(token, source, tl) {
|
||||
const { type } = token;
|
||||
const { label } = type;
|
||||
token.range = [token.start, token.end];
|
||||
@ -192,8 +184,8 @@ function convertToken(token, source) {
|
||||
return token;
|
||||
}
|
||||
|
||||
export default function convertTokens(tokens, code) {
|
||||
return convertTemplateType(tokens)
|
||||
module.exports = function convertTokens(tokens, code, tl) {
|
||||
return convertTemplateType(tokens, tl)
|
||||
.filter(t => t.type !== "CommentLine" && t.type !== "CommentBlock")
|
||||
.map(t => convertToken(t, code));
|
||||
}
|
||||
.map(t => convertToken(t, code, tl));
|
||||
};
|
||||
18
eslint/babel-eslint-parser/src/convert/index.cjs
Normal file
18
eslint/babel-eslint-parser/src/convert/index.cjs
Normal file
@ -0,0 +1,18 @@
|
||||
const convertTokens = require("./convertTokens.cjs");
|
||||
const convertComments = require("./convertComments.cjs");
|
||||
const convertAST = require("./convertAST.cjs");
|
||||
|
||||
exports.ast = function convert(ast, code, tokLabels, visitorKeys) {
|
||||
ast.tokens = convertTokens(ast.tokens, code, tokLabels);
|
||||
convertComments(ast.comments);
|
||||
convertAST(ast, visitorKeys);
|
||||
return ast;
|
||||
};
|
||||
|
||||
exports.error = function convertError(err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
err.lineNumber = err.loc.line;
|
||||
err.column = err.loc.column;
|
||||
}
|
||||
return err;
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
import convertTokens from "./convertTokens";
|
||||
import convertComments from "./convertComments";
|
||||
import convertAST from "./convertAST";
|
||||
|
||||
export default function (ast, code) {
|
||||
ast.tokens = convertTokens(ast.tokens, code);
|
||||
convertComments(ast.comments);
|
||||
convertAST(ast, code);
|
||||
}
|
||||
63
eslint/babel-eslint-parser/src/index.cjs
Normal file
63
eslint/babel-eslint-parser/src/index.cjs
Normal file
@ -0,0 +1,63 @@
|
||||
const semver = require("semver");
|
||||
const { normalizeESLintConfig } = require("./configuration.cjs");
|
||||
const analyzeScope = require("./analyze-scope.cjs");
|
||||
const {
|
||||
getVersion,
|
||||
getVisitorKeys,
|
||||
getTokLabels,
|
||||
maybeParse,
|
||||
} = require("./client.cjs");
|
||||
const convert = require("./convert/index.cjs");
|
||||
|
||||
const babelParser = require(require.resolve("@babel/parser", {
|
||||
paths: [require.resolve("@babel/core/package.json")],
|
||||
}));
|
||||
|
||||
let isRunningMinSupportedCoreVersion = null;
|
||||
|
||||
function baseParse(code, options) {
|
||||
// Ensure we're using a version of `@babel/core` that includes `parse()` and `tokTypes`.
|
||||
const minSupportedCoreVersion = ">=7.2.0";
|
||||
|
||||
if (typeof isRunningMinSupportedCoreVersion !== "boolean") {
|
||||
isRunningMinSupportedCoreVersion = semver.satisfies(
|
||||
getVersion(),
|
||||
minSupportedCoreVersion,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isRunningMinSupportedCoreVersion) {
|
||||
throw new Error(
|
||||
`@babel/eslint-parser@${
|
||||
PACKAGE_JSON.version
|
||||
} does not support @babel/core@${getVersion()}. Please upgrade to @babel/core@${minSupportedCoreVersion}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { ast, parserOptions } = maybeParse(code, options);
|
||||
|
||||
if (ast) return ast;
|
||||
|
||||
try {
|
||||
return convert.ast(
|
||||
babelParser.parse(code, parserOptions),
|
||||
code,
|
||||
getTokLabels(),
|
||||
getVisitorKeys(),
|
||||
);
|
||||
} catch (err) {
|
||||
throw convert.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
exports.parse = function (code, options = {}) {
|
||||
return baseParse(code, normalizeESLintConfig(options));
|
||||
};
|
||||
|
||||
exports.parseForESLint = function (code, options = {}) {
|
||||
const normalizedOptions = normalizeESLintConfig(options);
|
||||
const ast = baseParse(code, normalizedOptions);
|
||||
const scopeManager = analyzeScope(ast, normalizedOptions);
|
||||
|
||||
return { ast, scopeManager, visitorKeys: getVisitorKeys() };
|
||||
};
|
||||
@ -1,61 +0,0 @@
|
||||
import semver from "semver";
|
||||
import {
|
||||
version as babelCoreVersion,
|
||||
parseSync as babelParse,
|
||||
} from "@babel/core";
|
||||
import {
|
||||
normalizeBabelParseConfig,
|
||||
normalizeESLintConfig,
|
||||
} from "./configuration";
|
||||
import convert from "./convert";
|
||||
import analyzeScope from "./analyze-scope";
|
||||
import visitorKeys from "./visitor-keys";
|
||||
|
||||
let isRunningMinSupportedCoreVersion = null;
|
||||
|
||||
function baseParse(code, options) {
|
||||
// Ensure we're using a version of `@babel/core` that includes `parse()` and `tokTypes`.
|
||||
const minSupportedCoreVersion = ">=7.2.0";
|
||||
|
||||
if (typeof isRunningMinSupportedCoreVersion !== "boolean") {
|
||||
isRunningMinSupportedCoreVersion = semver.satisfies(
|
||||
babelCoreVersion,
|
||||
minSupportedCoreVersion,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isRunningMinSupportedCoreVersion) {
|
||||
throw new Error(
|
||||
`@babel/eslint-parser@${PACKAGE_JSON.version} does not support @babel/core@${babelCoreVersion}. Please upgrade to @babel/core@${minSupportedCoreVersion}.`,
|
||||
);
|
||||
}
|
||||
|
||||
let ast;
|
||||
|
||||
try {
|
||||
ast = babelParse(code, normalizeBabelParseConfig(options));
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
err.lineNumber = err.loc.line;
|
||||
err.column = err.loc.column;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
convert(ast, code);
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function parse(code, options = {}) {
|
||||
return baseParse(code, normalizeESLintConfig(options));
|
||||
}
|
||||
|
||||
export function parseForESLint(code, options = {}) {
|
||||
const normalizedOptions = normalizeESLintConfig(options);
|
||||
const ast = baseParse(code, normalizedOptions);
|
||||
const scopeManager = analyzeScope(ast, normalizedOptions);
|
||||
|
||||
return { ast, scopeManager, visitorKeys };
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
import { types as t } from "@babel/core";
|
||||
import { KEYS as ESLINT_VISITOR_KEYS } from "eslint-visitor-keys";
|
||||
|
||||
// AST Types that are not presented in Babel AST
|
||||
export const newTypes = {
|
||||
ChainExpression: ESLINT_VISITOR_KEYS.ChainExpression,
|
||||
ImportExpression: ESLINT_VISITOR_KEYS.ImportExpression,
|
||||
Literal: ESLINT_VISITOR_KEYS.Literal,
|
||||
MethodDefinition: ["decorators"].concat(ESLINT_VISITOR_KEYS.MethodDefinition),
|
||||
Property: ["decorators"].concat(ESLINT_VISITOR_KEYS.Property),
|
||||
PropertyDefinition: ["decorators"].concat(
|
||||
ESLINT_VISITOR_KEYS.PropertyDefinition,
|
||||
),
|
||||
};
|
||||
|
||||
// AST Types that shares `"type"` property with Babel but have different shape
|
||||
export const conflictTypes = {
|
||||
// todo: remove this when we drop Babel 7 support
|
||||
ClassPrivateMethod: ["decorators"].concat(
|
||||
ESLINT_VISITOR_KEYS.MethodDefinition,
|
||||
),
|
||||
ExportAllDeclaration: ESLINT_VISITOR_KEYS.ExportAllDeclaration,
|
||||
};
|
||||
|
||||
export default { ...newTypes, ...t.VISITOR_KEYS, ...conflictTypes };
|
||||
46
eslint/babel-eslint-parser/src/worker/ast-info.cjs
Normal file
46
eslint/babel-eslint-parser/src/worker/ast-info.cjs
Normal file
@ -0,0 +1,46 @@
|
||||
const ESLINT_VISITOR_KEYS = require("eslint-visitor-keys").KEYS;
|
||||
const babel = require("./babel-core.cjs");
|
||||
|
||||
let visitorKeys;
|
||||
exports.getVisitorKeys = function getVisitorKeys() {
|
||||
if (!visitorKeys) {
|
||||
// AST Types that are not presented in Babel AST
|
||||
const newTypes = {
|
||||
ChainExpression: ESLINT_VISITOR_KEYS.ChainExpression,
|
||||
ImportExpression: ESLINT_VISITOR_KEYS.ImportExpression,
|
||||
Literal: ESLINT_VISITOR_KEYS.Literal,
|
||||
MethodDefinition: ["decorators"].concat(
|
||||
ESLINT_VISITOR_KEYS.MethodDefinition,
|
||||
),
|
||||
Property: ["decorators"].concat(ESLINT_VISITOR_KEYS.Property),
|
||||
PropertyDefinition: ["decorators"].concat(
|
||||
ESLINT_VISITOR_KEYS.PropertyDefinition,
|
||||
),
|
||||
};
|
||||
|
||||
// AST Types that shares `"type"` property with Babel but have different shape
|
||||
const conflictTypes = {
|
||||
// todo: remove this when we drop Babel 7 support
|
||||
ClassPrivateMethod: ["decorators"].concat(
|
||||
ESLINT_VISITOR_KEYS.MethodDefinition,
|
||||
),
|
||||
ExportAllDeclaration: ESLINT_VISITOR_KEYS.ExportAllDeclaration,
|
||||
};
|
||||
|
||||
visitorKeys = {
|
||||
...newTypes,
|
||||
...babel.types.VISITOR_KEYS,
|
||||
...conflictTypes,
|
||||
};
|
||||
}
|
||||
return visitorKeys;
|
||||
};
|
||||
|
||||
let tokLabels;
|
||||
exports.getTokLabels = function getTokLabels() {
|
||||
return (tokLabels ||= (
|
||||
process.env.BABEL_8_BREAKING
|
||||
? Object.fromEntries
|
||||
: p => p.reduce((o, [k, v]) => ({ ...o, [k]: v }), {})
|
||||
)(Object.entries(babel.tokTypes).map(([key, tok]) => [key, tok.label])));
|
||||
};
|
||||
17
eslint/babel-eslint-parser/src/worker/babel-core.cjs
Normal file
17
eslint/babel-eslint-parser/src/worker/babel-core.cjs
Normal file
@ -0,0 +1,17 @@
|
||||
function initialize(babel) {
|
||||
exports.init = null;
|
||||
exports.version = babel.version;
|
||||
exports.traverse = babel.traverse;
|
||||
exports.types = babel.types;
|
||||
exports.tokTypes = babel.tokTypes;
|
||||
exports.parseSync = babel.parseSync;
|
||||
exports.loadPartialConfigSync = babel.loadPartialConfigSync;
|
||||
exports.loadPartialConfigAsync = babel.loadPartialConfigAsync;
|
||||
exports.createConfigItem = babel.createConfigItem;
|
||||
}
|
||||
|
||||
if (process.env.BABEL_8_BREAKING) {
|
||||
exports.init = import("@babel/core").then(ns => initialize(ns.default));
|
||||
} else {
|
||||
initialize(require("@babel/core"));
|
||||
}
|
||||
@ -1,25 +1,4 @@
|
||||
import { loadPartialConfig } from "@babel/core";
|
||||
|
||||
export function normalizeESLintConfig(options) {
|
||||
const {
|
||||
babelOptions = {},
|
||||
// ESLint sets ecmaVersion: undefined when ecmaVersion is not set in the config.
|
||||
ecmaVersion = 2020,
|
||||
sourceType = "module",
|
||||
allowImportExportEverywhere = false,
|
||||
requireConfigFile = true,
|
||||
...otherOptions
|
||||
} = options;
|
||||
|
||||
return {
|
||||
babelOptions,
|
||||
ecmaVersion,
|
||||
sourceType,
|
||||
allowImportExportEverywhere,
|
||||
requireConfigFile,
|
||||
...otherOptions,
|
||||
};
|
||||
}
|
||||
const babel = require("./babel-core.cjs");
|
||||
|
||||
/**
|
||||
* Merge user supplied estree plugin options to default estree plugin options
|
||||
@ -41,8 +20,8 @@ function getParserPlugins(babelOptions) {
|
||||
return [["estree", estreeOptions], ...babelParserPlugins];
|
||||
}
|
||||
|
||||
export function normalizeBabelParseConfig(options) {
|
||||
const parseOptions = {
|
||||
function normalizeParserOptions(options) {
|
||||
return {
|
||||
sourceType: options.sourceType,
|
||||
filename: options.filePath,
|
||||
...options.babelOptions,
|
||||
@ -60,11 +39,11 @@ export function normalizeBabelParseConfig(options) {
|
||||
...options.babelOptions.caller,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (options.requireConfigFile !== false) {
|
||||
const config = loadPartialConfig(parseOptions);
|
||||
|
||||
if (config !== null) {
|
||||
function validateResolvedConfig(config, options) {
|
||||
if (config !== null) {
|
||||
if (options.requireConfigFile !== false) {
|
||||
if (!config.hasFilesystemConfig()) {
|
||||
let error = `No Babel config file detected for ${config.options.filename}. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files.`;
|
||||
|
||||
@ -74,10 +53,20 @@ export function normalizeBabelParseConfig(options) {
|
||||
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return config.options;
|
||||
}
|
||||
return config.options;
|
||||
}
|
||||
|
||||
return parseOptions;
|
||||
}
|
||||
|
||||
module.exports = function normalizeBabelParseConfig(options) {
|
||||
const parseOptions = normalizeParserOptions(options);
|
||||
|
||||
if (process.env.BABEL_8_BREAKING) {
|
||||
return babel
|
||||
.loadPartialConfigAsync(parseOptions)
|
||||
.then(config => validateResolvedConfig(config, options) || parseOptions);
|
||||
} else {
|
||||
const config = babel.loadPartialConfigSync(parseOptions);
|
||||
return validateResolvedConfig(config, options) || parseOptions;
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
module.exports = function extractParserOptionsPlugin() {
|
||||
return {
|
||||
parserOverride(code, opts) {
|
||||
return opts;
|
||||
},
|
||||
};
|
||||
};
|
||||
66
eslint/babel-eslint-parser/src/worker/index.cjs
Normal file
66
eslint/babel-eslint-parser/src/worker/index.cjs
Normal file
@ -0,0 +1,66 @@
|
||||
const babel = require("./babel-core.cjs");
|
||||
const maybeParse = require("./maybeParse.cjs");
|
||||
const { getVisitorKeys, getTokLabels } = require("./ast-info.cjs");
|
||||
const normalizeBabelParseConfig = require("./configuration.cjs");
|
||||
|
||||
function handleMessage(action, payload) {
|
||||
switch (action) {
|
||||
case "GET_VERSION":
|
||||
return babel.version;
|
||||
case "GET_TYPES_INFO":
|
||||
return {
|
||||
FLOW_FLIPPED_ALIAS_KEYS: babel.types.FLIPPED_ALIAS_KEYS.Flow,
|
||||
VISITOR_KEYS: babel.types.VISITOR_KEYS,
|
||||
};
|
||||
case "GET_TOKEN_LABELS":
|
||||
return getTokLabels();
|
||||
case "GET_VISITOR_KEYS":
|
||||
return getVisitorKeys();
|
||||
case "MAYBE_PARSE":
|
||||
if (process.env.BABEL_8_BREAKING) {
|
||||
return normalizeBabelParseConfig(payload.options).then(options =>
|
||||
maybeParse(payload.code, options),
|
||||
);
|
||||
} else {
|
||||
return maybeParse(
|
||||
payload.code,
|
||||
normalizeBabelParseConfig(payload.options),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unknown internal parser worker action: ${action}`);
|
||||
}
|
||||
|
||||
if (process.env.BABEL_8_BREAKING) {
|
||||
const { parentPort } = require("worker_threads");
|
||||
|
||||
parentPort.addListener(
|
||||
"message",
|
||||
async ({ signal, port, action, payload }) => {
|
||||
let response;
|
||||
|
||||
try {
|
||||
if (babel.init) await babel.init;
|
||||
|
||||
response = { result: await handleMessage(action, payload) };
|
||||
} catch (error) {
|
||||
response = { error, errorData: { ...error } };
|
||||
}
|
||||
|
||||
try {
|
||||
port.postMessage(response);
|
||||
} catch {
|
||||
port.postMessage({
|
||||
error: new Error("Cannot serialize worker response"),
|
||||
});
|
||||
} finally {
|
||||
port.close();
|
||||
Atomics.store(signal, 0, 1);
|
||||
Atomics.notify(signal, 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
module.exports = handleMessage;
|
||||
}
|
||||
43
eslint/babel-eslint-parser/src/worker/maybeParse.cjs
Normal file
43
eslint/babel-eslint-parser/src/worker/maybeParse.cjs
Normal file
@ -0,0 +1,43 @@
|
||||
const babel = require("./babel-core.cjs");
|
||||
const convert = require("../convert/index.cjs");
|
||||
const { getVisitorKeys, getTokLabels } = require("./ast-info.cjs");
|
||||
const extractParserOptionsPlugin = require("./extract-parser-options-plugin.cjs");
|
||||
|
||||
const ref = {};
|
||||
let extractParserOptionsConfigItem;
|
||||
|
||||
const MULTIPLE_OVERRIDES = /More than one plugin attempted to override parsing/;
|
||||
|
||||
module.exports = function maybeParse(code, options) {
|
||||
if (!extractParserOptionsConfigItem) {
|
||||
extractParserOptionsConfigItem = babel.createConfigItem(
|
||||
[extractParserOptionsPlugin, ref],
|
||||
{ dirname: __dirname, type: "plugin" },
|
||||
);
|
||||
}
|
||||
options.plugins.push(extractParserOptionsConfigItem);
|
||||
|
||||
try {
|
||||
return {
|
||||
parserOptions: babel.parseSync(code, options),
|
||||
ast: null,
|
||||
};
|
||||
} catch (err) {
|
||||
if (!MULTIPLE_OVERRIDES.test(err.message)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
let ast;
|
||||
|
||||
try {
|
||||
ast = babel.parseSync(code, options);
|
||||
} catch (err) {
|
||||
throw convert.error(err);
|
||||
}
|
||||
|
||||
return {
|
||||
ast: convert.ast(ast, code, getTokLabels(), getVisitorKeys()),
|
||||
parserOptions: null,
|
||||
};
|
||||
};
|
||||
@ -3,7 +3,7 @@ import escope from "eslint-scope";
|
||||
import unpad from "dedent";
|
||||
import { fileURLToPath } from "url";
|
||||
import { createRequire } from "module";
|
||||
import { parseForESLint } from "../src";
|
||||
import { parseForESLint } from "../lib/index.cjs";
|
||||
|
||||
const BABEL_OPTIONS = {
|
||||
configFile: path.resolve(
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/eslint-parser": "workspace:^7.14.2",
|
||||
"@babel/preset-react": "workspace:^7.13.13",
|
||||
"dedent": "^0.7.0",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
|
||||
13
eslint/babel-eslint-tests/test/fixtures/mjs-config-file/.eslintrc.js
vendored
Normal file
13
eslint/babel-eslint-tests/test/fixtures/mjs-config-file/.eslintrc.js
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: "@babel/eslint-parser",
|
||||
parserOptions: {
|
||||
babelOptions: {
|
||||
configFile: __dirname + "/babel.config.mjs",
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"template-curly-spacing": "error",
|
||||
},
|
||||
};
|
||||
1
eslint/babel-eslint-tests/test/fixtures/mjs-config-file/a.js
vendored
Normal file
1
eslint/babel-eslint-tests/test/fixtures/mjs-config-file/a.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default () => <div />;
|
||||
3
eslint/babel-eslint-tests/test/fixtures/mjs-config-file/babel.config.mjs
vendored
Normal file
3
eslint/babel-eslint-tests/test/fixtures/mjs-config-file/babel.config.mjs
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export default {
|
||||
presets: ["@babel/preset-react"],
|
||||
};
|
||||
@ -2,7 +2,7 @@ import eslint from "eslint";
|
||||
import unpad from "dedent";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import * as parser from "../../../babel-eslint-parser";
|
||||
import * as parser from "../../../babel-eslint-parser/lib/index.cjs";
|
||||
|
||||
export default function verifyAndAssertMessages(
|
||||
code,
|
||||
|
||||
19
eslint/babel-eslint-tests/test/integration/config-files.js
Normal file
19
eslint/babel-eslint-tests/test/integration/config-files.js
Normal file
@ -0,0 +1,19 @@
|
||||
import eslint from "eslint";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
describe("Babel config files", () => {
|
||||
const babel8 = process.env.BABEL_8_BREAKING ? it : it.skip;
|
||||
|
||||
babel8("works with babel.config.mjs", () => {
|
||||
const engine = new eslint.CLIEngine({ ignore: false });
|
||||
expect(
|
||||
engine.executeOnFiles([
|
||||
path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
`../fixtures/mjs-config-file/a.js`,
|
||||
),
|
||||
]),
|
||||
).toMatchObject({ errorCount: 0 });
|
||||
});
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
import eslint from "eslint";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import * as parser from "../../../../../babel-eslint-parser";
|
||||
import * as parser from "../../../../../babel-eslint-parser/lib/index.cjs";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
eslint.linter.defineParser("@babel/eslint-parser", parser);
|
||||
|
||||
@ -42,6 +42,7 @@ export default function* parser(
|
||||
}
|
||||
return results[0];
|
||||
}
|
||||
// TODO: Add an error code
|
||||
throw new Error("More than one plugin attempted to override parsing.");
|
||||
} catch (err) {
|
||||
if (err.code === "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED") {
|
||||
|
||||
@ -311,6 +311,7 @@ __metadata:
|
||||
resolution: "@babel/eslint-tests@workspace:eslint/babel-eslint-tests"
|
||||
dependencies:
|
||||
"@babel/eslint-parser": "workspace:^7.14.2"
|
||||
"@babel/preset-react": "workspace:^7.13.13"
|
||||
dedent: ^0.7.0
|
||||
eslint: ^7.5.0
|
||||
eslint-plugin-import: ^2.22.0
|
||||
@ -3278,7 +3279,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/preset-react@workspace:*, @babel/preset-react@workspace:^7.12.13, @babel/preset-react@workspace:packages/babel-preset-react":
|
||||
"@babel/preset-react@workspace:*, @babel/preset-react@workspace:^7.12.13, @babel/preset-react@workspace:^7.13.13, @babel/preset-react@workspace:packages/babel-preset-react":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@babel/preset-react@workspace:packages/babel-preset-react"
|
||||
dependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user