[babel 8] Move ESLint parsing to a Worker (#13199)

This commit is contained in:
Nicolò Ribaudo 2021-05-17 17:04:10 +02:00 committed by GitHub
parent c2181343f1
commit 9d620c2d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 608 additions and 328 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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,
};
};

View 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);
};

View File

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

View File

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

View File

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

View 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;
};

View File

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

View 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() };
};

View File

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

View File

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

View 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])));
};

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

View File

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

View File

@ -0,0 +1,7 @@
module.exports = function extractParserOptionsPlugin() {
return {
parserOverride(code, opts) {
return opts;
},
};
};

View 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;
}

View 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,
};
};

View File

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

View File

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

View 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",
},
};

View File

@ -0,0 +1 @@
export default () => <div />;

View File

@ -0,0 +1,3 @@
export default {
presets: ["@babel/preset-react"],
};

View File

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

View 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 });
});
});

View File

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

View File

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

View File

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