-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expose
@babel/eslint-parser/experimental-worker
(#13398)
* Expose `@babel/eslint-parser/experimental-worker` * Fix `@babel/runtime` build on Windows
- Loading branch information
1 parent
830b99d
commit 885e1e0
Showing
19 changed files
with
315 additions
and
207 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,105 @@ | ||
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; | ||
}; | ||
const ACTIONS = { | ||
GET_VERSION: "GET_VERSION", | ||
GET_TYPES_INFO: "GET_TYPES_INFO", | ||
GET_VISITOR_KEYS: "GET_VISITOR_KEYS", | ||
GET_TOKEN_LABELS: "GET_TOKEN_LABELS", | ||
MAYBE_PARSE: "MAYBE_PARSE", | ||
MAYBE_PARSE_SYNC: "MAYBE_PARSE_SYNC", | ||
}; | ||
|
||
class Client { | ||
#send; | ||
|
||
constructor(send) { | ||
this.#send = send; | ||
} | ||
|
||
#vCache; | ||
getVersion() { | ||
return (this.#vCache ??= this.#send(ACTIONS.GET_VERSION, undefined)); | ||
} | ||
|
||
#tiCache; | ||
getTypesInfo() { | ||
return (this.#tiCache ??= this.#send(ACTIONS.GET_TYPES_INFO, undefined)); | ||
} | ||
|
||
#vkCache; | ||
getVisitorKeys() { | ||
return (this.#vkCache ??= this.#send(ACTIONS.GET_VISITOR_KEYS, undefined)); | ||
} | ||
|
||
#tlCache; | ||
getTokLabels() { | ||
return (this.#tlCache ??= this.#send(ACTIONS.GET_TOKEN_LABELS, undefined)); | ||
} | ||
|
||
maybeParse(code, options) { | ||
return this.#send(ACTIONS.MAYBE_PARSE, { code, options }); | ||
} | ||
} | ||
|
||
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( | ||
// 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. | ||
exports.WorkerClient = class WorkerClient extends Client { | ||
static #worker_threads_cache; | ||
static get #worker_threads() { | ||
return (WorkerClient.#worker_threads_cache ??= require("worker_threads")); | ||
} | ||
|
||
#worker = new WorkerClient.#worker_threads.Worker( | ||
path.resolve(__dirname, "../lib/worker/index.cjs"), | ||
{ env: SHARE_ENV }, | ||
{ env: WorkerClient.#worker_threads.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; | ||
#signal = new Int32Array(new SharedArrayBuffer(4)); | ||
|
||
constructor() { | ||
super((action, payload) => { | ||
this.#signal[0] = 0; | ||
const subChannel = new WorkerClient.#worker_threads.MessageChannel(); | ||
|
||
this.#worker.postMessage( | ||
{ signal: this.#signal, port: subChannel.port1, action, payload }, | ||
[subChannel.port1], | ||
); | ||
|
||
Atomics.wait(this.#signal, 0, 0); | ||
const { message } = WorkerClient.#worker_threads.receiveMessageOnPort( | ||
subChannel.port2, | ||
); | ||
|
||
if (message.error) throw Object.assign(message.error, message.errorData); | ||
else return message.result; | ||
}); | ||
|
||
// The worker will never exit by itself. Prevent it from keeping | ||
// the main process alive. | ||
this.#worker.unref(); | ||
} | ||
}; | ||
|
||
if (!process.env.BABEL_8_BREAKING) { | ||
exports.LocalClient = class LocalClient extends Client { | ||
static #handleMessage; | ||
|
||
constructor() { | ||
LocalClient.#handleMessage ??= require("./worker/handle-message.cjs"); | ||
|
||
super((action, payload) => { | ||
return LocalClient.#handleMessage( | ||
action === ACTIONS.MAYBE_PARSE ? ACTIONS.MAYBE_PARSE_SYNC : action, | ||
payload, | ||
); | ||
}); | ||
} | ||
}; | ||
} else { | ||
send = require("./worker/index.cjs"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const [major, minor] = process.versions.node.split(".").map(Number); | ||
|
||
if (major < 12 || (major === 12 && minor < 3)) { | ||
throw new Error( | ||
"@babel/eslint-parser/experimental-worker requires Node.js >= 12.3.0", | ||
); | ||
} | ||
|
||
const { normalizeESLintConfig } = require("./configuration.cjs"); | ||
const analyzeScope = require("./analyze-scope.cjs"); | ||
const baseParse = require("./parse.cjs"); | ||
|
||
const { WorkerClient } = require("./client.cjs"); | ||
const client = new WorkerClient(); | ||
|
||
exports.parseForESLint = function (code, options = {}) { | ||
const normalizedOptions = normalizeESLintConfig(options); | ||
const ast = baseParse(code, normalizedOptions, client); | ||
const scopeManager = analyzeScope(ast, normalizedOptions, client); | ||
|
||
return { ast, scopeManager, visitorKeys: client.getVisitorKeys() }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,20 @@ | ||
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 baseParse = require("./parse.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); | ||
} | ||
} | ||
const { LocalClient, WorkerClient } = require("./client.cjs"); | ||
const client = new ( | ||
process.env.BABEL_8_BREAKING ? WorkerClient : LocalClient | ||
)(); | ||
|
||
exports.parse = function (code, options = {}) { | ||
return baseParse(code, normalizeESLintConfig(options)); | ||
return baseParse(code, normalizeESLintConfig(options), client); | ||
}; | ||
|
||
exports.parseForESLint = function (code, options = {}) { | ||
const normalizedOptions = normalizeESLintConfig(options); | ||
const ast = baseParse(code, normalizedOptions); | ||
const scopeManager = analyzeScope(ast, normalizedOptions); | ||
const ast = baseParse(code, normalizedOptions, client); | ||
const scopeManager = analyzeScope(ast, normalizedOptions, client); | ||
|
||
return { ast, scopeManager, visitorKeys: getVisitorKeys() }; | ||
return { ast, scopeManager, visitorKeys: client.getVisitorKeys() }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"use strict"; | ||
|
||
const semver = require("semver"); | ||
const convert = require("./convert/index.cjs"); | ||
|
||
const babelParser = require(require.resolve("@babel/parser", { | ||
paths: [require.resolve("@babel/core/package.json")], | ||
})); | ||
|
||
let isRunningMinSupportedCoreVersion = null; | ||
|
||
module.exports = function parse(code, options, client) { | ||
// 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( | ||
client.getVersion(), | ||
minSupportedCoreVersion, | ||
); | ||
} | ||
|
||
if (!isRunningMinSupportedCoreVersion) { | ||
throw new Error( | ||
`@babel/eslint-parser@${ | ||
PACKAGE_JSON.version | ||
} does not support @babel/core@${client.getVersion()}. Please upgrade to @babel/core@${minSupportedCoreVersion}.`, | ||
); | ||
} | ||
|
||
const { ast, parserOptions } = client.maybeParse(code, options); | ||
|
||
if (ast) return ast; | ||
|
||
try { | ||
return convert.ast( | ||
babelParser.parse(code, parserOptions), | ||
code, | ||
client.getTokLabels(), | ||
client.getVisitorKeys(), | ||
); | ||
} catch (err) { | ||
throw convert.error(err); | ||
} | ||
}; |
Oops, something went wrong.