Update
This commit is contained in:
30
dist/index.d.ts
vendored
30
dist/index.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
// Generated by dts-bundle-generator v9.3.1
|
// Generated by dts-bundle-generator v9.4.0
|
||||||
|
|
||||||
import { ESLintUtils } from '@typescript-eslint/utils';
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||||
import { Rule } from 'eslint';
|
import { Rule } from 'eslint';
|
||||||
@ -26,32 +26,28 @@ export interface LocalRuleOptions {
|
|||||||
"rules/no-empty-object-literal": RuleEntry<unknown>;
|
"rules/no-empty-object-literal": RuleEntry<unknown>;
|
||||||
}
|
}
|
||||||
export type RuleOptions = Rules & Partial<LocalRuleOptions>;
|
export type RuleOptions = Rules & Partial<LocalRuleOptions>;
|
||||||
|
export interface CustomRule {
|
||||||
|
rule: () => Promise<{
|
||||||
|
default: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||||
|
}>;
|
||||||
|
options?: RuleLevel;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* ESLint Configuration.
|
* ESLint Configuration.
|
||||||
* @see [ESLint Configuration](https://eslint.org/docs/latest/user-guide/configuring/)
|
* @see [ESLint Configuration](https://eslint.org/docs/latest/user-guide/configuring/)
|
||||||
*/
|
*/
|
||||||
export type Config = Omit<ESLintConfig, "rules"> & {
|
export type InputConfig = Omit<ESLintConfig, "rules"> & {
|
||||||
/**
|
/**
|
||||||
* Rules.
|
* Rules.
|
||||||
* @see [Rules](https://eslint.org/docs/latest/user-guide/configuring/rules)
|
* @see [Rules](https://eslint.org/docs/latest/user-guide/configuring/rules)
|
||||||
*/
|
*/
|
||||||
rules?: RuleOptions;
|
rules?: RuleOptions;
|
||||||
/**
|
/**
|
||||||
|
* Glob pattern to find paths to custom rule files in JavaScript or TypeScript.
|
||||||
|
* Note this must be a string literal or an array of string literals since
|
||||||
|
* this is statically analyzed.
|
||||||
*/
|
*/
|
||||||
customRules?: {
|
customRuleFiles?: string | string[];
|
||||||
rule: () => Promise<{
|
|
||||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
|
||||||
}>;
|
|
||||||
options?: RuleLevel;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
export declare function defineCustomRule<Options extends readonly unknown[]>(rule: () => Promise<{
|
|
||||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, Options>;
|
|
||||||
}>, options?: Options): {
|
|
||||||
rule: () => Promise<{
|
|
||||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, Options>;
|
|
||||||
}>;
|
|
||||||
options: Options | undefined;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Returns a ESLint config object.
|
* Returns a ESLint config object.
|
||||||
@ -71,6 +67,6 @@ export declare function defineCustomRule<Options extends readonly unknown[]>(rul
|
|||||||
* Non bundled:
|
* Non bundled:
|
||||||
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
||||||
*/
|
*/
|
||||||
export declare function extendConfig({ plugins, settings, rules, extends: _extends, overrides, customRules, ...rest }?: Config): ESLintConfig;
|
export declare function extendConfig(of?: InputConfig): ESLintConfig;
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
4
dist/package.json
vendored
4
dist/package.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@aet/eslint-rules",
|
"name": "@aet/eslint-rules",
|
||||||
"version": "0.0.23",
|
"version": "0.0.24-beta.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
@ -28,6 +28,8 @@
|
|||||||
"eslint-plugin-es-x": "^7.6.0",
|
"eslint-plugin-es-x": "^7.6.0",
|
||||||
"eslint-plugin-jsdoc": "^48.2.3",
|
"eslint-plugin-jsdoc": "^48.2.3",
|
||||||
"eslint-plugin-unicorn": "^52.0.0",
|
"eslint-plugin-unicorn": "^52.0.0",
|
||||||
|
"esprima": "^4.0.1",
|
||||||
|
"esquery": "^1.5.0",
|
||||||
"estraverse": "^5.3.0",
|
"estraverse": "^5.3.0",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"get-tsconfig": "^4.7.3",
|
"get-tsconfig": "^4.7.3",
|
||||||
|
2
dist/prettier.d.ts
vendored
2
dist/prettier.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
// Generated by dts-bundle-generator v9.3.1
|
// Generated by dts-bundle-generator v9.4.0
|
||||||
|
|
||||||
import { Config } from 'prettier';
|
import { Config } from 'prettier';
|
||||||
|
|
||||||
|
12
dist/types.d.ts
vendored
12
dist/types.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
// Generated by dts-bundle-generator v9.3.1
|
// Generated by dts-bundle-generator v9.4.0
|
||||||
|
|
||||||
import { ESLintUtils } from '@typescript-eslint/utils';
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||||
import { Rule } from 'eslint';
|
import { Rule } from 'eslint';
|
||||||
@ -6,15 +6,9 @@ import { Rule } from 'eslint';
|
|||||||
export declare function defineRules(rules: {
|
export declare function defineRules(rules: {
|
||||||
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||||
}): {
|
}): {
|
||||||
[ruleName: string]:
|
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[], ESLintUtils.RuleListener>;
|
||||||
| Rule.RuleModule
|
|
||||||
| ESLintUtils.RuleModule<string, unknown[], ESLintUtils.RuleListener>;
|
|
||||||
};
|
};
|
||||||
export declare function defineRule({
|
export declare function defineRule({ name, create, ...meta }: Rule.RuleMetaData & {
|
||||||
name,
|
|
||||||
create,
|
|
||||||
...meta
|
|
||||||
}: Rule.RuleMetaData & {
|
|
||||||
name?: string;
|
name?: string;
|
||||||
create: (context: Rule.RuleContext) => Rule.RuleListener;
|
create: (context: Rule.RuleContext) => Rule.RuleListener;
|
||||||
}): Rule.RuleModule;
|
}): Rule.RuleModule;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
"@types/babel-plugin-macros": "^3.1.3",
|
"@types/babel-plugin-macros": "^3.1.3",
|
||||||
"@types/babel__core": "^7.20.5",
|
"@types/babel__core": "^7.20.5",
|
||||||
"@types/eslint": "^8.56.9",
|
"@types/eslint": "^8.56.9",
|
||||||
|
"@types/esprima": "^4.0.6",
|
||||||
|
"@types/esquery": "^1.5.3",
|
||||||
"@types/estree": "^1.0.5",
|
"@types/estree": "^1.0.5",
|
||||||
"@types/estree-jsx": "^1.0.5",
|
"@types/estree-jsx": "^1.0.5",
|
||||||
"@types/lodash": "^4.17.0",
|
"@types/lodash": "^4.17.0",
|
||||||
@ -29,6 +31,8 @@
|
|||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-define-config": "^1.24.1",
|
"eslint-define-config": "^1.24.1",
|
||||||
|
"esprima": "^4.0.1",
|
||||||
|
"esquery": "^1.5.0",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"find-cache-dir": "^5.0.0",
|
"find-cache-dir": "^5.0.0",
|
||||||
"json-schema-to-ts": "^3.0.1",
|
"json-schema-to-ts": "^3.0.1",
|
||||||
|
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@ -34,6 +34,12 @@ devDependencies:
|
|||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
specifier: ^8.56.9
|
specifier: ^8.56.9
|
||||||
version: 8.56.9
|
version: 8.56.9
|
||||||
|
'@types/esprima':
|
||||||
|
specifier: ^4.0.6
|
||||||
|
version: 4.0.6
|
||||||
|
'@types/esquery':
|
||||||
|
specifier: ^1.5.3
|
||||||
|
version: 1.5.3
|
||||||
'@types/estree':
|
'@types/estree':
|
||||||
specifier: ^1.0.5
|
specifier: ^1.0.5
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
@ -85,6 +91,12 @@ devDependencies:
|
|||||||
eslint-define-config:
|
eslint-define-config:
|
||||||
specifier: ^1.24.1
|
specifier: ^1.24.1
|
||||||
version: 1.24.1
|
version: 1.24.1
|
||||||
|
esprima:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
|
esquery:
|
||||||
|
specifier: ^1.5.0
|
||||||
|
version: 1.5.0
|
||||||
fast-glob:
|
fast-glob:
|
||||||
specifier: ^3.3.2
|
specifier: ^3.3.2
|
||||||
version: 3.3.2
|
version: 3.3.2
|
||||||
@ -1788,6 +1800,18 @@ packages:
|
|||||||
'@types/json-schema': 7.0.15
|
'@types/json-schema': 7.0.15
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/esprima@4.0.6:
|
||||||
|
resolution: {integrity: sha512-lIk+kSt9lGv5hxK6aZNjiUEGZqKmOTpmg0tKiJQI+Ow98fLillxsiZNik5+RcP7mXL929KiTH/D9jGtpDlMbVw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/esquery@1.5.3:
|
||||||
|
resolution: {integrity: sha512-c55hQOcoPkWDfuEN9EdP1YyNH4D909U40gUEpY0nB5PWHExWHEPxcx3sx0fJ1Gzf4j1OpWktmIgciIlpgHtfDg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/estree-jsx@1.0.5:
|
/@types/estree-jsx@1.0.5:
|
||||||
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2446,6 +2470,12 @@ packages:
|
|||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/esprima@4.0.1:
|
||||||
|
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/esquery@1.5.0:
|
/esquery@1.5.0:
|
||||||
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
|
resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
|
||||||
engines: {node: '>=0.10'}
|
engines: {node: '>=0.10'}
|
||||||
|
234
scripts/build.ts
234
scripts/build.ts
@ -1,218 +1,19 @@
|
|||||||
#!/usr/bin/env tsx
|
#!/usr/bin/env tsx
|
||||||
import assert from 'node:assert';
|
import { promises as fs } from 'node:fs';
|
||||||
import { readFileSync, promises as fs } from 'node:fs';
|
import { resolve, relative } from 'node:path';
|
||||||
import { resolve, extname, relative } from 'node:path';
|
|
||||||
import { isBuiltin } from 'node:module';
|
import { isBuiltin } from 'node:module';
|
||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import type { Loader, Plugin } from 'esbuild';
|
import type { Plugin } from 'esbuild';
|
||||||
import * as babel from '@babel/core';
|
|
||||||
import { memoize } from 'lodash';
|
import { memoize } from 'lodash';
|
||||||
import { gray, green } from 'picocolors';
|
import { gray, green } from 'picocolors';
|
||||||
import type { types as t, types } from '@babel/core';
|
|
||||||
import { dependencies } from '../dist/package.json';
|
import { dependencies } from '../dist/package.json';
|
||||||
import { createMacro, type MacroHandler } from 'babel-plugin-macros';
|
|
||||||
import * as polyfill from '../src/polyfill';
|
|
||||||
import { buildLocalRules } from '../src/build-local-rules';
|
import { buildLocalRules } from '../src/build-local-rules';
|
||||||
import { execSync } from 'node:child_process';
|
import { dts } from './dts';
|
||||||
|
import { babelPlugin } from './modifier';
|
||||||
const polyfills = Object.keys(polyfill);
|
|
||||||
|
|
||||||
const ENV = (process.env.NODE_ENV ??= 'production');
|
const ENV = (process.env.NODE_ENV ??= 'production');
|
||||||
const PROD = ENV === 'production';
|
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(
|
|
||||||
'array.prototype.toreversed',
|
|
||||||
proto(t => t.identifier('toReversed')),
|
|
||||||
)
|
|
||||||
.set(
|
|
||||||
'array.prototype.findlast',
|
|
||||||
proto(t => t.identifier('findLast')),
|
|
||||||
)
|
|
||||||
.set(
|
|
||||||
'string.prototype.matchall',
|
|
||||||
proto(t => t.identifier('matchAll')),
|
|
||||||
)
|
|
||||||
.set(
|
|
||||||
'string.prototype.includes',
|
|
||||||
proto(t => t.identifier('includes')),
|
|
||||||
)
|
|
||||||
.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)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
map.set(
|
|
||||||
'safe-regex-test',
|
|
||||||
replace(t => requirePolyfill(t, 'safeRegexTest')),
|
|
||||||
);
|
|
||||||
|
|
||||||
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 {
|
declare global {
|
||||||
interface Array<T> {
|
interface Array<T> {
|
||||||
filter(
|
filter(
|
||||||
@ -264,14 +65,14 @@ if (process.env.DEBUG) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function bundle(
|
function bundle(
|
||||||
entry: string,
|
entry: string,
|
||||||
outfile = entry
|
outfile = entry
|
||||||
.replace('./packages/', './dist/')
|
.replace('./packages/', './dist/')
|
||||||
.replace('src/', '')
|
.replace('src/', '')
|
||||||
.replace('.ts', '.js'),
|
.replace('.ts', '.js'),
|
||||||
) {
|
) {
|
||||||
await esbuild.build({
|
return esbuild.build({
|
||||||
entryPoints: [entry],
|
entryPoints: [entry],
|
||||||
outfile,
|
outfile,
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@ -281,9 +82,7 @@ async function bundle(
|
|||||||
sourcemap: 'linked',
|
sourcemap: 'linked',
|
||||||
plugins,
|
plugins,
|
||||||
define: {},
|
define: {},
|
||||||
alias: {
|
alias: {},
|
||||||
// esm modules
|
|
||||||
},
|
|
||||||
external: ['find-cache-dir'],
|
external: ['find-cache-dir'],
|
||||||
banner: {
|
banner: {
|
||||||
js: '/* eslint-disable */',
|
js: '/* eslint-disable */',
|
||||||
@ -317,18 +116,11 @@ async function useText(path: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bundleType(source: string, output: string) {
|
function bundleType(source: string, output: string) {
|
||||||
execSync(
|
return dts({
|
||||||
[
|
source,
|
||||||
'npx',
|
dist: output,
|
||||||
'dts-bundle-generator',
|
project: './tsconfig.build.json',
|
||||||
JSON.stringify(source),
|
});
|
||||||
'-o',
|
|
||||||
JSON.stringify(output),
|
|
||||||
'--project',
|
|
||||||
'"./tsconfig.build.json"',
|
|
||||||
'--no-check',
|
|
||||||
].join(' '),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
36
scripts/dts.ts
Normal file
36
scripts/dts.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import {
|
||||||
|
generateDtsBundle,
|
||||||
|
type EntryPointConfig,
|
||||||
|
} from 'dts-bundle-generator/dist/bundle-generator';
|
||||||
|
|
||||||
|
export function dts({
|
||||||
|
source,
|
||||||
|
dist,
|
||||||
|
project,
|
||||||
|
}: {
|
||||||
|
source: string;
|
||||||
|
dist: string;
|
||||||
|
project: string;
|
||||||
|
}): void {
|
||||||
|
const entry: EntryPointConfig = {
|
||||||
|
filePath: source,
|
||||||
|
failOnClass: false,
|
||||||
|
output: {
|
||||||
|
inlineDeclareExternals: false,
|
||||||
|
inlineDeclareGlobals: false,
|
||||||
|
sortNodes: false,
|
||||||
|
noBanner: false,
|
||||||
|
respectPreserveConstEnum: false,
|
||||||
|
exportReferencedTypes: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatedDts = generateDtsBundle([entry], {
|
||||||
|
preferredConfigPath: project,
|
||||||
|
followSymlinks: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ts.sys.writeFile(dist, generatedDts[0]);
|
||||||
|
}
|
214
scripts/modifier.ts
Normal file
214
scripts/modifier.ts
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env tsx
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import { readFileSync, promises as fs } 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';
|
||||||
|
import { buildLocalRules } from '../src/build-local-rules';
|
||||||
|
import { dts } from './dts';
|
||||||
|
|
||||||
|
const polyfills = Object.keys(polyfill);
|
||||||
|
|
||||||
|
const ENV = (process.env.NODE_ENV ??= 'production');
|
||||||
|
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(
|
||||||
|
'array.prototype.toreversed',
|
||||||
|
proto(t => t.identifier('toReversed')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'array.prototype.findlast',
|
||||||
|
proto(t => t.identifier('findLast')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'string.prototype.matchall',
|
||||||
|
proto(t => t.identifier('matchAll')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'string.prototype.includes',
|
||||||
|
proto(t => t.identifier('includes')),
|
||||||
|
)
|
||||||
|
.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)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.set(
|
||||||
|
'safe-regex-test',
|
||||||
|
replace(t => requirePolyfill(t, 'safeRegexTest')),
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
38
src/index.ts
38
src/index.ts
@ -56,11 +56,18 @@ export interface LocalRuleOptions {
|
|||||||
|
|
||||||
export type RuleOptions = Rules & Partial<LocalRuleOptions>;
|
export type RuleOptions = Rules & Partial<LocalRuleOptions>;
|
||||||
|
|
||||||
|
export interface CustomRule {
|
||||||
|
rule: () => Promise<{
|
||||||
|
default: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||||
|
}>;
|
||||||
|
options?: RuleLevel;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ESLint Configuration.
|
* ESLint Configuration.
|
||||||
* @see [ESLint Configuration](https://eslint.org/docs/latest/user-guide/configuring/)
|
* @see [ESLint Configuration](https://eslint.org/docs/latest/user-guide/configuring/)
|
||||||
*/
|
*/
|
||||||
type Config = Omit<ESLintConfig, 'rules'> & {
|
export type InputConfig = Omit<ESLintConfig, 'rules'> & {
|
||||||
/**
|
/**
|
||||||
* Rules.
|
* Rules.
|
||||||
* @see [Rules](https://eslint.org/docs/latest/user-guide/configuring/rules)
|
* @see [Rules](https://eslint.org/docs/latest/user-guide/configuring/rules)
|
||||||
@ -68,24 +75,13 @@ type Config = Omit<ESLintConfig, 'rules'> & {
|
|||||||
rules?: RuleOptions;
|
rules?: RuleOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Glob pattern to find paths to custom rule files in JavaScript or TypeScript.
|
||||||
|
* Note this must be a string literal or an array of string literals since
|
||||||
|
* this is statically analyzed.
|
||||||
*/
|
*/
|
||||||
customRules?: {
|
customRuleFiles?: string | string[];
|
||||||
rule: () => Promise<{
|
|
||||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
|
||||||
}>;
|
|
||||||
options?: RuleLevel;
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function defineCustomRule<Options extends readonly unknown[]>(
|
|
||||||
rule: () => Promise<{
|
|
||||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, Options>;
|
|
||||||
}>,
|
|
||||||
options?: Options,
|
|
||||||
) {
|
|
||||||
return { rule, options };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a ESLint config object.
|
* Returns a ESLint config object.
|
||||||
*
|
*
|
||||||
@ -104,15 +100,17 @@ export function defineCustomRule<Options extends readonly unknown[]>(
|
|||||||
* Non bundled:
|
* Non bundled:
|
||||||
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
||||||
*/
|
*/
|
||||||
export function extendConfig({
|
export function extendConfig(of: InputConfig = {}): ESLintConfig {
|
||||||
|
const {
|
||||||
plugins = [],
|
plugins = [],
|
||||||
settings,
|
settings,
|
||||||
rules,
|
rules,
|
||||||
extends: _extends,
|
extends: _extends,
|
||||||
overrides,
|
overrides,
|
||||||
customRules,
|
customRuleFiles,
|
||||||
...rest
|
...rest
|
||||||
}: Config = {}): ESLintConfig {
|
} = of;
|
||||||
|
|
||||||
const hasReact = plugins.includes('react');
|
const hasReact = plugins.includes('react');
|
||||||
const hasReactRefresh = plugins.includes('react-refresh');
|
const hasReactRefresh = plugins.includes('react-refresh');
|
||||||
const hasUnicorn = plugins.includes('unicorn');
|
const hasUnicorn = plugins.includes('unicorn');
|
||||||
@ -129,7 +127,7 @@ export function extendConfig({
|
|||||||
fs.mkdirSync(ruleDir, { recursive: true });
|
fs.mkdirSync(ruleDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: Config = {
|
const result: InputConfig = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: unique('@typescript-eslint', 'import', 'rules', plugins),
|
plugins: unique('@typescript-eslint', 'import', 'rules', plugins),
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
import type { ESLint } from 'eslint';
|
import type { ESLint } from 'eslint';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import { resolve, basename, extname } from 'node:path';
|
import { resolve, basename, extname } from 'node:path';
|
||||||
|
import { glob } from 'fast-glob';
|
||||||
function tryRequire(candidates: string[]) {
|
import { parseModule } from 'esprima';
|
||||||
for (const candidate of candidates) {
|
import query from 'esquery';
|
||||||
try {
|
import type { Node, Property } from 'estree';
|
||||||
require(candidate);
|
|
||||||
return;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/gulpjs/interpret
|
// https://github.com/gulpjs/interpret
|
||||||
tryRequire([
|
const transpilers = [
|
||||||
'esbin/register',
|
'esbin/register',
|
||||||
'esbuild-register',
|
'esbuild-register',
|
||||||
'ts-node/register/transpile-only',
|
'ts-node/register/transpile-only',
|
||||||
@ -20,20 +15,63 @@ tryRequire([
|
|||||||
'sucrase/register',
|
'sucrase/register',
|
||||||
'@babel/register',
|
'@babel/register',
|
||||||
'coffeescript/register',
|
'coffeescript/register',
|
||||||
]);
|
];
|
||||||
|
|
||||||
const folders = resolve(process.cwd(), 'eslint-local-rules');
|
function tryRequire() {
|
||||||
const files = fs.readdirSync(folders);
|
for (const candidate of transpilers) {
|
||||||
|
try {
|
||||||
|
require(candidate);
|
||||||
|
return;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unwrapDefault = <T = any>(module: any): T => module.default ?? module;
|
||||||
|
|
||||||
const plugin: ESLint.Plugin = {
|
const plugin: ESLint.Plugin = {
|
||||||
rules: {},
|
rules: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const file of files) {
|
function hydrateESTreeNode(n: Node): any {
|
||||||
const module = require(resolve(folders, file));
|
switch (n.type) {
|
||||||
const unwrap = module.default ?? module;
|
case 'Literal':
|
||||||
const name = unwrap.name ?? basename(file, extname(file));
|
return n.value;
|
||||||
plugin.rules![name] = unwrap;
|
case 'ArrayExpression':
|
||||||
|
return n.elements.filter(Boolean).map(hydrateESTreeNode);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported node type: ${n.type}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseConfigFile(js: string) {
|
||||||
|
const [node] = query(
|
||||||
|
parseModule(js),
|
||||||
|
'CallExpression[callee.name="extendConfig"] > ObjectExpression > Property[key.name="customRuleFiles"]',
|
||||||
|
);
|
||||||
|
return hydrateESTreeNode((node as Property).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const rootDir = process.cwd();
|
||||||
|
|
||||||
|
const eslintConfigFile = ['.eslintrc.js', '.eslintrc.cjs']
|
||||||
|
.map(file => resolve(rootDir, file))
|
||||||
|
.find(file => fs.existsSync(file));
|
||||||
|
|
||||||
|
if (!eslintConfigFile) return;
|
||||||
|
|
||||||
|
const eslintConfig = fs.readFileSync(eslintConfigFile, 'utf8');
|
||||||
|
const customRuleFiles = parseConfigFile(eslintConfig);
|
||||||
|
if (!customRuleFiles?.length) return;
|
||||||
|
|
||||||
|
tryRequire();
|
||||||
|
for (const file of glob.sync(customRuleFiles)) {
|
||||||
|
const module = unwrapDefault(require(file));
|
||||||
|
const name = module.name ?? basename(file, extname(file));
|
||||||
|
plugin.rules![name] = module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
export = plugin;
|
export = plugin;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { LocalRuleOptions } from '../index';
|
import type { LocalRuleOptions } from '..';
|
||||||
import { error } from '../constants';
|
import { error } from '../constants';
|
||||||
|
|
||||||
export const localRules: Partial<LocalRuleOptions> = {
|
export const localRules: Partial<LocalRuleOptions> = {
|
||||||
|
Reference in New Issue
Block a user