@darksheep/eslint 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +737 -0
- package/LICENSE +24 -0
- package/README.md +34 -0
- package/package.json +62 -0
- package/src/bin/eslint.cjs +11 -0
- package/src/configs/eslint-base.js +124 -0
- package/src/configs/eslint-complexity.js +28 -0
- package/src/configs/eslint-ignores.js +51 -0
- package/src/configs/eslint-recommended.js +9 -0
- package/src/configs/eslint-style.js +23 -0
- package/src/custom/index.js +11 -0
- package/src/custom/instance-of-array.js +72 -0
- package/src/custom/loose-types.js +204 -0
- package/src/custom/no-useless-expression.js +28 -0
- package/src/custom/sequence-expression.js +21 -0
- package/src/index.js +86 -0
- package/src/plugins/eslint-comments.js +27 -0
- package/src/plugins/import.js +139 -0
- package/src/plugins/jest.js +124 -0
- package/src/plugins/jsdoc.js +70 -0
- package/src/plugins/json.js +37 -0
- package/src/plugins/jsx-a11y.js +100 -0
- package/src/plugins/node.js +129 -0
- package/src/plugins/promise.js +16 -0
- package/src/plugins/react.js +128 -0
- package/src/plugins/regexp.js +13 -0
- package/src/plugins/sca.js +41 -0
- package/src/plugins/security.js +15 -0
- package/src/plugins/sonarjs.js +28 -0
- package/src/plugins/style.js +249 -0
- package/src/plugins/typescript.js +87 -0
- package/src/plugins/unicorn.js +58 -0
- package/src/plugins/unused-imports.js +52 -0
- package/src/types.d.ts +118 -0
- package/src/utilities/editorconfig.js +210 -0
- package/src/utilities/eslint-files.js +65 -0
- package/src/utilities/expand-glob.js +49 -0
- package/src/utilities/filesystem.js +73 -0
- package/src/utilities/make-compat.js +17 -0
- package/src/utilities/package.js +49 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getTypescriptFiles } from '../utilities/eslint-files.js';
|
|
2
|
+
import { getPackageJson } from '../utilities/package.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @template {Object} T
|
|
6
|
+
* @param {T | { default: T }} target The module to optionally check for default exports
|
|
7
|
+
* @returns {T}
|
|
8
|
+
*/
|
|
9
|
+
function idef(target) {
|
|
10
|
+
return /** @type {{ default: T }} */ (target)?.default ?? target;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get ESLint config for the typescript plugin
|
|
15
|
+
* @param {import('node:url').URL} root root url
|
|
16
|
+
* @returns {Promise<import('eslint').Linter.FlatConfig[]>}
|
|
17
|
+
*/
|
|
18
|
+
export async function createEslintTypescriptConfig(root) {
|
|
19
|
+
const packageJson = await getPackageJson(root, 'typescript');
|
|
20
|
+
|
|
21
|
+
// Typescript not installed
|
|
22
|
+
if (packageJson == null) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const typescriptParser = idef(await import('@typescript-eslint/parser'));
|
|
27
|
+
const typescriptPlugin = idef(await import('@typescript-eslint/eslint-plugin'));
|
|
28
|
+
|
|
29
|
+
return [ {
|
|
30
|
+
files: await getTypescriptFiles(),
|
|
31
|
+
languageOptions: {
|
|
32
|
+
parser: /** @type {*} */ (typescriptParser),
|
|
33
|
+
sourceType: 'module',
|
|
34
|
+
parserOptions: {
|
|
35
|
+
warnOnUnsupportedTypeScriptVersion: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
plugins: {
|
|
39
|
+
'@typescript-eslint': /** @type {*} */ (typescriptPlugin),
|
|
40
|
+
},
|
|
41
|
+
rules: {
|
|
42
|
+
// Rules that fight with typescript
|
|
43
|
+
'constructor-super': 'off',
|
|
44
|
+
'getter-return': 'off',
|
|
45
|
+
'no-const-assign': 'off',
|
|
46
|
+
'no-dupe-args': 'off',
|
|
47
|
+
'no-dupe-class-members': 'off',
|
|
48
|
+
'no-dupe-keys': 'off',
|
|
49
|
+
'no-func-assign': 'off',
|
|
50
|
+
'no-import-assign': 'off',
|
|
51
|
+
'no-new-symbol': 'off',
|
|
52
|
+
'no-obj-calls': 'off',
|
|
53
|
+
'no-redeclare': 'off',
|
|
54
|
+
'no-setter-return': 'off',
|
|
55
|
+
'no-this-before-super': 'off',
|
|
56
|
+
'no-undef': 'off',
|
|
57
|
+
'no-unreachable': 'off',
|
|
58
|
+
'no-unsafe-negation': 'off',
|
|
59
|
+
'no-array-constructor': 'off',
|
|
60
|
+
'no-loss-of-precision': 'off',
|
|
61
|
+
|
|
62
|
+
// Rules that prevert some commmon ts issues
|
|
63
|
+
'no-var': 'error',
|
|
64
|
+
'prefer-const': 'error',
|
|
65
|
+
'prefer-rest-params': 'error',
|
|
66
|
+
'prefer-spread': 'error',
|
|
67
|
+
|
|
68
|
+
// ts eslint recommended
|
|
69
|
+
'@typescript-eslint/ban-ts-comment': 'error',
|
|
70
|
+
'@typescript-eslint/ban-types': 'error',
|
|
71
|
+
'@typescript-eslint/no-array-constructor': 'error',
|
|
72
|
+
'@typescript-eslint/no-duplicate-enum-values': 'error',
|
|
73
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
74
|
+
'@typescript-eslint/no-extra-non-null-assertion': 'error',
|
|
75
|
+
'@typescript-eslint/no-loss-of-precision': 'error',
|
|
76
|
+
'@typescript-eslint/no-misused-new': 'error',
|
|
77
|
+
'@typescript-eslint/no-namespace': [ 'error', { allowDeclarations: true } ],
|
|
78
|
+
'@typescript-eslint/no-non-null-asserted-optional-chain': 'error',
|
|
79
|
+
'@typescript-eslint/no-this-alias': 'error',
|
|
80
|
+
'@typescript-eslint/no-unnecessary-type-constraint': 'error',
|
|
81
|
+
'@typescript-eslint/no-unsafe-declaration-merging': 'error',
|
|
82
|
+
'@typescript-eslint/no-var-requires': 'error',
|
|
83
|
+
'@typescript-eslint/prefer-as-const': 'error',
|
|
84
|
+
'@typescript-eslint/triple-slash-reference': 'error',
|
|
85
|
+
},
|
|
86
|
+
} ];
|
|
87
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { makeCompat } from '../utilities/make-compat.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get ESLint config for the unicorn plugin
|
|
5
|
+
* @param {import('node:url').URL} root root url
|
|
6
|
+
* @returns {Promise<import('eslint').Linter.FlatConfig[]>}
|
|
7
|
+
*/
|
|
8
|
+
export async function createEslintUnicornConfig(root) {
|
|
9
|
+
return makeCompat(root).config({
|
|
10
|
+
plugins: [ 'unicorn' ],
|
|
11
|
+
rules: {
|
|
12
|
+
/*
|
|
13
|
+
* Sanity checks
|
|
14
|
+
*/
|
|
15
|
+
'unicorn/better-regex': 'off',
|
|
16
|
+
'unicorn/no-empty-file': 'warn',
|
|
17
|
+
'unicorn/no-unsafe-regex': 'off',
|
|
18
|
+
'unicorn/no-zero-fractions': 'error',
|
|
19
|
+
'unicorn/no-unreadable-array-destructuring': 'error',
|
|
20
|
+
'unicorn/no-useless-undefined': 'error',
|
|
21
|
+
'unicorn/no-abusive-eslint-disable': 'warn',
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Error management
|
|
25
|
+
*/
|
|
26
|
+
'unicorn/catch-error-name': 'error',
|
|
27
|
+
'unicorn/custom-error-definition': 'off',
|
|
28
|
+
'unicorn/throw-new-error': 'error',
|
|
29
|
+
'unicorn/error-message': 'error',
|
|
30
|
+
'unicorn/prefer-type-error': 'error',
|
|
31
|
+
|
|
32
|
+
// This breaks babel
|
|
33
|
+
'unicorn/prefer-optional-catch-binding': 'off',
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Best practice
|
|
37
|
+
*/
|
|
38
|
+
'unicorn/consistent-destructuring': 'off',
|
|
39
|
+
'unicorn/escape-case': 'error',
|
|
40
|
+
'unicorn/filename-case': 'off',
|
|
41
|
+
'unicorn/no-console-spaces': 'error',
|
|
42
|
+
'unicorn/no-keyword-prefix': 'warn',
|
|
43
|
+
'unicorn/no-nested-ternary': 'warn',
|
|
44
|
+
'unicorn/no-new-array': 'error',
|
|
45
|
+
'unicorn/no-useless-promise-resolve-reject': 'error',
|
|
46
|
+
'unicorn/no-useless-switch-case': 'error',
|
|
47
|
+
'unicorn/prefer-add-event-listener': 'error',
|
|
48
|
+
'unicorn/prefer-default-parameters': 'error',
|
|
49
|
+
'unicorn/prefer-export-from': 'warn',
|
|
50
|
+
'unicorn/prefer-native-coercion-functions': 'error',
|
|
51
|
+
'unicorn/prefer-node-protocol': 'warn',
|
|
52
|
+
'unicorn/prefer-reflect-apply': 'warn',
|
|
53
|
+
'unicorn/prefer-regexp-test': 'warn',
|
|
54
|
+
'unicorn/require-array-join-separator': 'error',
|
|
55
|
+
'unicorn/text-encoding-identifier-case': 'error',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import unused from 'eslint-plugin-unused-imports';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getModuleFiles,
|
|
5
|
+
getTypescriptFiles,
|
|
6
|
+
} from '../utilities/eslint-files.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get ESLint config for the unused import plugin
|
|
10
|
+
* @param {import('node:url').URL} root root url
|
|
11
|
+
* @returns {Promise<import('eslint').Linter.FlatConfig[]>}
|
|
12
|
+
*/
|
|
13
|
+
export async function createEslintUnusedImportsConfig(root) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
files: await getModuleFiles(root),
|
|
17
|
+
plugins: { 'unused-imports': unused },
|
|
18
|
+
rules: {
|
|
19
|
+
'no-unused-vars': 'off',
|
|
20
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
21
|
+
|
|
22
|
+
'unused-imports/no-unused-imports': 'error',
|
|
23
|
+
'unused-imports/no-unused-vars': [
|
|
24
|
+
'error', {
|
|
25
|
+
argsIgnorePattern: '^_',
|
|
26
|
+
varsIgnorePattern: '^_',
|
|
27
|
+
args: 'after-used',
|
|
28
|
+
vars: 'local',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
files: await getTypescriptFiles(),
|
|
35
|
+
plugins: { 'unused-imports': unused },
|
|
36
|
+
rules: {
|
|
37
|
+
'no-unused-vars': 'off',
|
|
38
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
39
|
+
|
|
40
|
+
'unused-imports/no-unused-imports-ts': 'error',
|
|
41
|
+
'unused-imports/no-unused-vars-ts': [
|
|
42
|
+
'error', {
|
|
43
|
+
argsIgnorePattern: '^_',
|
|
44
|
+
varsIgnorePattern: '^_',
|
|
45
|
+
args: 'after-used',
|
|
46
|
+
vars: 'local',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/* eslint no-duplicate-imports: 0 */
|
|
2
|
+
|
|
3
|
+
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
|
4
|
+
|
|
5
|
+
declare module '@eslint-community/eslint-plugin-eslint-comments' {
|
|
6
|
+
import type { ESLint } from 'eslint';
|
|
7
|
+
export default {} as ESLint.Plugin;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module 'eslint-plugin-import' {
|
|
11
|
+
import type { ESLint, Linter } from 'eslint';
|
|
12
|
+
export default {} as {
|
|
13
|
+
rules: Required<ESLint.Plugin['rules']>;
|
|
14
|
+
configs: {
|
|
15
|
+
'recommended': {
|
|
16
|
+
plugins: Required<ESLint.ConfigData['plugins']>;
|
|
17
|
+
rules: Linter.RulesRecord;
|
|
18
|
+
parserOptions: Required<ESLint.ConfigData['parserOptions']>;
|
|
19
|
+
};
|
|
20
|
+
'errors': {
|
|
21
|
+
plugins: Required<ESLint.ConfigData['plugins']>;
|
|
22
|
+
rules: Linter.RulesRecord;
|
|
23
|
+
};
|
|
24
|
+
'warnings': {
|
|
25
|
+
plugins: Required<ESLint.ConfigData['plugins']>;
|
|
26
|
+
rules: Linter.RulesRecord;
|
|
27
|
+
};
|
|
28
|
+
'stage-0': {
|
|
29
|
+
plugins: Required<ESLint.ConfigData['plugins']>;
|
|
30
|
+
rules: Linter.RulesRecord;
|
|
31
|
+
};
|
|
32
|
+
'react': {
|
|
33
|
+
settings: Required<ESLint.ConfigData['settings']>;
|
|
34
|
+
parserOptions: Required<ESLint.ConfigData['parserOptions']>;
|
|
35
|
+
};
|
|
36
|
+
'react-native': {
|
|
37
|
+
settings: Required<ESLint.ConfigData['settings']>;
|
|
38
|
+
};
|
|
39
|
+
'electron': {
|
|
40
|
+
settings: Required<ESLint.ConfigData['settings']>;
|
|
41
|
+
};
|
|
42
|
+
'typescript': {
|
|
43
|
+
settings: Required<ESLint.ConfigData['settings']>;
|
|
44
|
+
rules: Linter.RulesRecord;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare module 'eslint-plugin-jest' {
|
|
51
|
+
import type { ESLint } from 'eslint';
|
|
52
|
+
export default {} as WithRequired<ESLint.Plugin, 'environments'>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
declare module 'eslint-plugin-jsdoc' {
|
|
56
|
+
import type { ESLint } from 'eslint';
|
|
57
|
+
export default {} as WithRequired<ESLint.Plugin, 'environments'>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare module '@stylistic/eslint-plugin' {
|
|
61
|
+
import type { ESLint } from 'eslint';
|
|
62
|
+
export default {} as WithRequired<ESLint.Plugin, 'environments'>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
declare module 'eslint-plugin-jsx-a11y' {
|
|
66
|
+
import type { ESLint } from 'eslint';
|
|
67
|
+
export default {} as ESLint.Plugin;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare module 'eslint-plugin-promise' {
|
|
71
|
+
import type { ESLint } from 'eslint';
|
|
72
|
+
export default {} as {
|
|
73
|
+
rules: Required<ESLint.Plugin['rules']>;
|
|
74
|
+
configs: {
|
|
75
|
+
recommended: WithRequired<ESLint.ConfigData, 'plugins' | 'rules'>;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare module 'eslint-plugin-react' {
|
|
81
|
+
import type { ESLint } from 'eslint';
|
|
82
|
+
export default {} as ESLint.Plugin;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
declare module 'eslint-plugin-security' {
|
|
86
|
+
import type { ESLint } from 'eslint';
|
|
87
|
+
export default {} as {
|
|
88
|
+
rules: Required<ESLint.Plugin['rules']>;
|
|
89
|
+
configs: {
|
|
90
|
+
recommended: WithRequired<ESLint.ConfigData, 'plugins' | 'rules'>;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
declare module 'eslint-plugin-unused-imports' {
|
|
96
|
+
import type { ESLint } from 'eslint';
|
|
97
|
+
export default {} as ESLint.Plugin;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare module 'eslint-plugin-n' {
|
|
101
|
+
import type { ESLint, Linter } from 'eslint';
|
|
102
|
+
export default {} as {
|
|
103
|
+
meta: {
|
|
104
|
+
name: string;
|
|
105
|
+
version: string;
|
|
106
|
+
};
|
|
107
|
+
rules: Required<ESLint.Plugin['rules']>;
|
|
108
|
+
configs: {
|
|
109
|
+
'recommended-module': ESLint.ConfigData;
|
|
110
|
+
'recommended-script': ESLint.ConfigData;
|
|
111
|
+
'recommended': ESLint.ConfigData;
|
|
112
|
+
'flat/recommended-module': Linter.FlatConfig;
|
|
113
|
+
'flat/recommended-script': Linter.FlatConfig;
|
|
114
|
+
'flat/recommended': Linter.FlatConfig;
|
|
115
|
+
'flat/mixed-esm-and-cjs': Linter.FlatConfig;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { parseBuffer } from 'editorconfig';
|
|
6
|
+
import { findUp } from './filesystem.js';
|
|
7
|
+
import { expandGlob } from './expand-glob.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} RuleSet
|
|
11
|
+
* @property {string} EOL end of line
|
|
12
|
+
* @property {string} LF line feed
|
|
13
|
+
* @property {string} TRAIL trailing spaces
|
|
14
|
+
* @property {string} INDENT indent size
|
|
15
|
+
* @property {string} [INDENT_BINARY] indent binary ops size
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('editorconfig').SectionBody} input Raw editor config converted into an object
|
|
20
|
+
* @param {RuleSet} RULES The rules used to create eslint rules options
|
|
21
|
+
* @returns {import('eslint').Linter.RulesRecord}
|
|
22
|
+
*/
|
|
23
|
+
function convert(input, RULES) {
|
|
24
|
+
/** @type {import('eslint').Linter.RulesRecord} */
|
|
25
|
+
const output = {};
|
|
26
|
+
|
|
27
|
+
if (input?.end_of_line === 'lf') {
|
|
28
|
+
output[RULES.LF] = [ 'error', 'unix' ];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (input?.end_of_line === 'crlf') {
|
|
32
|
+
output[RULES.LF] = [ 'error', 'windows' ];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (input?.trim_trailing_whitespace === 'true') {
|
|
36
|
+
output[RULES.TRAIL] = 'error';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (input?.trim_trailing_whitespace === 'false') {
|
|
40
|
+
output[RULES.TRAIL] = 'off';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (input?.insert_final_newline === 'true') {
|
|
44
|
+
output[RULES.EOL] = [ 'error', 'always' ];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (input?.insert_final_newline === 'false') {
|
|
48
|
+
output[RULES.EOL] = [ 'error', 'never' ];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (input?.indent_style === 'space') {
|
|
52
|
+
const size = Number.parseInt(input?.indent_size ?? 4, 10);
|
|
53
|
+
output[RULES.INDENT] = [ 'error', size, { SwitchCase: 1 } ];
|
|
54
|
+
if (RULES.INDENT_BINARY != null) {
|
|
55
|
+
output[RULES.INDENT_BINARY] = [ 'error', size ];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (input?.indent_style === 'tab') {
|
|
60
|
+
output[RULES.INDENT] = [ 'error', 'tab', { SwitchCase: 1 } ];
|
|
61
|
+
if (RULES.INDENT_BINARY != null) {
|
|
62
|
+
output[RULES.INDENT_BINARY] = [ 'error', 'tab' ];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @type {[string[], RuleSet][]} */
|
|
70
|
+
const extToRules = [
|
|
71
|
+
[
|
|
72
|
+
[ '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx' ],
|
|
73
|
+
{
|
|
74
|
+
EOL: 'style/eol-last',
|
|
75
|
+
LF: 'style/linebreak-style',
|
|
76
|
+
TRAIL: 'style/no-trailing-spaces',
|
|
77
|
+
INDENT: 'style/indent',
|
|
78
|
+
INDENT_BINARY: 'style/indent-binary-ops',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
[ '.json', '.jsonc', '.json5' ], {
|
|
83
|
+
EOL: 'style/eol-last',
|
|
84
|
+
LF: 'style/linebreak-style',
|
|
85
|
+
TRAIL: 'style/no-trailing-spaces',
|
|
86
|
+
INDENT: 'jsonc/indent',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Do any of the provided files match a js file?
|
|
93
|
+
* @param {string[]} files The list of files to check
|
|
94
|
+
* @param {string[]} extensions The allowed file extensions
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
*/
|
|
97
|
+
function filterMatchingFiles(files, extensions) {
|
|
98
|
+
return files.filter((file) => {
|
|
99
|
+
if (file.endsWith('*')) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const extension of extensions) {
|
|
104
|
+
if (file.endsWith(extension)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Do any of the provided files match a js file?
|
|
114
|
+
* @param {string[] | null | undefined} one The list of files to check
|
|
115
|
+
* @param {string[] | null | undefined} two The allowed file extensions
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
function equal(one, two) {
|
|
119
|
+
if (one == null && two == null) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (one == null || two == null) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
Array.isArray(one) === false ||
|
|
128
|
+
Array.isArray(two) === false ||
|
|
129
|
+
one.length !== two.length
|
|
130
|
+
) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const element of one) {
|
|
135
|
+
if (two.includes(element) === false) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {import('eslint').Linter.FlatConfig[]} configs [description]
|
|
145
|
+
* @param {import('eslint').Linter.FlatConfig} config [description]
|
|
146
|
+
*/
|
|
147
|
+
function addOrMergeConfig(configs, config) {
|
|
148
|
+
for (const previous of configs) {
|
|
149
|
+
if (equal(
|
|
150
|
+
/** @type {string[]} */ (previous.files),
|
|
151
|
+
/** @type {string[]} */ (config.files),
|
|
152
|
+
)) {
|
|
153
|
+
previous.rules = {
|
|
154
|
+
...previous.rules,
|
|
155
|
+
...config.rules,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
configs.push(config);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {import('node:url').URL} root root url
|
|
166
|
+
* @returns {Promise<import('eslint').Linter.FlatConfig[]>}
|
|
167
|
+
*/
|
|
168
|
+
export async function createEditorOverrides(root) {
|
|
169
|
+
const eslintConfigPath = resolve(fileURLToPath(root), 'eslint.config.js');
|
|
170
|
+
const path = await findUp(eslintConfigPath, '.editorconfig');
|
|
171
|
+
if (path == null) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const editorconfig = parseBuffer(await readFile(path));
|
|
176
|
+
|
|
177
|
+
/** @type {import('eslint').Linter.FlatConfig[]} */
|
|
178
|
+
const configs = [];
|
|
179
|
+
for (const [ name, body ] of editorconfig) {
|
|
180
|
+
if (name === null) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const allFiles = expandGlob(name);
|
|
185
|
+
if (allFiles == null) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const [ extensions, rules ] of extToRules) {
|
|
190
|
+
const files = filterMatchingFiles(allFiles, extensions);
|
|
191
|
+
if (files.length === 0) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (files.includes('*')) {
|
|
196
|
+
addOrMergeConfig(configs, {
|
|
197
|
+
files: extensions.map((extension) => `**/*${extension}`),
|
|
198
|
+
rules: convert(body, rules),
|
|
199
|
+
});
|
|
200
|
+
} else {
|
|
201
|
+
addOrMergeConfig(configs, {
|
|
202
|
+
files: files.map((file) => `**/${file}`),
|
|
203
|
+
rules: convert(body, rules),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return configs;
|
|
210
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getPackageJson } from './package.js';
|
|
2
|
+
import { expandGlob } from './expand-glob.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string[]} files [description]
|
|
6
|
+
* @param {string} type [description]
|
|
7
|
+
* @param {string} target [description]
|
|
8
|
+
* @returns {string[]}
|
|
9
|
+
*/
|
|
10
|
+
function getFiles(files, type, target) {
|
|
11
|
+
if (type === target) {
|
|
12
|
+
return [ '**/*.js', '**/*.jsx', ...files ];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return files;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get a list of globs for common js files
|
|
20
|
+
* @returns {Promise<string[]>}
|
|
21
|
+
*/
|
|
22
|
+
export async function getJsonFiles() {
|
|
23
|
+
return [ '**/*.json', '**/*.jsonc', '**/*.json5' ];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get a list of globs for common js files
|
|
28
|
+
* @param {import('node:url').URL} root root url
|
|
29
|
+
* @returns {Promise<string[]>}
|
|
30
|
+
*/
|
|
31
|
+
export async function getCommonFiles(root) {
|
|
32
|
+
const { type = 'commonjs' } = await getPackageJson(root) ?? {};
|
|
33
|
+
|
|
34
|
+
return getFiles([ '**/*.cjs' ], type, 'commonjs');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get a list of globs for module files
|
|
39
|
+
* @param {import('node:url').URL} root root url
|
|
40
|
+
* @returns {Promise<string[]>}
|
|
41
|
+
*/
|
|
42
|
+
export async function getModuleFiles(root) {
|
|
43
|
+
const { type = 'commonjs' } = await getPackageJson(root) ?? {};
|
|
44
|
+
return getFiles([ '**/*.mjs' ], type, 'module');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get a list of globs for typescript files
|
|
49
|
+
* @returns {Promise<string[]>}
|
|
50
|
+
*/
|
|
51
|
+
export async function getTypescriptFiles() {
|
|
52
|
+
return [ '**/*.ts', '**/*.tsx' ];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get a list of possible example files
|
|
57
|
+
* @returns {Promise<string[]>}
|
|
58
|
+
*/
|
|
59
|
+
export async function getExampleFiles() {
|
|
60
|
+
return [
|
|
61
|
+
...expandGlob('**/example.{ts,js,tsx,jsx,mts,cts,mjs,cjs}'),
|
|
62
|
+
...expandGlob('**/{example,examples}/*'),
|
|
63
|
+
...expandGlob('**/{example,examples}/**/*'),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const globNumRegex = /\{\d+\.\.\d+\}/;
|
|
2
|
+
const globEachRegex = /\{[\w.-]+(?:,[\w.-]+)+\}/;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} input The glob to process
|
|
6
|
+
* @returns {string[]}
|
|
7
|
+
*/
|
|
8
|
+
export function expandGlob(input) {
|
|
9
|
+
const globNumMatch = input.match(globNumRegex);
|
|
10
|
+
if (typeof globNumMatch?.index === 'number') {
|
|
11
|
+
const first = input.slice(0, globNumMatch.index);
|
|
12
|
+
const each = globNumMatch[0];
|
|
13
|
+
const last = input.slice(globNumMatch.index + each.length);
|
|
14
|
+
|
|
15
|
+
const [ a, b ] = each
|
|
16
|
+
.slice(1, -1)
|
|
17
|
+
.split('..')
|
|
18
|
+
.map((number) => Number.parseInt(number, 10));
|
|
19
|
+
|
|
20
|
+
const start = a > b ? b : a;
|
|
21
|
+
const end = a < b ? b : a;
|
|
22
|
+
|
|
23
|
+
const output = [];
|
|
24
|
+
for (let i = start; i <= end; i++) {
|
|
25
|
+
output.push(`${first}${i}${last}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return output.map(expandGlob).flat();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const globEachMatch = input.match(globEachRegex);
|
|
32
|
+
if (typeof globEachMatch?.index === 'number') {
|
|
33
|
+
const first = input.slice(0, globEachMatch.index);
|
|
34
|
+
const each = globEachMatch[0];
|
|
35
|
+
const last = input.slice(globEachMatch.index + each.length);
|
|
36
|
+
|
|
37
|
+
const output = each
|
|
38
|
+
.slice(1, -1).split(',')
|
|
39
|
+
.map((part) => `${first}${part}${last}`);
|
|
40
|
+
|
|
41
|
+
if (output.length > 1) {
|
|
42
|
+
return output.map(expandGlob).flat();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return [ input ];
|
|
49
|
+
}
|