Skip to content

Commit

Permalink
Land ESM-based config support without requiring opt-in
Browse files Browse the repository at this point in the history
  • Loading branch information
novemberborn committed Feb 7, 2021
1 parent dc4483f commit c7b7820
Show file tree
Hide file tree
Showing 48 changed files with 196 additions and 550 deletions.
174 changes: 34 additions & 140 deletions eslint-plugin-helper.js
Original file line number Diff line number Diff line change
@@ -1,155 +1,49 @@
'use strict';
// TODO: Clean up.
let isMainThread = true;
let supportsWorkers = false;
try {
({isMainThread} = require('worker_threads'));
supportsWorkers = true;
} catch {}

const {classify, hasExtension, isHelperish, matches, normalizeFileForMatching, normalizeGlobs, normalizePatterns} = require('./lib/globs');

let resolveGlobs;
let resolveGlobsSync;

if (!supportsWorkers || !isMainThread) {
const normalizeExtensions = require('./lib/extensions');
const {loadConfig, loadConfigSync} = require('./lib/load-config');
const providerManager = require('./lib/provider-manager');

const configCache = new Map();

const collectProviders = ({conf, projectDir}) => {
const providers = [];
if (Reflect.has(conf, 'babel')) {
const {level, main} = providerManager.babel(projectDir);
providers.push({
level,
main: main({config: conf.babel}),
type: 'babel'
});
}

if (Reflect.has(conf, 'typescript')) {
const {level, main} = providerManager.typescript(projectDir);
providers.push({
level,
main: main({config: conf.typescript}),
type: 'typescript'
});
}
const v8 = require('v8');
const {Worker} = require('worker_threads');

return providers;
};

const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => {
const extensions = overrideExtensions ?
normalizeExtensions(overrideExtensions) :
normalizeExtensions(conf.extensions, providers);

return {
cwd: projectDir,
...normalizeGlobs({
extensions,
files: overrideFiles ? overrideFiles : conf.files,
providers
})
};
};
const {classify, hasExtension, isHelperish, matches, normalizeFileForMatching, normalizePatterns} = require('./lib/globs');

resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => {
if (!configCache.has(projectDir)) {
const conf = loadConfigSync({resolveFrom: projectDir});
const providers = collectProviders({conf, projectDir});
configCache.set(projectDir, {conf, providers});
}
const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.

const {conf, providers} = configCache.get(projectDir);
return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
};
let data;
let sync;
let worker;

resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => {
if (!configCache.has(projectDir)) {
configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(conf => { // eslint-disable-line promise/prefer-await-to-then
const providers = collectProviders({conf, projectDir});
return {conf, providers};
}));
}
const resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => {
if (worker === undefined) {
const dataBuffer = new SharedArrayBuffer(MAX_DATA_LENGTH_EXCLUSIVE);
data = new Uint8Array(dataBuffer);

const {conf, providers} = await configCache.get(projectDir);
return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
};
}
const syncBuffer = new SharedArrayBuffer(4);
sync = new Int32Array(syncBuffer);

if (supportsWorkers) {
const v8 = require('v8');

const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.

if (isMainThread) {
const {Worker} = require('worker_threads');
let data;
let sync;
let worker;

resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => {
if (worker === undefined) {
const dataBuffer = new SharedArrayBuffer(MAX_DATA_LENGTH_EXCLUSIVE);
data = new Uint8Array(dataBuffer);

const syncBuffer = new SharedArrayBuffer(4);
sync = new Int32Array(syncBuffer);

worker = new Worker(__filename, {
workerData: {
dataBuffer,
syncBuffer,
firstMessage: {projectDir, overrideExtensions, overrideFiles}
}
});
worker.unref();
} else {
worker.postMessage({projectDir, overrideExtensions, overrideFiles});
worker = new Worker('./lib/eslint-plugin-helper-worker.js', {
workerData: {
dataBuffer,
syncBuffer,
firstMessage: {projectDir, overrideExtensions, overrideFiles}
}

Atomics.wait(sync, 0, 0);

const byteLength = Atomics.exchange(sync, 0, 0);
if (byteLength === MAX_DATA_LENGTH_EXCLUSIVE) {
throw new Error('Globs are over 100 KiB and cannot be resolved');
}

const globsOrError = v8.deserialize(data.slice(0, byteLength));
if (globsOrError instanceof Error) {
throw globsOrError;
}

return globsOrError;
};
});
worker.unref();
} else {
const {parentPort, workerData} = require('worker_threads');
const data = new Uint8Array(workerData.dataBuffer);
const sync = new Int32Array(workerData.syncBuffer);

const handleMessage = async ({projectDir, overrideExtensions, overrideFiles}) => {
let encoded;
try {
const globs = await resolveGlobs(projectDir, overrideExtensions, overrideFiles);
encoded = v8.serialize(globs);
} catch (error) {
encoded = v8.serialize(error);
}
worker.postMessage({projectDir, overrideExtensions, overrideFiles});
}

const byteLength = encoded.length < MAX_DATA_LENGTH_EXCLUSIVE ? encoded.copy(data) : MAX_DATA_LENGTH_EXCLUSIVE;
Atomics.store(sync, 0, byteLength);
Atomics.notify(sync, 0);
};
Atomics.wait(sync, 0, 0);

parentPort.on('message', handleMessage);
handleMessage(workerData.firstMessage);
delete workerData.firstMessage;
const byteLength = Atomics.exchange(sync, 0, 0);
if (byteLength === MAX_DATA_LENGTH_EXCLUSIVE) {
throw new Error('Globs are over 100 KiB and cannot be resolved');
}
}

const globsOrError = v8.deserialize(data.slice(0, byteLength));
if (globsOrError instanceof Error) {
throw globsOrError;
}

return globsOrError;
};

const helperCache = new Map();

Expand Down
83 changes: 83 additions & 0 deletions lib/eslint-plugin-helper-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';
const v8 = require('v8');
const {parentPort, workerData} = require('worker_threads');

const {normalizeGlobs} = require('./globs');
const normalizeExtensions = require('./extensions');
const {loadConfig} = require('./load-config');
const providerManager = require('./provider-manager');

const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.

const configCache = new Map();

const collectProviders = ({conf, projectDir}) => {
const providers = [];
if (Reflect.has(conf, 'babel')) {
const {level, main} = providerManager.babel(projectDir);
providers.push({
level,
main: main({config: conf.babel}),
type: 'babel'
});
}

if (Reflect.has(conf, 'typescript')) {
const {level, main} = providerManager.typescript(projectDir);
providers.push({
level,
main: main({config: conf.typescript}),
type: 'typescript'
});
}

return providers;
};

const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => {
const extensions = overrideExtensions ?
normalizeExtensions(overrideExtensions) :
normalizeExtensions(conf.extensions, providers);

return {
cwd: projectDir,
...normalizeGlobs({
extensions,
files: overrideFiles ? overrideFiles : conf.files,
providers
})
};
};

const resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => {
if (!configCache.has(projectDir)) {
configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(conf => { // eslint-disable-line promise/prefer-await-to-then
const providers = collectProviders({conf, projectDir});
return {conf, providers};
}));
}

const {conf, providers} = await configCache.get(projectDir);
return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
};

const data = new Uint8Array(workerData.dataBuffer);
const sync = new Int32Array(workerData.syncBuffer);

const handleMessage = async ({projectDir, overrideExtensions, overrideFiles}) => {
let encoded;
try {
const globs = await resolveGlobs(projectDir, overrideExtensions, overrideFiles);
encoded = v8.serialize(globs);
} catch (error) {
encoded = v8.serialize(error);
}

const byteLength = encoded.length < MAX_DATA_LENGTH_EXCLUSIVE ? encoded.copy(data) : MAX_DATA_LENGTH_EXCLUSIVE;
Atomics.store(sync, 0, byteLength);
Atomics.notify(sync, 0);
};

parentPort.on('message', handleMessage);
handleMessage(workerData.firstMessage);
delete workerData.firstMessage;
Loading

0 comments on commit c7b7820

Please sign in to comment.