Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
179cf83891 |
51
.eslint.ts
51
.eslint.ts
@ -1,51 +0,0 @@
|
||||
import type { FlatESLintConfig } from '@aet/eslint-define-config';
|
||||
import js from '@eslint/js';
|
||||
import * as tsParser from '@typescript-eslint/parser';
|
||||
import importPlugin from 'eslint-plugin-import-x';
|
||||
import unicorn from 'eslint-plugin-unicorn';
|
||||
import tsEslint from 'typescript-eslint';
|
||||
|
||||
import { importRules } from './src/presets/typescript';
|
||||
|
||||
export default [
|
||||
js.configs.recommended, //
|
||||
...tsEslint.configs.recommendedTypeChecked,
|
||||
unicorn.configs['flat/recommended'],
|
||||
importPlugin.flatConfigs.recommended,
|
||||
importPlugin.flatConfigs.react,
|
||||
importPlugin.flatConfigs.typescript,
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
projectService: true,
|
||||
ecmaVersion: 'latest',
|
||||
// https://github.com/unjs/jiti/issues/167 import.meta.dirname
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
'import-x/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx', '.mts', '.cts'],
|
||||
},
|
||||
'import-x/resolver': {
|
||||
typescript: true,
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
ignores: ['eslint.config.cjs'],
|
||||
rules: {
|
||||
...importRules,
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'unicorn/prevent-abbreviations': 'off',
|
||||
'unicorn/import-style': 'off',
|
||||
'unicorn/switch-case-braces': ['error', 'avoid'],
|
||||
'unicorn/no-null': 'off',
|
||||
},
|
||||
},
|
||||
] as FlatESLintConfig[];
|
62
.eslintrc
Normal file
62
.eslintrc
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "prettier"],
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"warn",
|
||||
{
|
||||
"paths": [
|
||||
"array-includes",
|
||||
"array.prototype.flat",
|
||||
"array.prototype.flatmap",
|
||||
"array.prototype.tosorted",
|
||||
"object.entries",
|
||||
"object.fromentries",
|
||||
"object.hasown",
|
||||
"object.values",
|
||||
"string.prototype.matchall",
|
||||
"has"
|
||||
]
|
||||
}
|
||||
],
|
||||
"arrow-body-style": ["error", "as-needed"],
|
||||
"class-methods-use-this": [
|
||||
"warn",
|
||||
{ "exceptMethods": ["toString", "shouldComponentUpdate"] }
|
||||
],
|
||||
"complexity": ["warn", { "max": 100 }],
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"no-async-promise-executor": "off",
|
||||
"no-case-declarations": "off",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-debugger": "off",
|
||||
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||
"no-inner-declarations": "off",
|
||||
"no-lonely-if": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-var": "error",
|
||||
"object-shorthand": ["error", "always", { "ignoreConstructors": true }],
|
||||
"one-var": ["error", { "var": "never", "let": "never" }],
|
||||
"prefer-const": ["error", { "destructuring": "all" }],
|
||||
"prefer-destructuring": [
|
||||
"warn",
|
||||
{ "AssignmentExpression": { "array": false, "object": false } }
|
||||
],
|
||||
"prefer-rest-params": "warn",
|
||||
"prefer-spread": "warn",
|
||||
"quote-props": ["error", "as-needed"],
|
||||
"spaced-comment": ["error", "always", { "markers": ["/"] }],
|
||||
"sort-imports": ["warn", { "ignoreDeclarationSort": true }],
|
||||
"yoda": ["error", "never", { "exceptRange": true }]
|
||||
}
|
||||
}
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,14 +1,9 @@
|
||||
drafts
|
||||
!/packages/eslint-plugin-react-hooks
|
||||
/packages/eslint-define-config
|
||||
/react
|
||||
/test
|
||||
src/types/rules
|
||||
|
||||
dist2
|
||||
dist/**/*.js
|
||||
dist/**/*.js.map
|
||||
!dist/default.js
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
3
.npmrc
3
.npmrc
@ -1,3 +1,2 @@
|
||||
registry http://raspberrypi.local:4873
|
||||
always-auth=true
|
||||
ignore-scripts=true
|
||||
always-auth=true
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -1,7 +1,6 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.runtime": "node",
|
||||
"eslint.useFlatConfig": true,
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true,
|
||||
|
10
README.md
10
README.md
@ -4,12 +4,6 @@ Personal ESLint config. Guaranteed to have no useless polyfills.
|
||||
|
||||
## flat config support
|
||||
|
||||
- ❌ [import](https://github.com/import-js/eslint-plugin-import/issues/2556)
|
||||
- ✅ [react](https://github.com/jsx-eslint/eslint-plugin-react/pull/3429)
|
||||
- ✅ [unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn/pull/1886)
|
||||
- ❌ [import](https://github.com/un-ts/eslint-plugin-import-x/issues/29)
|
||||
- ❌ [jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/978, supports flat config)
|
||||
|
||||
| Name | Flat Config | Issue |
|
||||
| ------- | ----------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| react | ✅ | [jsx-eslint/eslint-plugin-react#3429](https://github.com/jsx-eslint/eslint-plugin-react/pull/3429) |
|
||||
| unicorn | ✅ | [sindresorhus/eslint-plugin-unicorn#1886](https://github.com/sindresorhus/eslint-plugin-unicorn/pull/1886) |
|
||||
- ⏱️ [a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/pull/891)
|
||||
|
64
dist/config/index.d.ts
vendored
64
dist/config/index.d.ts
vendored
@ -1,64 +0,0 @@
|
||||
import type { FlatESLintConfig } from '@aet/eslint-define-config';
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
type MiddlewareResult = Linter.Config | Linter.Config[];
|
||||
|
||||
export type Middleware =
|
||||
| (() => Promise<MiddlewareResult>)
|
||||
| (() => Promise<{ default: MiddlewareResult }>);
|
||||
|
||||
export type Environment =
|
||||
| 'jsdoc'
|
||||
| 'lingui'
|
||||
| 'react'
|
||||
| 'reactQuery'
|
||||
| 'reactRefresh'
|
||||
| 'storybook'
|
||||
| 'tailwind'
|
||||
| 'testingLibrary'
|
||||
| 'vitest';
|
||||
|
||||
export interface NormalizedExtendConfigOptions {
|
||||
auto?: boolean;
|
||||
middlewares?: Middleware[];
|
||||
configs: FlatESLintConfig[];
|
||||
/**
|
||||
* Use `.gitignore` file to exclude files from ESLint.
|
||||
*/
|
||||
gitignore?: boolean;
|
||||
env?: {
|
||||
[key in Environment]?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type ExtendConfigOptions =
|
||||
| FlatESLintConfig
|
||||
| FlatESLintConfig[]
|
||||
| NormalizedExtendConfigOptions;
|
||||
|
||||
/**
|
||||
* Returns a ESLint config object.
|
||||
*
|
||||
* By default, it includes `["@typescript-eslint", "import-x", "prettier", "unicorn"]` configs.
|
||||
* Additional bundled plugins include:
|
||||
*
|
||||
* 1. [`react`](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules)
|
||||
* (automatically enables
|
||||
* [`react-hooks`](https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks))
|
||||
* 2. [`react-refresh`](https://github.com/ArnaudBarre/eslint-plugin-react-refresh)
|
||||
* 3. [`jsx-a11y`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules)
|
||||
* 4. [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn#rules)
|
||||
* 5. [`n`](https://github.com/eslint-community/eslint-plugin-n#-rules) (Node.js specific,
|
||||
* requires `minimatch`)
|
||||
* 6. [`jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc#rules)
|
||||
*
|
||||
* Non bundled:
|
||||
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
||||
*
|
||||
* @returns ESLint configuration object.
|
||||
*/
|
||||
export function extendConfig(options?: ExtendConfigOptions): Promise<FlatESLintConfig[]>;
|
||||
|
||||
export const error = 'error';
|
||||
export const warn = 'warn';
|
||||
export const off = 'off';
|
5
dist/default.d.ts
vendored
5
dist/default.d.ts
vendored
@ -1,5 +0,0 @@
|
||||
import type { FlatESLintConfig } from '@aet/eslint-define-config';
|
||||
|
||||
declare const _default: FlatESLintConfig[];
|
||||
|
||||
export default _default;
|
3
dist/default.js
vendored
3
dist/default.js
vendored
@ -1,3 +0,0 @@
|
||||
import { extendConfig } from './config/index.js';
|
||||
|
||||
export default await extendConfig();
|
2
dist/eslint-init.sh
vendored
2
dist/eslint-init.sh
vendored
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo 'export { default } from "@aet/eslint-rules/default";' > eslint.config.js
|
14
dist/eslint-plugin-import/index.d.ts
vendored
Normal file
14
dist/eslint-plugin-import/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export const rules: Readonly<Linter.RulesRecord>;
|
||||
|
||||
export const configs: {
|
||||
recommended: Linter.BaseConfig;
|
||||
errors: Linter.BaseConfig;
|
||||
warnings: Linter.BaseConfig;
|
||||
'stage-0': Linter.BaseConfig;
|
||||
react: Linter.BaseConfig;
|
||||
'react-native': Linter.BaseConfig;
|
||||
electron: Linter.BaseConfig;
|
||||
typescript: Linter.BaseConfig;
|
||||
};
|
8
dist/eslint-plugin-jsx-a11y/index.d.ts
vendored
Normal file
8
dist/eslint-plugin-jsx-a11y/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export const rules: Readonly<Linter.RulesRecord>;
|
||||
|
||||
export const configs: {
|
||||
recommended: Linter.BaseConfig;
|
||||
strict: Linter.BaseConfig;
|
||||
};
|
12
dist/eslint-plugin-react-hooks/index.d.ts
vendored
Normal file
12
dist/eslint-plugin-react-hooks/index.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Linter, Rule } from 'eslint';
|
||||
|
||||
export const __EXPERIMENTAL__: false;
|
||||
|
||||
export const configs: {
|
||||
recommended: Linter.BaseConfig;
|
||||
};
|
||||
|
||||
export const rules: {
|
||||
'rules-of-hooks': Rule.RuleModule;
|
||||
'exhaustive-deps': Rule.RuleModule;
|
||||
};
|
9
dist/eslint-plugin-react/index.d.ts
vendored
Normal file
9
dist/eslint-plugin-react/index.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export const deprecatedRules: Readonly<Linter.RulesRecord>;
|
||||
|
||||
export const configs: {
|
||||
recommended: Linter.BaseConfig;
|
||||
all: Linter.BaseConfig;
|
||||
'jsx-runtime': Linter.BaseConfig;
|
||||
};
|
72
dist/index.d.ts
vendored
Normal file
72
dist/index.d.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// Generated by dts-bundle-generator v9.4.0
|
||||
|
||||
import { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import { Rule } from 'eslint';
|
||||
import { ESLintConfig, Rules } from 'eslint-define-config';
|
||||
|
||||
export declare const error = "error";
|
||||
export declare const warn = "warn";
|
||||
export declare const off = "off";
|
||||
export type RuleLevel = "error" | "warn" | "off" | 0 | 1 | 2;
|
||||
export type RuleEntry<Options> = RuleLevel | [
|
||||
RuleLevel,
|
||||
Partial<Options>
|
||||
];
|
||||
export interface LocalRuleOptions {
|
||||
/** Bans import from the specifier '.' and '..' and replaces it with '.+/index' */
|
||||
"rules/no-import-dot": RuleEntry<unknown>;
|
||||
/**
|
||||
* Enforce template literal expressions to be of `string` type
|
||||
* @see [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions)
|
||||
*/
|
||||
"rules/restrict-template-expressions": RuleEntry<{
|
||||
allow: string[];
|
||||
}>;
|
||||
/** Ban assignment of empty object literals `{}` and replace them with `Object.create(null)` */
|
||||
"rules/no-empty-object-literal": RuleEntry<unknown>;
|
||||
}
|
||||
export type RuleOptions = Rules & Partial<LocalRuleOptions>;
|
||||
export interface CustomRule {
|
||||
rule: () => Promise<{
|
||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||
}>;
|
||||
options?: RuleLevel;
|
||||
}
|
||||
/**
|
||||
* ESLint Configuration.
|
||||
* @see [ESLint Configuration](https://eslint.org/docs/latest/user-guide/configuring/)
|
||||
*/
|
||||
export type InputConfig = Omit<ESLintConfig, "rules"> & {
|
||||
/**
|
||||
* Rules.
|
||||
* @see [Rules](https://eslint.org/docs/latest/user-guide/configuring/rules)
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
customRuleFiles?: string | string[];
|
||||
};
|
||||
/**
|
||||
* Returns a ESLint config object.
|
||||
*
|
||||
* By default, it includes `["@typescript-eslint", "import", "prettier"]` configs.
|
||||
* Additional bundled plugins include:
|
||||
*
|
||||
* 1. [`react`](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules)
|
||||
* (automatically enables
|
||||
* [`react-hooks`](https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks))
|
||||
* 2. [`react-refresh`](https://github.com/ArnaudBarre/eslint-plugin-react-refresh)
|
||||
* 3. [`jsx-a11y`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules)
|
||||
* 4. [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn#rules)
|
||||
* 5. [`n`](https://github.com/eslint-community/eslint-plugin-n#-rules) (Node.js specific)
|
||||
* 6. [`jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc#rules)
|
||||
*
|
||||
* Non bundled:
|
||||
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
||||
*/
|
||||
export declare function extendConfig(of?: InputConfig): ESLintConfig;
|
||||
|
||||
export {};
|
107
dist/package.json
vendored
107
dist/package.json
vendored
@ -1,81 +1,60 @@
|
||||
{
|
||||
"name": "@aet/eslint-rules",
|
||||
"version": "2.0.51",
|
||||
"version": "0.0.24-beta.1",
|
||||
"license": "UNLICENSED",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"eslint-init": "eslint-init.sh",
|
||||
"eslint-install": "install.js",
|
||||
"eslint-print": "print-config.sh"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^9.15.0",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"exports": {
|
||||
".": "./config/index.js",
|
||||
"./default": "./default.js",
|
||||
"./prettier": "./prettier.js",
|
||||
"./tsconfig": "./tsconfig.json",
|
||||
"./types": "./types.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tanstack/eslint-plugin-query": "^5.62.1"
|
||||
"eslint": "^8.57.0",
|
||||
"typescript": "^5.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aet/eslint-define-config": "^0.1.15",
|
||||
"@antfu/install-pkg": "^1.1.0",
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@eslint-react/eslint-plugin": "1.51.3",
|
||||
"@eslint/js": "^9.28.0",
|
||||
"@nolyfill/is-core-module": "^1.0.39",
|
||||
"@stylistic/eslint-plugin": "^4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
||||
"@typescript-eslint/parser": "^8.34.0",
|
||||
"@typescript-eslint/type-utils": "^8.34.0",
|
||||
"@typescript-eslint/utils": "^8.34.0",
|
||||
"aria-query": "^5.3.2",
|
||||
"axe-core": "^4.10.3",
|
||||
"axobject-query": "4.1.0",
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/eslint": "^8.56.9",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"@typescript-eslint/type-utils": "^7.7.0",
|
||||
"@typescript-eslint/utils": "^7.7.0",
|
||||
"aria-query": "^5.3.0",
|
||||
"axe-core": "^4.9.0",
|
||||
"axobject-query": "^4.0.0",
|
||||
"damerau-levenshtein": "1.0.8",
|
||||
"debug": "^4.4.1",
|
||||
"debug": "^4.3.4",
|
||||
"doctrine": "^3.0.0",
|
||||
"emoji-regex": "^10.4.0",
|
||||
"enhanced-resolve": "^5.18.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"emoji-regex": "^10.3.0",
|
||||
"enhanced-resolve": "^5.16.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-define-config": "^1.24.1",
|
||||
"eslint-import-resolver-node": "^0.3.9",
|
||||
"eslint-import-resolver-typescript": "^4.4.3",
|
||||
"eslint-module-utils": "^2.12.0",
|
||||
"eslint-plugin-import-x": "^4.15.1",
|
||||
"eslint-plugin-regexp": "^2.9.0",
|
||||
"eslint-plugin-unicorn": "^59.0.1",
|
||||
"eslint-module-utils": "^2.8.1",
|
||||
"eslint-plugin-es-x": "^7.6.0",
|
||||
"eslint-plugin-jsdoc": "^48.2.3",
|
||||
"eslint-plugin-unicorn": "^52.0.0",
|
||||
"esprima": "^4.0.1",
|
||||
"esquery": "^1.6.0",
|
||||
"esquery": "^1.5.0",
|
||||
"estraverse": "^5.3.0",
|
||||
"fast-glob": "^3.3.3",
|
||||
"get-tsconfig": "^4.10.1",
|
||||
"globals": "^16.2.0",
|
||||
"ignore": "^7.0.5",
|
||||
"is-bun-module": "^2.0.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"get-tsconfig": "^4.7.3",
|
||||
"ignore": "^5.3.1",
|
||||
"is-builtin-module": "^3.2.1",
|
||||
"is-glob": "^4.0.3",
|
||||
"language-tags": "^2.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.0.1",
|
||||
"semver": "^7.7.2",
|
||||
"typescript-eslint": "^8.34.0"
|
||||
"language-tags": "^1.0.9",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^9.0.4",
|
||||
"resolve": "^2.0.0-next.5",
|
||||
"semver": "^7.6.0",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"overrides": {
|
||||
"supports-preserve-symlinks-flag": "file:./overrides/supports-preserve-symlinks-flag",
|
||||
"is-core-module": "file:./overrides/is-core-module"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/supports-preserve-symlinks-flag": "file:./overrides/supports-preserve-symlinks-flag",
|
||||
"**/is-core-module": "file:./overrides/is-core-module"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"is-core-module": "file:./overrides/is-core-module",
|
||||
"supports-preserve-symlinks-flag": "file:./overrides/supports-preserve-symlinks-flag"
|
||||
"supports-preserve-symlinks-flag": "file:./overrides/supports-preserve-symlinks-flag",
|
||||
"is-core-module": "file:./overrides/is-core-module"
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"is-core-module": "file:./overrides/is-core-module",
|
||||
"supports-preserve-symlinks-flag": "file:./overrides/supports-preserve-symlinks-flag"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/is-core-module": "file:./overrides/is-core-module",
|
||||
"**/supports-preserve-symlinks-flag": "file:./overrides/supports-preserve-symlinks-flag"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
dist/print-config.sh
vendored
2
dist/print-config.sh
vendored
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
node -e "import('./eslint.config.mjs').then(config => console.dir(config, { depth: null }))"
|
19
dist/tsconfig.json
vendored
19
dist/tsconfig.json
vendored
@ -1,19 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowArbitraryExtensions": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"jsx": "react-jsx",
|
||||
"module": "Preserve",
|
||||
"moduleResolution": "Bundler",
|
||||
"noEmit": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"stripInternal": true,
|
||||
"target": "ESNext",
|
||||
"verbatimModuleSyntax": true
|
||||
}
|
||||
}
|
2
dist/types.d.ts
vendored
2
dist/types.d.ts
vendored
@ -6,7 +6,7 @@ import { Rule } from 'eslint';
|
||||
export declare function defineRules(rules: {
|
||||
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||
}): {
|
||||
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[], unknown, ESLintUtils.RuleListener>;
|
||||
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[], ESLintUtils.RuleListener>;
|
||||
};
|
||||
export declare function defineRule({ name, create, ...meta }: Rule.RuleMetaData & {
|
||||
name?: string;
|
||||
|
1130
dist/yarn.lock
vendored
Normal file
1130
dist/yarn.lock
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
||||
/* eslint-disable */
|
||||
require('@swc-node/register');
|
||||
module.exports = require('./.eslint.ts').default;
|
138
package.json
138
package.json
@ -1,98 +1,49 @@
|
||||
{
|
||||
"name": "@aet/eslint-configs",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build": "./scripts/build.ts",
|
||||
"check-import": "./scripts/check-imports.ts",
|
||||
"define": "/usr/local/bin/codium ./packages/eslint-define-config",
|
||||
"sync": "./scripts/sync-deps.ts",
|
||||
"do": "(cd dist && ver bump && npm publish && ver unpub)"
|
||||
"build": "./scripts/build-all.ts",
|
||||
"build-types": "cd ./packages/eslint-define-config && ./scripts/index.ts",
|
||||
"check-import": "./scripts/check-imports.ts"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@aet/eslint-define-config": "^0.1.16",
|
||||
"@antfu/install-pkg": "^1.1.0",
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@eslint-react/eslint-plugin": "1.52.2",
|
||||
"@eslint/js": "^9.28.0",
|
||||
"@nolyfill/is-core-module": "^1.0.39",
|
||||
"@stylistic/eslint-plugin": "^4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
||||
"@typescript-eslint/parser": "^8.34.0",
|
||||
"@typescript-eslint/type-utils": "^8.34.0",
|
||||
"@typescript-eslint/utils": "^8.34.0",
|
||||
"aria-query": "^5.3.2",
|
||||
"axe-core": "^4.10.3",
|
||||
"axobject-query": "4.1.0",
|
||||
"damerau-levenshtein": "1.0.8",
|
||||
"debug": "^4.4.1",
|
||||
"doctrine": "^3.0.0",
|
||||
"emoji-regex": "^10.4.0",
|
||||
"enhanced-resolve": "^5.18.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-import-resolver-node": "^0.3.9",
|
||||
"eslint-import-resolver-typescript": "^4.4.3",
|
||||
"eslint-module-utils": "^2.12.0",
|
||||
"eslint-plugin-import-x": "^4.15.2",
|
||||
"eslint-plugin-regexp": "^2.9.0",
|
||||
"eslint-plugin-unicorn": "^59.0.1",
|
||||
"esprima": "^4.0.1",
|
||||
"esquery": "^1.6.0",
|
||||
"estraverse": "^5.3.0",
|
||||
"fast-glob": "^3.3.3",
|
||||
"get-tsconfig": "^4.10.1",
|
||||
"globals": "^16.2.0",
|
||||
"ignore": "^7.0.5",
|
||||
"is-bun-module": "^2.0.0",
|
||||
"is-glob": "^4.0.3",
|
||||
"language-tags": "^2.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.0.3",
|
||||
"semver": "^7.7.2",
|
||||
"typescript-eslint": "^8.34.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@graphql-eslint/eslint-plugin": "^4.4.0",
|
||||
"@swc-node/register": "^1.10.10",
|
||||
"@tanstack/eslint-plugin-query": "^5.78.0",
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.24.1",
|
||||
"@babel/preset-env": "^7.24.4",
|
||||
"@types/babel-plugin-macros": "^3.1.3",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/eslint-config-prettier": "^6.11.3",
|
||||
"@types/eslint-plugin-tailwindcss": "^3.17.0",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/esprima": "^4.0.6",
|
||||
"@types/esquery": "^1.5.4",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/esquery": "^1.5.3",
|
||||
"@types/estree": "^1.0.5",
|
||||
"@types/estree-jsx": "^1.0.5",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/react-refresh": "^0.14.6",
|
||||
"@typescript-eslint/types": "^8.34.0",
|
||||
"@typescript-eslint/typescript-estree": "^8.34.0",
|
||||
"@vitest/eslint-plugin": "^1.2.4",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/node": "^20.12.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.7.0",
|
||||
"@typescript-eslint/type-utils": "^7.7.0",
|
||||
"@typescript-eslint/types": "^7.7.0",
|
||||
"@typescript-eslint/typescript-estree": "^7.7.0",
|
||||
"@typescript-eslint/utils": "^7.7.0",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"dts-bundle-generator": "9.5.1",
|
||||
"esbuild": "0.25.5",
|
||||
"dts-bundle-generator": "^9.5.0",
|
||||
"esbin": "0.0.4",
|
||||
"esbuild": "0.20.2",
|
||||
"esbuild-plugin-alias": "^0.2.1",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-jsdoc": "^50.7.1",
|
||||
"eslint-plugin-lingui": "^0.10.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"eslint-plugin-storybook": "^9.0.9",
|
||||
"eslint-plugin-testing-library": "^7.5.2",
|
||||
"graphql": "^16.11.0",
|
||||
"json-schema-to-ts": "^3.1.1",
|
||||
"nolyfill": "^1.0.44",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-define-config": "file:./src/types",
|
||||
"esprima": "^4.0.1",
|
||||
"esquery": "^1.5.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"find-cache-dir": "^5.0.0",
|
||||
"json-schema-to-ts": "^3.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^9.0.4",
|
||||
"patch-package": "^8.0.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^3.5.3",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prop-types": "^15.8.1",
|
||||
"terser": "^5.42.0",
|
||||
"type-fest": "^4.41.0",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"prettier": {
|
||||
"arrowParens": "avoid",
|
||||
@ -104,22 +55,15 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@typescript-eslint/utils": "8.0.0",
|
||||
"function-bind": "npm:@nolyfill/function-bind@^1",
|
||||
"has-proto": "npm:@nolyfill/has-proto@^1",
|
||||
"has-symbols": "npm:@nolyfill/has-symbols@^1",
|
||||
"hasown": "npm:@nolyfill/hasown@^1",
|
||||
"isarray": "npm:@nolyfill/isarray@^1",
|
||||
"jsonify": "npm:@nolyfill/jsonify@^1",
|
||||
"object-keys": "npm:@nolyfill/object-keys@^1",
|
||||
"set-function-length": "npm:@nolyfill/set-function-length@^1",
|
||||
"@babel/types": "7.25.2",
|
||||
"is-core-module": "npm:@nolyfill/is-core-module@^1",
|
||||
"json-stable-stringify": "npm:@nolyfill/json-stable-stringify@^1"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@typescript-eslint/typescript-estree@8.0.0": "patches/@typescript-eslint__typescript-estree@8.0.0.patch",
|
||||
"dts-bundle-generator": "patches/dts-bundle-generator.patch"
|
||||
"function-bind": "npm:@nolyfill/function-bind@latest",
|
||||
"has-proto": "npm:@nolyfill/has-proto@latest",
|
||||
"has-symbols": "npm:@nolyfill/has-symbols@latest",
|
||||
"hasown": "npm:@nolyfill/hasown@latest",
|
||||
"isarray": "npm:@nolyfill/isarray@latest",
|
||||
"jsonify": "npm:@nolyfill/jsonify@latest",
|
||||
"object-keys": "npm:@nolyfill/object-keys@latest",
|
||||
"set-function-length": "npm:@nolyfill/set-function-length@latest",
|
||||
"@babel/types": "7.24.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
packages/eslint-define-config/LICENSE
Normal file
21
packages/eslint-define-config/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Christopher Quadflieg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
144
packages/eslint-define-config/README.md
Normal file
144
packages/eslint-define-config/README.md
Normal file
@ -0,0 +1,144 @@
|
||||
<p>
|
||||
<a href="https://www.npmjs.com/package/eslint-define-config" target="_blank">
|
||||
<img alt="NPM package" src="https://img.shields.io/npm/v/eslint-define-config.svg">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/eslint-define-config" target="_blank">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dt/eslint-define-config.svg">
|
||||
</a>
|
||||
<a href="https://github.com/eslint-types/eslint-define-config/actions/workflows/ci.yml">
|
||||
<img alt="Build Status" src="https://github.com/eslint-types/eslint-define-config/actions/workflows/ci.yml/badge.svg?branch=main">
|
||||
</a>
|
||||
<a href="https://github.com/eslint-types/eslint-define-config/blob/main/LICENSE">
|
||||
<img alt="License: MIT" src="https://img.shields.io/github/license/eslint-types/eslint-define-config.svg">
|
||||
</a>
|
||||
<a href="https://prettier.io" target="_blank">
|
||||
<img alt="Code Style: Prettier" src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg">
|
||||
</a>
|
||||
<a href="https://www.paypal.com/donate?hosted_button_id=L7GY729FBKTZY" target="_blank">
|
||||
<img alt="Donate: PayPal" src="https://img.shields.io/badge/Donate-PayPal-blue.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# eslint-define-config
|
||||
|
||||
Provide a `defineConfig` function for `.eslintrc.js`, and a `defineFlatConfig` function for `eslint.config.js` files.
|
||||
|
||||
> This project is written by a human and only partially automatically generated!
|
||||
> Some rules are even enhanced by hand!
|
||||
> Unfortunately, this has the disadvantage that not everything is immediately defined. For example, if a rule is not defined, it falls back to a basic definition.
|
||||
> However, the advantage is that you get documentation for pretty much everything in the code and usually get a direct link to the respective plugin or eslint rule. The types are also strictly typed.
|
||||
>
|
||||
> So if you are missing something like a rule or a plugin that should also be supported or a rule definition is e.g. out of date, feel free to open an issue or PR for it.
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
# add eslint and eslint-define-config to project’s dev dependencies
|
||||
npm add --save-dev eslint eslint-define-config
|
||||
# or
|
||||
yarn add --dev eslint eslint-define-config
|
||||
# or
|
||||
pnpm add --save-dev eslint eslint-define-config
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
`.eslintrc.js`
|
||||
|
||||
```ts
|
||||
// @ts-check
|
||||
|
||||
// To activate auto-suggestions for Rules of specific plugins, you need to add a `/// <reference types="eslint-plugin-PLUGIN_NAME/define-config-support" />` comment.
|
||||
// ⚠️ This feature is very new and requires the support of the respective plugin owners.
|
||||
|
||||
/// <reference types="@typescript-eslint/eslint-plugin/define-config-support" />
|
||||
|
||||
const { defineConfig } = require('eslint-define-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
root: true,
|
||||
rules: {
|
||||
// rules...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Flat Config
|
||||
|
||||
`eslint.config.js`
|
||||
|
||||
```ts
|
||||
// @ts-check
|
||||
const { defineFlatConfig } = require('eslint-define-config');
|
||||
|
||||
module.exports = defineFlatConfig([
|
||||
'eslint:recommended',
|
||||
{
|
||||
plugins: {
|
||||
// plugins...
|
||||
},
|
||||
rules: {
|
||||
// rules...
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
# Why?
|
||||
|
||||
Improve your eslint configuration experience with:
|
||||
|
||||
- auto-suggestions
|
||||
- type checking (Use `// @ts-check` at the first line in your `.eslintrc.js` or `eslint.config.js`)
|
||||
- documentation
|
||||
- deprecation warnings
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/7195563/112484789-8a416480-8d7a-11eb-9337-d8b5bc16de17.png" alt="Image" width="600px"/>
|
||||
|
||||
## Video
|
||||
|
||||
_Click on the thumbnail to play the video_
|
||||
|
||||
<a href="https://user-images.githubusercontent.com/7195563/112726158-4a19e780-8f1c-11eb-8cc6-4ea6c100137f.mp4" target="_blank">
|
||||
<img src="https://user-images.githubusercontent.com/7195563/112726343-30c56b00-8f1d-11eb-9b92-260c530caf1b.png" alt="Video" width="600px"/>
|
||||
</a>
|
||||
|
||||
## Want to support your own plugin?
|
||||
|
||||
:warning: **This feature is very new and requires the support of the respective plugin owners**
|
||||
|
||||
Add a `declare module` to your plugin package like this:
|
||||
|
||||
```ts
|
||||
declare module 'eslint-define-config' {
|
||||
export interface CustomRuleOptions {
|
||||
/**
|
||||
* Require consistently using either `T[]` or `Array<T>` for arrays.
|
||||
*
|
||||
* @see [array-type](https://typescript-eslint.io/rules/array-type)
|
||||
*/
|
||||
'@typescript-eslint/array-type': [
|
||||
{
|
||||
default?: 'array' | 'generic' | 'array-simple';
|
||||
readonly?: 'array' | 'generic' | 'array-simple';
|
||||
},
|
||||
];
|
||||
|
||||
// ... more Rules
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are other interfaces that can be extended.
|
||||
|
||||
- `CustomExtends`
|
||||
- `CustomParserOptions`
|
||||
- `CustomParsers`
|
||||
- `CustomPlugins`
|
||||
- `CustomSettings`
|
||||
|
||||
# Credits
|
||||
|
||||
- [Proposal Idea](https://github.com/eslint/eslint/issues/14249)
|
||||
- [Vite](https://github.com/vitejs/vite) and [Evan You](https://github.com/yyx990803) for the idea
|
||||
- [@antfu](https://github.com/antfu) and his [tweet](https://twitter.com/antfu7/status/1365907188338753536)
|
120
packages/eslint-define-config/package.json
Normal file
120
packages/eslint-define-config/package.json
Normal file
@ -0,0 +1,120 @@
|
||||
{
|
||||
"name": "eslint-define-config",
|
||||
"version": "1.24.1",
|
||||
"description": "Provide a defineConfig function for .eslintrc.js files",
|
||||
"scripts": {
|
||||
"clean": "rimraf coverage .eslintcache dist pnpm-lock.yaml node_modules",
|
||||
"format": "prettier --cache --write .",
|
||||
"lint:run": "eslint --cache --cache-strategy content --report-unused-disable-directives .",
|
||||
"lint": "run-s build lint:run",
|
||||
"typecheck": "vitest typecheck",
|
||||
"ts-check": "tsc",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"generate:rules": "tsx ./scripts/generate-rule-files/cli.ts",
|
||||
"prepublishOnly": "pnpm run clean && pnpm install && pnpm run build",
|
||||
"preflight": "pnpm install && run-s format lint ts-check test typecheck"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"config",
|
||||
"configuration",
|
||||
"define-config",
|
||||
"eslint-config",
|
||||
"eslint",
|
||||
"eslintconfig",
|
||||
"typed",
|
||||
"typescript"
|
||||
],
|
||||
"author": {
|
||||
"name": "Christopher Quadflieg",
|
||||
"email": "chrissi92@hotmail.de",
|
||||
"url": "https://github.com/Shinigami92"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eslint-types/eslint-define-config.git"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/Shinigami92"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://www.paypal.com/donate/?hosted_button_id=L7GY729FBKTZY"
|
||||
}
|
||||
],
|
||||
"bugs": "https://github.com/eslint-types/eslint-define-config/issues",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@graphql-eslint/eslint-plugin": "~3.20.1",
|
||||
"@intlify/eslint-plugin-vue-i18n": "~2.0.0",
|
||||
"@poppinss/cliui": "~6.4.1",
|
||||
"@types/dedent": "^0.7.2",
|
||||
"@types/eslint": "~8.44.3",
|
||||
"@types/json-schema": "~7.0.13",
|
||||
"@types/node": "~20.8.3",
|
||||
"@typescript-eslint/eslint-plugin": "~6.7.4",
|
||||
"@typescript-eslint/parser": "~6.7.4",
|
||||
"@vitest/coverage-v8": "~0.34.6",
|
||||
"change-case": "~5.4.4",
|
||||
"dedent": "^1.5.3",
|
||||
"eslint-config-prettier": "~9.0.0",
|
||||
"eslint-gitignore": "~0.1.0",
|
||||
"eslint-plugin-deprecation": "~2.0.0",
|
||||
"eslint-plugin-eslint-comments": "~3.2.0",
|
||||
"eslint-plugin-import": "~2.28.1",
|
||||
"eslint-plugin-inclusive-language": "~2.2.1",
|
||||
"eslint-plugin-jsdoc": "~46.8.2",
|
||||
"eslint-plugin-jsonc": "~2.9.0",
|
||||
"eslint-plugin-jsx-a11y": "~6.7.1",
|
||||
"eslint-plugin-mdx": "~3.1.5",
|
||||
"eslint-plugin-n": "~16.1.0",
|
||||
"eslint-plugin-node": "~11.1.0",
|
||||
"eslint-plugin-prettier": "~5.0.0",
|
||||
"eslint-plugin-promise": "~6.1.1",
|
||||
"eslint-plugin-react": "~7.33.2",
|
||||
"eslint-plugin-react-hooks": "~4.6.0",
|
||||
"eslint-plugin-sonarjs": "~0.21.0",
|
||||
"eslint-plugin-spellcheck": "~0.0.20",
|
||||
"eslint-plugin-testing-library": "~6.0.2",
|
||||
"eslint-plugin-unicorn": "~48.0.1",
|
||||
"eslint-plugin-vitest": "~0.3.2",
|
||||
"eslint-plugin-vue": "~9.17.0",
|
||||
"eslint-plugin-vue-pug": "~0.6.0",
|
||||
"eslint-plugin-yml": "~1.10.0",
|
||||
"expect-type": "~0.17.3",
|
||||
"graphql": "~16.8.1",
|
||||
"json-schema": "~0.4.0",
|
||||
"json-schema-to-ts": "~2.9.2",
|
||||
"json-schema-to-typescript": "~13.1.1",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"rimraf": "~5.0.5",
|
||||
"tsup": "~7.2.0",
|
||||
"vue-eslint-parser": "~9.3.2"
|
||||
},
|
||||
"packageManager": "pnpm@8.8.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
6894
packages/eslint-define-config/pnpm-lock.yaml
generated
Normal file
6894
packages/eslint-define-config/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
diff --git b/lib/rules/eslint/no-constructor-return.d.ts a/lib/rules/eslint/no-constructor-return.d.ts
|
||||
index fedeca4..3e1fd03 100644
|
||||
--- b/lib/rules/eslint/no-constructor-return.d.ts
|
||||
+++ a/lib/rules/eslint/no-constructor-return.d.ts
|
||||
@@ -1,16 +1,9 @@
|
||||
import type { RuleConfig } from '../rule-config';
|
||||
|
||||
-/**
|
||||
- * Option.
|
||||
- */
|
||||
-export interface NoConstructorReturnOption {
|
||||
- [k: string]: any;
|
||||
-}
|
||||
-
|
||||
/**
|
||||
* Options.
|
||||
*/
|
||||
-export type NoConstructorReturnOptions = NoConstructorReturnOption;
|
||||
+export type NoConstructorReturnOptions = [];
|
||||
|
||||
/**
|
||||
* Disallow returning value from constructor.
|
@ -0,0 +1,14 @@
|
||||
diff --git b/lib/rules/graphql-eslint/naming-convention.d.ts a/lib/rules/graphql-eslint/naming-convention.d.ts
|
||||
index 60b228b..5e01373 100644
|
||||
--- b/lib/rules/graphql-eslint/naming-convention.d.ts
|
||||
+++ a/lib/rules/graphql-eslint/naming-convention.d.ts
|
||||
@@ -78,8 +78,7 @@ export type NamingConventionOption =
|
||||
VariableDefinition?: AsString | AsObject;
|
||||
allowLeadingUnderscore?: boolean;
|
||||
allowTrailingUnderscore?: boolean;
|
||||
- /**
|
||||
- */
|
||||
+ } & {
|
||||
[k: string]: AsString | AsObject;
|
||||
},
|
||||
];
|
@ -0,0 +1,13 @@
|
||||
diff --git b/lib/rules/node/file-extension-in-import.d.ts a/lib/rules/node/file-extension-in-import.d.ts
|
||||
index 652b18d..7261252 100644
|
||||
--- b/lib/rules/node/file-extension-in-import.d.ts
|
||||
+++ a/lib/rules/node/file-extension-in-import.d.ts
|
||||
@@ -5,7 +5,7 @@ import type { RuleConfig } from '../rule-config';
|
||||
*/
|
||||
export interface FileExtensionInImportConfig {
|
||||
tryExtensions?: string[];
|
||||
- [k: string]: 'always' | 'never';
|
||||
+ [ext: `.${string}`]: 'always' | 'never';
|
||||
}
|
||||
|
||||
/**
|
@ -0,0 +1,23 @@
|
||||
diff --git a/lib/rules/react/jsx-no-constructed-context-values.d.ts b/lib/rules/react/jsx-no-constructed-context-values.d.ts
|
||||
index 410f060..e356693 100644
|
||||
--- a/lib/rules/react/jsx-no-constructed-context-values.d.ts
|
||||
+++ b/lib/rules/react/jsx-no-constructed-context-values.d.ts
|
||||
@@ -1,17 +1,9 @@
|
||||
import type { RuleConfig } from '../rule-config';
|
||||
|
||||
-/**
|
||||
- * Option.
|
||||
- */
|
||||
-export interface JsxNoConstructedContextValuesOption {
|
||||
- [k: string]: any;
|
||||
-}
|
||||
-
|
||||
/**
|
||||
* Options.
|
||||
*/
|
||||
-export type JsxNoConstructedContextValuesOptions =
|
||||
- JsxNoConstructedContextValuesOption;
|
||||
+export type JsxNoConstructedContextValuesOptions = [];
|
||||
|
||||
/**
|
||||
* Disallows JSX context provider values from taking values that will cause needless rerenders.
|
@ -0,0 +1,13 @@
|
||||
diff --git a/lib/rules/react/jsx-props-no-spreading.d.ts b/lib/rules/react/jsx-props-no-spreading.d.ts
|
||||
index c1e0069..ba1e1bc 100644
|
||||
--- a/lib/rules/react/jsx-props-no-spreading.d.ts
|
||||
+++ b/lib/rules/react/jsx-props-no-spreading.d.ts
|
||||
@@ -8,8 +8,6 @@ export type JsxPropsNoSpreadingOption = {
|
||||
custom?: 'enforce' | 'ignore';
|
||||
exceptions?: string[];
|
||||
[k: string]: any;
|
||||
-} & {
|
||||
- [k: string]: any;
|
||||
};
|
||||
|
||||
/**
|
@ -0,0 +1,21 @@
|
||||
diff --git a/lib/rules/vitest/valid-title.d.ts b/lib/rules/vitest/valid-title.d.ts
|
||||
index 160be76..834ac9b 100644
|
||||
--- a/lib/rules/vitest/valid-title.d.ts
|
||||
+++ b/lib/rules/vitest/valid-title.d.ts
|
||||
@@ -7,15 +7,7 @@ export interface ValidTitleOption {
|
||||
ignoreTypeOfDescribeName?: boolean;
|
||||
allowArguments?: boolean;
|
||||
disallowedWords?: string[];
|
||||
- /**
|
||||
- */
|
||||
- [k: string]:
|
||||
- | string
|
||||
- | [string]
|
||||
- | [string, string]
|
||||
- | {
|
||||
- [k: string]: string | [string] | [string, string];
|
||||
- };
|
||||
+ [k: string]: any;
|
||||
}
|
||||
|
||||
/**
|
178
packages/eslint-define-config/scripts/index.ts
Executable file
178
packages/eslint-define-config/scripts/index.ts
Executable file
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bun
|
||||
import { Logger, colors as cliColors } from '@poppinss/cliui';
|
||||
import { pascalCase } from 'change-case';
|
||||
import type { Rule } from 'eslint';
|
||||
import { existsSync, promises as fs } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { format } from 'prettier';
|
||||
import { buildJSDoc, prettierConfig, type Plugin } from './utils';
|
||||
import { PLUGIN_REGISTRY, loadPlugin } from './plugins-map';
|
||||
import { RuleFile } from './rule-file';
|
||||
|
||||
interface FailedRule {
|
||||
ruleName: string;
|
||||
err: unknown;
|
||||
}
|
||||
|
||||
const logger = new Logger();
|
||||
const colors = cliColors.ansi();
|
||||
|
||||
const getRuleName = (name: string) =>
|
||||
name === 'ESLint' ? 'ESLintRule' : `${pascalCase(name)}Rule`;
|
||||
|
||||
const index: [string, string][] = [];
|
||||
|
||||
/**
|
||||
* Generate the `index.d.ts` file for the plugin's rules that will re-export all rules.
|
||||
*/
|
||||
async function generateRuleIndexFile(
|
||||
pluginDirectory: string,
|
||||
{ rules, name, id }: Plugin,
|
||||
failedRules: FailedRule[],
|
||||
): Promise<void> {
|
||||
const generatedRules = Object.keys(rules!).filter(
|
||||
ruleName => !failedRules.some(failedRule => failedRule.ruleName === ruleName),
|
||||
);
|
||||
|
||||
/**
|
||||
* Build all the import statements for the rules.
|
||||
*/
|
||||
const rulesImports = generatedRules
|
||||
.map(name => `import type { ${getRuleName(name)} } from './${name}';`)
|
||||
.join('\n');
|
||||
|
||||
/**
|
||||
* Build the exported type that is an intersection of all the rules.
|
||||
*/
|
||||
const rulesFinalIntersection = generatedRules
|
||||
.map(name => `${getRuleName(name)}`)
|
||||
.sort()
|
||||
.join(' & ');
|
||||
|
||||
const pluginRulesType = `
|
||||
${buildJSDoc([`All ${name} rules.`])}
|
||||
export type ${name}Rules = ${rulesFinalIntersection};
|
||||
`;
|
||||
|
||||
/**
|
||||
* Write the final `index.d.ts` file.
|
||||
*/
|
||||
const fileContent = `
|
||||
${rulesImports}
|
||||
|
||||
${pluginRulesType}
|
||||
`;
|
||||
|
||||
const indexPath = join(pluginDirectory, 'index.d.ts');
|
||||
await fs.writeFile(indexPath, await format(fileContent, prettierConfig));
|
||||
|
||||
index.push([`${name}Rules`, `./${id}/index`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a report after having generated rules files for a plugin.
|
||||
*/
|
||||
function printGenerationReport(
|
||||
rules: Array<[string, Rule.RuleModule]>,
|
||||
failedRules: FailedRule[],
|
||||
): void {
|
||||
const msg: string = ` ✅ Generated ${rules.length - failedRules.length} rules`;
|
||||
logger.logUpdate(colors.green(msg));
|
||||
logger.logUpdatePersist();
|
||||
|
||||
if (failedRules.length) {
|
||||
logger.log(colors.red(` ❌ Failed ${failedRules.length} rules`));
|
||||
failedRules.forEach(({ ruleName, err }) => {
|
||||
logger.log(colors.red(` - ${ruleName}: ${String(err)}`));
|
||||
});
|
||||
}
|
||||
|
||||
logger.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `.d.ts` file for each rule in the given plugin.
|
||||
*/
|
||||
async function generateRulesFiles(
|
||||
plugin: Plugin,
|
||||
pluginDirectory: string,
|
||||
): Promise<{ failedRules: FailedRule[] }> {
|
||||
const failedRules: FailedRule[] = [];
|
||||
|
||||
const pluginRules = plugin.rules;
|
||||
if (!pluginRules) {
|
||||
throw new Error(
|
||||
`Plugin ${plugin.name} doesn't have any rules. Did you forget to load them?`,
|
||||
);
|
||||
}
|
||||
|
||||
const rules = Object.entries(pluginRules);
|
||||
for (const [ruleName, rule] of rules) {
|
||||
logger.logUpdate(colors.yellow(` Generating > ${ruleName}`));
|
||||
|
||||
try {
|
||||
await RuleFile(plugin, pluginDirectory, ruleName, rule);
|
||||
} catch (err) {
|
||||
failedRules.push({ ruleName, err });
|
||||
}
|
||||
}
|
||||
|
||||
printGenerationReport(rules, failedRules);
|
||||
|
||||
return { failedRules };
|
||||
}
|
||||
|
||||
const rulesDirectory = resolve(__dirname, '../../../src/types/rules');
|
||||
|
||||
/**
|
||||
* If it doesn't exist, create the directory that will contain the plugin's rule files.
|
||||
*/
|
||||
async function createPluginDirectory(pluginName: string): Promise<string> {
|
||||
const pluginDirectory = join(rulesDirectory, pluginName);
|
||||
|
||||
if (existsSync(pluginDirectory)) {
|
||||
await fs.rm(pluginDirectory, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
await fs.mkdir(pluginDirectory, { mode: 0o755, recursive: true });
|
||||
|
||||
return pluginDirectory;
|
||||
}
|
||||
|
||||
export interface RunOptions {
|
||||
plugins?: string[];
|
||||
targetDirectory?: string;
|
||||
}
|
||||
|
||||
for (const plugin of PLUGIN_REGISTRY) {
|
||||
logger.info(`Generating ${plugin.name} rules.`);
|
||||
logger.logUpdate(colors.yellow(` Loading plugin > ${plugin.name}`));
|
||||
const loadedPlugin = await loadPlugin(plugin);
|
||||
|
||||
const pluginDir = await createPluginDirectory(plugin.id);
|
||||
const { failedRules } = await generateRulesFiles(loadedPlugin, pluginDir);
|
||||
|
||||
await generateRuleIndexFile(pluginDir, loadedPlugin, failedRules);
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
resolve(rulesDirectory, 'index.d.ts'),
|
||||
await format(
|
||||
[
|
||||
'import type { RuleConfig } from "./rule-config";',
|
||||
...index.map(([name, path]) => `import { ${name} } from ${JSON.stringify(path)};`),
|
||||
'',
|
||||
'/**',
|
||||
' * Rules.',
|
||||
' *',
|
||||
' * @see [Rules](https://eslint.org/docs/user-guide/configuring/rules)',
|
||||
' */',
|
||||
'',
|
||||
'export type Rules = Partial<',
|
||||
...index.map(([name]) => ` ${name} &`),
|
||||
' Record<string, RuleConfig>',
|
||||
'>;',
|
||||
].join('\n'),
|
||||
prettierConfig,
|
||||
),
|
||||
);
|
58
packages/eslint-define-config/scripts/json-schema-to-ts.ts
Normal file
58
packages/eslint-define-config/scripts/json-schema-to-ts.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { JSONSchema4 } from 'json-schema';
|
||||
import { compile } from 'json-schema-to-typescript';
|
||||
|
||||
/**
|
||||
* Remove unnecessary comments that are generated by `json-schema-to-typescript`.
|
||||
*/
|
||||
function cleanJsDoc(content: string): string {
|
||||
const patterns = [
|
||||
/\* This interface was referenced by .+ JSON-Schema definition/,
|
||||
/\* via the `.+` "/,
|
||||
];
|
||||
|
||||
return content
|
||||
.split('\n')
|
||||
.filter(line => !patterns.some(ignoredLine => ignoredLine.test(line)))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace some types that are generated by `json-schema-to-typescript`.
|
||||
*/
|
||||
export function patchTypes(content: string): string {
|
||||
const replacements: [RegExp, string][] = [
|
||||
[
|
||||
/\(string & \{\s*\[k: string\]: any\s*\} & \{\s*\[k: string\]: any\s*\}\)\[\]/,
|
||||
'string[]',
|
||||
],
|
||||
];
|
||||
|
||||
for (const [pattern, replacement] of replacements) {
|
||||
content = content.replace(pattern, replacement);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a type from the given JSON schema.
|
||||
*/
|
||||
export async function generateTypeFromSchema(
|
||||
schema: JSONSchema4,
|
||||
typeName: string,
|
||||
): Promise<string> {
|
||||
schema = JSON.parse(
|
||||
JSON.stringify(schema).replace(/#\/items\/0\/\$defs\//g, '#/$defs/'),
|
||||
);
|
||||
const result = await compile(schema, typeName, {
|
||||
format: false,
|
||||
bannerComment: '',
|
||||
style: {
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
},
|
||||
unknownAny: false,
|
||||
});
|
||||
|
||||
return patchTypes(cleanJsDoc(result));
|
||||
}
|
145
packages/eslint-define-config/scripts/plugins-map.ts
Normal file
145
packages/eslint-define-config/scripts/plugins-map.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import type { Plugin, PluginRules } from './utils';
|
||||
|
||||
/**
|
||||
* Map of plugins for which the script will generate rule files.
|
||||
*/
|
||||
export const PLUGIN_REGISTRY: Plugin[] = [
|
||||
{
|
||||
id: 'deprecation',
|
||||
name: 'Deprecation',
|
||||
module: () => import('eslint-plugin-deprecation'),
|
||||
},
|
||||
{
|
||||
id: 'eslint',
|
||||
name: 'ESLint',
|
||||
module: () => import('eslint'),
|
||||
},
|
||||
{
|
||||
id: 'typescript-eslint',
|
||||
name: 'TypeScript',
|
||||
prefix: '@typescript-eslint',
|
||||
module: () => import('@typescript-eslint/eslint-plugin'),
|
||||
},
|
||||
{
|
||||
id: 'import',
|
||||
name: 'Import',
|
||||
module: () => import('eslint-plugin-import'),
|
||||
},
|
||||
{
|
||||
id: 'eslint-comments',
|
||||
name: 'EslintComments',
|
||||
module: () => import('eslint-plugin-eslint-comments'),
|
||||
},
|
||||
{
|
||||
id: 'graphql-eslint',
|
||||
name: 'GraphQL',
|
||||
prefix: '@graphql-eslint',
|
||||
module: () => import('@graphql-eslint/eslint-plugin'),
|
||||
},
|
||||
{
|
||||
id: 'jsdoc',
|
||||
name: 'JSDoc',
|
||||
prefix: 'jsdoc',
|
||||
module: () => import('eslint-plugin-jsdoc'),
|
||||
},
|
||||
{
|
||||
id: 'jsonc',
|
||||
name: 'Jsonc',
|
||||
module: () => import('eslint-plugin-jsonc'),
|
||||
},
|
||||
{
|
||||
id: 'jsx-a11y',
|
||||
name: 'JsxA11y',
|
||||
module: () => import('eslint-plugin-jsx-a11y'),
|
||||
},
|
||||
{
|
||||
id: 'mdx',
|
||||
name: 'Mdx',
|
||||
module: () => import('eslint-plugin-mdx'),
|
||||
},
|
||||
{
|
||||
id: 'n',
|
||||
name: 'N',
|
||||
module: () => import('eslint-plugin-n'),
|
||||
},
|
||||
{
|
||||
id: 'node',
|
||||
name: 'Node',
|
||||
module: () => import('eslint-plugin-node'),
|
||||
},
|
||||
{
|
||||
id: 'promise',
|
||||
name: 'Promise',
|
||||
module: () => import('eslint-plugin-promise'),
|
||||
},
|
||||
{
|
||||
id: 'react',
|
||||
name: 'React',
|
||||
module: () => import('eslint-plugin-react'),
|
||||
},
|
||||
{
|
||||
id: 'react-hooks',
|
||||
name: 'ReactHooks',
|
||||
module: () => import('eslint-plugin-react-hooks'),
|
||||
},
|
||||
{
|
||||
id: 'sonarjs',
|
||||
name: 'SonarJS',
|
||||
prefix: 'sonarjs',
|
||||
module: () => import('eslint-plugin-sonarjs'),
|
||||
},
|
||||
{
|
||||
id: 'spellcheck',
|
||||
name: 'Spellcheck',
|
||||
module: () => import('eslint-plugin-spellcheck'),
|
||||
},
|
||||
{
|
||||
id: 'testing-library',
|
||||
name: 'TestingLibrary',
|
||||
module: () => import('eslint-plugin-testing-library'),
|
||||
},
|
||||
{
|
||||
id: 'unicorn',
|
||||
name: 'Unicorn',
|
||||
module: () => import('eslint-plugin-unicorn'),
|
||||
},
|
||||
{
|
||||
id: 'vitest',
|
||||
name: 'Vitest',
|
||||
module: () => import('eslint-plugin-vitest'),
|
||||
},
|
||||
{
|
||||
id: 'vue',
|
||||
name: 'Vue',
|
||||
module: () => import('eslint-plugin-vue'),
|
||||
},
|
||||
{
|
||||
id: 'vue-i18n',
|
||||
name: 'VueI18n',
|
||||
prefix: '@intlify/vue-i18n',
|
||||
module: () => import('@intlify/eslint-plugin-vue-i18n'),
|
||||
},
|
||||
{
|
||||
id: 'vue-pug',
|
||||
name: 'VuePug',
|
||||
module: () => import('eslint-plugin-vue-pug'),
|
||||
},
|
||||
{
|
||||
id: 'yml',
|
||||
name: 'Yml',
|
||||
module: () => import('eslint-plugin-yml'),
|
||||
},
|
||||
] as const;
|
||||
|
||||
export async function loadPlugin(plugin: Plugin): Promise<Plugin> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mod: any = await plugin.module();
|
||||
const rules: PluginRules =
|
||||
plugin.name === 'ESLint'
|
||||
? Object.fromEntries(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
new mod.Linter().getRules().entries(),
|
||||
)
|
||||
: mod.rules ?? mod.default.rules;
|
||||
return { ...plugin, rules };
|
||||
}
|
168
packages/eslint-define-config/scripts/rule-file.ts
Normal file
168
packages/eslint-define-config/scripts/rule-file.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import { Logger, colors as cliColors } from '@poppinss/cliui';
|
||||
import { pascalCase, kebabCase } from 'change-case';
|
||||
import type { Rule } from 'eslint';
|
||||
import type { JSONSchema4 } from 'json-schema';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { format } from 'prettier';
|
||||
import { generateTypeFromSchema } from './json-schema-to-ts';
|
||||
import { buildJSDoc, prettierConfig, type Plugin } from './utils';
|
||||
|
||||
const logger = new Logger();
|
||||
const colors = cliColors.ansi();
|
||||
|
||||
/**
|
||||
* Build the rule description to append to the JSDoc.
|
||||
*/
|
||||
function buildDescription(description = ''): string {
|
||||
description = description.charAt(0).toUpperCase() + description.slice(1);
|
||||
if (description.length > 0 && !description.endsWith('.')) {
|
||||
description += '.';
|
||||
}
|
||||
|
||||
return description.replace('*/', '');
|
||||
}
|
||||
|
||||
export async function RuleFile(
|
||||
plugin: Plugin,
|
||||
pluginDirectory: string,
|
||||
ruleName: string,
|
||||
rule: Rule.RuleModule,
|
||||
) {
|
||||
let content = '';
|
||||
|
||||
const rulePath = resolve(pluginDirectory, `${ruleName}.d.ts`);
|
||||
const ruleNamePascalCase = pascalCase(ruleName);
|
||||
const schema: JSONSchema4 | JSONSchema4[] | undefined = rule.meta?.schema;
|
||||
const isSchemaArray = Array.isArray(schema);
|
||||
|
||||
const mainSchema = isSchemaArray ? schema[0] : schema;
|
||||
const sideSchema = isSchemaArray && schema.length > 1 ? schema[1] : undefined;
|
||||
const thirdSchema = isSchemaArray && schema.length > 2 ? schema[2] : undefined;
|
||||
|
||||
/**
|
||||
* Generate a JSDoc with the rule description and `@see` url.
|
||||
*/
|
||||
function generateTypeJsDoc(): string {
|
||||
const { meta } = rule;
|
||||
const seeDocLink: string = meta?.docs?.url
|
||||
? `@see [${ruleName}](${meta.docs.url})`
|
||||
: '';
|
||||
|
||||
return buildJSDoc([
|
||||
buildDescription(rule.meta?.docs?.description),
|
||||
' ',
|
||||
rule.meta?.deprecated ? '@deprecated' : '',
|
||||
rule.meta?.deprecated ? ' ' : '',
|
||||
seeDocLink,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a type from a JSON schema and append it to the file content.
|
||||
*/
|
||||
async function appendJsonSchemaType(
|
||||
schema: JSONSchema4,
|
||||
comment: string,
|
||||
): Promise<void> {
|
||||
const type = await generateTypeFromSchema(schema, ruleNamePascalCase + comment);
|
||||
const jsdoc = buildJSDoc([`${comment}.`]);
|
||||
content += `\n${jsdoc}`;
|
||||
content += `\n${type}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoped rule name ESLint config uses.
|
||||
*/
|
||||
function prefixedRuleName(): string {
|
||||
const { prefix, name } = plugin;
|
||||
const rulePrefix = (prefix ?? kebabCase(name)) + '/';
|
||||
return name === 'ESLint' ? ruleName : `${rulePrefix}${ruleName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the `import type { RuleConfig } from '../rule-config'` at the top of the file.
|
||||
*/
|
||||
const nestedDepth = ruleName.split('/').length;
|
||||
content += `import type { RuleConfig } from '${'../'.repeat(nestedDepth)}rule-config'\n\n`;
|
||||
|
||||
/**
|
||||
* Generate and append types for the rule schemas.
|
||||
*/
|
||||
if (thirdSchema) {
|
||||
await appendJsonSchemaType(thirdSchema, 'Setting');
|
||||
}
|
||||
if (sideSchema) {
|
||||
await appendJsonSchemaType(sideSchema, 'Config');
|
||||
}
|
||||
if (mainSchema) {
|
||||
await appendJsonSchemaType(mainSchema, 'Option');
|
||||
}
|
||||
|
||||
if (mainSchema) {
|
||||
/**
|
||||
* Append the rule type options to the file content.
|
||||
*/
|
||||
let type: string = '';
|
||||
if (!isSchemaArray) {
|
||||
type = `${ruleNamePascalCase}Option`;
|
||||
} else if (thirdSchema) {
|
||||
type = `[${ruleNamePascalCase}Option?, ${ruleNamePascalCase}Config?, ${ruleNamePascalCase}Setting?]`;
|
||||
} else if (sideSchema) {
|
||||
type = `[${ruleNamePascalCase}Option?, ${ruleNamePascalCase}Config?]`;
|
||||
} else if (mainSchema) {
|
||||
type = `[${ruleNamePascalCase}Option?]`;
|
||||
}
|
||||
|
||||
content += buildJSDoc(['Options.']) + '\n';
|
||||
content += `export type ${ruleNamePascalCase}Options = ${type}\n\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the rule config type to the file content.
|
||||
*/
|
||||
content += generateTypeJsDoc() + '\n';
|
||||
content += `export type ${ruleNamePascalCase}RuleConfig = RuleConfig<${mainSchema ? `${ruleNamePascalCase}Options` : '[]'}>;\n\n`;
|
||||
|
||||
/**
|
||||
* Append the final rule interface to the file content.
|
||||
*/
|
||||
content += generateTypeJsDoc() + '\n';
|
||||
content += `export interface ${ruleNamePascalCase}Rule {`;
|
||||
content += `${generateTypeJsDoc()}\n`;
|
||||
content += `'${prefixedRuleName()}': ${ruleNamePascalCase}RuleConfig;`;
|
||||
content += '}';
|
||||
|
||||
content = await format(content, prettierConfig);
|
||||
|
||||
/**
|
||||
* Create the directory of the rule file if it doesn't exist.
|
||||
*/
|
||||
const subPath = dirname(rulePath);
|
||||
if (!existsSync(subPath)) {
|
||||
mkdirSync(subPath, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(rulePath, content);
|
||||
|
||||
/**
|
||||
* Apply a patch to the generated content if a diff file exists for it.
|
||||
*
|
||||
* Must be called after `generate()`.
|
||||
*/
|
||||
const pathParts = rulePath.split('/');
|
||||
const diffFile = resolve(
|
||||
__dirname,
|
||||
'./diffs/rules',
|
||||
pathParts.at(-2) ?? '',
|
||||
`${pathParts.at(-1) ?? ''}.diff`,
|
||||
);
|
||||
|
||||
if (existsSync(diffFile)) {
|
||||
logger.logUpdate(colors.yellow(` 🧹 Adjusting ${prefixedRuleName()}`));
|
||||
logger.logUpdatePersist();
|
||||
|
||||
execSync(`git apply ${diffFile}`);
|
||||
}
|
||||
}
|
24
packages/eslint-define-config/scripts/utils.ts
Normal file
24
packages/eslint-define-config/scripts/utils.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Config } from 'prettier';
|
||||
|
||||
export function buildJSDoc(content: string[]) {
|
||||
return ['/**', ...content.filter(Boolean).map(line => ` * ${line}`), ' */'].join('\n');
|
||||
}
|
||||
|
||||
export const prettierConfig: Config = {
|
||||
plugins: ['prettier-plugin-organize-imports'],
|
||||
parser: 'typescript',
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
|
||||
export type MaybeArray<T> = T | T[];
|
||||
export type PluginRules = Record<string, Rule.RuleModule>;
|
||||
|
||||
export interface Plugin {
|
||||
id: string;
|
||||
name: string;
|
||||
prefix?: string;
|
||||
module: () => Promise<any>;
|
||||
rules?: PluginRules;
|
||||
}
|
1
packages/eslint-import-resolver-typescript
Submodule
1
packages/eslint-import-resolver-typescript
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7a02ac08b5aaac8c217f0e87142f97eafcc38fbc
|
1
packages/eslint-plugin-import
Submodule
1
packages/eslint-plugin-import
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f77ceb679d59ced5d9a633123385470a9eea10d9
|
@ -1 +1 @@
|
||||
Subproject commit a7d1a12a6198d546c4a06477b385b4fde03b762e
|
||||
Subproject commit 0d5321a5457c5f0da0ca216053cc5b4f571b53ae
|
@ -1 +1 @@
|
||||
Subproject commit 42464abe64c5cefb709e8e0a9072b6bb1cd7fcdc
|
||||
Subproject commit eb11b5b35a6a797dc7fba6df53b1c4dada3a2a55
|
1
packages/eslint-plugin-react
Submodule
1
packages/eslint-plugin-react
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 4467db503e38b9356517cf6926d11be544ccf4b1
|
@ -8,25 +8,24 @@
|
||||
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||
import type { Rule, Scope } from 'eslint';
|
||||
import type {
|
||||
FunctionDeclaration,
|
||||
CallExpression,
|
||||
Expression,
|
||||
Super,
|
||||
Node,
|
||||
ArrowFunctionExpression,
|
||||
FunctionExpression,
|
||||
SpreadElement,
|
||||
Identifier,
|
||||
VariableDeclarator,
|
||||
MemberExpression,
|
||||
ChainExpression,
|
||||
Pattern,
|
||||
OptionalMemberExpression,
|
||||
ArrayExpression,
|
||||
VariableDeclaration,
|
||||
ArrowFunctionExpression,
|
||||
CallExpression,
|
||||
ChainExpression,
|
||||
Expression,
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
Identifier,
|
||||
MemberExpression,
|
||||
Node,
|
||||
OptionalMemberExpression,
|
||||
Pattern,
|
||||
SpreadElement,
|
||||
Super,
|
||||
TSAsExpression,
|
||||
VariableDeclarator,
|
||||
} from 'estree';
|
||||
import type { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
import { __EXPERIMENTAL__ } from './index';
|
||||
|
||||
const schema = {
|
||||
@ -84,23 +83,7 @@ const rule: Rule.RuleModule = {
|
||||
context.report(problem);
|
||||
}
|
||||
|
||||
/**
|
||||
* SourceCode#getText that also works down to ESLint 3.0.0
|
||||
*/
|
||||
const getSource =
|
||||
typeof context.getSource === 'function'
|
||||
? (node: Node) => context.getSource(node)
|
||||
: (node: Node) => context.sourceCode.getText(node);
|
||||
|
||||
/**
|
||||
* SourceCode#getScope that also works down to ESLint 3.0.0
|
||||
*/
|
||||
const getScope =
|
||||
typeof context.getScope === 'function'
|
||||
? () => context.getScope()
|
||||
: (node: Node) => context.sourceCode.getScope(node);
|
||||
|
||||
const scopeManager = context.getSourceCode().scopeManager;
|
||||
const scopeManager = context.sourceCode.scopeManager;
|
||||
|
||||
// Should be shared between visitors.
|
||||
const setStateCallSites = new WeakMap<Expression, Pattern>();
|
||||
@ -128,7 +111,7 @@ const rule: Rule.RuleModule = {
|
||||
*/
|
||||
function visitFunctionWithDependencies(
|
||||
node: ArrowFunctionExpression | FunctionExpression | FunctionDeclaration,
|
||||
declaredDependenciesNode: SpreadElement | Expression,
|
||||
declaredDependenciesNode: SpreadElement | Expression | undefined,
|
||||
reactiveHook: Super | Expression,
|
||||
reactiveHookName: string,
|
||||
isEffect: boolean,
|
||||
@ -192,8 +175,6 @@ const rule: Rule.RuleModule = {
|
||||
// ^^^ true for this reference
|
||||
// const [state, dispatch] = useReducer() / React.useReducer()
|
||||
// ^^^ true for this reference
|
||||
// const [state, dispatch] = useActionState() / React.useActionState()
|
||||
// ^^^ true for this reference
|
||||
// const ref = useRef()
|
||||
// ^^^ true for this reference
|
||||
// const onStuff = useEffectEvent(() => {})
|
||||
@ -208,11 +189,10 @@ const rule: Rule.RuleModule = {
|
||||
return false;
|
||||
}
|
||||
// Look for `let stuff = ...`
|
||||
const node = def.node as Node;
|
||||
if (node.type !== 'VariableDeclarator') {
|
||||
if (def.node.type !== 'VariableDeclarator') {
|
||||
return false;
|
||||
}
|
||||
let init = node.init;
|
||||
let init = (def.node as VariableDeclarator).init;
|
||||
if (init == null) {
|
||||
return false;
|
||||
}
|
||||
@ -221,19 +201,19 @@ const rule: Rule.RuleModule = {
|
||||
}
|
||||
// Detect primitive constants
|
||||
// const foo = 42
|
||||
let declaration = node.parent;
|
||||
let declaration = def.node.parent;
|
||||
if (declaration == null) {
|
||||
// This might happen if variable is declared after the callback.
|
||||
// In that case ESLint won't set up .parent refs.
|
||||
// So we'll set them up manually.
|
||||
fastFindReferenceWithParent(componentScope.block, node.id);
|
||||
declaration = node.parent;
|
||||
fastFindReferenceWithParent(componentScope.block, def.node.id);
|
||||
declaration = def.node.parent;
|
||||
if (declaration == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (
|
||||
(declaration as VariableDeclaration).kind === 'const' &&
|
||||
declaration.kind === 'const' &&
|
||||
init.type === 'Literal' &&
|
||||
(typeof init.value === 'string' ||
|
||||
typeof init.value === 'number' ||
|
||||
@ -274,11 +254,7 @@ const rule: Rule.RuleModule = {
|
||||
}
|
||||
// useEffectEvent() return value is always unstable.
|
||||
return true;
|
||||
} else if (
|
||||
name === 'useState' ||
|
||||
name === 'useReducer' ||
|
||||
name === 'useActionState'
|
||||
) {
|
||||
} else if (name === 'useState' || name === 'useReducer') {
|
||||
// Only consider second value in initializing tuple stable.
|
||||
if (
|
||||
id.type === 'ArrayPattern' &&
|
||||
@ -290,14 +266,14 @@ const rule: Rule.RuleModule = {
|
||||
if (name === 'useState') {
|
||||
const references = resolved.references;
|
||||
let writeCount = 0;
|
||||
for (const reference of references) {
|
||||
if (reference.isWrite()) {
|
||||
for (let i = 0; i < references.length; i++) {
|
||||
if (references[i].isWrite()) {
|
||||
writeCount++;
|
||||
}
|
||||
if (writeCount > 1) {
|
||||
return false;
|
||||
}
|
||||
setStateCallSites.set(reference.identifier, id.elements[0]!);
|
||||
setStateCallSites.set(references[i].identifier, id.elements[0]!);
|
||||
}
|
||||
}
|
||||
// Setter is stable.
|
||||
@ -305,25 +281,27 @@ const rule: Rule.RuleModule = {
|
||||
} else if (id.elements[0] === resolved.identifiers[0]) {
|
||||
if (name === 'useState') {
|
||||
const references = resolved.references;
|
||||
for (const reference of references) {
|
||||
stateVariables.add(reference.identifier);
|
||||
for (let i = 0; i < references.length; i++) {
|
||||
stateVariables.add(references[i].identifier);
|
||||
}
|
||||
}
|
||||
// State variable itself is dynamic.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
} else if (name === 'useTransition') {
|
||||
// Only consider second value in initializing tuple stable.
|
||||
name === 'useTransition' &&
|
||||
id.type === 'ArrayPattern' &&
|
||||
id.elements.length === 2 &&
|
||||
Array.isArray(resolved.identifiers) &&
|
||||
// Is second tuple value the same reference we're checking?
|
||||
id.elements[1] === resolved.identifiers[0]
|
||||
) {
|
||||
// Setter is stable.
|
||||
return true;
|
||||
if (
|
||||
id.type === 'ArrayPattern' &&
|
||||
id.elements.length === 2 &&
|
||||
Array.isArray(resolved.identifiers)
|
||||
) {
|
||||
// Is second tuple value the same reference we're checking?
|
||||
if (id.elements[1] === resolved.identifiers[0]) {
|
||||
// Setter is stable.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// By default assume it's dynamic.
|
||||
return false;
|
||||
@ -343,7 +321,7 @@ const rule: Rule.RuleModule = {
|
||||
}
|
||||
// Search the direct component subscopes for
|
||||
// top-level function definitions matching this reference.
|
||||
const fnNode = def.node as Node;
|
||||
const fnNode = def.node;
|
||||
const childScopes = componentScope.childScopes;
|
||||
let fnScope = null;
|
||||
let i;
|
||||
@ -448,9 +426,9 @@ const rule: Rule.RuleModule = {
|
||||
dependencyNode.type === 'Identifier' &&
|
||||
(dependencyNode.parent!.type === 'MemberExpression' ||
|
||||
dependencyNode.parent!.type === 'OptionalMemberExpression') &&
|
||||
!dependencyNode.parent.computed &&
|
||||
dependencyNode.parent.property.type === 'Identifier' &&
|
||||
dependencyNode.parent.property.name === 'current' &&
|
||||
!dependencyNode.parent!.computed &&
|
||||
dependencyNode.parent!.property.type === 'Identifier' &&
|
||||
dependencyNode.parent!.property.name === 'current' &&
|
||||
// ...in a cleanup function or below...
|
||||
isInsideEffectCleanup(reference)
|
||||
) {
|
||||
@ -503,11 +481,12 @@ const rule: Rule.RuleModule = {
|
||||
|
||||
// Warn about accessing .current in cleanup effects.
|
||||
currentRefsInEffectCleanup.forEach(({ reference, dependencyNode }, dependency) => {
|
||||
const references: Scope.Reference[] = reference.resolved.references;
|
||||
const references: Scope.Reference[] = reference.resolved!.references;
|
||||
// Is React managing this ref or us?
|
||||
// Let's see if we can find a .current assignment.
|
||||
let foundCurrentAssignment = false;
|
||||
for (const { identifier } of references) {
|
||||
for (let i = 0; i < references.length; i++) {
|
||||
const { identifier } = references[i];
|
||||
const { parent } = identifier;
|
||||
if (
|
||||
parent != null &&
|
||||
@ -519,7 +498,7 @@ const rule: Rule.RuleModule = {
|
||||
parent.property.name === 'current' &&
|
||||
// ref.current = <something>
|
||||
parent.parent!.type === 'AssignmentExpression' &&
|
||||
parent.parent.left === parent
|
||||
parent.parent!.left === parent
|
||||
) {
|
||||
foundCurrentAssignment = true;
|
||||
break;
|
||||
@ -552,11 +531,11 @@ const rule: Rule.RuleModule = {
|
||||
node: writeExpr,
|
||||
message:
|
||||
`Assignments to the '${key}' variable from inside React Hook ` +
|
||||
`${getSource(reactiveHook)} will be lost after each ` +
|
||||
`${context.getSource(reactiveHook)} will be lost after each ` +
|
||||
`render. To preserve the value over time, store it in a useRef ` +
|
||||
`Hook and keep the mutable value in the '.current' property. ` +
|
||||
`Otherwise, you can move this variable directly inside ` +
|
||||
`${getSource(reactiveHook)}.`,
|
||||
`${context.getSource(reactiveHook)}.`,
|
||||
});
|
||||
}
|
||||
|
||||
@ -566,11 +545,11 @@ const rule: Rule.RuleModule = {
|
||||
if (isStable) {
|
||||
stableDependencies.add(key);
|
||||
}
|
||||
for (const reference of references) {
|
||||
references.forEach(reference => {
|
||||
if (reference.writeExpr) {
|
||||
reportStaleAssignment(reference.writeExpr, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (staleAssignments.size > 0) {
|
||||
@ -586,15 +565,15 @@ const rule: Rule.RuleModule = {
|
||||
if (setStateInsideEffectWithoutDeps) {
|
||||
return;
|
||||
}
|
||||
for (const reference of references) {
|
||||
references.forEach(reference => {
|
||||
if (setStateInsideEffectWithoutDeps) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
const id = reference.identifier;
|
||||
const isSetState: boolean = setStateCallSites.has(id);
|
||||
if (!isSetState) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
let fnScope: Scope.Scope = reference.from;
|
||||
@ -606,8 +585,9 @@ const rule: Rule.RuleModule = {
|
||||
// TODO: we could potentially ignore early returns.
|
||||
setStateInsideEffectWithoutDeps = key;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (setStateInsideEffectWithoutDeps) {
|
||||
const { suggestedDependencies } = collectRecommendations({
|
||||
dependencies,
|
||||
@ -653,45 +633,45 @@ const rule: Rule.RuleModule = {
|
||||
reportProblem({
|
||||
node: declaredDependenciesNode,
|
||||
message:
|
||||
`React Hook ${getSource(reactiveHook)} was passed a ` +
|
||||
`React Hook ${context.getSource(reactiveHook)} was passed a ` +
|
||||
'dependency list that is not an array literal. This means we ' +
|
||||
"can't statically verify whether you've passed the correct " +
|
||||
'dependencies.',
|
||||
});
|
||||
} else {
|
||||
const arrayExpression = isTSAsArrayExpression
|
||||
? declaredDependenciesNode.expression
|
||||
: declaredDependenciesNode;
|
||||
|
||||
for (const declaredDependencyNode of (arrayExpression as ArrayExpression)
|
||||
.elements) {
|
||||
? ((declaredDependenciesNode as TSAsExpression).expression as ArrayExpression)
|
||||
: (declaredDependenciesNode as ArrayExpression);
|
||||
arrayExpression.elements.forEach(declaredDependencyNode => {
|
||||
// Skip elided elements.
|
||||
if (declaredDependencyNode === null) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
// If we see a spread element then add a special warning.
|
||||
if (declaredDependencyNode.type === 'SpreadElement') {
|
||||
reportProblem({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`React Hook ${getSource(reactiveHook)} has a spread ` +
|
||||
`React Hook ${context.getSource(reactiveHook)} has a spread ` +
|
||||
"element in its dependency array. This means we can't " +
|
||||
"statically verify whether you've passed the " +
|
||||
'correct dependencies.',
|
||||
});
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (useEffectEventVariables.has(declaredDependencyNode)) {
|
||||
reportProblem({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
'Functions returned from `useEffectEvent` must not be included in the dependency array. ' +
|
||||
`Remove \`${getSource(declaredDependencyNode)}\` from the list.`,
|
||||
`Remove \`${context.getSource(declaredDependencyNode)}\` from the list.`,
|
||||
suggest: [
|
||||
{
|
||||
desc: `Remove the dependency \`${getSource(declaredDependencyNode)}\``,
|
||||
desc: `Remove the dependency \`${context.getSource(
|
||||
declaredDependencyNode,
|
||||
)}\``,
|
||||
fix(fixer) {
|
||||
return fixer.removeRange(declaredDependencyNode.range);
|
||||
return fixer.removeRange(declaredDependencyNode.range!);
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -725,13 +705,13 @@ const rule: Rule.RuleModule = {
|
||||
reportProblem({
|
||||
node: declaredDependencyNode,
|
||||
message:
|
||||
`React Hook ${getSource(reactiveHook)} has a ` +
|
||||
`React Hook ${context.getSource(reactiveHook)} has a ` +
|
||||
`complex expression in the dependency array. ` +
|
||||
'Extract it to a separate variable so it can be statically checked.',
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
return;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@ -760,7 +740,7 @@ const rule: Rule.RuleModule = {
|
||||
if (!isDeclaredInComponent) {
|
||||
externalDependencies.add(declaredDependency);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
@ -811,7 +791,9 @@ const rule: Rule.RuleModule = {
|
||||
|
||||
const message =
|
||||
`The '${construction.name.name}' ${depType} ${causation} the dependencies of ` +
|
||||
`${reactiveHookName} Hook (at line ${declaredDependenciesNode.loc!.start.line}) ` +
|
||||
`${reactiveHookName} Hook (at line ${
|
||||
declaredDependenciesNode.loc!.start.line
|
||||
}) ` +
|
||||
`change on every render. ${advice}`;
|
||||
|
||||
let suggest: Rule.SuggestionReportDescriptor[] | undefined;
|
||||
@ -865,7 +847,7 @@ const rule: Rule.RuleModule = {
|
||||
// in some extra deduplication. We can't do this
|
||||
// for effects though because those have legit
|
||||
// use cases for over-specifying deps.
|
||||
if (!isEffect && missingDependencies.size > 0) {
|
||||
if (!isEffect && missingDependencies.size) {
|
||||
suggestedDeps = collectRecommendations({
|
||||
dependencies,
|
||||
declaredDependencies: [], // Pretend we don't know
|
||||
@ -881,7 +863,7 @@ const rule: Rule.RuleModule = {
|
||||
return true;
|
||||
}
|
||||
const declaredDepKeys = declaredDependencies.map(dep => dep.key);
|
||||
const sortedDeclaredDepKeys = [...declaredDepKeys].sort();
|
||||
const sortedDeclaredDepKeys = declaredDepKeys.slice().sort();
|
||||
return declaredDepKeys.join(',') === sortedDeclaredDepKeys.join(',');
|
||||
}
|
||||
|
||||
@ -922,7 +904,11 @@ const rule: Rule.RuleModule = {
|
||||
' ' +
|
||||
(deps.size > 1 ? 'dependencies' : 'dependency') +
|
||||
': ' +
|
||||
joinEnglish([...deps].sort().map(name => "'" + formatDependency(name) + "'")) +
|
||||
joinEnglish(
|
||||
Array.from(deps)
|
||||
.sort()
|
||||
.map(name => "'" + formatDependency(name) + "'"),
|
||||
) +
|
||||
`. Either ${fixVerb} ${
|
||||
deps.size > 1 ? 'them' : 'it'
|
||||
} or remove the dependency array.`
|
||||
@ -932,20 +918,20 @@ const rule: Rule.RuleModule = {
|
||||
let extraWarning = '';
|
||||
if (unnecessaryDependencies.size > 0) {
|
||||
let badRef: string | null = null;
|
||||
for (const key of unnecessaryDependencies.keys()) {
|
||||
Array.from(unnecessaryDependencies.keys()).forEach(key => {
|
||||
if (badRef !== null) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (key.endsWith('.current')) {
|
||||
badRef = key;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (badRef !== null) {
|
||||
extraWarning =
|
||||
` Mutable values like '${badRef}' aren't valid dependencies ` +
|
||||
"because mutating them doesn't re-render the component.";
|
||||
} else if (externalDependencies.size > 0) {
|
||||
const dep = [...externalDependencies][0];
|
||||
const dep = Array.from(externalDependencies)[0];
|
||||
// Don't show this warning for things that likely just got moved *inside* the callback
|
||||
// because in that case they're clearly not referring to globals.
|
||||
if (!scope.set.has(dep)) {
|
||||
@ -994,11 +980,11 @@ const rule: Rule.RuleModule = {
|
||||
` However, 'props' will change when *any* prop changes, so the ` +
|
||||
`preferred fix is to destructure the 'props' object outside of ` +
|
||||
`the ${reactiveHookName} call and refer to those specific props ` +
|
||||
`inside ${getSource(reactiveHook)}.`;
|
||||
`inside ${context.getSource(reactiveHook)}.`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!extraWarning && missingDependencies.size > 0) {
|
||||
if (!extraWarning && missingDependencies.size) {
|
||||
// See if the user is trying to avoid specifying a callable prop.
|
||||
// This usually means they're unaware of useCallback.
|
||||
let missingCallbackDep: string | null = null;
|
||||
@ -1064,7 +1050,7 @@ const rule: Rule.RuleModule = {
|
||||
let id: Identifier;
|
||||
let maybeCall: Node | null;
|
||||
for (let i = 0; i < references.length; i++) {
|
||||
id = references[i].identifier;
|
||||
id = references[i].identifier as Identifier;
|
||||
maybeCall = id.parent!;
|
||||
// Try to see if we have setState(someExpr(missingDep)).
|
||||
while (maybeCall != null && maybeCall !== componentScope.block) {
|
||||
@ -1148,7 +1134,7 @@ const rule: Rule.RuleModule = {
|
||||
reportProblem({
|
||||
node: declaredDependenciesNode,
|
||||
message:
|
||||
`React Hook ${getSource(reactiveHook)} has ` +
|
||||
`React Hook ${context.getSource(reactiveHook)} has ` +
|
||||
// To avoid a long message, show the next actionable item.
|
||||
(getWarningMessage(missingDependencies, 'a', 'missing', 'include') ||
|
||||
getWarningMessage(unnecessaryDependencies, 'an', 'unnecessary', 'exclude') ||
|
||||
@ -1239,7 +1225,7 @@ const rule: Rule.RuleModule = {
|
||||
isEffect,
|
||||
);
|
||||
return; // Handled
|
||||
case 'Identifier': {
|
||||
case 'Identifier':
|
||||
if (!declaredDependenciesNode) {
|
||||
// No deps, no problems.
|
||||
return; // Handled
|
||||
@ -1257,7 +1243,7 @@ const rule: Rule.RuleModule = {
|
||||
return; // Handled
|
||||
}
|
||||
// We'll do our best effort to find it, complain otherwise.
|
||||
const variable = getScope(callback).set.get(callback.name);
|
||||
const variable = context.getScope().set.get(callback.name);
|
||||
if (variable == null || variable.defs == null) {
|
||||
// If it's not in scope, we don't care.
|
||||
return; // Handled
|
||||
@ -1307,7 +1293,6 @@ const rule: Rule.RuleModule = {
|
||||
break; // Unhandled
|
||||
}
|
||||
break; // Unhandled
|
||||
}
|
||||
default:
|
||||
// useEffect(generateEffectBody(), []);
|
||||
reportProblem({
|
||||
@ -1395,33 +1380,33 @@ function collectRecommendations({
|
||||
|
||||
function createDepTree(): DepTree {
|
||||
return {
|
||||
isUsed: false, // True if used in code
|
||||
isSatisfiedRecursively: false, // True if specified in deps
|
||||
isSubtreeUsed: false, // True if something deeper is used by code
|
||||
children: new Map(), // Nodes for properties
|
||||
isUsed: false,
|
||||
isSatisfiedRecursively: false,
|
||||
isSubtreeUsed: false,
|
||||
children: new Map<string, never>(),
|
||||
};
|
||||
}
|
||||
|
||||
// Mark all required nodes first.
|
||||
// Imagine exclamation marks next to each used deep property.
|
||||
for (const key of dependencies.keys()) {
|
||||
dependencies.forEach((_, key) => {
|
||||
const node = getOrCreateNodeByPath(depTree, key);
|
||||
node.isUsed = true;
|
||||
markAllParentsByPath(depTree, key, parent => {
|
||||
parent.isSubtreeUsed = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mark all satisfied nodes.
|
||||
// Imagine checkmarks next to each declared dependency.
|
||||
for (const { key } of declaredDependencies) {
|
||||
declaredDependencies.forEach(({ key }) => {
|
||||
const node = getOrCreateNodeByPath(depTree, key);
|
||||
node.isSatisfiedRecursively = true;
|
||||
}
|
||||
for (const key of stableDependencies) {
|
||||
});
|
||||
stableDependencies.forEach(key => {
|
||||
const node = getOrCreateNodeByPath(depTree, key);
|
||||
node.isSatisfiedRecursively = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Tree manipulation helpers.
|
||||
function getOrCreateNodeByPath(rootNode: DepTree, path: string): DepTree {
|
||||
@ -1497,15 +1482,15 @@ function collectRecommendations({
|
||||
const suggestedDependencies: string[] = [];
|
||||
const unnecessaryDependencies = new Set<string>();
|
||||
const duplicateDependencies = new Set<string>();
|
||||
for (const { key } of declaredDependencies) {
|
||||
declaredDependencies.forEach(({ key }) => {
|
||||
// Does this declared dep satisfy a real need?
|
||||
if (satisfyingDependencies.has(key)) {
|
||||
if (suggestedDependencies.includes(key)) {
|
||||
// Duplicate.
|
||||
duplicateDependencies.add(key);
|
||||
} else {
|
||||
if (!suggestedDependencies.includes(key)) {
|
||||
// Good one.
|
||||
suggestedDependencies.push(key);
|
||||
} else {
|
||||
// Duplicate.
|
||||
duplicateDependencies.add(key);
|
||||
}
|
||||
} else {
|
||||
if (isEffect && !key.endsWith('.current') && !externalDependencies.has(key)) {
|
||||
@ -1513,7 +1498,7 @@ function collectRecommendations({
|
||||
// Such as resetting scroll when ID changes.
|
||||
// Consider them legit.
|
||||
// The exception is ref.current which is always wrong.
|
||||
if (!suggestedDependencies.includes(key)) {
|
||||
if (suggestedDependencies.indexOf(key) === -1) {
|
||||
suggestedDependencies.push(key);
|
||||
}
|
||||
} else {
|
||||
@ -1521,12 +1506,12 @@ function collectRecommendations({
|
||||
unnecessaryDependencies.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Then add the missing ones at the end.
|
||||
for (const key of missingDependencies) {
|
||||
missingDependencies.forEach(key => {
|
||||
suggestedDependencies.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
suggestedDependencies,
|
||||
@ -1660,13 +1645,12 @@ function scanForConstructions({
|
||||
while (currentScope !== scope && currentScope != null) {
|
||||
currentScope = currentScope.upper!;
|
||||
}
|
||||
if (
|
||||
currentScope !== scope &&
|
||||
if (currentScope !== scope) {
|
||||
// This reference is outside the Hook callback.
|
||||
// It can only be legit if it's the deps array.
|
||||
!isAncestorNodeOf(declaredDependenciesNode, reference.identifier)
|
||||
) {
|
||||
return true;
|
||||
if (!isAncestorNodeOf(declaredDependenciesNode, reference.identifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -1691,6 +1675,7 @@ function getDependency(node: Node): Node {
|
||||
if (
|
||||
(parent.type === 'MemberExpression' || parent.type === 'OptionalMemberExpression') &&
|
||||
parent.object === node &&
|
||||
parent.property.type === 'Identifier' &&
|
||||
parent.property.name !== 'current' &&
|
||||
!parent.computed &&
|
||||
!(
|
||||
@ -1786,7 +1771,7 @@ function analyzePropertyChain(
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeWithoutReactNamespace(node: Identifier | MemberExpression) {
|
||||
function getNodeWithoutReactNamespace(node: Expression | Super) {
|
||||
if (
|
||||
node.type === 'MemberExpression' &&
|
||||
node.object.type === 'Identifier' &&
|
||||
@ -1833,7 +1818,7 @@ function getReactiveHookCallbackIndex(
|
||||
try {
|
||||
name = analyzePropertyChain(node, null);
|
||||
} catch (error) {
|
||||
if (/Unsupported node type/.test((error as Error).message)) {
|
||||
if (/Unsupported node type/.test(error.message)) {
|
||||
return 0;
|
||||
} else {
|
||||
throw error;
|
||||
@ -1879,12 +1864,12 @@ function fastFindReferenceWithParent(start: Node, target: Node): Node | null {
|
||||
value.parent = item;
|
||||
queue.push(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
for (const val of value) {
|
||||
value.forEach(val => {
|
||||
if (isNodeLike(val)) {
|
||||
val.parent = item;
|
||||
queue.push(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1907,7 +1892,7 @@ function joinEnglish(arr: string[]): string {
|
||||
return s;
|
||||
}
|
||||
|
||||
function isNodeLike(val: unknown): val is Node {
|
||||
function isNodeLike(val: any): boolean {
|
||||
return (
|
||||
typeof val === 'object' &&
|
||||
val !== null &&
|
||||
|
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
/* global BigInt */
|
||||
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||
import type { Rule, Scope } from 'eslint';
|
||||
import type {
|
||||
CallExpression,
|
||||
@ -15,7 +16,6 @@ import type {
|
||||
Identifier,
|
||||
BaseFunction,
|
||||
} from 'estree';
|
||||
|
||||
import { __EXPERIMENTAL__ } from './index';
|
||||
|
||||
/**
|
||||
@ -24,7 +24,7 @@ import { __EXPERIMENTAL__ } from './index';
|
||||
*/
|
||||
|
||||
function isHookName(s: string) {
|
||||
return s === 'use' || /^use[\dA-Z]/.test(s);
|
||||
return s === 'use' || /^use[A-Z0-9]/.test(s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +56,7 @@ function isComponentName(node: Node) {
|
||||
return node.type === 'Identifier' && /^[A-Z]/.test(node.name);
|
||||
}
|
||||
|
||||
function isReactFunction(node: Expression | Super, functionName: string) {
|
||||
function isReactFunction(node: Node, functionName: string) {
|
||||
return (
|
||||
(node as Identifier).name === functionName ||
|
||||
(node.type === 'MemberExpression' &&
|
||||
@ -91,8 +91,10 @@ function isMemoCallback(node: Rule.Node) {
|
||||
function isInsideComponentOrHook(node: Rule.Node) {
|
||||
while (node) {
|
||||
const functionName = getFunctionName(node);
|
||||
if (functionName && (isComponentName(functionName) || isHook(functionName))) {
|
||||
return true;
|
||||
if (functionName) {
|
||||
if (isComponentName(functionName) || isHook(functionName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (isForwardRefCallback(node) || isMemoCallback(node)) {
|
||||
return true;
|
||||
@ -110,7 +112,7 @@ function isUseEffectEventIdentifier(node: Node) {
|
||||
}
|
||||
|
||||
function isUseIdentifier(node: Node) {
|
||||
return isReactFunction(node as Expression, 'use');
|
||||
return isReactFunction(node, 'use');
|
||||
}
|
||||
|
||||
const rule: Rule.RuleModule = {
|
||||
@ -153,22 +155,6 @@ const rule: Rule.RuleModule = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SourceCode#getText that also works down to ESLint 3.0.0
|
||||
*/
|
||||
const getSource =
|
||||
typeof context.getSource === 'function'
|
||||
? (node: Node) => context.getSource(node)
|
||||
: (node: Node) => context.sourceCode.getText(node);
|
||||
|
||||
/**
|
||||
* SourceCode#getScope that also works down to ESLint 3.0.0
|
||||
*/
|
||||
const getScope =
|
||||
typeof context.getScope === 'function'
|
||||
? () => context.getScope()
|
||||
: (node: Node) => context.sourceCode.getScope(node);
|
||||
|
||||
return {
|
||||
// Maintain code segment path stack as we traverse.
|
||||
onCodePathSegmentStart: segment => codePathSegmentStack.push(segment),
|
||||
@ -487,7 +473,7 @@ const rule: Rule.RuleModule = {
|
||||
context.report({
|
||||
node: hook,
|
||||
message:
|
||||
`React Hook "${getSource(hook)}" may be executed ` +
|
||||
`React Hook "${context.getSource(hook)}" may be executed ` +
|
||||
'more than once. Possibly because it is called in a loop. ' +
|
||||
'React Hooks must be called in the exact same order in ' +
|
||||
'every component render.',
|
||||
@ -506,7 +492,7 @@ const rule: Rule.RuleModule = {
|
||||
context.report({
|
||||
node: hook,
|
||||
message:
|
||||
`React Hook "${getSource(hook)}" cannot be ` +
|
||||
`React Hook "${context.getSource(hook)}" cannot be ` +
|
||||
'called in an async function.',
|
||||
});
|
||||
}
|
||||
@ -521,7 +507,7 @@ const rule: Rule.RuleModule = {
|
||||
!isUseIdentifier(hook) // `use(...)` can be called conditionally.
|
||||
) {
|
||||
const message =
|
||||
`React Hook "${getSource(hook)}" is called ` +
|
||||
`React Hook "${context.getSource(hook)}" is called ` +
|
||||
'conditionally. React Hooks must be called in the exact ' +
|
||||
'same order in every component render.' +
|
||||
(possiblyHasEarlyReturn
|
||||
@ -538,15 +524,15 @@ const rule: Rule.RuleModule = {
|
||||
) {
|
||||
// Custom message for hooks inside a class
|
||||
const message =
|
||||
`React Hook "${getSource(hook)}" cannot be called ` +
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'in a class component. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.';
|
||||
context.report({ node: hook, message });
|
||||
} else if (codePathFunctionName) {
|
||||
// Custom message if we found an invalid function name.
|
||||
const message =
|
||||
`React Hook "${getSource(hook)}" is called in ` +
|
||||
`function "${getSource(codePathFunctionName)}" ` +
|
||||
`React Hook "${context.getSource(hook)}" is called in ` +
|
||||
`function "${context.getSource(codePathFunctionName)}" ` +
|
||||
'that is neither a React function component nor a custom ' +
|
||||
'React Hook function.' +
|
||||
' React component names must start with an uppercase letter.' +
|
||||
@ -555,7 +541,7 @@ const rule: Rule.RuleModule = {
|
||||
} else if (codePathNode.type === 'Program') {
|
||||
// These are dangerous if you have inline requires enabled.
|
||||
const message =
|
||||
`React Hook "${getSource(hook)}" cannot be called ` +
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'at the top level. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.';
|
||||
context.report({ node: hook, message });
|
||||
@ -568,7 +554,7 @@ const rule: Rule.RuleModule = {
|
||||
// `use(...)` can be called in callbacks.
|
||||
if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
|
||||
const message =
|
||||
`React Hook "${getSource(hook)}" cannot be called ` +
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'inside a callback. React Hooks must be called in a ' +
|
||||
'React function component or a custom React Hook function.';
|
||||
context.report({ node: hook, message });
|
||||
@ -620,7 +606,7 @@ const rule: Rule.RuleModule = {
|
||||
context.report({
|
||||
node,
|
||||
message:
|
||||
`\`${getSource(
|
||||
`\`${context.getSource(
|
||||
node,
|
||||
)}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
|
||||
'the same component. They cannot be assigned to variables or passed down.',
|
||||
@ -637,14 +623,14 @@ const rule: Rule.RuleModule = {
|
||||
FunctionDeclaration(node) {
|
||||
// function MyComponent() { const onClick = useEffectEvent(...) }
|
||||
if (isInsideComponentOrHook(node)) {
|
||||
recordAllUseEffectEventFunctions(getScope(node));
|
||||
recordAllUseEffectEventFunctions(context.getScope());
|
||||
}
|
||||
},
|
||||
|
||||
ArrowFunctionExpression(node) {
|
||||
// const MyComponent = () => { const onClick = useEffectEvent(...) }
|
||||
if (isInsideComponentOrHook(node)) {
|
||||
recordAllUseEffectEventFunctions(getScope(node));
|
||||
recordAllUseEffectEventFunctions(context.getScope());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -4,30 +4,23 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Linter } from 'eslint';
|
||||
|
||||
import ExhaustiveDeps from './ExhaustiveDeps';
|
||||
import { name, version } from './package.json';
|
||||
import type { Linter } from 'eslint';
|
||||
import RulesOfHooks from './RulesOfHooks';
|
||||
import ExhaustiveDeps from './ExhaustiveDeps';
|
||||
|
||||
export const __EXPERIMENTAL__ = false;
|
||||
|
||||
export const flatConfigs = {
|
||||
export const configs = {
|
||||
recommended: {
|
||||
name: 'react-hooks/recommended',
|
||||
plugins: {
|
||||
'react-hooks': {
|
||||
meta: { name, version },
|
||||
rules: {
|
||||
'rules-of-hooks': RulesOfHooks,
|
||||
'exhaustive-deps': ExhaustiveDeps,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: ['react-hooks'],
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
},
|
||||
} satisfies Linter.Config,
|
||||
} as Linter.BaseConfig,
|
||||
};
|
||||
|
||||
export const rules = {
|
||||
'rules-of-hooks': RulesOfHooks,
|
||||
'exhaustive-deps': ExhaustiveDeps,
|
||||
};
|
||||
|
@ -1,13 +1,10 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"version": "4.2.0",
|
||||
"upstream": {
|
||||
"version": 1,
|
||||
"comment": "https://github.com/facebook/react/pull/30774",
|
||||
"sources": {
|
||||
"main": {
|
||||
"repository": "git@github.com:facebook/react.git",
|
||||
"commit": "899cb95f52cc83ab5ca1eb1e268c909d3f0961e7",
|
||||
"repository": "https://github.com/facebook/react",
|
||||
"commit": "0e0b69321a6fcfe8a3eaae3b1016beb110437b38",
|
||||
"branch": "main"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a8ca8f70331b02db537b0b5cf72ea10e3d6c9377
|
||||
Subproject commit 5943318eaf23764eec3ff397ebb969613d728a95
|
28
patch/eslint-import-resolver-typescript.patch
Normal file
28
patch/eslint-import-resolver-typescript.patch
Normal file
@ -0,0 +1,28 @@
|
||||
diff --git a/package.json b/package.json
|
||||
index 98370b5..da6cd9b 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -62,8 +62,7 @@
|
||||
"typecov": "type-coverage"
|
||||
},
|
||||
"peerDependencies": {
|
||||
- "eslint": "*",
|
||||
- "eslint-plugin-import": "*"
|
||||
+ "eslint": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
diff --git a/tsconfig.json b/tsconfig.json
|
||||
deleted file mode 100644
|
||||
index a303861..0000000
|
||||
--- a/tsconfig.json
|
||||
+++ /dev/null
|
||||
@@ -1,8 +0,0 @@
|
||||
-{
|
||||
- "extends": "@1stg/tsconfig/node16",
|
||||
- "compilerOptions": {
|
||||
- "module": "Node16",
|
||||
- "outDir": "./lib"
|
||||
- },
|
||||
- "include": ["./src", "./shim.d.ts"]
|
||||
-}
|
1022
patch/eslint-plugin-import.patch
Normal file
1022
patch/eslint-plugin-import.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,10 @@
|
||||
diff --git a/src/index.js b/src/index.js
|
||||
index 980081e..3cf8018 100644
|
||||
index 7b931fe..eaea267 100644
|
||||
--- a/src/index.js
|
||||
+++ b/src/index.js
|
||||
@@ -1,48 +1,90 @@
|
||||
@@ -1,296 +1,344 @@
|
||||
/* eslint-disable global-require */
|
||||
-const flatConfigBase = require('./configs/flat-config-base');
|
||||
-const legacyConfigBase = require('./configs/legacy-config-base');
|
||||
-const { name, version } = require('../package.json');
|
||||
+import flatConfigBase from './configs/flat-config-base';
|
||||
+import legacyConfigBase from './configs/legacy-config-base';
|
||||
+import { name, version } from '../package.json';
|
||||
+
|
||||
+// @ts-check
|
||||
+import accessibleEmoji from './rules/accessible-emoji';
|
||||
+import altText from './rules/alt-text';
|
||||
+import anchorAmbiguousText from './rules/anchor-ambiguous-text';
|
||||
@ -51,175 +45,579 @@ index 980081e..3cf8018 100644
|
||||
+import scope from './rules/scope';
|
||||
+import tabindexNoPositive from './rules/tabindex-no-positive';
|
||||
|
||||
const allRules = {
|
||||
- 'accessible-emoji': require('./rules/accessible-emoji'),
|
||||
- 'alt-text': require('./rules/alt-text'),
|
||||
- 'anchor-ambiguous-text': require('./rules/anchor-ambiguous-text'),
|
||||
- 'anchor-has-content': require('./rules/anchor-has-content'),
|
||||
- 'anchor-is-valid': require('./rules/anchor-is-valid'),
|
||||
- 'aria-activedescendant-has-tabindex': require('./rules/aria-activedescendant-has-tabindex'),
|
||||
- 'aria-props': require('./rules/aria-props'),
|
||||
- 'aria-proptypes': require('./rules/aria-proptypes'),
|
||||
- 'aria-role': require('./rules/aria-role'),
|
||||
- 'aria-unsupported-elements': require('./rules/aria-unsupported-elements'),
|
||||
- 'autocomplete-valid': require('./rules/autocomplete-valid'),
|
||||
- 'click-events-have-key-events': require('./rules/click-events-have-key-events'),
|
||||
- 'control-has-associated-label': require('./rules/control-has-associated-label'),
|
||||
- 'heading-has-content': require('./rules/heading-has-content'),
|
||||
- 'html-has-lang': require('./rules/html-has-lang'),
|
||||
- 'iframe-has-title': require('./rules/iframe-has-title'),
|
||||
- 'img-redundant-alt': require('./rules/img-redundant-alt'),
|
||||
- 'interactive-supports-focus': require('./rules/interactive-supports-focus'),
|
||||
- 'label-has-associated-control': require('./rules/label-has-associated-control'),
|
||||
- 'label-has-for': require('./rules/label-has-for'),
|
||||
- lang: require('./rules/lang'),
|
||||
- 'media-has-caption': require('./rules/media-has-caption'),
|
||||
- 'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'),
|
||||
- 'no-access-key': require('./rules/no-access-key'),
|
||||
- 'no-aria-hidden-on-focusable': require('./rules/no-aria-hidden-on-focusable'),
|
||||
- 'no-autofocus': require('./rules/no-autofocus'),
|
||||
- 'no-distracting-elements': require('./rules/no-distracting-elements'),
|
||||
- 'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'),
|
||||
- 'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'),
|
||||
- 'no-noninteractive-element-to-interactive-role': require('./rules/no-noninteractive-element-to-interactive-role'),
|
||||
- 'no-noninteractive-tabindex': require('./rules/no-noninteractive-tabindex'),
|
||||
- 'no-onchange': require('./rules/no-onchange'),
|
||||
- 'no-redundant-roles': require('./rules/no-redundant-roles'),
|
||||
- 'no-static-element-interactions': require('./rules/no-static-element-interactions'),
|
||||
- 'prefer-tag-over-role': require('./rules/prefer-tag-over-role'),
|
||||
- 'role-has-required-aria-props': require('./rules/role-has-required-aria-props'),
|
||||
- 'role-supports-aria-props': require('./rules/role-supports-aria-props'),
|
||||
- scope: require('./rules/scope'),
|
||||
- 'tabindex-no-positive': require('./rules/tabindex-no-positive'),
|
||||
+ 'accessible-emoji': accessibleEmoji,
|
||||
+ 'alt-text': altText,
|
||||
+ 'anchor-ambiguous-text': anchorAmbiguousText,
|
||||
+ 'anchor-has-content': anchorHasContent,
|
||||
+ 'anchor-is-valid': anchorIsValid,
|
||||
+ 'aria-activedescendant-has-tabindex': ariaActivedescendantHasTabindex,
|
||||
+ 'aria-props': ariaProps,
|
||||
+ 'aria-proptypes': ariaProptypes,
|
||||
+ 'aria-role': ariaRole,
|
||||
+ 'aria-unsupported-elements': ariaUnsupportedElements,
|
||||
+ 'autocomplete-valid': autocompleteValid,
|
||||
+ 'click-events-have-key-events': clickEventsHaveKeyEvents,
|
||||
+ 'control-has-associated-label': controlHasAssociatedLabel,
|
||||
+ 'heading-has-content': headingHasContent,
|
||||
+ 'html-has-lang': htmlHasLang,
|
||||
+ 'iframe-has-title': iframeHasTitle,
|
||||
+ 'img-redundant-alt': imgRedundantAlt,
|
||||
+ 'interactive-supports-focus': interactiveSupportsFocus,
|
||||
+ 'label-has-associated-control': labelHasAssociatedControl,
|
||||
+ 'label-has-for': labelHasFor,
|
||||
-module.exports = {
|
||||
- rules: {
|
||||
- 'accessible-emoji': require('./rules/accessible-emoji'),
|
||||
- 'alt-text': require('./rules/alt-text'),
|
||||
- 'anchor-ambiguous-text': require('./rules/anchor-ambiguous-text'),
|
||||
- 'anchor-has-content': require('./rules/anchor-has-content'),
|
||||
- 'anchor-is-valid': require('./rules/anchor-is-valid'),
|
||||
- 'aria-activedescendant-has-tabindex': require('./rules/aria-activedescendant-has-tabindex'),
|
||||
- 'aria-props': require('./rules/aria-props'),
|
||||
- 'aria-proptypes': require('./rules/aria-proptypes'),
|
||||
- 'aria-role': require('./rules/aria-role'),
|
||||
- 'aria-unsupported-elements': require('./rules/aria-unsupported-elements'),
|
||||
- 'autocomplete-valid': require('./rules/autocomplete-valid'),
|
||||
- 'click-events-have-key-events': require('./rules/click-events-have-key-events'),
|
||||
- 'control-has-associated-label': require('./rules/control-has-associated-label'),
|
||||
- 'heading-has-content': require('./rules/heading-has-content'),
|
||||
- 'html-has-lang': require('./rules/html-has-lang'),
|
||||
- 'iframe-has-title': require('./rules/iframe-has-title'),
|
||||
- 'img-redundant-alt': require('./rules/img-redundant-alt'),
|
||||
- 'interactive-supports-focus': require('./rules/interactive-supports-focus'),
|
||||
- 'label-has-associated-control': require('./rules/label-has-associated-control'),
|
||||
- 'label-has-for': require('./rules/label-has-for'),
|
||||
- lang: require('./rules/lang'),
|
||||
- 'media-has-caption': require('./rules/media-has-caption'),
|
||||
- 'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'),
|
||||
- 'no-access-key': require('./rules/no-access-key'),
|
||||
- 'no-aria-hidden-on-focusable': require('./rules/no-aria-hidden-on-focusable'),
|
||||
- 'no-autofocus': require('./rules/no-autofocus'),
|
||||
- 'no-distracting-elements': require('./rules/no-distracting-elements'),
|
||||
- 'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'),
|
||||
- 'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'),
|
||||
- 'no-noninteractive-element-to-interactive-role': require('./rules/no-noninteractive-element-to-interactive-role'),
|
||||
- 'no-noninteractive-tabindex': require('./rules/no-noninteractive-tabindex'),
|
||||
- 'no-onchange': require('./rules/no-onchange'),
|
||||
- 'no-redundant-roles': require('./rules/no-redundant-roles'),
|
||||
- 'no-static-element-interactions': require('./rules/no-static-element-interactions'),
|
||||
- 'prefer-tag-over-role': require('./rules/prefer-tag-over-role'),
|
||||
- 'role-has-required-aria-props': require('./rules/role-has-required-aria-props'),
|
||||
- 'role-supports-aria-props': require('./rules/role-supports-aria-props'),
|
||||
- scope: require('./rules/scope'),
|
||||
- 'tabindex-no-positive': require('./rules/tabindex-no-positive'),
|
||||
- },
|
||||
- configs: {
|
||||
- recommended: {
|
||||
- plugins: [
|
||||
- 'jsx-a11y',
|
||||
+export const rules = kebabCase({
|
||||
+ accessibleEmoji,
|
||||
+ altText,
|
||||
+ anchorAmbiguousText,
|
||||
+ anchorHasContent,
|
||||
+ anchorIsValid,
|
||||
+ ariaActivedescendantHasTabindex,
|
||||
+ ariaProps,
|
||||
+ ariaProptypes,
|
||||
+ ariaRole,
|
||||
+ ariaUnsupportedElements,
|
||||
+ autocompleteValid,
|
||||
+ clickEventsHaveKeyEvents,
|
||||
+ controlHasAssociatedLabel,
|
||||
+ headingHasContent,
|
||||
+ htmlHasLang,
|
||||
+ iframeHasTitle,
|
||||
+ imgRedundantAlt,
|
||||
+ interactiveSupportsFocus,
|
||||
+ labelHasAssociatedControl,
|
||||
+ labelHasFor,
|
||||
+ lang,
|
||||
+ 'media-has-caption': mediaHasCaption,
|
||||
+ 'mouse-events-have-key-events': mouseEventsHaveKeyEvents,
|
||||
+ 'no-access-key': noAccessKey,
|
||||
+ 'no-aria-hidden-on-focusable': noAriaHiddenOnFocusable,
|
||||
+ 'no-autofocus': noAutofocus,
|
||||
+ 'no-distracting-elements': noDistractingElements,
|
||||
+ 'no-interactive-element-to-noninteractive-role':
|
||||
+ noInteractiveElementToNoninteractiveRole,
|
||||
+ 'no-noninteractive-element-interactions': noNoninteractiveElementInteractions,
|
||||
+ 'no-noninteractive-element-to-interactive-role':
|
||||
+ noNoninteractiveElementToInteractiveRole,
|
||||
+ 'no-noninteractive-tabindex': noNoninteractiveTabindex,
|
||||
+ 'no-onchange': noOnChange,
|
||||
+ 'no-redundant-roles': noRedundantRoles,
|
||||
+ 'no-static-element-interactions': noStaticElementInteractions,
|
||||
+ 'prefer-tag-over-role': preferTagOverRole,
|
||||
+ 'role-has-required-aria-props': roleHasRequiredAriaProps,
|
||||
+ 'role-supports-aria-props': roleSupportsAriaProps,
|
||||
+ mediaHasCaption,
|
||||
+ mouseEventsHaveKeyEvents,
|
||||
+ noAccessKey,
|
||||
+ noAriaHiddenOnFocusable,
|
||||
+ noAutofocus,
|
||||
+ noDistractingElements,
|
||||
+ noInteractiveElementToNoninteractiveRole,
|
||||
+ noNoninteractiveElementInteractions,
|
||||
+ noNoninteractiveElementToInteractiveRole,
|
||||
+ noNoninteractiveTabindex,
|
||||
+ noOnChange,
|
||||
+ noRedundantRoles,
|
||||
+ noStaticElementInteractions,
|
||||
+ preferTagOverRole,
|
||||
+ roleHasRequiredAriaProps,
|
||||
+ roleSupportsAriaProps,
|
||||
+ scope,
|
||||
+ 'tabindex-no-positive': tabindexNoPositive,
|
||||
};
|
||||
|
||||
const recommendedRules = {
|
||||
@@ -294,15 +336,15 @@ const jsxA11y = {
|
||||
* Given a ruleset and optionally a flat config name, generate a config.
|
||||
* @param {object} rules - ruleset for this config
|
||||
* @param {string} flatConfigName - name for the config if flat
|
||||
- * @returns Config for this set of rules.
|
||||
+ * @returns {import('eslint').Linter.Config} Config for this set of rules.
|
||||
*/
|
||||
const createConfig = (rules, flatConfigName) => ({
|
||||
...(flatConfigName
|
||||
? {
|
||||
- ...flatConfigBase,
|
||||
- name: `jsx-a11y/${flatConfigName}`,
|
||||
- plugins: { 'jsx-a11y': jsxA11y },
|
||||
- }
|
||||
+ ...flatConfigBase,
|
||||
+ name: `jsx-a11y/${flatConfigName}`,
|
||||
+ plugins: { 'jsx-a11y': jsxA11y },
|
||||
+ }
|
||||
: { ...legacyConfigBase, plugins: ['jsx-a11y'] }),
|
||||
rules: { ...rules },
|
||||
});
|
||||
@@ -317,4 +359,4 @@ const flatConfigs = {
|
||||
strict: createConfig(strictRules, 'strict'),
|
||||
};
|
||||
|
||||
-module.exports = Object.assign(jsxA11y, { configs, flatConfigs });
|
||||
+export default { ...jsxA11y, configs, flatConfigs };
|
||||
diff --git a/src/rules/autocomplete-valid.js b/src/rules/autocomplete-valid.js
|
||||
index df7b6b8..c4d0da1 100644
|
||||
--- a/src/rules/autocomplete-valid.js
|
||||
+++ b/src/rules/autocomplete-valid.js
|
||||
@@ -6,7 +6,7 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// ----------------------------------------------------------------------------
|
||||
-import { runVirtualRule } from 'axe-core';
|
||||
+import axe from 'axe-core';
|
||||
import { getLiteralPropValue, getProp } from 'jsx-ast-utils';
|
||||
import { generateObjSchema, arraySchema } from '../util/schemas';
|
||||
import getElementType from '../util/getElementType';
|
||||
@@ -24,23 +24,25 @@ export default {
|
||||
schema: [schema],
|
||||
+ tabindexNoPositive,
|
||||
+});
|
||||
+export const configs = {
|
||||
+ recommended: {
|
||||
+ plugins: [
|
||||
+ 'jsx-a11y',
|
||||
+ ],
|
||||
+ parserOptions: {
|
||||
+ ecmaFeatures: {
|
||||
+ jsx: true,
|
||||
+ },
|
||||
+ },
|
||||
+ rules: {
|
||||
+ 'jsx-a11y/alt-text': 'error',
|
||||
+ 'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: error
|
||||
+ 'jsx-a11y/anchor-has-content': 'error',
|
||||
+ 'jsx-a11y/anchor-is-valid': 'error',
|
||||
+ 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
|
||||
+ 'jsx-a11y/aria-props': 'error',
|
||||
+ 'jsx-a11y/aria-proptypes': 'error',
|
||||
+ 'jsx-a11y/aria-role': 'error',
|
||||
+ 'jsx-a11y/aria-unsupported-elements': 'error',
|
||||
+ 'jsx-a11y/autocomplete-valid': 'error',
|
||||
+ 'jsx-a11y/click-events-have-key-events': 'error',
|
||||
+ 'jsx-a11y/control-has-associated-label': ['off', {
|
||||
+ ignoreElements: [
|
||||
+ 'audio',
|
||||
+ 'canvas',
|
||||
+ 'embed',
|
||||
+ 'input',
|
||||
+ 'textarea',
|
||||
+ 'tr',
|
||||
+ 'video',
|
||||
+ ],
|
||||
+ ignoreRoles: [
|
||||
+ 'grid',
|
||||
+ 'listbox',
|
||||
+ 'menu',
|
||||
+ 'menubar',
|
||||
+ 'radiogroup',
|
||||
+ 'row',
|
||||
+ 'tablist',
|
||||
+ 'toolbar',
|
||||
+ 'tree',
|
||||
+ 'treegrid',
|
||||
+ ],
|
||||
+ includeRoles: [
|
||||
+ 'alert',
|
||||
+ 'dialog',
|
||||
+ ],
|
||||
+ }],
|
||||
+ 'jsx-a11y/heading-has-content': 'error',
|
||||
+ 'jsx-a11y/html-has-lang': 'error',
|
||||
+ 'jsx-a11y/iframe-has-title': 'error',
|
||||
+ 'jsx-a11y/img-redundant-alt': 'error',
|
||||
+ 'jsx-a11y/interactive-supports-focus': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ tabbable: [
|
||||
+ 'button',
|
||||
+ 'checkbox',
|
||||
+ 'link',
|
||||
+ 'searchbox',
|
||||
+ 'spinbutton',
|
||||
+ 'switch',
|
||||
+ 'textbox',
|
||||
+ ],
|
||||
+ },
|
||||
],
|
||||
- parserOptions: {
|
||||
- ecmaFeatures: {
|
||||
- jsx: true,
|
||||
+ 'jsx-a11y/label-has-associated-control': 'error',
|
||||
+ 'jsx-a11y/label-has-for': 'off',
|
||||
+ 'jsx-a11y/media-has-caption': 'error',
|
||||
+ 'jsx-a11y/mouse-events-have-key-events': 'error',
|
||||
+ 'jsx-a11y/no-access-key': 'error',
|
||||
+ 'jsx-a11y/no-autofocus': 'error',
|
||||
+ 'jsx-a11y/no-distracting-elements': 'error',
|
||||
+ 'jsx-a11y/no-interactive-element-to-noninteractive-role': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ tr: ['none', 'presentation'],
|
||||
+ canvas: ['img'],
|
||||
},
|
||||
- },
|
||||
- rules: {
|
||||
- 'jsx-a11y/alt-text': 'error',
|
||||
- 'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: error
|
||||
- 'jsx-a11y/anchor-has-content': 'error',
|
||||
- 'jsx-a11y/anchor-is-valid': 'error',
|
||||
- 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
|
||||
- 'jsx-a11y/aria-props': 'error',
|
||||
- 'jsx-a11y/aria-proptypes': 'error',
|
||||
- 'jsx-a11y/aria-role': 'error',
|
||||
- 'jsx-a11y/aria-unsupported-elements': 'error',
|
||||
- 'jsx-a11y/autocomplete-valid': 'error',
|
||||
- 'jsx-a11y/click-events-have-key-events': 'error',
|
||||
- 'jsx-a11y/control-has-associated-label': ['off', {
|
||||
- ignoreElements: [
|
||||
- 'audio',
|
||||
- 'canvas',
|
||||
- 'embed',
|
||||
- 'input',
|
||||
- 'textarea',
|
||||
- 'tr',
|
||||
- 'video',
|
||||
+ ],
|
||||
+ 'jsx-a11y/no-noninteractive-element-interactions': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ handlers: [
|
||||
+ 'onClick',
|
||||
+ 'onError',
|
||||
+ 'onLoad',
|
||||
+ 'onMouseDown',
|
||||
+ 'onMouseUp',
|
||||
+ 'onKeyPress',
|
||||
+ 'onKeyDown',
|
||||
+ 'onKeyUp',
|
||||
],
|
||||
- ignoreRoles: [
|
||||
- 'grid',
|
||||
+ alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
|
||||
+ body: ['onError', 'onLoad'],
|
||||
+ dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
|
||||
+ iframe: ['onError', 'onLoad'],
|
||||
+ img: ['onError', 'onLoad'],
|
||||
+ },
|
||||
+ ],
|
||||
+ 'jsx-a11y/no-noninteractive-element-to-interactive-role': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ ul: [
|
||||
'listbox',
|
||||
'menu',
|
||||
'menubar',
|
||||
'radiogroup',
|
||||
- 'row',
|
||||
'tablist',
|
||||
- 'toolbar',
|
||||
'tree',
|
||||
'treegrid',
|
||||
],
|
||||
- includeRoles: [
|
||||
- 'alert',
|
||||
- 'dialog',
|
||||
- ],
|
||||
- }],
|
||||
- 'jsx-a11y/heading-has-content': 'error',
|
||||
- 'jsx-a11y/html-has-lang': 'error',
|
||||
- 'jsx-a11y/iframe-has-title': 'error',
|
||||
- 'jsx-a11y/img-redundant-alt': 'error',
|
||||
- 'jsx-a11y/interactive-supports-focus': [
|
||||
- 'error',
|
||||
- {
|
||||
- tabbable: [
|
||||
- 'button',
|
||||
- 'checkbox',
|
||||
- 'link',
|
||||
- 'searchbox',
|
||||
- 'spinbutton',
|
||||
- 'switch',
|
||||
- 'textbox',
|
||||
- ],
|
||||
- },
|
||||
- ],
|
||||
- 'jsx-a11y/label-has-associated-control': 'error',
|
||||
- 'jsx-a11y/label-has-for': 'off',
|
||||
- 'jsx-a11y/media-has-caption': 'error',
|
||||
- 'jsx-a11y/mouse-events-have-key-events': 'error',
|
||||
- 'jsx-a11y/no-access-key': 'error',
|
||||
- 'jsx-a11y/no-autofocus': 'error',
|
||||
- 'jsx-a11y/no-distracting-elements': 'error',
|
||||
- 'jsx-a11y/no-interactive-element-to-noninteractive-role': [
|
||||
- 'error',
|
||||
- {
|
||||
- tr: ['none', 'presentation'],
|
||||
- canvas: ['img'],
|
||||
- },
|
||||
- ],
|
||||
- 'jsx-a11y/no-noninteractive-element-interactions': [
|
||||
- 'error',
|
||||
- {
|
||||
- handlers: [
|
||||
- 'onClick',
|
||||
- 'onError',
|
||||
- 'onLoad',
|
||||
- 'onMouseDown',
|
||||
- 'onMouseUp',
|
||||
- 'onKeyPress',
|
||||
- 'onKeyDown',
|
||||
- 'onKeyUp',
|
||||
- ],
|
||||
- alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
|
||||
- body: ['onError', 'onLoad'],
|
||||
- dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
|
||||
- iframe: ['onError', 'onLoad'],
|
||||
- img: ['onError', 'onLoad'],
|
||||
- },
|
||||
- ],
|
||||
- 'jsx-a11y/no-noninteractive-element-to-interactive-role': [
|
||||
- 'error',
|
||||
- {
|
||||
- ul: [
|
||||
- 'listbox',
|
||||
- 'menu',
|
||||
- 'menubar',
|
||||
- 'radiogroup',
|
||||
- 'tablist',
|
||||
- 'tree',
|
||||
- 'treegrid',
|
||||
- ],
|
||||
- ol: [
|
||||
- 'listbox',
|
||||
- 'menu',
|
||||
- 'menubar',
|
||||
- 'radiogroup',
|
||||
- 'tablist',
|
||||
- 'tree',
|
||||
- 'treegrid',
|
||||
- ],
|
||||
- li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
|
||||
- table: ['grid'],
|
||||
- td: ['gridcell'],
|
||||
- fieldset: ['radiogroup', 'presentation'],
|
||||
- },
|
||||
- ],
|
||||
- 'jsx-a11y/no-noninteractive-tabindex': [
|
||||
- 'error',
|
||||
- {
|
||||
- tags: [],
|
||||
- roles: ['tabpanel'],
|
||||
- allowExpressionValues: true,
|
||||
- },
|
||||
- ],
|
||||
- 'jsx-a11y/no-redundant-roles': 'error',
|
||||
- 'jsx-a11y/no-static-element-interactions': [
|
||||
- 'error',
|
||||
- {
|
||||
- allowExpressionValues: true,
|
||||
- handlers: [
|
||||
- 'onClick',
|
||||
- 'onMouseDown',
|
||||
- 'onMouseUp',
|
||||
- 'onKeyPress',
|
||||
- 'onKeyDown',
|
||||
- 'onKeyUp',
|
||||
- ],
|
||||
- },
|
||||
- ],
|
||||
- 'jsx-a11y/role-has-required-aria-props': 'error',
|
||||
- 'jsx-a11y/role-supports-aria-props': 'error',
|
||||
- 'jsx-a11y/scope': 'error',
|
||||
- 'jsx-a11y/tabindex-no-positive': 'error',
|
||||
- },
|
||||
- },
|
||||
- strict: {
|
||||
- plugins: [
|
||||
- 'jsx-a11y',
|
||||
- ],
|
||||
- parserOptions: {
|
||||
- ecmaFeatures: {
|
||||
- jsx: true,
|
||||
- },
|
||||
- },
|
||||
- rules: {
|
||||
- 'jsx-a11y/alt-text': 'error',
|
||||
- 'jsx-a11y/anchor-has-content': 'error',
|
||||
- 'jsx-a11y/anchor-is-valid': 'error',
|
||||
- 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
|
||||
- 'jsx-a11y/aria-props': 'error',
|
||||
- 'jsx-a11y/aria-proptypes': 'error',
|
||||
- 'jsx-a11y/aria-role': 'error',
|
||||
- 'jsx-a11y/aria-unsupported-elements': 'error',
|
||||
- 'jsx-a11y/autocomplete-valid': 'error',
|
||||
- 'jsx-a11y/click-events-have-key-events': 'error',
|
||||
- 'jsx-a11y/control-has-associated-label': ['off', {
|
||||
- ignoreElements: [
|
||||
- 'audio',
|
||||
- 'canvas',
|
||||
- 'embed',
|
||||
- 'input',
|
||||
- 'textarea',
|
||||
- 'tr',
|
||||
- 'video',
|
||||
- ],
|
||||
- ignoreRoles: [
|
||||
- 'grid',
|
||||
+ ol: [
|
||||
'listbox',
|
||||
'menu',
|
||||
'menubar',
|
||||
'radiogroup',
|
||||
- 'row',
|
||||
'tablist',
|
||||
- 'toolbar',
|
||||
'tree',
|
||||
'treegrid',
|
||||
],
|
||||
- includeRoles: [
|
||||
- 'alert',
|
||||
- 'dialog',
|
||||
+ li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
|
||||
+ table: ['grid'],
|
||||
+ td: ['gridcell'],
|
||||
+ fieldset: ['radiogroup', 'presentation'],
|
||||
+ },
|
||||
+ ],
|
||||
+ 'jsx-a11y/no-noninteractive-tabindex': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ tags: [],
|
||||
+ roles: ['tabpanel'],
|
||||
+ allowExpressionValues: true,
|
||||
+ },
|
||||
+ ],
|
||||
+ 'jsx-a11y/no-redundant-roles': 'error',
|
||||
+ 'jsx-a11y/no-static-element-interactions': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ allowExpressionValues: true,
|
||||
+ handlers: [
|
||||
+ 'onClick',
|
||||
+ 'onMouseDown',
|
||||
+ 'onMouseUp',
|
||||
+ 'onKeyPress',
|
||||
+ 'onKeyDown',
|
||||
+ 'onKeyUp',
|
||||
],
|
||||
- }],
|
||||
- 'jsx-a11y/heading-has-content': 'error',
|
||||
- 'jsx-a11y/html-has-lang': 'error',
|
||||
- 'jsx-a11y/iframe-has-title': 'error',
|
||||
- 'jsx-a11y/img-redundant-alt': 'error',
|
||||
- 'jsx-a11y/interactive-supports-focus': [
|
||||
- 'error',
|
||||
- {
|
||||
- tabbable: [
|
||||
- 'button',
|
||||
- 'checkbox',
|
||||
- 'link',
|
||||
- 'progressbar',
|
||||
- 'searchbox',
|
||||
- 'slider',
|
||||
- 'spinbutton',
|
||||
- 'switch',
|
||||
- 'textbox',
|
||||
- ],
|
||||
- },
|
||||
+ },
|
||||
+ ],
|
||||
+ 'jsx-a11y/role-has-required-aria-props': 'error',
|
||||
+ 'jsx-a11y/role-supports-aria-props': 'error',
|
||||
+ 'jsx-a11y/scope': 'error',
|
||||
+ 'jsx-a11y/tabindex-no-positive': 'error',
|
||||
+ },
|
||||
+ },
|
||||
+ strict: {
|
||||
+ plugins: [
|
||||
+ 'jsx-a11y',
|
||||
+ ],
|
||||
+ parserOptions: {
|
||||
+ ecmaFeatures: {
|
||||
+ jsx: true,
|
||||
+ },
|
||||
+ },
|
||||
+ rules: {
|
||||
+ 'jsx-a11y/alt-text': 'error',
|
||||
+ 'jsx-a11y/anchor-has-content': 'error',
|
||||
+ 'jsx-a11y/anchor-is-valid': 'error',
|
||||
+ 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
|
||||
+ 'jsx-a11y/aria-props': 'error',
|
||||
+ 'jsx-a11y/aria-proptypes': 'error',
|
||||
+ 'jsx-a11y/aria-role': 'error',
|
||||
+ 'jsx-a11y/aria-unsupported-elements': 'error',
|
||||
+ 'jsx-a11y/autocomplete-valid': 'error',
|
||||
+ 'jsx-a11y/click-events-have-key-events': 'error',
|
||||
+ 'jsx-a11y/control-has-associated-label': ['off', {
|
||||
+ ignoreElements: [
|
||||
+ 'audio',
|
||||
+ 'canvas',
|
||||
+ 'embed',
|
||||
+ 'input',
|
||||
+ 'textarea',
|
||||
+ 'tr',
|
||||
+ 'video',
|
||||
],
|
||||
- 'jsx-a11y/label-has-for': 'off',
|
||||
- 'jsx-a11y/label-has-associated-control': 'error',
|
||||
- 'jsx-a11y/media-has-caption': 'error',
|
||||
- 'jsx-a11y/mouse-events-have-key-events': 'error',
|
||||
- 'jsx-a11y/no-access-key': 'error',
|
||||
- 'jsx-a11y/no-autofocus': 'error',
|
||||
- 'jsx-a11y/no-distracting-elements': 'error',
|
||||
- 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
|
||||
- 'jsx-a11y/no-noninteractive-element-interactions': [
|
||||
- 'error',
|
||||
- {
|
||||
- body: ['onError', 'onLoad'],
|
||||
- iframe: ['onError', 'onLoad'],
|
||||
- img: ['onError', 'onLoad'],
|
||||
- },
|
||||
+ ignoreRoles: [
|
||||
+ 'grid',
|
||||
+ 'listbox',
|
||||
+ 'menu',
|
||||
+ 'menubar',
|
||||
+ 'radiogroup',
|
||||
+ 'row',
|
||||
+ 'tablist',
|
||||
+ 'toolbar',
|
||||
+ 'tree',
|
||||
+ 'treegrid',
|
||||
],
|
||||
- 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
|
||||
- 'jsx-a11y/no-noninteractive-tabindex': 'error',
|
||||
- 'jsx-a11y/no-redundant-roles': 'error',
|
||||
- 'jsx-a11y/no-static-element-interactions': 'error',
|
||||
- 'jsx-a11y/role-has-required-aria-props': 'error',
|
||||
- 'jsx-a11y/role-supports-aria-props': 'error',
|
||||
- 'jsx-a11y/scope': 'error',
|
||||
- 'jsx-a11y/tabindex-no-positive': 'error',
|
||||
- },
|
||||
+ includeRoles: [
|
||||
+ 'alert',
|
||||
+ 'dialog',
|
||||
+ ],
|
||||
+ }],
|
||||
+ 'jsx-a11y/heading-has-content': 'error',
|
||||
+ 'jsx-a11y/html-has-lang': 'error',
|
||||
+ 'jsx-a11y/iframe-has-title': 'error',
|
||||
+ 'jsx-a11y/img-redundant-alt': 'error',
|
||||
+ 'jsx-a11y/interactive-supports-focus': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ tabbable: [
|
||||
+ 'button',
|
||||
+ 'checkbox',
|
||||
+ 'link',
|
||||
+ 'progressbar',
|
||||
+ 'searchbox',
|
||||
+ 'slider',
|
||||
+ 'spinbutton',
|
||||
+ 'switch',
|
||||
+ 'textbox',
|
||||
+ ],
|
||||
+ },
|
||||
+ ],
|
||||
+ 'jsx-a11y/label-has-for': 'off',
|
||||
+ 'jsx-a11y/label-has-associated-control': 'error',
|
||||
+ 'jsx-a11y/media-has-caption': 'error',
|
||||
+ 'jsx-a11y/mouse-events-have-key-events': 'error',
|
||||
+ 'jsx-a11y/no-access-key': 'error',
|
||||
+ 'jsx-a11y/no-autofocus': 'error',
|
||||
+ 'jsx-a11y/no-distracting-elements': 'error',
|
||||
+ 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
|
||||
+ 'jsx-a11y/no-noninteractive-element-interactions': [
|
||||
+ 'error',
|
||||
+ {
|
||||
+ body: ['onError', 'onLoad'],
|
||||
+ iframe: ['onError', 'onLoad'],
|
||||
+ img: ['onError', 'onLoad'],
|
||||
+ },
|
||||
+ ],
|
||||
+ 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
|
||||
+ 'jsx-a11y/no-noninteractive-tabindex': 'error',
|
||||
+ 'jsx-a11y/no-redundant-roles': 'error',
|
||||
+ 'jsx-a11y/no-static-element-interactions': 'error',
|
||||
+ 'jsx-a11y/role-has-required-aria-props': 'error',
|
||||
+ 'jsx-a11y/role-supports-aria-props': 'error',
|
||||
+ 'jsx-a11y/scope': 'error',
|
||||
+ 'jsx-a11y/tabindex-no-positive': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
- create: (context) => {
|
||||
+ create: context => {
|
||||
const elementType = getElementType(context);
|
||||
return {
|
||||
- JSXOpeningElement: (node) => {
|
||||
+ JSXOpeningElement: node => {
|
||||
const options = context.options[0] || {};
|
||||
const { inputComponents = [] } = options;
|
||||
const inputTypes = ['input'].concat(inputComponents);
|
||||
|
||||
const elType = elementType(node);
|
||||
- const autocomplete = getLiteralPropValue(getProp(node.attributes, 'autocomplete'));
|
||||
+ const autocomplete = getLiteralPropValue(
|
||||
+ getProp(node.attributes, 'autocomplete'),
|
||||
+ );
|
||||
|
||||
if (typeof autocomplete !== 'string' || !inputTypes.includes(elType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const type = getLiteralPropValue(getProp(node.attributes, 'type'));
|
||||
- const { violations } = runVirtualRule('autocomplete-valid', {
|
||||
+ const { violations } = axe.runVirtualRule('autocomplete-valid', {
|
||||
nodeName: 'input',
|
||||
attributes: {
|
||||
autocomplete,
|
||||
diff --git a/src/rules/label-has-associated-control.js b/src/rules/label-has-associated-control.js
|
||||
index d65abe9..22ecee7 100644
|
||||
--- a/src/rules/label-has-associated-control.js
|
||||
+++ b/src/rules/label-has-associated-control.js
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import { hasProp, getProp, getPropValue } from 'jsx-ast-utils';
|
||||
import type { JSXElement } from 'ast-types-flow';
|
||||
-import minimatch from 'minimatch';
|
||||
+import { minimatch } from 'minimatch';
|
||||
import { generateObjSchema, arraySchema } from '../util/schemas';
|
||||
import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint';
|
||||
import getElementType from '../util/getElementType';
|
||||
};
|
||||
+
|
||||
+/** @param {object} obj */
|
||||
+function kebabCase(obj) {
|
||||
+ return Object.fromEntries(
|
||||
+ Object.entries(obj).map(([key, value]) => [
|
||||
+ key.replace(/([A-Z])/g, '-$1').toLowerCase(),
|
||||
+ value,
|
||||
+ ]),
|
||||
+ );
|
||||
+}
|
||||
diff --git a/src/util/mayContainChildComponent.js b/src/util/mayContainChildComponent.js
|
||||
index 65000a0..09b199a 100644
|
||||
index 43a03ef..5e1035e 100644
|
||||
--- a/src/util/mayContainChildComponent.js
|
||||
+++ b/src/util/mayContainChildComponent.js
|
||||
@@ -9,7 +9,7 @@
|
||||
@ -231,16 +629,3 @@ index 65000a0..09b199a 100644
|
||||
|
||||
export default function mayContainChildComponent(
|
||||
root: Node,
|
||||
diff --git a/src/util/mayHaveAccessibleLabel.js b/src/util/mayHaveAccessibleLabel.js
|
||||
index 186ef5e..3dd7d4d 100644
|
||||
--- a/src/util/mayHaveAccessibleLabel.js
|
||||
+++ b/src/util/mayHaveAccessibleLabel.js
|
||||
@@ -11,7 +11,7 @@
|
||||
import includes from 'array-includes';
|
||||
import { getPropValue, propName, elementType as rawElementType } from 'jsx-ast-utils';
|
||||
import type { JSXOpeningElement, Node } from 'ast-types-flow';
|
||||
-import minimatch from 'minimatch';
|
||||
+import { minimatch } from 'minimatch';
|
||||
|
||||
function tryTrim(value: any) {
|
||||
return typeof value === 'string' ? value.trim() : value;
|
||||
|
@ -1,8 +1,8 @@
|
||||
diff --git a/lib/index.js b/lib/index.js
|
||||
index de95218..e30a3df 100644
|
||||
index 49fd4c7..a0fdd81 100644
|
||||
--- a/lib/index.js
|
||||
+++ b/lib/index.js
|
||||
@@ -1,17 +1,17 @@
|
||||
@@ -1,9 +1,9 @@
|
||||
"use strict"
|
||||
|
||||
-const pkg = require("../package.json")
|
||||
@ -14,110 +14,16 @@ index de95218..e30a3df 100644
|
||||
+import cjsConfig from "./configs/recommended-script"
|
||||
+import recommendedConfig from "./configs/recommended"
|
||||
|
||||
/** @import { ESLint, Linter } from 'eslint' */
|
||||
|
||||
/** @type {ESLint.Plugin} */
|
||||
const base = {
|
||||
/**
|
||||
* @typedef {{
|
||||
@@ -20,8 +20,8 @@ const recommendedConfig = require("./configs/recommended")
|
||||
/** @type {import('eslint').ESLint.Plugin & { configs: Configs }} */
|
||||
const plugin = {
|
||||
meta: {
|
||||
- name: pkg.name,
|
||||
- version: pkg.version,
|
||||
+ name,
|
||||
+ version,
|
||||
},
|
||||
rules: {
|
||||
rules: /** @type {Record<string, import('eslint').Rule.RuleModule>} */ ({
|
||||
"callback-return": require("./rules/callback-return"),
|
||||
diff --git a/tests/fixtures/no-extraneous/dependencies/node_modules/@bbb/aaa.js b/tests/fixtures/no-extraneous/dependencies/node_modules/@bbb/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/dependencies/node_modules/aaa.js b/tests/fixtures/no-extraneous/dependencies/node_modules/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/dependencies/node_modules/bbb/index.js b/tests/fixtures/no-extraneous/dependencies/node_modules/bbb/index.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/dependencies/node_modules/bbb/package.json b/tests/fixtures/no-extraneous/dependencies/node_modules/bbb/package.json
|
||||
deleted file mode 100644
|
||||
index b7d25e2..0000000
|
||||
--- a/tests/fixtures/no-extraneous/dependencies/node_modules/bbb/package.json
|
||||
+++ /dev/null
|
||||
@@ -1,4 +0,0 @@
|
||||
-{
|
||||
- "name": "bbb",
|
||||
- "main": "index.js"
|
||||
-}
|
||||
\ No newline at end of file
|
||||
diff --git a/tests/fixtures/no-extraneous/devDependencies/node_modules/@bbb/aaa.js b/tests/fixtures/no-extraneous/devDependencies/node_modules/@bbb/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/devDependencies/node_modules/aaa.js b/tests/fixtures/no-extraneous/devDependencies/node_modules/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/devDependencies/node_modules/bbb/index.js b/tests/fixtures/no-extraneous/devDependencies/node_modules/bbb/index.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/devDependencies/node_modules/bbb/package.json b/tests/fixtures/no-extraneous/devDependencies/node_modules/bbb/package.json
|
||||
deleted file mode 100644
|
||||
index b7d25e2..0000000
|
||||
--- a/tests/fixtures/no-extraneous/devDependencies/node_modules/bbb/package.json
|
||||
+++ /dev/null
|
||||
@@ -1,4 +0,0 @@
|
||||
-{
|
||||
- "name": "bbb",
|
||||
- "main": "index.js"
|
||||
-}
|
||||
\ No newline at end of file
|
||||
diff --git a/tests/fixtures/no-extraneous/noDependencies/node_modules/@bbb/aaa.js b/tests/fixtures/no-extraneous/noDependencies/node_modules/@bbb/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/noDependencies/node_modules/aaa.js b/tests/fixtures/no-extraneous/noDependencies/node_modules/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/noDependencies/node_modules/bbb.js b/tests/fixtures/no-extraneous/noDependencies/node_modules/bbb.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/optionalDependencies/node_modules/@bbb/aaa.js b/tests/fixtures/no-extraneous/optionalDependencies/node_modules/@bbb/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/optionalDependencies/node_modules/aaa.js b/tests/fixtures/no-extraneous/optionalDependencies/node_modules/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/optionalDependencies/node_modules/bbb/index.js b/tests/fixtures/no-extraneous/optionalDependencies/node_modules/bbb/index.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/optionalDependencies/node_modules/bbb/package.json b/tests/fixtures/no-extraneous/optionalDependencies/node_modules/bbb/package.json
|
||||
deleted file mode 100644
|
||||
index b7d25e2..0000000
|
||||
--- a/tests/fixtures/no-extraneous/optionalDependencies/node_modules/bbb/package.json
|
||||
+++ /dev/null
|
||||
@@ -1,4 +0,0 @@
|
||||
-{
|
||||
- "name": "bbb",
|
||||
- "main": "index.js"
|
||||
-}
|
||||
\ No newline at end of file
|
||||
diff --git a/tests/fixtures/no-extraneous/peerDependencies/node_modules/@bbb/aaa.js b/tests/fixtures/no-extraneous/peerDependencies/node_modules/@bbb/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/peerDependencies/node_modules/aaa.js b/tests/fixtures/no-extraneous/peerDependencies/node_modules/aaa.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/peerDependencies/node_modules/bbb/index.js b/tests/fixtures/no-extraneous/peerDependencies/node_modules/bbb/index.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-extraneous/peerDependencies/node_modules/bbb/package.json b/tests/fixtures/no-extraneous/peerDependencies/node_modules/bbb/package.json
|
||||
deleted file mode 100644
|
||||
index b7d25e2..0000000
|
||||
--- a/tests/fixtures/no-extraneous/peerDependencies/node_modules/bbb/package.json
|
||||
+++ /dev/null
|
||||
@@ -1,4 +0,0 @@
|
||||
-{
|
||||
- "name": "bbb",
|
||||
- "main": "index.js"
|
||||
-}
|
||||
\ No newline at end of file
|
||||
diff --git a/tests/fixtures/no-hide-core-modules/indirect-thirdparty/node_modules/util/index.js b/tests/fixtures/no-hide-core-modules/indirect-thirdparty/node_modules/util/index.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/tests/fixtures/no-hide-core-modules/thirdparty/node_modules/util/index.js b/tests/fixtures/no-hide-core-modules/thirdparty/node_modules/util/index.js
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
|
336
patch/eslint-plugin-react.patch
Normal file
336
patch/eslint-plugin-react.patch
Normal file
@ -0,0 +1,336 @@
|
||||
diff --git a/.eslintrc b/.eslintrc
|
||||
deleted file mode 100644
|
||||
index 4991f200..00000000
|
||||
--- a/.eslintrc
|
||||
+++ /dev/null
|
||||
@@ -1,82 +0,0 @@
|
||||
-{
|
||||
- "root": true,
|
||||
- "extends": ["airbnb-base", "plugin:eslint-plugin/recommended"],
|
||||
- "plugins": ["eslint-plugin"],
|
||||
- "env": {
|
||||
- "es6": true,
|
||||
- "node": true
|
||||
- },
|
||||
- "parserOptions": {
|
||||
- "ecmaVersion": 6,
|
||||
- "ecmaFeatures": {
|
||||
- "jsx": true
|
||||
- },
|
||||
- "sourceType": "script",
|
||||
- },
|
||||
- "ignorePatterns": [
|
||||
- "coverage/",
|
||||
- ".nyc_output/",
|
||||
- ],
|
||||
- "rules": {
|
||||
- "comma-dangle": [2, "always-multiline"],
|
||||
- "object-shorthand": [2, "always", {
|
||||
- "ignoreConstructors": false,
|
||||
- "avoidQuotes": false, // this is the override vs airbnb
|
||||
- }],
|
||||
- "max-len": [2, 120, {
|
||||
- "ignoreStrings": true,
|
||||
- "ignoreTemplateLiterals": true,
|
||||
- "ignoreComments": true,
|
||||
- }],
|
||||
- "consistent-return": 0,
|
||||
-
|
||||
- "prefer-destructuring": [2, { "array": false, "object": false }, { "enforceForRenamedProperties": false }],
|
||||
- "prefer-object-spread": 0, // until node 8 is required
|
||||
- "prefer-rest-params": 0, // until node 6 is required
|
||||
- "prefer-spread": 0, // until node 6 is required
|
||||
- "function-call-argument-newline": 1, // TODO: enable
|
||||
- "function-paren-newline": 0,
|
||||
- "no-plusplus": [2, {"allowForLoopAfterthoughts": true}],
|
||||
- "no-param-reassign": 1,
|
||||
- "no-restricted-syntax": [2, {
|
||||
- "selector": "ObjectPattern",
|
||||
- "message": "Object destructuring is not compatible with Node v4"
|
||||
- }],
|
||||
- "strict": [2, "safe"],
|
||||
- "valid-jsdoc": [2, {
|
||||
- "requireReturn": false,
|
||||
- "requireParamDescription": false,
|
||||
- "requireReturnDescription": false,
|
||||
- }],
|
||||
-
|
||||
- "eslint-plugin/consistent-output": 0,
|
||||
- "eslint-plugin/require-meta-docs-description": [2, { "pattern": "^(Enforce|Require|Disallow)" }],
|
||||
- "eslint-plugin/require-meta-schema": 0,
|
||||
- "eslint-plugin/require-meta-type": 0
|
||||
- },
|
||||
- "overrides": [
|
||||
- {
|
||||
- "files": "tests/**",
|
||||
- "rules": {
|
||||
- "no-template-curly-in-string": 1,
|
||||
- },
|
||||
- },
|
||||
- {
|
||||
- "files": "markdown.config.js",
|
||||
- "rules": {
|
||||
- "no-console": 0,
|
||||
- },
|
||||
- },
|
||||
- {
|
||||
- "files": ".github/workflows/*.js",
|
||||
- "parserOptions": {
|
||||
- "ecmaVersion": 2019,
|
||||
- },
|
||||
- "rules": {
|
||||
- "camelcase": 0,
|
||||
- "no-console": 0,
|
||||
- "no-restricted-syntax": 0,
|
||||
- },
|
||||
- },
|
||||
- ],
|
||||
-}
|
||||
diff --git a/index.js b/index.js
|
||||
index 4140c6c8..03e623af 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,31 +1,25 @@
|
||||
-'use strict';
|
||||
-
|
||||
-const configAll = require('./configs/all');
|
||||
-const configRecommended = require('./configs/recommended');
|
||||
-const configRuntime = require('./configs/jsx-runtime');
|
||||
-
|
||||
-const allRules = require('./lib/rules');
|
||||
+import configAll from './configs/all';
|
||||
+import configRecommended from './configs/recommended';
|
||||
+import configRuntime from './configs/jsx-runtime';
|
||||
+import { name } from './package.json';
|
||||
+export { default as rules } from './lib/rules';
|
||||
|
||||
// for legacy config system
|
||||
-const plugins = [
|
||||
- 'react',
|
||||
-];
|
||||
+const plugins = [name];
|
||||
+
|
||||
+export const deprecatedRules = configAll.plugins.react.deprecatedRules;
|
||||
|
||||
-module.exports = {
|
||||
- deprecatedRules: configAll.plugins.react.deprecatedRules,
|
||||
- rules: allRules,
|
||||
- configs: {
|
||||
- recommended: Object.assign({}, configRecommended, {
|
||||
- parserOptions: configRecommended.languageOptions.parserOptions,
|
||||
- plugins,
|
||||
- }),
|
||||
- all: Object.assign({}, configAll, {
|
||||
- parserOptions: configAll.languageOptions.parserOptions,
|
||||
- plugins,
|
||||
- }),
|
||||
- 'jsx-runtime': Object.assign({}, configRuntime, {
|
||||
- parserOptions: configRuntime.languageOptions.parserOptions,
|
||||
- plugins,
|
||||
- }),
|
||||
- },
|
||||
+export const configs = {
|
||||
+ recommended: Object.assign({}, configRecommended, {
|
||||
+ parserOptions: configRecommended.languageOptions.parserOptions,
|
||||
+ plugins,
|
||||
+ }),
|
||||
+ all: Object.assign({}, configAll, {
|
||||
+ parserOptions: configAll.languageOptions.parserOptions,
|
||||
+ plugins,
|
||||
+ }),
|
||||
+ 'jsx-runtime': Object.assign({}, configRuntime, {
|
||||
+ parserOptions: configRuntime.languageOptions.parserOptions,
|
||||
+ plugins,
|
||||
+ }),
|
||||
};
|
||||
diff --git a/lib/rules/button-has-type.js b/lib/rules/button-has-type.js
|
||||
index 204a33c4..01d992c2 100644
|
||||
--- a/lib/rules/button-has-type.js
|
||||
+++ b/lib/rules/button-has-type.js
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const getProp = require('jsx-ast-utils/getProp');
|
||||
-const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
|
||||
+const { getProp, getLiteralPropValue } = require('jsx-ast-utils');
|
||||
const docsUrl = require('../util/docsUrl');
|
||||
const isCreateElement = require('../util/isCreateElement');
|
||||
const report = require('../util/report');
|
||||
diff --git a/lib/rules/jsx-fragments.js b/lib/rules/jsx-fragments.js
|
||||
index 38b4dd8b..d0575572 100644
|
||||
--- a/lib/rules/jsx-fragments.js
|
||||
+++ b/lib/rules/jsx-fragments.js
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const elementType = require('jsx-ast-utils/elementType');
|
||||
+import { elementType } from 'jsx-ast-utils';
|
||||
const pragmaUtil = require('../util/pragma');
|
||||
const variableUtil = require('../util/variable');
|
||||
const testReactVersion = require('../util/version').testReactVersion;
|
||||
diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js
|
||||
index 7ea874d0..48df0dba 100644
|
||||
--- a/lib/rules/jsx-key.js
|
||||
+++ b/lib/rules/jsx-key.js
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const hasProp = require('jsx-ast-utils/hasProp');
|
||||
-const propName = require('jsx-ast-utils/propName');
|
||||
+import { hasProp, propName } from 'jsx-ast-utils';
|
||||
const values = require('object.values');
|
||||
const docsUrl = require('../util/docsUrl');
|
||||
const pragmaUtil = require('../util/pragma');
|
||||
diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js
|
||||
index 17e56e2e..cb6dec1a 100644
|
||||
--- a/lib/rules/jsx-no-bind.js
|
||||
+++ b/lib/rules/jsx-no-bind.js
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const propName = require('jsx-ast-utils/propName');
|
||||
+import { propName } from 'jsx-ast-utils';
|
||||
const docsUrl = require('../util/docsUrl');
|
||||
const jsxUtil = require('../util/jsx');
|
||||
const report = require('../util/report');
|
||||
diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js
|
||||
index efeef403..33df4653 100644
|
||||
--- a/lib/rules/jsx-pascal-case.js
|
||||
+++ b/lib/rules/jsx-pascal-case.js
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const elementType = require('jsx-ast-utils/elementType');
|
||||
+import { elementType } from 'jsx-ast-utils';
|
||||
const minimatch = require('minimatch');
|
||||
const docsUrl = require('../util/docsUrl');
|
||||
const jsxUtil = require('../util/jsx');
|
||||
diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js
|
||||
index 3ca1724e..faf58f91 100644
|
||||
--- a/lib/rules/jsx-sort-props.js
|
||||
+++ b/lib/rules/jsx-sort-props.js
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const propName = require('jsx-ast-utils/propName');
|
||||
+import { propName } from 'jsx-ast-utils';
|
||||
const includes = require('array-includes');
|
||||
const toSorted = require('array.prototype.tosorted');
|
||||
|
||||
diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js
|
||||
index d7559f5e..fbfad23a 100644
|
||||
--- a/lib/rules/no-namespace.js
|
||||
+++ b/lib/rules/no-namespace.js
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const elementType = require('jsx-ast-utils/elementType');
|
||||
+import { elementType } from 'jsx-ast-utils';
|
||||
const docsUrl = require('../util/docsUrl');
|
||||
const isCreateElement = require('../util/isCreateElement');
|
||||
const report = require('../util/report');
|
||||
diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js
|
||||
index 9491f9c6..44396948 100644
|
||||
--- a/lib/rules/no-unknown-property.js
|
||||
+++ b/lib/rules/no-unknown-property.js
|
||||
@@ -543,7 +543,7 @@ module.exports = {
|
||||
|
||||
create(context) {
|
||||
function getIgnoreConfig() {
|
||||
- return (context.options[0] && context.options[0].ignore) || DEFAULTS.ignore;
|
||||
+ return context.options[0]?.ignore || DEFAULTS.ignore;
|
||||
}
|
||||
|
||||
function getRequireDataLowercase() {
|
||||
@@ -556,7 +556,7 @@ module.exports = {
|
||||
JSXAttribute(node) {
|
||||
const ignoreNames = getIgnoreConfig();
|
||||
const actualName = context.getSourceCode().getText(node.name);
|
||||
- if (ignoreNames.indexOf(actualName) >= 0) {
|
||||
+ if (ignoreNames.includes(actualName)) {
|
||||
return;
|
||||
}
|
||||
const name = normalizeAttributeCase(actualName);
|
||||
diff --git a/lib/util/annotations.js b/lib/util/annotations.js
|
||||
index 60aaef8c..ad8dc0bf 100644
|
||||
--- a/lib/util/annotations.js
|
||||
+++ b/lib/util/annotations.js
|
||||
@@ -27,6 +27,6 @@ function isAnnotatedFunctionPropsDeclaration(node, context) {
|
||||
return (isAnnotated && (isDestructuredProps || isProps));
|
||||
}
|
||||
|
||||
-module.exports = {
|
||||
+export {
|
||||
isAnnotatedFunctionPropsDeclaration,
|
||||
};
|
||||
diff --git a/lib/util/ast.js b/lib/util/ast.js
|
||||
index fd6019a3..3cbc293e 100644
|
||||
--- a/lib/util/ast.js
|
||||
+++ b/lib/util/ast.js
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const estraverse = require('estraverse');
|
||||
+import estraverse from 'estraverse';
|
||||
// const pragmaUtil = require('./pragma');
|
||||
|
||||
/**
|
||||
@@ -428,7 +428,7 @@ function isTSTypeParameterInstantiation(node) {
|
||||
return nodeType === 'TSTypeParameterInstantiation';
|
||||
}
|
||||
|
||||
-module.exports = {
|
||||
+export {
|
||||
traverse,
|
||||
findReturnStatement,
|
||||
getFirstNodeInLine,
|
||||
diff --git a/lib/util/jsx.js b/lib/util/jsx.js
|
||||
index 55073bfe..efc07af1 100644
|
||||
--- a/lib/util/jsx.js
|
||||
+++ b/lib/util/jsx.js
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
-const elementType = require('jsx-ast-utils/elementType');
|
||||
+import { elementType } from 'jsx-ast-utils';
|
||||
|
||||
const astUtil = require('./ast');
|
||||
const isCreateElement = require('./isCreateElement');
|
||||
diff --git a/tsconfig.json b/tsconfig.json
|
||||
deleted file mode 100644
|
||||
index 39187b7f..00000000
|
||||
--- a/tsconfig.json
|
||||
+++ /dev/null
|
||||
@@ -1,23 +0,0 @@
|
||||
-{
|
||||
- "compilerOptions": {
|
||||
- "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
- "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
- // "lib": ["es2015"], /* Specify library files to be included in the compilation. */
|
||||
- "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
- "checkJs": true, /* Report errors in .js files. */
|
||||
- "noEmit": true, /* Do not emit outputs. */
|
||||
- "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
-
|
||||
- /* Strict Type-Checking Options */
|
||||
- // "strict": true, /* Enable all strict type-checking options. */
|
||||
- "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
- // "strictNullChecks": true, /* Enable strict null checks. */
|
||||
- // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
- "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
- "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
- "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
- "alwaysStrict": false, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
- "resolveJsonModule": true
|
||||
- },
|
||||
- "include": ["lib"],
|
||||
-}
|
@ -1,25 +0,0 @@
|
||||
diff --git a/dist/parseSettings/createParseSettings.js b/dist/parseSettings/createParseSettings.js
|
||||
index 4c8b40ae895d45bd7dfcf64c8e49e29ce48dd663..0a62880ff50b7341fa909155293cbdb77fa99c97 100644
|
||||
--- a/dist/parseSettings/createParseSettings.js
|
||||
+++ b/dist/parseSettings/createParseSettings.js
|
||||
@@ -1,4 +1,5 @@
|
||||
"use strict";
|
||||
+var fs = require("node:fs");
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
@@ -89,10 +90,12 @@ function createParseSettings(code, tsestreeOptions = {}) {
|
||||
tsestreeOptions.extraFileExtensions.every(ext => typeof ext === 'string')
|
||||
? tsestreeOptions.extraFileExtensions
|
||||
: [],
|
||||
- filePath: (0, shared_1.ensureAbsolutePath)(typeof tsestreeOptions.filePath === 'string' &&
|
||||
+ filePath: fs.realpathSync(
|
||||
+ (0, shared_1.ensureAbsolutePath)(typeof tsestreeOptions.filePath === 'string' &&
|
||||
tsestreeOptions.filePath !== '<input>'
|
||||
? tsestreeOptions.filePath
|
||||
- : getFileName(tsestreeOptions.jsx), tsconfigRootDir),
|
||||
+ : getFileName(tsestreeOptions.jsx), tsconfigRootDir)
|
||||
+ ),
|
||||
jsDocParsingMode,
|
||||
jsx: tsestreeOptions.jsx === true,
|
||||
loc: tsestreeOptions.loc === true,
|
@ -1,13 +0,0 @@
|
||||
diff --git a/dist/eslint-utils/getParserServices.js b/dist/eslint-utils/getParserServices.js
|
||||
index 3b3020f601ba9cc92fdaf643ee3a8bdc44d1291a..730fccd5838b388b496a8861705e0d9883fc2fcb 100644
|
||||
--- a/dist/eslint-utils/getParserServices.js
|
||||
+++ b/dist/eslint-utils/getParserServices.js
|
||||
@@ -24,7 +24,7 @@ function getParserServices(context, allowWithoutFullTypeInformation = false) {
|
||||
// this forces the user to supply parserOptions.project
|
||||
if (context.sourceCode.parserServices.program == null &&
|
||||
!allowWithoutFullTypeInformation) {
|
||||
- throwError(parser);
|
||||
+ // throwError(parser);
|
||||
}
|
||||
return context.sourceCode.parserServices;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
diff --git a/dist/helpers/check-diagnostics-errors.js b/dist/helpers/check-diagnostics-errors.js
|
||||
index 3ff0a59509fe381189764a253e6b668241e3b921..9b1eadf36278cea8dadc6cb5cfed4c4a89e91609 100644
|
||||
--- a/dist/helpers/check-diagnostics-errors.js
|
||||
+++ b/dist/helpers/check-diagnostics-errors.js
|
||||
@@ -20,6 +20,5 @@ function checkDiagnosticsErrors(diagnostics, failMessage) {
|
||||
return;
|
||||
}
|
||||
(0, logger_1.errorLog)(ts.formatDiagnostics(diagnostics, formatDiagnosticsHost).trim());
|
||||
- throw new Error(failMessage);
|
||||
}
|
||||
exports.checkDiagnosticsErrors = checkDiagnosticsErrors;
|
8533
pnpm-lock.yaml
generated
8533
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,24 +1,19 @@
|
||||
#!/usr/bin/env tsx
|
||||
#!/usr/bin/env bun
|
||||
import './build-local-rules';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { resolve, relative } from 'node:path';
|
||||
import { isBuiltin } from 'node:module';
|
||||
import { relative, resolve } from 'node:path';
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import type { Plugin } from 'esbuild';
|
||||
import { memoize } from 'lodash-es';
|
||||
import c from 'picocolors';
|
||||
import { minify_sync } from 'terser';
|
||||
|
||||
import { memoize } from 'lodash';
|
||||
import { gray, green } from 'picocolors';
|
||||
import { dependencies } from '../dist/package.json';
|
||||
|
||||
import { dts } from './dts';
|
||||
import { babelPlugin } from './modifier';
|
||||
|
||||
const ENV = (process.env.NODE_ENV ??= 'production');
|
||||
const PROD = ENV === 'production';
|
||||
|
||||
const { gray, green } = c;
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
filter(
|
||||
@ -70,12 +65,14 @@ if (process.env.DEBUG) {
|
||||
});
|
||||
}
|
||||
|
||||
async function bundle(
|
||||
function bundle(
|
||||
entry: string,
|
||||
outfile: string,
|
||||
options?: esbuild.BuildOptions & { treeShaking?: boolean },
|
||||
outfile = entry
|
||||
.replace('./packages/', './dist/')
|
||||
.replace('src/', '')
|
||||
.replace('.ts', '.js'),
|
||||
) {
|
||||
const output = await esbuild.build({
|
||||
return esbuild.build({
|
||||
entryPoints: [entry],
|
||||
outfile,
|
||||
bundle: true,
|
||||
@ -86,36 +83,11 @@ async function bundle(
|
||||
plugins,
|
||||
define: {},
|
||||
alias: {},
|
||||
format: 'esm',
|
||||
external: ['find-cache-dir'],
|
||||
banner: {
|
||||
js: '/* eslint-disable */',
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
if (options?.treeShaking) {
|
||||
const [text, setText] = await useText(outfile);
|
||||
const minified = minify_sync(text, {
|
||||
module: true,
|
||||
compress: {
|
||||
conditionals: true,
|
||||
dead_code: true,
|
||||
defaults: false,
|
||||
evaluate: true,
|
||||
passes: 3,
|
||||
pure_new: true,
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
},
|
||||
mangle: false,
|
||||
format: {
|
||||
comments: true,
|
||||
},
|
||||
});
|
||||
await setText(minified.code!);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
async function editPackageJson() {
|
||||
@ -138,58 +110,44 @@ async function editPackageJson() {
|
||||
}
|
||||
|
||||
async function useText(path: string) {
|
||||
const state = await fs.readFile(path, 'utf8');
|
||||
const state = await fs.readFile(path, 'utf-8');
|
||||
const setState = (text: string) => fs.writeFile(path, text);
|
||||
return [state, setState] as const;
|
||||
}
|
||||
|
||||
function bundleType(source: string, output: string) {
|
||||
try {
|
||||
return dts({
|
||||
source,
|
||||
dist: output,
|
||||
project: './tsconfig.build.json',
|
||||
});
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
return dts({
|
||||
source,
|
||||
dist: output,
|
||||
project: './tsconfig.build.json',
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Building type definitions…');
|
||||
try {
|
||||
await fs.rm('dist/config', { recursive: true });
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
console.log('Building type definitions...');
|
||||
bundleType('./src/index.ts', './dist/index.d.ts');
|
||||
bundleType('./src/prettier.ts', './dist/prettier.d.ts');
|
||||
bundleType('./src/types.ts', './dist/types.d.ts');
|
||||
|
||||
const unminify = { minify: false };
|
||||
|
||||
console.log('Building packages…');
|
||||
console.log('Building packages...');
|
||||
await Promise.all([
|
||||
bundle('./src/index.ts', undefined!, {
|
||||
format: 'esm',
|
||||
splitting: true,
|
||||
outdir: './dist/config',
|
||||
...unminify,
|
||||
}),
|
||||
bundle('./src/types.ts', './dist/types.js', unminify),
|
||||
bundle('./src/prettier.ts', './dist/prettier.js', unminify),
|
||||
bundle('./src/install.ts', './dist/install.js', {
|
||||
treeShaking: true,
|
||||
minify: false,
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node\n/* eslint-disable */',
|
||||
},
|
||||
}),
|
||||
bundle('./packages/eslint-plugin-react/index.js'),
|
||||
bundle('./packages/eslint-plugin-import/src/index.js'),
|
||||
bundle('./packages/eslint-plugin-jsx-a11y/src/index.js'),
|
||||
bundle('./packages/eslint-plugin-react-hooks/index.ts'),
|
||||
bundle('./packages/eslint-plugin-n/lib/index.js', './dist/eslint-plugin-n/index.js'),
|
||||
bundle('./packages/eslint-import-resolver-typescript/src/index.ts'),
|
||||
bundle('./src/rules/index.ts', './dist/eslint-plugin-rules/index.js'),
|
||||
bundle('./src/local/index.ts', './dist/eslint-plugin-local/index.js'),
|
||||
bundle('./src/index.ts', './dist/index.js'),
|
||||
bundle('./src/types.ts', './dist/types.js'),
|
||||
bundle('./src/prettier.ts', './dist/prettier.js'),
|
||||
editPackageJson(),
|
||||
]);
|
||||
|
||||
// bundleType('./src/index.ts', './dist/config/index.d.ts');
|
||||
await fs.copyFile('./src/config.d.ts', './dist/config/index.d.ts');
|
||||
console.log('Removing redirect...');
|
||||
const [distIndex, setDistIndex] = await useText('./dist/index.js');
|
||||
await setDistIndex(distIndex.replace(/import.*redirect.*;/g, ''));
|
||||
}
|
||||
|
||||
void main();
|
24
scripts/build-local-rules.ts
Executable file
24
scripts/build-local-rules.ts
Executable file
@ -0,0 +1,24 @@
|
||||
import { promises as fs } from 'node:fs';
|
||||
import { camelCase } from 'lodash';
|
||||
|
||||
const files = (await fs.readdir('./src/rules'))
|
||||
.filter(file => file.endsWith('.ts'))
|
||||
.filter(file => file !== 'index.ts')
|
||||
.map(file => file.slice(0, -3));
|
||||
|
||||
const entryFile = /* js */ `
|
||||
import type { Rule } from 'eslint';
|
||||
import type { ESLintUtils } from '@typescript-eslint/utils';
|
||||
|
||||
${files.map(file => `import ${camelCase(file)} from './${file}';`).join('\n')}
|
||||
|
||||
export const rules: Record<
|
||||
string,
|
||||
Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>
|
||||
> = {
|
||||
${files.map(file => `'${file}': ${camelCase(file)},`).join('\n ')}
|
||||
};
|
||||
`.trim();
|
||||
|
||||
console.log('Building local rules...');
|
||||
await fs.writeFile('./src/rules/index.ts', entryFile + '\n');
|
@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bun
|
||||
import fs from 'node:fs';
|
||||
import { builtinModules } from 'node:module';
|
||||
|
||||
import glob from 'fast-glob';
|
||||
import { uniq } from 'lodash-es';
|
||||
|
||||
import { dependencies, peerDependencies } from '../dist/package.json';
|
||||
import fs from 'fs';
|
||||
import { builtinModules } from 'module';
|
||||
import { uniq } from 'lodash';
|
||||
import { dependencies, peerDependencies, overrides } from '../dist/package.json';
|
||||
|
||||
function checkImports() {
|
||||
const deps = Object.keys({ ...dependencies, ...peerDependencies }).concat('eslint');
|
||||
const deps = Object.keys({ ...dependencies, ...peerDependencies, ...overrides }).concat(
|
||||
'eslint',
|
||||
);
|
||||
const builtIn = new Set(builtinModules.flatMap(module => [module, `node:${module}`]));
|
||||
|
||||
function findRequires(text: string) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
type EntryPointConfig,
|
||||
generateDtsBundle,
|
||||
} from 'dts-bundle-generator/dist/bundle-generator';
|
||||
import * as ts from 'typescript';
|
||||
import {
|
||||
generateDtsBundle,
|
||||
type EntryPointConfig,
|
||||
} from 'dts-bundle-generator/dist/bundle-generator';
|
||||
|
||||
export function dts({
|
||||
source,
|
||||
@ -17,14 +17,22 @@ export function dts({
|
||||
const entry: EntryPointConfig = {
|
||||
filePath: source,
|
||||
failOnClass: false,
|
||||
libraries: {
|
||||
importedLibraries: ['eslint-define-config'],
|
||||
},
|
||||
output: {
|
||||
inlineDeclareExternals: false,
|
||||
inlineDeclareGlobals: false,
|
||||
sortNodes: false,
|
||||
noBanner: false,
|
||||
respectPreserveConstEnum: false,
|
||||
exportReferencedTypes: true,
|
||||
},
|
||||
};
|
||||
|
||||
const generatedDts = generateDtsBundle([entry], {
|
||||
preferredConfigPath: project,
|
||||
followSymlinks: true,
|
||||
followSymlinks: false,
|
||||
});
|
||||
|
||||
ts.sys.writeFile(dist, generatedDts[0]);
|
||||
|
@ -1,24 +1,23 @@
|
||||
#!/usr/bin/env tsx
|
||||
import assert from 'node:assert';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { extname, resolve } from 'node:path';
|
||||
|
||||
import { resolve, extname } from 'node:path';
|
||||
import type { Loader, Plugin } from 'esbuild';
|
||||
import * as babel from '@babel/core';
|
||||
import type { types as t, types } from '@babel/core';
|
||||
import babelMacros, { type MacroHandler } from 'babel-plugin-macros';
|
||||
import type { Loader, Plugin } from 'esbuild';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
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 ??= 'production');
|
||||
|
||||
class HandlerMap {
|
||||
map = new Map<string, MacroHandler>();
|
||||
|
||||
set(names: string | string[], handler: MacroHandler) {
|
||||
names = Array.isArray(names) ? names : [names];
|
||||
const macro = babelMacros.createMacro(handler);
|
||||
const macro = createMacro(handler);
|
||||
for (const name of names) {
|
||||
this.map.set(name, macro);
|
||||
}
|
||||
@ -26,7 +25,7 @@ class HandlerMap {
|
||||
}
|
||||
|
||||
get keys() {
|
||||
return [...this.map.keys()];
|
||||
return Array.from(this.map.keys());
|
||||
}
|
||||
|
||||
resolvePath = (module: string) => module;
|
||||
@ -99,14 +98,14 @@ const map = new HandlerMap()
|
||||
'object.groupby',
|
||||
replace(t =>
|
||||
t.memberExpression(
|
||||
t.callExpression(t.identifier('require'), [t.stringLiteral('lodash-es')]),
|
||||
t.callExpression(t.identifier('require'), [t.stringLiteral('lodash')]),
|
||||
t.identifier('groupBy'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// es-iterator-helpers/Iterator.prototype.*
|
||||
const polyfillPath = resolve(import.meta.dirname, '../src/polyfill.ts');
|
||||
const polyfillPath = resolve(__dirname, '../src/polyfill.ts');
|
||||
const requirePolyfill = (t: typeof types, name: string) =>
|
||||
t.memberExpression(
|
||||
t.callExpression(t.identifier('require'), [t.stringLiteral(polyfillPath)]),
|
||||
@ -130,15 +129,15 @@ map.set(
|
||||
|
||||
function replace(getReplacement: (types: typeof t) => t.Expression): MacroHandler {
|
||||
return ({ references, babel: { types: t } }) => {
|
||||
for (const referencePath of references.default) {
|
||||
references.default.forEach(referencePath => {
|
||||
referencePath.replaceWith(getReplacement(t));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function proto(getProperty: (types: typeof t) => t.Expression): MacroHandler {
|
||||
return ({ references, babel: { types: t } }) => {
|
||||
for (const referencePath of references.default) {
|
||||
references.default.forEach(referencePath => {
|
||||
const { parent, parentPath } = referencePath;
|
||||
assert(t.isCallExpression(parent));
|
||||
const [callee, ...rest] = parent.arguments;
|
||||
@ -148,7 +147,7 @@ function proto(getProperty: (types: typeof t) => t.Expression): MacroHandler {
|
||||
rest,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@ -163,14 +162,21 @@ export const babelPlugin: Plugin = {
|
||||
return null;
|
||||
}
|
||||
|
||||
const source = readFileSync(path, 'utf8')
|
||||
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(PropTypes)),
|
||||
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;
|
||||
|
||||
|
@ -20,6 +20,9 @@ pull() {
|
||||
echo
|
||||
}
|
||||
|
||||
pull import-js eslint-import-resolver-typescript
|
||||
pull import-js eslint-plugin-import
|
||||
pull jsx-eslint eslint-plugin-jsx-a11y
|
||||
pull eslint-community eslint-plugin-n
|
||||
pull jsx-eslint eslint-plugin-react
|
||||
pull jsx-eslint jsx-ast-utils
|
||||
|
@ -3,6 +3,9 @@ sync() (
|
||||
cd "packages/$1" && git diff HEAD > "../../patch/$1.patch"
|
||||
)
|
||||
|
||||
sync eslint-import-resolver-typescript
|
||||
sync eslint-plugin-import
|
||||
sync eslint-plugin-jsx-a11y
|
||||
sync eslint-plugin-n
|
||||
sync eslint-plugin-react
|
||||
sync jsx-ast-utils
|
||||
|
@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
|
||||
const up = JSON.parse(
|
||||
fs.readFileSync('package.json', 'utf8'),
|
||||
) as typeof import('../package.json');
|
||||
|
||||
const down = JSON.parse(
|
||||
fs.readFileSync('dist/package.json', 'utf8'),
|
||||
) as typeof import('../dist/package.json');
|
||||
|
||||
down.dependencies = up.dependencies;
|
||||
|
||||
fs.writeFileSync('dist/package.json', JSON.stringify(down, null, 2));
|
@ -1,32 +1,38 @@
|
||||
{
|
||||
"eslint-plugin-import": {
|
||||
"hash": "6554bd5c30976290024cecc44ef1e96746cf3cf7",
|
||||
"date": "2024-05-23T12:47:41-07:00",
|
||||
"hash": "f77ceb679d59ced5d9a633123385470a9eea10d9",
|
||||
"date": "2024-04-07T12:55:28+12:00",
|
||||
"committer": "Jordan Harband",
|
||||
"subject": "[meta] add `repository.directory` field"
|
||||
"subject": "[actions] cancel in-progress runs on PR updates"
|
||||
},
|
||||
"eslint-import-resolver-typescript": {
|
||||
"hash": "7a02ac08b5aaac8c217f0e87142f97eafcc38fbc",
|
||||
"date": "2024-04-01T01:06:20+00:00",
|
||||
"committer": "GitHub",
|
||||
"subject": "chore(deps): update dependency npm-run-all2 to ^5.0.2 (#277)"
|
||||
},
|
||||
"eslint-plugin-jsx-a11y": {
|
||||
"hash": "a7d1a12a6198d546c4a06477b385b4fde03b762e",
|
||||
"date": "2025-06-05T12:28:53-07:00",
|
||||
"hash": "0d5321a5457c5f0da0ca216053cc5b4f571b53ae",
|
||||
"date": "2024-01-27T22:18:19-08:00",
|
||||
"committer": "Jordan Harband",
|
||||
"subject": "[Tests] fix linting errors introduced in 2d9ad55"
|
||||
"subject": "[Deps] update `@babel/runtime`, `safe-regex-test`"
|
||||
},
|
||||
"eslint-plugin-n": {
|
||||
"hash": "42464abe64c5cefb709e8e0a9072b6bb1cd7fcdc",
|
||||
"date": "2025-06-13T01:37:54+08:00",
|
||||
"hash": "eb11b5b35a6a797dc7fba6df53b1c4dada3a2a55",
|
||||
"date": "2024-04-17T17:40:32+08:00",
|
||||
"committer": "GitHub",
|
||||
"subject": "chore(master): release 17.20.0 (#448)"
|
||||
"subject": "chore: upgrade globals v15 (#241)"
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"hash": "983b88dd3cb5e07919517d3fde4085f60883ded7",
|
||||
"date": "2024-07-24T15:26:33-07:00",
|
||||
"hash": "4467db503e38b9356517cf6926d11be544ccf4b1",
|
||||
"date": "2024-03-16T12:54:58+09:00",
|
||||
"committer": "Jordan Harband",
|
||||
"subject": "[Tests] `no-array-index-key`: actually run valid tests"
|
||||
"subject": "[Fix] `boolean-prop-naming`: avoid a crash with a non-TSTypeReference type"
|
||||
},
|
||||
"jsx-ast-utils": {
|
||||
"hash": "a8ca8f70331b02db537b0b5cf72ea10e3d6c9377",
|
||||
"date": "2025-02-20T08:51:06-08:00",
|
||||
"hash": "5943318eaf23764eec3ff397ebb969613d728a95",
|
||||
"date": "2023-07-28T18:34:04-07:00",
|
||||
"committer": "Jordan Harband",
|
||||
"subject": "[Dev Deps] pin `psl` due to breaking change in a minor version"
|
||||
"subject": "v3.3.5"
|
||||
}
|
||||
}
|
||||
|
64
src/config.d.ts
vendored
64
src/config.d.ts
vendored
@ -1,64 +0,0 @@
|
||||
import type { FlatESLintConfig } from '@aet/eslint-define-config';
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
type MiddlewareResult = Linter.Config | Linter.Config[];
|
||||
|
||||
export type Middleware =
|
||||
| (() => Promise<MiddlewareResult>)
|
||||
| (() => Promise<{ default: MiddlewareResult }>);
|
||||
|
||||
export type Environment =
|
||||
| 'jsdoc'
|
||||
| 'lingui'
|
||||
| 'react'
|
||||
| 'reactQuery'
|
||||
| 'reactRefresh'
|
||||
| 'storybook'
|
||||
| 'tailwind'
|
||||
| 'testingLibrary'
|
||||
| 'vitest';
|
||||
|
||||
export interface NormalizedExtendConfigOptions {
|
||||
auto?: boolean;
|
||||
middlewares?: Middleware[];
|
||||
configs: FlatESLintConfig[];
|
||||
/**
|
||||
* Use `.gitignore` file to exclude files from ESLint.
|
||||
*/
|
||||
gitignore?: boolean;
|
||||
env?: {
|
||||
[key in Environment]?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type ExtendConfigOptions =
|
||||
| FlatESLintConfig
|
||||
| FlatESLintConfig[]
|
||||
| NormalizedExtendConfigOptions;
|
||||
|
||||
/**
|
||||
* Returns a ESLint config object.
|
||||
*
|
||||
* By default, it includes `["@typescript-eslint", "import-x", "prettier", "unicorn"]` configs.
|
||||
* Additional bundled plugins include:
|
||||
*
|
||||
* 1. [`react`](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules)
|
||||
* (automatically enables
|
||||
* [`react-hooks`](https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks))
|
||||
* 2. [`react-refresh`](https://github.com/ArnaudBarre/eslint-plugin-react-refresh)
|
||||
* 3. [`jsx-a11y`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules)
|
||||
* 4. [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn#rules)
|
||||
* 5. [`n`](https://github.com/eslint-community/eslint-plugin-n#-rules) (Node.js specific,
|
||||
* requires `minimatch`)
|
||||
* 6. [`jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc#rules)
|
||||
*
|
||||
* Non bundled:
|
||||
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
||||
*
|
||||
* @returns ESLint configuration object.
|
||||
*/
|
||||
export function extendConfig(options?: ExtendConfigOptions): Promise<FlatESLintConfig[]>;
|
||||
|
||||
export const error = 'error';
|
||||
export const warn = 'warn';
|
||||
export const off = 'off';
|
@ -1,44 +0,0 @@
|
||||
import type { ESLint } from 'eslint';
|
||||
|
||||
import noEmptyObjectLiteral from './no-empty-object-literal';
|
||||
import noImportDot from './no-import-dot';
|
||||
import noUselessImportAlias from './no-useless-import-alias';
|
||||
import requireImportAttribute from './require-import-attribute';
|
||||
import restrictTemplateExpressions from './restrict-template-expressions';
|
||||
|
||||
type RuleLevel = 'error' | 'warn' | 'off' | 0 | 1 | 2;
|
||||
type RuleEntry<Options> = RuleLevel | [RuleLevel, Partial<Options>];
|
||||
|
||||
export interface LocalRuleOptions {
|
||||
/** Bans import from the specifier '.' and '..' and replaces it with '.+/index' */
|
||||
'aet/no-import-dot': RuleEntry<unknown>;
|
||||
/**
|
||||
* Enforce template literal expressions to be of `string` type
|
||||
* @see [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions)
|
||||
*/
|
||||
'typed-aet/restrict-template-expressions': RuleEntry<{ allow: string[] }>;
|
||||
/** Ban assignment of empty object literals `{}` and replace them with `Object.create(null)` */
|
||||
'aet/no-empty-object-literal': RuleEntry<unknown>;
|
||||
/** Ban useless import alias */
|
||||
'aet/no-useless-import-alias': RuleEntry<unknown>;
|
||||
/** Require the use of `{ type: "json" }` for JSON imports. */
|
||||
'aet/require-import-attribute': RuleEntry<unknown>;
|
||||
}
|
||||
|
||||
export const plugin: ESLint.Plugin = {
|
||||
name: 'aet',
|
||||
rules: {
|
||||
'no-empty-object-literal': noEmptyObjectLiteral,
|
||||
'no-import-dot': noImportDot,
|
||||
'no-useless-import-alias': noUselessImportAlias,
|
||||
'require-import-attribute': requireImportAttribute,
|
||||
},
|
||||
};
|
||||
|
||||
export const typedPlugin: ESLint.Plugin = {
|
||||
name: 'typed-aet',
|
||||
rules: {
|
||||
// @ts-expect-error type mismatch
|
||||
'restrict-template-expressions': restrictTemplateExpressions,
|
||||
},
|
||||
};
|
@ -1,45 +0,0 @@
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Position } from 'estree';
|
||||
|
||||
const rule: Rule.RuleModule = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
"Ban useless import aliasing like `import { abc as abc } from 'module'`",
|
||||
category: 'Best Practices',
|
||||
recommended: true,
|
||||
},
|
||||
fixable: 'code',
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
if (node.specifiers.length === 0) return;
|
||||
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.type !== 'ImportSpecifier') continue;
|
||||
|
||||
const { imported, local } = specifier;
|
||||
if (
|
||||
imported.name === local.name &&
|
||||
!arePositionsEqual(imported.loc!.start, local.loc!.start)
|
||||
) {
|
||||
context.report({
|
||||
node: specifier,
|
||||
message: `Useless aliasing of '${imported.name}'?`,
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([imported.range![1], local.range![1]]);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const arePositionsEqual = (a: Position, b: Position) =>
|
||||
a.line === b.line && a.column === b.column;
|
||||
|
||||
export default rule;
|
@ -1,61 +0,0 @@
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Identifier, SimpleLiteral } from 'estree';
|
||||
|
||||
interface ImportAttribute {
|
||||
key: Identifier | SimpleLiteral;
|
||||
value: SimpleLiteral;
|
||||
}
|
||||
|
||||
const requireImportAttribute: Rule.RuleModule = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Requires JSON imports to have a "with { type: \'json\' }" import attribute.',
|
||||
category: 'Possible Errors',
|
||||
recommended: false,
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [],
|
||||
messages: {
|
||||
missingJsonTypeAttribute:
|
||||
'JSON imports must have a "with { type: \'json\' }" import attribute.',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
// Only check modules that end with ".json"
|
||||
if (
|
||||
typeof node.source.value === 'string' &&
|
||||
node.source.value.endsWith('.json')
|
||||
) {
|
||||
// Cast the node to our extended interface to access the assertions.
|
||||
const importNode = node;
|
||||
const assertions = (
|
||||
importNode as {
|
||||
assertions?: ImportAttribute[];
|
||||
}
|
||||
).assertions;
|
||||
|
||||
const hasJsonType = assertions?.some(attr => {
|
||||
const keyName =
|
||||
(attr.key as Identifier).name ?? (attr.key as SimpleLiteral).value;
|
||||
return keyName === 'type' && attr.value.value === 'json';
|
||||
});
|
||||
|
||||
if (!hasJsonType) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'missingJsonTypeAttribute',
|
||||
fix: fixer =>
|
||||
fixer.insertTextAfterRange(node.source.range!, ' with { type: "json" }'),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default requireImportAttribute;
|
@ -1,118 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import type { Environment, NormalizedExtendConfigOptions } from './config';
|
||||
import type { Middleware } from './middleware';
|
||||
import { reactQuery, storybook, vitest } from './presets/misc';
|
||||
import { react, reactRefresh } from './presets/react';
|
||||
|
||||
type Pkg = typeof import('../package.json');
|
||||
type Dependency = keyof Pkg['dependencies'] | RemoveType<keyof Pkg['devDependencies']>;
|
||||
|
||||
type RemoveType<T extends string> = T extends `@types/${infer U}__${infer V}`
|
||||
? `@${U}/${V}`
|
||||
: T extends `@types/${infer U}`
|
||||
? U
|
||||
: T;
|
||||
|
||||
export const middlewares = {
|
||||
react,
|
||||
reactRefresh,
|
||||
tailwind: () => import('./presets/tailwind'),
|
||||
storybook,
|
||||
reactQuery,
|
||||
testingLibrary: () => import('./presets/testing-library'),
|
||||
jsdoc: () => import('./presets/jsdoc'),
|
||||
vitest,
|
||||
lingui: () => import('./presets/lingui'),
|
||||
} satisfies {
|
||||
[key in Environment]: Middleware;
|
||||
};
|
||||
|
||||
export const envs: {
|
||||
dependency: string[];
|
||||
eslintPlugin?: Dependency;
|
||||
middleware: keyof typeof middlewares;
|
||||
}[] = [
|
||||
{
|
||||
dependency: ['react'],
|
||||
middleware: 'react',
|
||||
},
|
||||
{
|
||||
dependency: ['@vitejs/plugin-react', '@vitejs/plugin-react-swc'],
|
||||
eslintPlugin: 'eslint-plugin-react-refresh',
|
||||
middleware: 'reactRefresh',
|
||||
},
|
||||
{
|
||||
dependency: ['tailwindcss'],
|
||||
eslintPlugin: 'eslint-plugin-tailwindcss',
|
||||
middleware: 'tailwind',
|
||||
},
|
||||
{
|
||||
dependency: ['storybook'],
|
||||
eslintPlugin: 'eslint-plugin-storybook',
|
||||
middleware: 'storybook',
|
||||
},
|
||||
{
|
||||
dependency: ['@lingui/core', '@lingui/react'],
|
||||
eslintPlugin: 'eslint-plugin-lingui',
|
||||
middleware: 'lingui',
|
||||
},
|
||||
{
|
||||
dependency: ['@tanstack/react-query'],
|
||||
eslintPlugin: '@tanstack/eslint-plugin-query',
|
||||
middleware: 'reactQuery',
|
||||
},
|
||||
{
|
||||
dependency: ['@testing-library/react'],
|
||||
eslintPlugin: 'eslint-plugin-testing-library',
|
||||
middleware: 'testingLibrary',
|
||||
},
|
||||
{
|
||||
dependency: ['vitest'],
|
||||
eslintPlugin: '@vitest/eslint-plugin',
|
||||
middleware: 'vitest',
|
||||
},
|
||||
];
|
||||
|
||||
export function getProjectDependencies() {
|
||||
const rootDir = process.cwd();
|
||||
|
||||
const pkgJsonPath = path.resolve(rootDir, 'package.json');
|
||||
const pkgJson = fs.existsSync(pkgJsonPath)
|
||||
? (JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')) as {
|
||||
dependencies?: Record<string, string>;
|
||||
devDependencies?: Record<string, string>;
|
||||
peerDependencies?: Record<string, string>;
|
||||
})
|
||||
: {};
|
||||
|
||||
return new Set(
|
||||
Object.keys({
|
||||
...pkgJson.dependencies,
|
||||
...pkgJson.devDependencies,
|
||||
...pkgJson.peerDependencies,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function* checkEnv(): Generator<Middleware> {
|
||||
const deps = getProjectDependencies();
|
||||
|
||||
for (const { dependency, eslintPlugin, middleware } of envs) {
|
||||
if (
|
||||
dependency.some(dep => deps.has(dep)) &&
|
||||
(!eslintPlugin || deps.has(eslintPlugin))
|
||||
) {
|
||||
yield middlewares[middleware];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* fromEnvironments(envs: NormalizedExtendConfigOptions['env']) {
|
||||
for (const [env, enabled] of Object.entries(envs ?? {}) as [Environment, boolean][]) {
|
||||
if (enabled) {
|
||||
yield middlewares[env];
|
||||
}
|
||||
}
|
||||
}
|
352
src/index.ts
352
src/index.ts
@ -1,168 +1,214 @@
|
||||
/* eslint-disable import-x/no-named-as-default-member */
|
||||
/// <reference path="./modules.d.ts" />
|
||||
import './redirect';
|
||||
import fs from 'node:fs';
|
||||
|
||||
import type { FlatESLintConfig } from '@aet/eslint-define-config';
|
||||
import * as tsParser from '@typescript-eslint/parser';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import importPlugin from 'eslint-plugin-import-x';
|
||||
import * as regexpPlugin from 'eslint-plugin-regexp';
|
||||
import { uniq } from 'lodash-es';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
import type { ExtendConfigOptions, NormalizedExtendConfigOptions } from './config';
|
||||
import { off } from './constants';
|
||||
import { checkEnv, fromEnvironments } from './environment';
|
||||
import type { Middleware } from './middleware';
|
||||
import type { Rule } from 'eslint';
|
||||
import type { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import type { ESLintConfig, Rules } from 'eslint-define-config';
|
||||
import { typescriptRules } from './presets/typescript';
|
||||
import { unicornRules } from './presets/unicorn';
|
||||
import { eslintRules } from './presets/eslint';
|
||||
import stylistic from './presets/stylistic';
|
||||
import { importRules, typescriptRules } from './presets/typescript';
|
||||
import unicorn from './presets/unicorn';
|
||||
import { reactRules } from './presets/react';
|
||||
import { importRules } from './presets/import';
|
||||
import { jsDocRules } from './presets/jsdoc';
|
||||
import { graphqlRules } from './presets/graphql';
|
||||
import { localRules } from './presets/local';
|
||||
import { error, warn, off } from './constants';
|
||||
import { tailwindRules } from './presets/tailwind';
|
||||
|
||||
export { error, warn, off } from './constants';
|
||||
export { error, warn, off };
|
||||
|
||||
function normalizeExtendConfig(
|
||||
options: ExtendConfigOptions,
|
||||
): NormalizedExtendConfigOptions {
|
||||
if (Array.isArray(options)) {
|
||||
options = { configs: options };
|
||||
} else if ('rules' in options) {
|
||||
options = { configs: [options] };
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
filter(
|
||||
predicate: BooleanConstructor,
|
||||
): Exclude<T, null | undefined | false | '' | 0>[];
|
||||
}
|
||||
|
||||
const {
|
||||
auto = true,
|
||||
middlewares = [],
|
||||
configs = [],
|
||||
gitignore = true,
|
||||
env,
|
||||
} = options as NormalizedExtendConfigOptions;
|
||||
|
||||
return {
|
||||
auto,
|
||||
middlewares,
|
||||
configs,
|
||||
gitignore,
|
||||
env,
|
||||
};
|
||||
}
|
||||
|
||||
export async function extendConfig(
|
||||
options: ExtendConfigOptions = [],
|
||||
): Promise<FlatESLintConfig[]> {
|
||||
const unique = (...arr: (false | undefined | string | string[])[]): string[] => [
|
||||
...new Set(arr.flat(1).filter(Boolean)),
|
||||
];
|
||||
|
||||
const ensureArray = <T>(value?: T | T[]): T[] =>
|
||||
value == null ? [] : Array.isArray(value) ? value : [value];
|
||||
|
||||
type RuleLevel = 'error' | 'warn' | 'off' | 0 | 1 | 2;
|
||||
type RuleEntry<Options> = RuleLevel | [RuleLevel, Partial<Options>];
|
||||
|
||||
declare module 'eslint-define-config' {
|
||||
interface NoUnknownPropertyOption {
|
||||
extends: ('next' | 'emotion')[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface LocalRuleOptions {
|
||||
/** Bans import from the specifier '.' and '..' and replaces it with '.+/index' */
|
||||
'rules/no-import-dot': RuleEntry<unknown>;
|
||||
/**
|
||||
* Enforce template literal expressions to be of `string` type
|
||||
* @see [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions)
|
||||
*/
|
||||
'rules/restrict-template-expressions': RuleEntry<{ allow: string[] }>;
|
||||
/** Ban assignment of empty object literals `{}` and replace them with `Object.create(null)` */
|
||||
'rules/no-empty-object-literal': RuleEntry<unknown>;
|
||||
}
|
||||
|
||||
export type RuleOptions = Rules & Partial<LocalRuleOptions>;
|
||||
|
||||
export interface CustomRule {
|
||||
rule: () => Promise<{
|
||||
default: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||
}>;
|
||||
options?: RuleLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* ESLint Configuration.
|
||||
* @see [ESLint Configuration](https://eslint.org/docs/latest/user-guide/configuring/)
|
||||
*/
|
||||
export type InputConfig = Omit<ESLintConfig, 'rules'> & {
|
||||
/**
|
||||
* Rules.
|
||||
* @see [Rules](https://eslint.org/docs/latest/user-guide/configuring/rules)
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
customRuleFiles?: string | string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a ESLint config object.
|
||||
*
|
||||
* By default, it includes `["@typescript-eslint", "import", "prettier"]` configs.
|
||||
* Additional bundled plugins include:
|
||||
*
|
||||
* 1. [`react`](https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules)
|
||||
* (automatically enables
|
||||
* [`react-hooks`](https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks))
|
||||
* 2. [`react-refresh`](https://github.com/ArnaudBarre/eslint-plugin-react-refresh)
|
||||
* 3. [`jsx-a11y`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules)
|
||||
* 4. [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn#rules)
|
||||
* 5. [`n`](https://github.com/eslint-community/eslint-plugin-n#-rules) (Node.js specific)
|
||||
* 6. [`jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc#rules)
|
||||
*
|
||||
* Non bundled:
|
||||
* 1. [`graphql`](https://the-guild.dev/graphql/eslint/rules)
|
||||
*/
|
||||
export function extendConfig(of: InputConfig = {}): ESLintConfig {
|
||||
const {
|
||||
auto,
|
||||
middlewares: addMiddlewares = [],
|
||||
configs,
|
||||
gitignore,
|
||||
env,
|
||||
} = normalizeExtendConfig(options);
|
||||
plugins = [],
|
||||
settings,
|
||||
rules,
|
||||
extends: _extends,
|
||||
overrides,
|
||||
customRuleFiles,
|
||||
...rest
|
||||
} = of;
|
||||
|
||||
const middlewares: Middleware[] = uniq([
|
||||
() => import('./presets/custom'),
|
||||
...(auto ? checkEnv() : []),
|
||||
...fromEnvironments(env),
|
||||
...addMiddlewares,
|
||||
]);
|
||||
const result: FlatESLintConfig[] = [
|
||||
{
|
||||
name: 'eslint-rules/eslint',
|
||||
rules: eslintRules,
|
||||
},
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
importPlugin.flatConfigs.recommended,
|
||||
importPlugin.flatConfigs.react,
|
||||
importPlugin.flatConfigs.typescript,
|
||||
...unicorn,
|
||||
stylistic,
|
||||
{
|
||||
plugins: { regexp: regexpPlugin },
|
||||
rules: {
|
||||
...regexpPlugin.configs['flat/recommended'].rules,
|
||||
// https://github.com/ota-meshi/eslint-plugin-regexp/issues/445
|
||||
'regexp/strict': off,
|
||||
'regexp/match-any': off,
|
||||
// https://github.com/ota-meshi/eslint-plugin-regexp/issues/743
|
||||
'regexp/letter-case': off,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eslint-rules/typescript-and-import-x',
|
||||
files: ['**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tsParser,
|
||||
projectService: true,
|
||||
ecmaVersion: 'latest',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
'import-x/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx', '.mts', '.cts'],
|
||||
},
|
||||
'import-x/resolver': {
|
||||
typescript: true,
|
||||
node: true,
|
||||
},
|
||||
'import-x/core-modules': ['node:sqlite'],
|
||||
},
|
||||
ignores: ['eslint.config.cjs'],
|
||||
rules: {
|
||||
...importRules,
|
||||
...typescriptRules,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eslint-rules: Disable type checking',
|
||||
files: ['**/*.js', '**/*.mjs', '**/*.cjs', '**/*.jsx'],
|
||||
...tseslint.configs.disableTypeChecked,
|
||||
rules: {
|
||||
'import-x/no-commonjs': off,
|
||||
'import-x/unambiguous': off,
|
||||
'@typescript-eslint/no-require-imports': off,
|
||||
'typed-custom/restrict-template-expressions': off,
|
||||
...tseslint.configs.disableTypeChecked.rules,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eslint-rules/.d.ts-files',
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': off,
|
||||
'import-x/unambiguous': off,
|
||||
},
|
||||
},
|
||||
] as FlatESLintConfig[];
|
||||
const hasReact = plugins.includes('react');
|
||||
const hasReactRefresh = plugins.includes('react-refresh');
|
||||
const hasUnicorn = plugins.includes('unicorn');
|
||||
const hasJsDoc = plugins.includes('jsdoc');
|
||||
const hasGraphQL = plugins.includes('@graphql-eslint');
|
||||
const hasNext = ensureArray(_extends).some(name => name.includes(':@next/next'));
|
||||
const hasTailwind = ensureArray(_extends).some(name =>
|
||||
name.includes('plugin:tailwindcss/'),
|
||||
);
|
||||
|
||||
for (const middleware of middlewares) {
|
||||
let fn = await middleware();
|
||||
if ('default' in fn) {
|
||||
fn = fn.default;
|
||||
}
|
||||
if (Array.isArray(fn)) {
|
||||
result.push(...(fn as FlatESLintConfig[]).flat(Infinity));
|
||||
} else {
|
||||
result.push(fn as unknown as FlatESLintConfig);
|
||||
}
|
||||
const ruleDir = false; // ?? findCacheDirectory({ name: '_eslint-rules' });
|
||||
if (ruleDir) {
|
||||
fs.rmSync(ruleDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(ruleDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (configs?.length) {
|
||||
result.push(...configs);
|
||||
}
|
||||
|
||||
result.push(prettier);
|
||||
|
||||
if (gitignore && fs.existsSync('.gitignore')) {
|
||||
const ignores = fs
|
||||
.readFileSync('.gitignore', 'utf8')
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line && !line.startsWith('#'));
|
||||
|
||||
result.push({ ignores });
|
||||
}
|
||||
const result: InputConfig = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: unique('@typescript-eslint', 'import', 'rules', plugins),
|
||||
env: { node: true, browser: true, es2023: true },
|
||||
reportUnusedDisableDirectives: true,
|
||||
parserOptions: {
|
||||
project: true,
|
||||
},
|
||||
extends: unique(
|
||||
'eslint:recommended',
|
||||
'prettier',
|
||||
'plugin:@typescript-eslint/recommended-type-checked',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/typescript',
|
||||
hasReact && [
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
],
|
||||
hasJsDoc && 'plugin:jsdoc/recommended-typescript',
|
||||
hasGraphQL && 'plugin:@graphql-eslint/recommended',
|
||||
_extends,
|
||||
),
|
||||
settings: {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx', '.mts', '.cts'],
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
},
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
...settings,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['.eslintrc.js', '.eslintrc.cjs', '*.config.js', 'index.js'],
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
rules: {
|
||||
'rules/restrict-template-expressions': off,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.d.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': off,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['repl.ts', 'scripts/**/*.ts'],
|
||||
rules: {
|
||||
'no-console': off,
|
||||
},
|
||||
},
|
||||
...(overrides ?? []),
|
||||
],
|
||||
rules: {
|
||||
...eslintRules,
|
||||
...typescriptRules,
|
||||
...importRules,
|
||||
...localRules,
|
||||
...(hasReact && {
|
||||
...reactRules,
|
||||
'react/no-unknown-property': [
|
||||
error,
|
||||
{ ignore: hasNext ? ['css', 'next'] : ['css'] },
|
||||
],
|
||||
}),
|
||||
...(hasReactRefresh && {
|
||||
'react-refresh/only-export-components': [warn, { allowConstantExport: true }],
|
||||
}),
|
||||
...(hasUnicorn && unicornRules),
|
||||
...(hasJsDoc && jsDocRules),
|
||||
...(hasGraphQL && graphqlRules),
|
||||
...(hasTailwind && tailwindRules),
|
||||
...rules,
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { installPackage } from '@antfu/install-pkg';
|
||||
import { uniq } from 'lodash-es';
|
||||
|
||||
import { envs, getProjectDependencies } from './environment';
|
||||
|
||||
const deps = getProjectDependencies();
|
||||
const packages = uniq(
|
||||
envs
|
||||
.filter(
|
||||
_ =>
|
||||
_.dependency.some(dep => deps.has(dep)) &&
|
||||
_.eslintPlugin &&
|
||||
!deps.has(_.eslintPlugin),
|
||||
)
|
||||
.map(_ => _.eslintPlugin!),
|
||||
);
|
||||
|
||||
console.log('Installing missing ESLint plugins.\n');
|
||||
|
||||
void installPackage(packages, {
|
||||
silent: false,
|
||||
});
|
77
src/local/index.ts
Normal file
77
src/local/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import type { ESLint } from 'eslint';
|
||||
import * as fs from 'node:fs';
|
||||
import { resolve, basename, extname } from 'node:path';
|
||||
import { glob } from 'fast-glob';
|
||||
import { parseModule } from 'esprima';
|
||||
import query from 'esquery';
|
||||
import type { Node, Property } from 'estree';
|
||||
|
||||
// https://github.com/gulpjs/interpret
|
||||
const transpilers = [
|
||||
'esbin/register',
|
||||
'esbuild-register',
|
||||
'ts-node/register/transpile-only',
|
||||
'@swc/register',
|
||||
'sucrase/register',
|
||||
'@babel/register',
|
||||
'coffeescript/register',
|
||||
];
|
||||
|
||||
function tryRequire() {
|
||||
for (const candidate of transpilers) {
|
||||
try {
|
||||
require(candidate);
|
||||
return;
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
const unwrapDefault = <T = any>(module: any): T => module.default ?? module;
|
||||
|
||||
const plugin: ESLint.Plugin = {
|
||||
rules: {},
|
||||
};
|
||||
|
||||
function hydrateESTreeNode(n: Node): any {
|
||||
switch (n.type) {
|
||||
case 'Literal':
|
||||
return n.value;
|
||||
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;
|
@ -1,9 +0,0 @@
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
type MiddlewareResult = Linter.Config | Linter.Config[];
|
||||
|
||||
export type Middleware =
|
||||
| (() => Promise<MiddlewareResult>)
|
||||
| (() => Promise<{ default: MiddlewareResult }>);
|
||||
|
||||
export const def = <T>(module: { default: T }): T => module.default;
|
38
src/modules.d.ts
vendored
38
src/modules.d.ts
vendored
@ -1,4 +1,25 @@
|
||||
// eslint-disable-next-line import-x/unambiguous
|
||||
declare module '@typescript-eslint/utils' {
|
||||
export * from '@typescript-eslint/utils/dist/index';
|
||||
}
|
||||
declare module '@typescript-eslint/typescript-estree' {
|
||||
export * from '@typescript-eslint/typescript-estree/dist/index';
|
||||
}
|
||||
declare module '@typescript-eslint/type-utils' {
|
||||
export * from '@typescript-eslint/type-utils/dist/index';
|
||||
}
|
||||
declare module '@typescript-eslint/utils/eslint-utils' {
|
||||
export * from '@typescript-eslint/utils/dist/eslint-utils';
|
||||
}
|
||||
declare module '@typescript-eslint/utils/json-schema' {
|
||||
export * from '@typescript-eslint/utils/dist/json-schema';
|
||||
}
|
||||
declare module '@typescript-eslint/scope-manager' {
|
||||
export * from '@typescript-eslint/scope-manager/dist/index';
|
||||
}
|
||||
declare module '@typescript-eslint/types' {
|
||||
export * from '@typescript-eslint/types/dist/index';
|
||||
}
|
||||
|
||||
declare module 'module' {
|
||||
export function _resolveFilename(
|
||||
request: string,
|
||||
@ -13,18 +34,3 @@ declare module 'module' {
|
||||
options?: Record<PropertyKey, unknown>,
|
||||
): string;
|
||||
}
|
||||
|
||||
declare module 'eslint-plugin-storybook' {
|
||||
import type { Linter } from 'eslint';
|
||||
|
||||
export const configs: {
|
||||
/** @deprecated */
|
||||
csf: Linter.Config;
|
||||
/** @deprecated */
|
||||
recommended: Linter.Config;
|
||||
'flat/csf': Linter.Config;
|
||||
'flat/recommended': Linter.Config;
|
||||
'flat/csf-strict': Linter.Config;
|
||||
'flat/addon-interactions': Linter.Config;
|
||||
};
|
||||
}
|
||||
|
@ -1,164 +0,0 @@
|
||||
[
|
||||
"addEventListener",
|
||||
"blur",
|
||||
"caches",
|
||||
"captureEvents",
|
||||
"clientInformation",
|
||||
"close",
|
||||
"closed",
|
||||
"crossOriginIsolated",
|
||||
"devicePixelRatio",
|
||||
"dispatchEvent",
|
||||
"event",
|
||||
"external",
|
||||
"focus",
|
||||
"innerHeight",
|
||||
"innerWidth",
|
||||
"length",
|
||||
"locationbar",
|
||||
"menubar",
|
||||
"name",
|
||||
"onabort",
|
||||
"onafterprint",
|
||||
"onanimationcancel",
|
||||
"onanimationend",
|
||||
"onanimationiteration",
|
||||
"onanimationstart",
|
||||
"onauxclick",
|
||||
"onbeforeinput",
|
||||
"onbeforeprint",
|
||||
"onbeforetoggle",
|
||||
"onbeforeunload",
|
||||
"onblur",
|
||||
"oncancel",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"onclose",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondevicemotion",
|
||||
"ondeviceorientation",
|
||||
"ondeviceorientationabsolute",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onformdata",
|
||||
"ongamepadconnected",
|
||||
"ongamepaddisconnected",
|
||||
"ongotpointercapture",
|
||||
"onhashchange",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onlanguagechange",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onlostpointercapture",
|
||||
"onmessage",
|
||||
"onmessageerror",
|
||||
"onmousedown",
|
||||
"onmouseenter",
|
||||
"onmouseleave",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"onorientationchange",
|
||||
"onpagehide",
|
||||
"onpageshow",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"onpointercancel",
|
||||
"onpointerdown",
|
||||
"onpointerenter",
|
||||
"onpointerleave",
|
||||
"onpointermove",
|
||||
"onpointerout",
|
||||
"onpointerover",
|
||||
"onpointerup",
|
||||
"onpopstate",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onrejectionhandled",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onscrollend",
|
||||
"onsecuritypolicyviolation",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onselectionchange",
|
||||
"onselectstart",
|
||||
"onslotchange",
|
||||
"onstalled",
|
||||
"onstorage",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"ontouchcancel",
|
||||
"ontouchend",
|
||||
"ontouchmove",
|
||||
"ontouchstart",
|
||||
"ontransitioncancel",
|
||||
"ontransitionend",
|
||||
"ontransitionrun",
|
||||
"ontransitionstart",
|
||||
"onunhandledrejection",
|
||||
"onunload",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwebkitanimationend",
|
||||
"onwebkitanimationiteration",
|
||||
"onwebkitanimationstart",
|
||||
"onwebkittransitionend",
|
||||
"onwheel",
|
||||
"orientation",
|
||||
"origin",
|
||||
"outerHeight",
|
||||
"outerWidth",
|
||||
"pageXOffset",
|
||||
"pageYOffset",
|
||||
"personalbar",
|
||||
"releaseEvents",
|
||||
"removeEventListener",
|
||||
"reportError",
|
||||
"screenLeft",
|
||||
"screenTop",
|
||||
"screenX",
|
||||
"screenY",
|
||||
"scroll",
|
||||
"scrollbars",
|
||||
"scrollBy",
|
||||
"scrollTo",
|
||||
"scrollX",
|
||||
"scrollY",
|
||||
"status",
|
||||
"statusbar",
|
||||
"stop",
|
||||
"toolbar",
|
||||
"top"
|
||||
]
|
@ -1,24 +0,0 @@
|
||||
import { error } from '../constants';
|
||||
import { plugin, typedPlugin, LocalRuleOptions } from '../custom/index';
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: 'eslint-rules/aet',
|
||||
plugins: { aet: plugin },
|
||||
rules: {
|
||||
'aet/no-import-dot': error,
|
||||
'aet/no-useless-import-alias': error,
|
||||
'aet/require-import-attribute': error,
|
||||
} satisfies Partial<LocalRuleOptions>,
|
||||
},
|
||||
{
|
||||
name: 'eslint-rules/typed-aet',
|
||||
plugins: { 'typed-aet': typedPlugin },
|
||||
files: ['**/*.ts'],
|
||||
ignores: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'typed-aet/restrict-template-expressions': error,
|
||||
} satisfies Partial<LocalRuleOptions>,
|
||||
},
|
||||
]);
|
@ -1,12 +1,9 @@
|
||||
import type { EslintRulesObject } from '@aet/eslint-define-config/src/rules/eslint';
|
||||
import { error, warn, off } from '../constants';
|
||||
import { ESLintRules } from 'eslint-define-config/rules/eslint';
|
||||
|
||||
import { error, off, warn } from '../constants';
|
||||
|
||||
import restrictedGlobals from './_restrictedGlobals.json';
|
||||
|
||||
export const eslintRules: Partial<EslintRulesObject> = {
|
||||
export const eslintRules: Partial<ESLintRules> = {
|
||||
'arrow-body-style': [error, 'as-needed'],
|
||||
'class-methods-use-this': warn,
|
||||
'class-methods-use-this': off,
|
||||
'func-style': [error, 'declaration', { allowArrowFunctions: true }],
|
||||
'no-async-promise-executor': off,
|
||||
'no-case-declarations': off,
|
||||
@ -17,7 +14,7 @@ export const eslintRules: Partial<EslintRulesObject> = {
|
||||
'no-empty': [error, { allowEmptyCatch: true }],
|
||||
'no-inner-declarations': off,
|
||||
'no-lonely-if': error,
|
||||
'no-restricted-globals': [error, ...restrictedGlobals],
|
||||
'no-restricted-globals': [error, 'event', 'name', 'length'],
|
||||
'no-restricted-imports': [
|
||||
error,
|
||||
{
|
||||
@ -33,7 +30,7 @@ export const eslintRules: Partial<EslintRulesObject> = {
|
||||
'no-template-curly-in-string': error,
|
||||
'no-var': error,
|
||||
'object-shorthand': [error, 'always', { ignoreConstructors: true }],
|
||||
'one-var': [error, { var: 'never', let: 'never', const: 'never' }],
|
||||
'one-var': [error, { var: 'never', let: 'never' }],
|
||||
'prefer-arrow-callback': error,
|
||||
'prefer-const': [error, { destructuring: 'all' }],
|
||||
'prefer-destructuring': [
|
||||
@ -45,6 +42,7 @@ export const eslintRules: Partial<EslintRulesObject> = {
|
||||
'prefer-spread': warn,
|
||||
'quote-props': [error, 'as-needed'],
|
||||
'sort-imports': [warn, { ignoreDeclarationSort: true }],
|
||||
'spaced-comment': [error, 'always', { markers: ['/', '#', '@'] }],
|
||||
complexity: [warn, { max: 100 }],
|
||||
curly: [error, 'multi-line', 'consistent'],
|
||||
eqeqeq: [error, 'smart'],
|
||||
|
@ -1,13 +1,4 @@
|
||||
// Not usable. https://github.com/dimaMachina/graphql-eslint/issues/2178
|
||||
import type { GraphQLRulesObject } from '@aet/eslint-define-config/src/rules/graphql-eslint';
|
||||
import * as graphql from '@graphql-eslint/eslint-plugin';
|
||||
|
||||
import { defineConfig } from '../types';
|
||||
import { GraphQLRules } from 'eslint-define-config/rules/graphql-eslint';
|
||||
|
||||
// https://the-guild.dev/graphql/eslint/rules
|
||||
const graphqlRules: Partial<GraphQLRulesObject> = {};
|
||||
|
||||
export default defineConfig({
|
||||
processor: graphql.processors.graphql,
|
||||
rules: graphqlRules,
|
||||
});
|
||||
export const graphqlRules: Partial<GraphQLRules> = {};
|
||||
|
8
src/presets/import.ts
Normal file
8
src/presets/import.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { error, off } from '../constants';
|
||||
import { ImportRules } from 'eslint-define-config/rules/import';
|
||||
|
||||
export const importRules: Partial<ImportRules> = {
|
||||
'import/export': off,
|
||||
'import/no-duplicates': error,
|
||||
'import/order': [error, { groups: ['builtin', 'external'] }],
|
||||
};
|
@ -1,14 +1,3 @@
|
||||
import type { JSDocRulesObject } from '@aet/eslint-define-config/src/rules/jsdoc';
|
||||
import module from 'eslint-plugin-jsdoc';
|
||||
import { JSDocRules } from 'eslint-define-config/rules/jsdoc';
|
||||
|
||||
import { off } from '../constants';
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
const jsdocRules: Partial<JSDocRulesObject> = {
|
||||
'jsdoc/require-jsdoc': off,
|
||||
};
|
||||
|
||||
export default defineConfig([
|
||||
module.configs['flat/recommended-typescript'],
|
||||
{ name: 'eslint-rules/jsdoc', rules: jsdocRules },
|
||||
]);
|
||||
export const jsDocRules: Partial<JSDocRules> = {};
|
||||
|
@ -1,12 +0,0 @@
|
||||
import type { LinguiRulesObject } from '@aet/eslint-define-config/src/rules/lingui';
|
||||
import * as pluginLingui from 'eslint-plugin-lingui';
|
||||
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
// https://the-guild.dev/graphql/eslint/rules
|
||||
const linguiRules: Partial<LinguiRulesObject> = {};
|
||||
|
||||
export default defineConfig([
|
||||
pluginLingui.configs['flat/recommended'],
|
||||
{ rules: linguiRules },
|
||||
]);
|
7
src/presets/local.ts
Normal file
7
src/presets/local.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { LocalRuleOptions } from '../index';
|
||||
import { error } from '../constants';
|
||||
|
||||
export const localRules: Partial<LocalRuleOptions> = {
|
||||
'rules/no-import-dot': error,
|
||||
'rules/restrict-template-expressions': error,
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import { def } from '../middleware';
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
export async function storybook() {
|
||||
const { configs } = def(await import('eslint-plugin-storybook'));
|
||||
return defineConfig([configs['flat/recommended']]);
|
||||
}
|
||||
|
||||
export async function reactQuery() {
|
||||
const { configs } = def(await import('@tanstack/eslint-plugin-query'));
|
||||
return defineConfig(configs['flat/recommended']);
|
||||
}
|
||||
|
||||
export async function vitest() {
|
||||
const { configs } = def(await import('@vitest/eslint-plugin'));
|
||||
return defineConfig([
|
||||
configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
'vitest/expect-expect': [
|
||||
'error',
|
||||
{
|
||||
assertFunctionNames: ['expect*', 'request.**.expect'],
|
||||
additionalTestBlockFunctions: ['describe*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
@ -1,55 +1,9 @@
|
||||
import type { ReactRulesObject } from '@aet/eslint-define-config/src/rules/react';
|
||||
import type { ReactRefreshRulesObject } from '@aet/eslint-define-config/src/rules/react-refresh';
|
||||
import type { Linter, ESLint } from 'eslint';
|
||||
import { error, off } from '../constants';
|
||||
import { ReactRules } from 'eslint-define-config/rules/react';
|
||||
|
||||
import { error, off, warn } from '../constants';
|
||||
import { def } from '../middleware';
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
const reactRules: Partial<ReactRulesObject> = {
|
||||
'@eslint-react/no-missing-component-display-name': off,
|
||||
'@eslint-react/no-children-prop': error,
|
||||
'@eslint-react/no-leaked-conditional-rendering': error,
|
||||
'@eslint-react/prefer-read-only-props': off,
|
||||
'@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': off,
|
||||
export const reactRules: Partial<ReactRules> = {
|
||||
'react/display-name': off,
|
||||
'react/no-children-prop': error,
|
||||
'react/prop-types': off,
|
||||
'react/react-in-jsx-scope': off,
|
||||
};
|
||||
|
||||
export async function react() {
|
||||
const reactPlugin = def(await import('@eslint-react/eslint-plugin'));
|
||||
const a11y = def(await import('../../packages/eslint-plugin-jsx-a11y/src/index'));
|
||||
const hooks = await import('../../packages/eslint-plugin-react-hooks');
|
||||
|
||||
return defineConfig([
|
||||
reactPlugin.configs['recommended-type-checked'] as unknown as Linter.Config,
|
||||
hooks.flatConfigs.recommended,
|
||||
a11y.flatConfigs.recommended,
|
||||
{
|
||||
name: 'eslint-rules/react',
|
||||
files: ['**/*.tsx'],
|
||||
rules: reactRules,
|
||||
},
|
||||
{
|
||||
name: 'eslint-rules/react/test-files',
|
||||
files: ['**/*.test.tsx'],
|
||||
rules: {
|
||||
'@eslint-react/no-clone-element': off,
|
||||
'@eslint-react/no-create-ref': off,
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const refreshRules: Partial<ReactRefreshRulesObject> = {
|
||||
'react-refresh/only-export-components': [warn, { allowConstantExport: true }],
|
||||
};
|
||||
|
||||
export async function reactRefresh() {
|
||||
const refreshPlugin = def(await import('eslint-plugin-react-refresh'));
|
||||
return defineConfig({
|
||||
name: 'eslint-rules/react-refresh',
|
||||
plugins: {
|
||||
'react-refresh': refreshPlugin as unknown as ESLint.Plugin,
|
||||
},
|
||||
rules: refreshRules,
|
||||
});
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
import type { StylisticRulesObject } from '@aet/eslint-define-config/src/rules/stylistic';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
|
||||
import { error } from '../constants';
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
const stylisticRules: Partial<StylisticRulesObject> = {
|
||||
'stylistic/spaced-comment': [
|
||||
error,
|
||||
'always',
|
||||
{ block: { exceptions: ['@__PURE__', '#__PURE__'] } },
|
||||
],
|
||||
'stylistic/jsx-sort-props': [
|
||||
error,
|
||||
{ callbacksLast: true, shorthandFirst: true, multiline: 'last' },
|
||||
],
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
name: 'eslint-rules/stylistic',
|
||||
plugins: {
|
||||
stylistic,
|
||||
},
|
||||
rules: stylisticRules,
|
||||
});
|
@ -1,23 +1,5 @@
|
||||
import type { TailwindRulesObject } from '@aet/eslint-define-config/src/rules/tailwind';
|
||||
import tailwind from 'eslint-plugin-tailwindcss';
|
||||
|
||||
import { off } from '../constants';
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
const tailwindRules: Partial<TailwindRulesObject> = {
|
||||
export const tailwindRules = {
|
||||
'tailwindcss/no-custom-classname': off,
|
||||
} as const;
|
||||
|
||||
export default defineConfig([
|
||||
...tailwind.configs['flat/recommended'],
|
||||
{
|
||||
name: 'eslint-rules/tailwind',
|
||||
rules: tailwindRules,
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
callees: ['classnames', 'clsx', 'tw', 'twx'],
|
||||
classRegex: /^(css|class(Name)?)$/.source,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
@ -1,16 +0,0 @@
|
||||
import type { TestingLibraryRulesObject } from '@aet/eslint-define-config/src/rules/testing-library';
|
||||
import testingLibrary from 'eslint-plugin-testing-library';
|
||||
|
||||
import { defineConfig } from '../types';
|
||||
|
||||
const testingLibraryRules: Partial<TestingLibraryRulesObject> = {};
|
||||
|
||||
export default defineConfig({
|
||||
name: 'eslint-rules/testing-library',
|
||||
files: ['**/*.(spec|test).{ts,tsx}'],
|
||||
...testingLibrary.configs['flat/react'],
|
||||
rules: {
|
||||
...testingLibrary.configs['flat/react'].rules,
|
||||
...testingLibraryRules,
|
||||
},
|
||||
});
|
@ -1,25 +1,7 @@
|
||||
import type { ImportXRulesObject } from '@aet/eslint-define-config/src/rules/import-x';
|
||||
import type { TypeScriptRulesObject } from '@aet/eslint-define-config/src/rules/typescript-eslint';
|
||||
|
||||
import { error, off, warn } from '../constants';
|
||||
import type { TypeScriptRules } from 'eslint-define-config/rules/typescript-eslint';
|
||||
|
||||
export const importRules: Partial<ImportXRulesObject> = {
|
||||
'import-x/first': error,
|
||||
'import-x/no-absolute-path': error,
|
||||
'import-x/no-duplicates': warn,
|
||||
'import-x/no-useless-path-segments': error,
|
||||
'import-x/order': [
|
||||
warn,
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: { order: 'asc', caseInsensitive: true },
|
||||
},
|
||||
],
|
||||
'import-x/unambiguous': error,
|
||||
};
|
||||
|
||||
export const typescriptRules: Partial<TypeScriptRulesObject> = {
|
||||
export const typescriptRules: Partial<TypeScriptRules> = {
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
error,
|
||||
{
|
||||
@ -29,6 +11,7 @@ export const typescriptRules: Partial<TypeScriptRulesObject> = {
|
||||
'ts-nocheck': 'allow-with-description',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/ban-types': [error, { extendDefaults: true }],
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
error,
|
||||
{ disallowTypeAnnotations: false, fixStyle: 'inline-type-imports' },
|
||||
@ -37,17 +20,8 @@ export const typescriptRules: Partial<TypeScriptRulesObject> = {
|
||||
warn,
|
||||
{ accessibility: 'no-public' },
|
||||
],
|
||||
'@typescript-eslint/no-empty-object-type': off,
|
||||
'@typescript-eslint/no-empty-interface': [error, { allowSingleExtends: true }],
|
||||
'@typescript-eslint/no-explicit-any': off,
|
||||
'@typescript-eslint/no-floating-promises': [
|
||||
'warn',
|
||||
{
|
||||
allowForKnownSafeCalls: [
|
||||
{ from: 'package', name: ['it', 'describe', 'test'], package: 'node:test' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-misused-promises': [error, { checksVoidReturn: false }],
|
||||
'@typescript-eslint/no-namespace': off,
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': error,
|
||||
|
@ -1,49 +1,31 @@
|
||||
import type { UnicornRulesObject } from '@aet/eslint-define-config/src/rules/unicorn';
|
||||
import unicorn from 'eslint-plugin-unicorn';
|
||||
import globals from 'globals';
|
||||
|
||||
import { error, off, warn } from '../constants';
|
||||
import { defineConfig } from '../types';
|
||||
import { error, warn } from '../constants';
|
||||
import { UnicornRules } from 'eslint-define-config/rules/unicorn';
|
||||
|
||||
const suggest = (suggest: string) => ({ suggest, fix: false });
|
||||
|
||||
// https://github.com/sindresorhus/eslint-plugin-unicorn/tree/1774135a5ddbded2c89f82952e37a3e3bb01cdfa
|
||||
const unicornRules: Partial<UnicornRulesObject> = {
|
||||
// https://github.com/sindresorhus/eslint-plugin-unicorn/tree/28e7498ad06679bb92343db53bb40a7b5ba2990a
|
||||
export const unicornRules: Partial<UnicornRules> = {
|
||||
'unicorn/better-regex': error,
|
||||
'unicorn/consistent-destructuring': warn,
|
||||
'unicorn/consistent-empty-array-spread': error,
|
||||
'unicorn/consistent-existence-index-check': error,
|
||||
'unicorn/consistent-function-scoping': warn,
|
||||
'unicorn/escape-case': error,
|
||||
'unicorn/no-array-for-each': warn,
|
||||
'unicorn/no-array-method-this-argument': error,
|
||||
'unicorn/no-array-push-push': warn,
|
||||
'unicorn/no-await-in-promise-methods': error,
|
||||
'unicorn/no-console-spaces': warn,
|
||||
'unicorn/no-for-loop': warn,
|
||||
'unicorn/no-instanceof-array': error,
|
||||
'unicorn/no-invalid-fetch-options': error,
|
||||
'unicorn/no-invalid-remove-event-listener': error,
|
||||
'unicorn/no-length-as-slice-end': error,
|
||||
'unicorn/no-lonely-if': warn,
|
||||
'unicorn/no-negation-in-equality-check': error,
|
||||
'unicorn/no-new-buffer': error,
|
||||
'unicorn/no-single-promise-in-promise-methods': error,
|
||||
'unicorn/no-static-only-class': error,
|
||||
'unicorn/no-typeof-undefined': error,
|
||||
'unicorn/no-unnecessary-await': error,
|
||||
'unicorn/no-unnecessary-polyfills': error,
|
||||
'unicorn/no-unreadable-array-destructuring': warn,
|
||||
// 'unicorn/no-unused-properties': warn,
|
||||
'unicorn/no-useless-fallback-in-spread': error,
|
||||
'unicorn/no-useless-promise-resolve-reject': error,
|
||||
'unicorn/no-useless-spread': error,
|
||||
'unicorn/no-useless-switch-case': error,
|
||||
'unicorn/no-useless-undefined': error,
|
||||
'unicorn/no-zero-fractions': error,
|
||||
'unicorn/number-literal-case': error,
|
||||
'unicorn/prefer-array-index-of': error,
|
||||
|
||||
// https://github.com/prettier/eslint-config-prettier/issues/51
|
||||
// 'unicorn/number-literal-case': error,
|
||||
'unicorn/prefer-array-find': error,
|
||||
'unicorn/prefer-array-flat': error,
|
||||
'unicorn/prefer-array-flat-map': error,
|
||||
'unicorn/prefer-array-some': error,
|
||||
'unicorn/prefer-at': error,
|
||||
@ -52,35 +34,23 @@ const unicornRules: Partial<UnicornRulesObject> = {
|
||||
'unicorn/prefer-default-parameters': warn,
|
||||
'unicorn/prefer-dom-node-dataset': error,
|
||||
'unicorn/prefer-dom-node-remove': error,
|
||||
'unicorn/prefer-dom-node-text-content': warn,
|
||||
'unicorn/prefer-export-from': [error, { ignoreUsedVariables: false }],
|
||||
'unicorn/prefer-includes': error,
|
||||
'unicorn/prefer-json-parse-buffer': warn,
|
||||
'unicorn/prefer-keyboard-event-key': warn,
|
||||
'unicorn/prefer-logical-operator-over-ternary': warn,
|
||||
'unicorn/prefer-math-min-max': error,
|
||||
'unicorn/prefer-math-trunc': warn,
|
||||
'unicorn/prefer-modern-dom-apis': error,
|
||||
'unicorn/prefer-math-trunc': error,
|
||||
'unicorn/prefer-modern-math-apis': error,
|
||||
'unicorn/prefer-negative-index': error,
|
||||
'unicorn/prefer-node-protocol': error,
|
||||
'unicorn/prefer-object-from-entries': error,
|
||||
'unicorn/prefer-optional-catch-binding': error,
|
||||
'unicorn/prefer-prototype-methods': error,
|
||||
'unicorn/prefer-reflect-apply': error,
|
||||
'unicorn/prefer-regexp-test': error,
|
||||
'unicorn/prefer-set-has': warn,
|
||||
'unicorn/prefer-set-size': error,
|
||||
'unicorn/prefer-string-raw': warn,
|
||||
'unicorn/prefer-string-slice': error,
|
||||
'unicorn/prefer-string-starts-ends-with': warn,
|
||||
'unicorn/prefer-string-trim-start-end': error,
|
||||
'unicorn/prefer-switch': warn,
|
||||
'unicorn/prefer-ternary': warn,
|
||||
'unicorn/prefer-type-error': warn,
|
||||
'unicorn/relative-url-style': warn,
|
||||
'unicorn/require-number-to-fixed-digits-argument': error,
|
||||
'unicorn/require-post-message-target-origin': warn,
|
||||
'unicorn/string-content': [
|
||||
warn,
|
||||
{
|
||||
@ -94,35 +64,8 @@ const unicornRules: Partial<UnicornRulesObject> = {
|
||||
'<=>': suggest('⇔'),
|
||||
'\\.\\.\\.': suggest('…'),
|
||||
"'s ": suggest('’s '),
|
||||
"'d ": suggest('’d '),
|
||||
"'t ": suggest('’t '),
|
||||
"l'": suggest('l’'),
|
||||
"d'": suggest('d’'),
|
||||
"qu'": suggest('qu’'),
|
||||
'\\?!': suggest('⁈'),
|
||||
'!\\?': suggest('⁉'),
|
||||
},
|
||||
},
|
||||
],
|
||||
'unicorn/template-indent': warn,
|
||||
};
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
name: 'eslint-rules/unicorn',
|
||||
languageOptions: {
|
||||
globals: globals.builtin,
|
||||
},
|
||||
plugins: {
|
||||
unicorn,
|
||||
},
|
||||
rules: unicornRules,
|
||||
},
|
||||
{
|
||||
name: 'eslint-rules/unicorn/tests',
|
||||
files: ['**/*.test.ts', '**/*.test.tsx'],
|
||||
rules: {
|
||||
'unicorn/no-useless-undefined': off,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
@ -10,15 +10,28 @@ const prettier: Config = {
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export default function defineConfig(
|
||||
config: Partial<Config> & {
|
||||
tailwind?: boolean;
|
||||
},
|
||||
) {
|
||||
export default function defineConfig({
|
||||
tailwind,
|
||||
...config
|
||||
}: Partial<Config> & {
|
||||
tailwind?: boolean;
|
||||
}) {
|
||||
const result: Config = {
|
||||
...prettier,
|
||||
...config,
|
||||
};
|
||||
|
||||
if (tailwind) {
|
||||
ensureHas(result.plugins!, 'prettier-plugin-tailwindcss');
|
||||
result.tailwindAttributes ??= ['css'];
|
||||
result.tailwindFunctions ??= ['tw'];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function ensureHas<T>(list: T[], item: T) {
|
||||
if (!list.includes(item)) {
|
||||
list.push(item);
|
||||
}
|
||||
}
|
||||
|
26
src/redirect.ts
Normal file
26
src/redirect.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import Module from 'module';
|
||||
const { name } = [require][0]('./package.json');
|
||||
|
||||
const _resolveFilename = Module._resolveFilename;
|
||||
const alias = new Set([
|
||||
'eslint-import-resolver-typescript',
|
||||
'eslint-plugin-import',
|
||||
'eslint-plugin-jsx-a11y',
|
||||
'eslint-plugin-local',
|
||||
'eslint-plugin-n',
|
||||
'eslint-plugin-react-hooks',
|
||||
'eslint-plugin-react',
|
||||
'eslint-plugin-rules',
|
||||
]);
|
||||
|
||||
type CDR<T> = T extends [any, ...infer R] ? R : [];
|
||||
|
||||
Module._resolveFilename = function (
|
||||
request: string,
|
||||
...args: CDR<Parameters<typeof _resolveFilename>>
|
||||
) {
|
||||
if (alias.has(request)) {
|
||||
request = `${name}/${request}`;
|
||||
}
|
||||
return _resolveFilename(request, ...args);
|
||||
};
|
15
src/rules/index.ts
Normal file
15
src/rules/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { Rule } from 'eslint';
|
||||
import type { ESLintUtils } from '@typescript-eslint/utils';
|
||||
|
||||
import noImportDot from './no-import-dot';
|
||||
import noEmptyObjectLiteral from './no-empty-object-literal';
|
||||
import restrictTemplateExpressions from './restrict-template-expressions';
|
||||
|
||||
export const rules: Record<
|
||||
string,
|
||||
Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>
|
||||
> = {
|
||||
'no-import-dot': noImportDot,
|
||||
'no-empty-object-literal': noEmptyObjectLiteral,
|
||||
'restrict-template-expressions': restrictTemplateExpressions,
|
||||
};
|
@ -1,19 +1,14 @@
|
||||
// https://github.com/typescript-eslint/typescript-eslint/blob/75c128856b1ce05a4fec799bfa6de03b3dab03d0/packages/eslint-plugin/src/rules/restrict-template-expressions.ts
|
||||
import * as ts from 'typescript';
|
||||
import { ESLintUtils, type TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils';
|
||||
import {
|
||||
getConstrainedTypeAtLocation,
|
||||
getTypeName,
|
||||
isTypeAnyType,
|
||||
isTypeFlagSet,
|
||||
isTypeNeverType,
|
||||
getConstrainedTypeAtLocation,
|
||||
} from '@typescript-eslint/type-utils';
|
||||
import {
|
||||
AST_NODE_TYPES,
|
||||
ESLintUtils,
|
||||
ParserServicesWithTypeInformation,
|
||||
type TSESTree,
|
||||
} from '@typescript-eslint/utils';
|
||||
import { getParserServices } from '@typescript-eslint/utils/eslint-utils';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator(
|
||||
name => `https://typescript-eslint.io/rules/${name}`,
|
||||
@ -34,6 +29,8 @@ export default createRule<Option[], MessageId>({
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Enforce template literal expressions to be of `string` type',
|
||||
recommended: 'recommended',
|
||||
requiresTypeChecking: true,
|
||||
},
|
||||
messages: {
|
||||
invalidType: 'Invalid type "{{type}}" of template literal expression.',
|
||||
@ -57,16 +54,8 @@ export default createRule<Option[], MessageId>({
|
||||
},
|
||||
defaultOptions: [defaultOption],
|
||||
create(context, [options]) {
|
||||
let services: ParserServicesWithTypeInformation | undefined;
|
||||
try {
|
||||
services = getParserServices(context);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!services?.program) return {};
|
||||
|
||||
const checker = services.program.getTypeChecker();
|
||||
const services = getParserServices(context);
|
||||
const checker = services.program!.getTypeChecker();
|
||||
const allowed = new Set(options.allow);
|
||||
|
||||
const { StringLike, NumberLike, BigIntLike, BooleanLike, Null, Undefined } =
|
13
src/types.ts
13
src/types.ts
@ -1,5 +1,5 @@
|
||||
import type { Rule } from 'eslint';
|
||||
import type { ESLintUtils } from '@typescript-eslint/utils';
|
||||
import type { Rule, Linter } from 'eslint';
|
||||
|
||||
export function defineRules(rules: {
|
||||
[ruleName: string]: Rule.RuleModule | ESLintUtils.RuleModule<string, unknown[]>;
|
||||
@ -7,17 +7,6 @@ export function defineRules(rules: {
|
||||
return rules;
|
||||
}
|
||||
|
||||
export function defineConfig(config: Linter.Config): Linter.Config;
|
||||
export function defineConfig(config: Linter.Config[]): Linter.Config[];
|
||||
|
||||
export function defineConfig(config: Linter.Config | Linter.Config[]) {
|
||||
if (!config || (Array.isArray(config) && config.some(c => !c))) {
|
||||
console.trace();
|
||||
throw new Error('Config cannot be empty');
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export function defineRule({
|
||||
name,
|
||||
create,
|
||||
|
171
src/types/config/env.d.ts
vendored
Normal file
171
src/types/config/env.d.ts
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* An environment provides predefined global variables.
|
||||
*
|
||||
* @see [Environments](https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments)
|
||||
*/
|
||||
export interface Environments extends Partial<Record<string, boolean>> {
|
||||
/**
|
||||
* Browser global variables.
|
||||
*/
|
||||
browser?: boolean;
|
||||
|
||||
/**
|
||||
* Node.js global variables and Node.js scoping.
|
||||
*/
|
||||
node?: boolean;
|
||||
|
||||
/**
|
||||
* CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack).
|
||||
*/
|
||||
commonjs?: boolean;
|
||||
|
||||
/**
|
||||
* Globals common to both Node.js and Browser.
|
||||
*/
|
||||
'shared-node-browser'?: boolean;
|
||||
|
||||
/**
|
||||
* Enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6).
|
||||
*/
|
||||
es6?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2016 globals and automatically sets the `ecmaVersion` parser option to 7.
|
||||
*/
|
||||
es2016?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8.
|
||||
*/
|
||||
es2017?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2018 globals and automatically sets the `ecmaVersion` parser option to 9.
|
||||
*/
|
||||
es2018?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2019 globals and automatically sets the `ecmaVersion` parser option to 10.
|
||||
*/
|
||||
es2019?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11.
|
||||
*/
|
||||
es2020?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12.
|
||||
*/
|
||||
es2021?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13.
|
||||
*/
|
||||
es2022?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all ECMAScript 2023 globals and automatically sets the `ecmaVersion` parser option to 14.
|
||||
*/
|
||||
es2023?: boolean;
|
||||
|
||||
/**
|
||||
* Web workers global variables.
|
||||
*/
|
||||
worker?: boolean;
|
||||
|
||||
/**
|
||||
* Defines `require()` and `define()` as global variables as per the amd spec.
|
||||
*/
|
||||
amd?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all of the Mocha testing global variables.
|
||||
*/
|
||||
mocha?: boolean;
|
||||
|
||||
/**
|
||||
* Adds all of the Jasmine testing global variables for version 1.3 and 2.0.
|
||||
*/
|
||||
jasmine?: boolean;
|
||||
|
||||
/**
|
||||
* Jest global variables.
|
||||
*/
|
||||
jest?: boolean;
|
||||
|
||||
/**
|
||||
* PhantomJS global variables.
|
||||
*/
|
||||
phantomjs?: boolean;
|
||||
|
||||
/**
|
||||
* Protractor global variables.
|
||||
*/
|
||||
protractor?: boolean;
|
||||
|
||||
/**
|
||||
* QUnit global variables.
|
||||
*/
|
||||
qunit?: boolean;
|
||||
|
||||
/**
|
||||
* jQuery global variables.
|
||||
*/
|
||||
jquery?: boolean;
|
||||
|
||||
/**
|
||||
* Prototype.js global variables.
|
||||
*/
|
||||
prototypejs?: boolean;
|
||||
|
||||
/**
|
||||
* ShellJS global variables.
|
||||
*/
|
||||
shelljs?: boolean;
|
||||
|
||||
/**
|
||||
* Meteor global variables.
|
||||
*/
|
||||
meteor?: boolean;
|
||||
|
||||
/**
|
||||
* MongoDB global variables.
|
||||
*/
|
||||
mongo?: boolean;
|
||||
|
||||
/**
|
||||
* AppleScript global variables.
|
||||
*/
|
||||
applescript?: boolean;
|
||||
|
||||
/**
|
||||
* Java 8 Nashorn global variables.
|
||||
*/
|
||||
nashorn?: boolean;
|
||||
|
||||
/**
|
||||
* Service Worker global variables.
|
||||
*/
|
||||
serviceworker?: boolean;
|
||||
|
||||
/**
|
||||
* Atom test helper globals.
|
||||
*/
|
||||
atomtest?: boolean;
|
||||
|
||||
/**
|
||||
* Ember test helper globals.
|
||||
*/
|
||||
embertest?: boolean;
|
||||
|
||||
/**
|
||||
* WebExtensions globals.
|
||||
*/
|
||||
webextensions?: boolean;
|
||||
|
||||
/**
|
||||
* GreaseMonkey globals.
|
||||
*/
|
||||
greasemonkey?: boolean;
|
||||
}
|
6
src/types/config/extends/eslint-plugin-eslint-comment.d.ts
vendored
Normal file
6
src/types/config/extends/eslint-plugin-eslint-comment.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Eslint EslintComments extends.
|
||||
*
|
||||
* @see [Eslint EslintComments extends](https://mysticatea.github.io/eslint-plugin-eslint-comments/#%F0%9F%93%96-usage)
|
||||
*/
|
||||
export type EslintCommentsExtends = 'plugin:eslint-comments/recommended';
|
11
src/types/config/extends/eslint-plugin-graphql.d.ts
vendored
Normal file
11
src/types/config/extends/eslint-plugin-graphql.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Eslint GraphQL extends.
|
||||
*
|
||||
* @see [Eslint GraphQL extends](https://the-guild.dev/graphql/eslint/docs/configs)
|
||||
*/
|
||||
export type GraphqlExtends =
|
||||
| 'plugin:@graphql-eslint/operations-all'
|
||||
| 'plugin:@graphql-eslint/operations-recommended'
|
||||
| 'plugin:@graphql-eslint/relay'
|
||||
| 'plugin:@graphql-eslint/schema-all'
|
||||
| 'plugin:@graphql-eslint/schema-recommended';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user