291 lines
8.1 KiB
TypeScript
Executable File
291 lines
8.1 KiB
TypeScript
Executable File
#!/usr/bin/env tsx
|
|
import assert from 'node:assert';
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve, extname, relative } from 'node:path';
|
|
import { isBuiltin } from 'node:module';
|
|
import esbuild from 'esbuild';
|
|
import type { Loader, Plugin } from 'esbuild';
|
|
import * as babel from '@babel/core';
|
|
import { memoize } from 'lodash';
|
|
import { gray, green } from 'picocolors';
|
|
import type { types as t, types } from '@babel/core';
|
|
import { dependencies } from './dist/package.json';
|
|
import { createMacro, type MacroHandler } from 'babel-plugin-macros';
|
|
import * as polyfill from './src/polyfill';
|
|
|
|
const polyfills = Object.keys(polyfill);
|
|
|
|
const ENV = process.env.NODE_ENV || 'development';
|
|
const PROD = ENV === 'production';
|
|
|
|
class HandlerMap {
|
|
map = new Map<string, MacroHandler>();
|
|
|
|
set(names: string | string[], handler: MacroHandler) {
|
|
names = Array.isArray(names) ? names : [names];
|
|
const macro = createMacro(handler);
|
|
for (const name of names) {
|
|
this.map.set(name, macro);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
get keys() {
|
|
return Array.from(this.map.keys());
|
|
}
|
|
|
|
resolvePath = (module: string) => module;
|
|
require = (module: string) => this.map.get(module);
|
|
isMacrosName = (module: string) => this.map.has(module);
|
|
}
|
|
|
|
const map = new HandlerMap()
|
|
.set(
|
|
'object.assign',
|
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('assign'))),
|
|
)
|
|
.set(
|
|
['object-values', 'object.values'],
|
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('values'))),
|
|
)
|
|
.set(
|
|
'object.fromentries',
|
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('fromEntries'))),
|
|
)
|
|
.set(
|
|
'object.entries',
|
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('entries'))),
|
|
)
|
|
.set(
|
|
'hasown',
|
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('hasOwn'))),
|
|
)
|
|
.set(
|
|
'has',
|
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('hasOwn'))),
|
|
)
|
|
.set(
|
|
'array-includes',
|
|
proto(t => t.identifier('includes')),
|
|
)
|
|
.set(
|
|
'array.prototype.flatmap',
|
|
proto(t => t.identifier('flatMap')),
|
|
)
|
|
.set(
|
|
'array.prototype.flat',
|
|
proto(t => t.identifier('flat')),
|
|
)
|
|
.set(
|
|
'array.prototype.findlastindex',
|
|
proto(t => t.identifier('findLastIndex')),
|
|
)
|
|
.set(
|
|
'array.prototype.tosorted',
|
|
proto(t => t.identifier('toSorted')),
|
|
)
|
|
.set(
|
|
'string.prototype.matchall',
|
|
proto(t => t.identifier('matchAll')),
|
|
)
|
|
.set(
|
|
'object.groupby',
|
|
replace(t =>
|
|
t.memberExpression(
|
|
t.callExpression(t.identifier('require'), [t.stringLiteral('lodash')]),
|
|
t.identifier('groupBy'),
|
|
),
|
|
),
|
|
);
|
|
|
|
// es-iterator-helpers/Iterator.prototype.*
|
|
const polyfillPath = resolve(__dirname, './src/polyfill.ts');
|
|
const requirePolyfill = (t: typeof types, name: string) =>
|
|
t.memberExpression(
|
|
t.callExpression(t.identifier('require'), [t.stringLiteral(polyfillPath)]),
|
|
t.identifier(name),
|
|
);
|
|
map.set(
|
|
`es-iterator-helpers/Iterator.from`,
|
|
replace(t => requirePolyfill(t, 'from')),
|
|
);
|
|
for (const name of polyfills) {
|
|
map.set(
|
|
`es-iterator-helpers/Iterator.prototype.${name}`,
|
|
replace(t => requirePolyfill(t, name)),
|
|
);
|
|
}
|
|
|
|
function replace(getReplacement: (types: typeof t) => t.Expression): MacroHandler {
|
|
return ({ references, babel: { types: t } }) => {
|
|
references.default.forEach(referencePath => {
|
|
referencePath.replaceWith(getReplacement(t));
|
|
});
|
|
};
|
|
}
|
|
|
|
function proto(getProperty: (types: typeof t) => t.Expression): MacroHandler {
|
|
return ({ references, babel: { types: t } }) => {
|
|
references.default.forEach(referencePath => {
|
|
const { parent, parentPath } = referencePath;
|
|
assert(t.isCallExpression(parent));
|
|
const [callee, ...rest] = parent.arguments;
|
|
parentPath!.replaceWith(
|
|
t.callExpression(
|
|
t.memberExpression(callee as t.Expression, getProperty(t)),
|
|
rest,
|
|
),
|
|
);
|
|
});
|
|
};
|
|
}
|
|
|
|
export const babelPlugin: Plugin = {
|
|
name: 'babel',
|
|
setup(build) {
|
|
const { keys, ...macroOptions } = map;
|
|
|
|
build.onLoad({ filter: /\.[jt]sx?$/ }, args => {
|
|
const { path } = args;
|
|
if (path.includes('node_modules/')) {
|
|
return null;
|
|
}
|
|
|
|
let source = readFileSync(path, 'utf-8')
|
|
.replaceAll("require('object.hasown/polyfill')()", 'Object.hasOwn')
|
|
.replaceAll("require('object.fromentries/polyfill')()", 'Object.fromEntries')
|
|
.replaceAll(
|
|
"Object.keys(require('prop-types'))",
|
|
JSON.stringify(Object.keys(require('prop-types'))),
|
|
);
|
|
|
|
if (
|
|
path.includes('packages/eslint-plugin-import/src/rules/') ||
|
|
path.includes('packages/eslint-plugin-import/config/')
|
|
) {
|
|
source = source.replace('\nmodule.exports = {', '\nexport default {');
|
|
}
|
|
|
|
const isFlow = source.includes('@flow');
|
|
const loader = extname(path).slice(1) as Loader;
|
|
|
|
if (!isFlow && !keys.some(key => source.includes(key))) {
|
|
return { contents: source, loader };
|
|
}
|
|
|
|
const res = babel.transformSync(source, {
|
|
filename: path,
|
|
babelrc: false,
|
|
configFile: false,
|
|
parserOpts: {
|
|
plugins: [isFlow ? 'flow' : 'typescript'],
|
|
},
|
|
plugins: [
|
|
isFlow && '@babel/plugin-transform-flow-strip-types',
|
|
['babel-plugin-macros', macroOptions],
|
|
].filter(Boolean),
|
|
})!;
|
|
|
|
return {
|
|
contents: res.code!,
|
|
loader,
|
|
};
|
|
});
|
|
},
|
|
};
|
|
|
|
declare global {
|
|
interface Array<T> {
|
|
filter(
|
|
predicate: BooleanConstructor,
|
|
): Exclude<T, null | undefined | false | '' | 0>[];
|
|
}
|
|
}
|
|
|
|
const log = memoize(console.log);
|
|
|
|
const plugins: Plugin[] = [
|
|
babelPlugin,
|
|
{
|
|
name: 'alias',
|
|
setup(build) {
|
|
build.onResolve({ filter: /^jsx-ast-utils$/ }, () => ({
|
|
path: resolve('./packages/jsx-ast-utils/src/index.js'),
|
|
}));
|
|
build.onResolve({ filter: /^jsx-ast-utils\/.+$/ }, ({ path }) => ({
|
|
path:
|
|
resolve('./packages/jsx-ast-utils/', path.slice('jsx-ast-utils/'.length)) +
|
|
'.js',
|
|
}));
|
|
},
|
|
},
|
|
];
|
|
if (process.env.DEBUG) {
|
|
plugins.push({
|
|
name: 'deps-check',
|
|
setup(build) {
|
|
const declared = new Set(Object.keys(dependencies));
|
|
|
|
build.onResolve({ filter: /^.*$/ }, ({ path, importer }) => {
|
|
if (
|
|
!path.startsWith('./') &&
|
|
!path.startsWith('../') &&
|
|
!isBuiltin(path) &&
|
|
path !== 'eslint' &&
|
|
!path.startsWith('eslint/') &&
|
|
!path.startsWith('eslint-module-utils/') &&
|
|
!declared.has(path)
|
|
) {
|
|
log(green(path), gray('from'), './' + relative(process.cwd(), importer));
|
|
}
|
|
return null;
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
async function main(
|
|
entry: string,
|
|
outfile = entry
|
|
.replace('./packages/', './dist/')
|
|
.replace('src/', '')
|
|
.replace('.ts', '.js'),
|
|
) {
|
|
await esbuild.build({
|
|
entryPoints: [entry],
|
|
outfile,
|
|
bundle: true,
|
|
minify: PROD,
|
|
platform: 'node',
|
|
packages: 'external',
|
|
sourcemap: 'linked',
|
|
plugins,
|
|
define: {},
|
|
alias: {
|
|
// esm modules
|
|
'find-cache-dir': require.resolve('find-cache-dir'),
|
|
},
|
|
banner: {
|
|
js: '/* eslint-disable */',
|
|
},
|
|
});
|
|
|
|
// https://github.com/eslint-types/define-config-plugin-types/issues/32
|
|
// const distPackageJson = JSON.parse(await fs.readFile('./dist/package.json', 'utf-8'));
|
|
// Object.assign(distPackageJson.dependencies, pkg.dependencies);
|
|
// await fs.writeFile(
|
|
// './dist/package.json',
|
|
// JSON.stringify(distPackageJson, null, 2) + '\n',
|
|
// );
|
|
}
|
|
|
|
main('./packages/eslint-plugin-react/index.js');
|
|
main('./packages/eslint-plugin-import/src/index.js');
|
|
main('./packages/eslint-plugin-jsx-a11y/src/index.js');
|
|
main('./packages/eslint-plugin-react-hooks/index.ts');
|
|
main('./packages/eslint-plugin-n/lib/index.js', './dist/eslint-plugin-n/index.js');
|
|
main('./packages/eslint-import-resolver-typescript/src/index.ts');
|
|
main('./src/rules/index.ts', './dist/eslint-plugin-rules/index.js');
|
|
main('./src/local/index.ts', './dist/eslint-plugin-local/index.js');
|
|
main('./src/index.ts', './dist/index.js');
|