Skip to content

Commit

Permalink
Expose @babel/eslint-parser/experimental-worker (#13398)
Browse files Browse the repository at this point in the history
* Expose `@babel/eslint-parser/experimental-worker`

* Fix `@babel/runtime` build on Windows
  • Loading branch information
nicolo-ribaudo committed Aug 3, 2021
1 parent 830b99d commit 885e1e0
Show file tree
Hide file tree
Showing 19 changed files with 315 additions and 207 deletions.
1 change: 1 addition & 0 deletions eslint/babel-eslint-parser/package.json
Expand Up @@ -23,6 +23,7 @@
"type": "commonjs",
"exports": {
".": "./lib/index.cjs",
"./experimental-worker": "./lib/experimental-worker.cjs",
"./package.json": "./package.json"
},
"peerDependencies": {
Expand Down
21 changes: 13 additions & 8 deletions eslint/babel-eslint-parser/src/analyze-scope.cjs
Expand Up @@ -4,13 +4,11 @@ const OriginalPatternVisitor = require("eslint-scope/lib/pattern-visitor");
const OriginalReferencer = require("eslint-scope/lib/referencer");
const { getKeys: fallback } = require("eslint-visitor-keys");

const { getTypesInfo, getVisitorKeys } = require("./client.cjs");

let visitorKeysMap;
function getVisitorValues(nodeType) {
function getVisitorValues(nodeType, client) {
if (visitorKeysMap) return visitorKeysMap[nodeType];

const { FLOW_FLIPPED_ALIAS_KEYS, VISITOR_KEYS } = getTypesInfo();
const { FLOW_FLIPPED_ALIAS_KEYS, VISITOR_KEYS } = client.getTypesInfo();

const flowFlippedAliasKeys = FLOW_FLIPPED_ALIAS_KEYS.concat([
"ArrayPattern",
Expand Down Expand Up @@ -63,6 +61,13 @@ class PatternVisitor extends OriginalPatternVisitor {
}

class Referencer extends OriginalReferencer {
#client;

constructor(options, scopeManager, client) {
super(options, scopeManager);
this.#client = client;
}

// inherits.
visitPattern(node, options, callback) {
if (!node) {
Expand Down Expand Up @@ -264,7 +269,7 @@ class Referencer extends OriginalReferencer {
}

// get property to check (params, id, etc...)
const visitorValues = getVisitorValues(node.type);
const visitorValues = getVisitorValues(node.type, this.#client);
if (!visitorValues) {
return;
}
Expand Down Expand Up @@ -328,7 +333,7 @@ class Referencer extends OriginalReferencer {
}
}

module.exports = function analyzeScope(ast, parserOptions) {
module.exports = function analyzeScope(ast, parserOptions, client) {
const options = {
ignoreEval: true,
optimistic: false,
Expand All @@ -343,10 +348,10 @@ module.exports = function analyzeScope(ast, parserOptions) {
fallback,
};

options.childVisitorKeys = getVisitorKeys();
options.childVisitorKeys = client.getVisitorKeys();

const scopeManager = new escope.ScopeManager(options);
const referencer = new Referencer(options, scopeManager);
const referencer = new Referencer(options, scopeManager, client);

referencer.visit(ast);

Expand Down
154 changes: 96 additions & 58 deletions eslint/babel-eslint-parser/src/client.cjs
@@ -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");
}
22 changes: 22 additions & 0 deletions eslint/babel-eslint-parser/src/experimental-worker.cjs
@@ -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() };
};
61 changes: 9 additions & 52 deletions eslint/babel-eslint-parser/src/index.cjs
@@ -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() };
};
45 changes: 45 additions & 0 deletions eslint/babel-eslint-parser/src/parse.cjs
@@ -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);
}
};

0 comments on commit 885e1e0

Please sign in to comment.