eslint-interactive 12.0.0 → 13.0.1
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/README.md +34 -58
- package/bin/eslint-interactive.js +8 -11
- package/dist/action/index.d.ts +0 -2
- package/dist/action/index.d.ts.map +1 -1
- package/dist/action/index.js +0 -2
- package/dist/action/index.js.map +1 -1
- package/dist/action/print-result-details.d.ts.map +1 -1
- package/dist/action/print-result-details.js +6 -1
- package/dist/action/print-result-details.js.map +1 -1
- package/dist/cli/log.d.ts +3 -3
- package/dist/cli/log.d.ts.map +1 -1
- package/dist/cli/log.js +5 -4
- package/dist/cli/log.js.map +1 -1
- package/dist/cli/parse-argv.d.ts +2 -22
- package/dist/cli/parse-argv.d.ts.map +1 -1
- package/dist/cli/parse-argv.js +37 -43
- package/dist/cli/parse-argv.js.map +1 -1
- package/dist/cli/prompt.d.ts +1 -11
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +3 -44
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +6 -20
- package/dist/cli/run.js.map +1 -1
- package/dist/core-worker.d.ts +2 -4
- package/dist/core-worker.d.ts.map +1 -1
- package/dist/core-worker.js +3 -10
- package/dist/core-worker.js.map +1 -1
- package/dist/core.d.ts +8 -5
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +61 -47
- package/dist/core.js.map +1 -1
- package/dist/eslint/linter.d.ts +2 -3
- package/dist/eslint/linter.d.ts.map +1 -1
- package/dist/eslint/linter.js.map +1 -1
- package/dist/eslint/source-code-fixer.d.ts +1 -5
- package/dist/eslint/source-code-fixer.d.ts.map +1 -1
- package/dist/fix/make-fixable-and-fix.d.ts +2 -3
- package/dist/fix/make-fixable-and-fix.d.ts.map +1 -1
- package/dist/fix/make-fixable-and-fix.js +14 -41
- package/dist/fix/make-fixable-and-fix.js.map +1 -1
- package/dist/formatter/format-by-files.d.ts.map +1 -1
- package/dist/formatter/format-by-files.js +1 -0
- package/dist/formatter/format-by-files.js.map +1 -1
- package/dist/formatter/format-by-rules.d.ts +6 -1
- package/dist/formatter/format-by-rules.d.ts.map +1 -1
- package/dist/formatter/format-by-rules.js +7 -2
- package/dist/formatter/format-by-rules.js.map +1 -1
- package/dist/formatter/index.d.ts +3 -1
- package/dist/formatter/index.d.ts.map +1 -1
- package/dist/formatter/index.js +3 -2
- package/dist/formatter/index.js.map +1 -1
- package/dist/formatter/sort-rule-statistics.d.ts +5 -0
- package/dist/formatter/sort-rule-statistics.d.ts.map +1 -0
- package/dist/formatter/sort-rule-statistics.js +34 -0
- package/dist/formatter/sort-rule-statistics.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +14 -0
- package/dist/plugin.js.map +1 -1
- package/dist/scene/lint.d.ts.map +1 -1
- package/dist/scene/lint.js +13 -14
- package/dist/scene/lint.js.map +1 -1
- package/dist/scene/select-action.d.ts.map +1 -1
- package/dist/scene/select-action.js +3 -9
- package/dist/scene/select-action.js.map +1 -1
- package/dist/type.d.ts +12 -0
- package/dist/type.d.ts.map +1 -0
- package/dist/type.js +2 -0
- package/dist/type.js.map +1 -0
- package/dist/util/eslint.d.ts.map +1 -1
- package/dist/util/eslint.js +1 -1
- package/dist/util/eslint.js.map +1 -1
- package/dist/util/type-check.d.ts +0 -3
- package/dist/util/type-check.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/action/index.ts +0 -2
- package/src/action/print-result-details.ts +7 -1
- package/src/cli/log.ts +5 -4
- package/src/cli/parse-argv.ts +45 -61
- package/src/cli/prompt.ts +4 -48
- package/src/cli/run.ts +6 -22
- package/src/core-worker.ts +6 -21
- package/src/core.ts +52 -50
- package/src/eslint/linter.ts +2 -3
- package/src/fix/make-fixable-and-fix.ts +15 -47
- package/src/formatter/format-by-files.ts +1 -0
- package/src/formatter/format-by-rules.ts +18 -3
- package/src/formatter/index.ts +8 -3
- package/src/formatter/sort-rule-statistics.ts +35 -0
- package/src/index.ts +2 -2
- package/src/plugin.ts +14 -0
- package/src/scene/lint.ts +16 -18
- package/src/scene/select-action.ts +1 -8
- package/src/type.ts +13 -0
- package/src/util/eslint.ts +2 -2
- package/src/util/type-check.ts +0 -4
- package/bin/_eslint-interactive.js +0 -9
- package/dist/action/apply-suggestions.d.ts +0 -6
- package/dist/action/apply-suggestions.d.ts.map +0 -1
- package/dist/action/apply-suggestions.js +0 -27
- package/dist/action/apply-suggestions.js.map +0 -1
- package/dist/action/make-fixable-and-fix.d.ts +0 -6
- package/dist/action/make-fixable-and-fix.d.ts.map +0 -1
- package/dist/action/make-fixable-and-fix.js +0 -27
- package/dist/action/make-fixable-and-fix.js.map +0 -1
- package/dist/config.d.ts +0 -47
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -98
- package/dist/config.js.map +0 -1
- package/dist/eslint/use-at-your-own-risk.d.ts +0 -5
- package/dist/eslint/use-at-your-own-risk.d.ts.map +0 -1
- package/dist/eslint/use-at-your-own-risk.js +0 -6
- package/dist/eslint/use-at-your-own-risk.js.map +0 -1
- package/dist/util/file-system.d.ts +0 -5
- package/dist/util/file-system.d.ts.map +0 -1
- package/dist/util/file-system.js +0 -10
- package/dist/util/file-system.js.map +0 -1
- package/dist/util/filter-script.d.ts +0 -6
- package/dist/util/filter-script.d.ts.map +0 -1
- package/dist/util/filter-script.js +0 -38
- package/dist/util/filter-script.js.map +0 -1
- package/src/action/apply-suggestions.ts +0 -39
- package/src/action/make-fixable-and-fix.ts +0 -39
- package/src/config.ts +0 -142
- package/src/eslint/use-at-your-own-risk.js +0 -6
- package/src/util/file-system.ts +0 -10
- package/src/util/filter-script.ts +0 -45
- package/static/example-filter-script.js +0 -40
- package/static/example-fixable-maker-script.js +0 -38
package/src/cli/parse-argv.ts
CHANGED
|
@@ -1,46 +1,23 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Config, SortField, SortOrder } from '../type.js';
|
|
3
3
|
import { VERSION } from './package.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
formatterName: string | undefined;
|
|
8
|
-
quiet: boolean | undefined;
|
|
9
|
-
useEslintrc: boolean | undefined;
|
|
10
|
-
overrideConfigFile: string | undefined;
|
|
11
|
-
extensions: string[] | undefined;
|
|
12
|
-
rulePaths: string[] | undefined;
|
|
13
|
-
ignorePath: string | undefined;
|
|
14
|
-
cache: boolean | undefined;
|
|
15
|
-
cacheLocation: string | undefined;
|
|
16
|
-
resolvePluginsRelativeTo: string | undefined;
|
|
17
|
-
flags: string[] | undefined;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/** Default CLI Options */
|
|
21
|
-
export const cliOptionsDefaults = {
|
|
22
|
-
formatterName: 'stylish',
|
|
23
|
-
quiet: false,
|
|
24
|
-
useEslintrc: true,
|
|
25
|
-
cache: false,
|
|
26
|
-
} satisfies DeepPartial<ParsedCLIOptions>;
|
|
5
|
+
const VALID_SORT_FIELDS: readonly SortField[] = ['rule', 'error', 'warning', 'fixable', 'suggestions'];
|
|
6
|
+
const VALID_SORT_ORDERS: readonly SortOrder[] = ['asc', 'desc'];
|
|
27
7
|
|
|
28
8
|
/** Parse CLI options */
|
|
29
|
-
export function parseArgv(argv: string[]):
|
|
9
|
+
export function parseArgv(argv: string[]): Config {
|
|
30
10
|
const options = {
|
|
31
|
-
'eslintrc': { type: 'boolean', default: cliOptionsDefaults.useEslintrc },
|
|
32
11
|
'config': { type: 'string', short: 'c' },
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'ignore-path': { type: 'string' },
|
|
37
|
-
'format': { type: 'string', default: cliOptionsDefaults.formatterName },
|
|
38
|
-
'quiet': { type: 'boolean', default: cliOptionsDefaults.quiet },
|
|
39
|
-
'cache': { type: 'boolean', default: cliOptionsDefaults.cache },
|
|
12
|
+
'format': { type: 'string' },
|
|
13
|
+
'quiet': { type: 'boolean' },
|
|
14
|
+
'cache': { type: 'boolean' },
|
|
40
15
|
'cache-location': { type: 'string' },
|
|
41
16
|
'version': { type: 'boolean' },
|
|
42
17
|
'help': { type: 'boolean' },
|
|
43
18
|
'flag': { type: 'string', multiple: true },
|
|
19
|
+
'sort': { type: 'string' },
|
|
20
|
+
'sort-order': { type: 'string' },
|
|
44
21
|
} as const;
|
|
45
22
|
|
|
46
23
|
const { values, positionals } = parseArgs({
|
|
@@ -51,6 +28,20 @@ export function parseArgv(argv: string[]): ParsedCLIOptions {
|
|
|
51
28
|
options,
|
|
52
29
|
});
|
|
53
30
|
|
|
31
|
+
// Validate `--sort` and `--sort-order`
|
|
32
|
+
if (values.sort !== undefined && !VALID_SORT_FIELDS.includes(values.sort as SortField)) {
|
|
33
|
+
console.error(`Invalid --sort value: "${values.sort}". Must be one of: ${VALID_SORT_FIELDS.join(', ')}`);
|
|
34
|
+
// eslint-disable-next-line n/no-process-exit
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
if (values['sort-order'] !== undefined && !VALID_SORT_ORDERS.includes(values['sort-order'] as SortOrder)) {
|
|
38
|
+
console.error(
|
|
39
|
+
`Invalid --sort-order value: "${values['sort-order']}". Must be one of: ${VALID_SORT_ORDERS.join(', ')}`,
|
|
40
|
+
);
|
|
41
|
+
// eslint-disable-next-line n/no-process-exit
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
54
45
|
if (values.version) {
|
|
55
46
|
console.log(VERSION);
|
|
56
47
|
// eslint-disable-next-line n/no-process-exit
|
|
@@ -58,53 +49,46 @@ export function parseArgv(argv: string[]): ParsedCLIOptions {
|
|
|
58
49
|
}
|
|
59
50
|
|
|
60
51
|
if (values.help) {
|
|
61
|
-
console.log(
|
|
62
|
-
|
|
52
|
+
console.log(
|
|
53
|
+
`
|
|
54
|
+
eslint-interactive [...patterns]
|
|
63
55
|
|
|
64
56
|
Options:
|
|
65
|
-
--help
|
|
66
|
-
--version
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
--
|
|
70
|
-
--
|
|
71
|
-
--
|
|
72
|
-
--
|
|
73
|
-
--
|
|
74
|
-
--
|
|
75
|
-
--cache Only check changed files [boolean] [default: false]
|
|
76
|
-
--cache-location Path to the cache file or directory [string]
|
|
77
|
-
--flag Enable a feature flag (requires ESLint v9.6.0+) [array]
|
|
57
|
+
--help Show help
|
|
58
|
+
--version Show version number
|
|
59
|
+
-c, --config <path> Use this configuration, overriding config options if present
|
|
60
|
+
--format <nameOrPath> Specify the format to be used for the "Display problem messages" action
|
|
61
|
+
--quiet Report errors only
|
|
62
|
+
--cache Only check changed files
|
|
63
|
+
--cache-location <path> Path to the cache file or directory
|
|
64
|
+
--flag <name> Enable a feature flag (requires ESLint v9.6.0+)
|
|
65
|
+
--sort <field> Sort rules by: rule, error, warning, fixable, suggestions
|
|
66
|
+
--sort-order <direction> Sort direction: asc, desc (default: desc for counts, asc for rule)
|
|
78
67
|
|
|
79
68
|
Examples:
|
|
80
|
-
eslint-interactive
|
|
81
|
-
eslint-interactive
|
|
82
|
-
eslint-interactive '
|
|
83
|
-
eslint-interactive
|
|
84
|
-
eslint-interactive
|
|
85
|
-
|
|
86
|
-
|
|
69
|
+
eslint-interactive Lint all files in the project
|
|
70
|
+
eslint-interactive src test Lint specified directories
|
|
71
|
+
eslint-interactive 'src/**/*.{ts,tsx,vue}' Lint with glob pattern
|
|
72
|
+
eslint-interactive --sort error Sort rules by error count (descending)
|
|
73
|
+
eslint-interactive --sort rule Sort rules by rule name (ascending)
|
|
74
|
+
`.trim(),
|
|
75
|
+
);
|
|
87
76
|
// eslint-disable-next-line n/no-process-exit
|
|
88
77
|
process.exit(0);
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
const patterns = positionals.map((pattern) => pattern.toString());
|
|
92
|
-
const rulePaths = values.rulesdir?.map((rulePath) => rulePath.toString());
|
|
93
|
-
const extensions = values.ext?.map((extension) => extension.toString()).flatMap((extension) => extension.split(','));
|
|
94
81
|
const formatterName = values.format;
|
|
95
82
|
|
|
96
83
|
return {
|
|
97
84
|
patterns,
|
|
98
85
|
formatterName,
|
|
99
86
|
quiet: values.quiet,
|
|
100
|
-
useEslintrc: values.eslintrc,
|
|
101
87
|
overrideConfigFile: values.config,
|
|
102
|
-
extensions,
|
|
103
|
-
rulePaths,
|
|
104
|
-
ignorePath: values['ignore-path'],
|
|
105
88
|
cache: values.cache,
|
|
106
89
|
cacheLocation: values['cache-location'],
|
|
107
|
-
resolvePluginsRelativeTo: values['resolve-plugins-relative-to'],
|
|
108
90
|
flags: values.flag,
|
|
91
|
+
sort: values.sort as SortField | undefined,
|
|
92
|
+
sortOrder: values['sort-order'] as SortOrder | undefined,
|
|
109
93
|
};
|
|
110
94
|
}
|
package/src/cli/prompt.ts
CHANGED
|
@@ -20,8 +20,7 @@ export type Action =
|
|
|
20
20
|
| 'disablePerLine'
|
|
21
21
|
| 'disablePerFile'
|
|
22
22
|
| 'convertErrorToWarningPerFile'
|
|
23
|
-
| '
|
|
24
|
-
| 'makeFixableAndFix'
|
|
23
|
+
| 'relintAndReselectRules'
|
|
25
24
|
| 'reselectRules';
|
|
26
25
|
|
|
27
26
|
/**
|
|
@@ -78,9 +77,8 @@ export async function promptToInputAction(
|
|
|
78
77
|
const foldedStatistics = ruleStatistics.reduce(
|
|
79
78
|
(a, b) => ({
|
|
80
79
|
isFixableCount: a.isFixableCount + b.isFixableCount,
|
|
81
|
-
hasSuggestionsCount: a.hasSuggestionsCount + b.hasSuggestionsCount,
|
|
82
80
|
}),
|
|
83
|
-
{ isFixableCount: 0
|
|
81
|
+
{ isFixableCount: 0 },
|
|
84
82
|
);
|
|
85
83
|
|
|
86
84
|
const choices = [
|
|
@@ -89,16 +87,8 @@ export async function promptToInputAction(
|
|
|
89
87
|
{ name: 'disablePerLine', message: '🔧 Disable per line' },
|
|
90
88
|
{ name: 'disablePerFile', message: '🔧 Disable per file' },
|
|
91
89
|
{ name: 'convertErrorToWarningPerFile', message: '🔧 Convert error to warning per file' },
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
message: '🔧 Apply suggestions (experimental, for experts)',
|
|
95
|
-
disabled: foldedStatistics.hasSuggestionsCount === 0,
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
name: 'makeFixableAndFix',
|
|
99
|
-
message: '🔧 Make forcibly fixable and run `eslint --fix` (experimental, for experts)',
|
|
100
|
-
},
|
|
101
|
-
{ name: 'reselectRules', message: '↩️ Reselect rules' },
|
|
90
|
+
{ name: 'relintAndReselectRules', message: '↩️ Go back (with re-lint)' },
|
|
91
|
+
{ name: 'reselectRules', message: '↩️ Go back' },
|
|
102
92
|
];
|
|
103
93
|
|
|
104
94
|
const { action } = await prompt<{
|
|
@@ -199,37 +189,3 @@ export async function promptToInputWhatToDoNext(): Promise<NextStep> {
|
|
|
199
189
|
]);
|
|
200
190
|
return nextStep;
|
|
201
191
|
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Ask the user if they want to reuse the filter script.
|
|
205
|
-
* @returns If it reuses, `true`, if not, `false`.
|
|
206
|
-
*/
|
|
207
|
-
export async function promptToInputReuseFilterScript(): Promise<boolean> {
|
|
208
|
-
const { reuseFilterScript } = await prompt<{ reuseFilterScript: boolean }>([
|
|
209
|
-
{
|
|
210
|
-
name: 'reuseFilterScript',
|
|
211
|
-
type: 'confirm',
|
|
212
|
-
message: 'Do you want to reuse a previously edited filter script?',
|
|
213
|
-
initial: true,
|
|
214
|
-
onCancel,
|
|
215
|
-
},
|
|
216
|
-
]);
|
|
217
|
-
return reuseFilterScript;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Ask the user if they want to reuse the script.
|
|
222
|
-
* @returns If it reuses, `true`, if not, `false`.
|
|
223
|
-
*/
|
|
224
|
-
export async function promptToInputReuseScript(): Promise<boolean> {
|
|
225
|
-
const { reuseScript } = await prompt<{ reuseScript: boolean }>([
|
|
226
|
-
{
|
|
227
|
-
name: 'reuseScript',
|
|
228
|
-
type: 'confirm',
|
|
229
|
-
message: 'Do you want to reuse a previously edited script?',
|
|
230
|
-
initial: true,
|
|
231
|
-
onCancel,
|
|
232
|
-
},
|
|
233
|
-
]);
|
|
234
|
-
return reuseScript;
|
|
235
|
-
}
|
package/src/cli/run.ts
CHANGED
|
@@ -3,13 +3,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { Worker } from 'node:worker_threads';
|
|
4
4
|
import { wrap } from 'comlink';
|
|
5
5
|
import nodeEndpoint from 'comlink/dist/esm/node-adapter.mjs';
|
|
6
|
-
import isInstalledGlobally from 'is-installed-globally';
|
|
7
6
|
import terminalLink from 'terminal-link';
|
|
8
|
-
import { warn } from '../cli/log.js';
|
|
9
7
|
import { parseArgv } from '../cli/parse-argv.js';
|
|
10
|
-
import { translateCLIOptions } from '../config.js';
|
|
11
8
|
import type { SerializableCore } from '../core-worker.js';
|
|
12
|
-
import { shouldUseFlatConfig } from '../eslint/use-at-your-own-risk.js';
|
|
13
9
|
import type { NextScene } from '../scene/index.js';
|
|
14
10
|
import { checkResults, lint, selectAction, selectRuleIds } from '../scene/index.js';
|
|
15
11
|
|
|
@@ -21,30 +17,18 @@ export type Options = {
|
|
|
21
17
|
* Run eslint-interactive.
|
|
22
18
|
*/
|
|
23
19
|
export async function run(options: Options) {
|
|
24
|
-
|
|
25
|
-
warn(
|
|
26
|
-
'eslint-interactive is installed globally. ' +
|
|
27
|
-
'The globally installed eslint-interactive is not officially supported because some features do not work. ' +
|
|
28
|
-
'It is recommended to install eslint-interactive locally.',
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
const parsedCLIOptions = parseArgv(options.argv);
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
33
|
-
const usingFlatConfig = await shouldUseFlatConfig();
|
|
34
|
-
const config = translateCLIOptions(parsedCLIOptions, usingFlatConfig ? 'flat' : 'eslintrc');
|
|
20
|
+
const config = parseArgv(options.argv);
|
|
35
21
|
|
|
36
22
|
// Directly executing the Core API will hog the main thread and halt the spinner.
|
|
37
23
|
// So we wrap it with comlink and run it on the Worker.
|
|
38
24
|
const worker = new Worker(join(dirname(fileURLToPath(import.meta.url)), '..', 'core-worker.js'), {
|
|
39
25
|
env: {
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
// - However, due to the specifications of Node.js, the decision does not work well on worker_threads.
|
|
45
|
-
// - So here we use a special environment variable to force the printing mode to be switched.
|
|
46
|
-
// ref: https://github.com/chalk/supports-color/issues/97, https://github.com/nodejs/node/issues/26946
|
|
26
|
+
// In worker threads, stdin is recognized as noTTY. Therefore, `util.styleText` and `terminalLink` disable colors and links.
|
|
27
|
+
// To work around this, we use environment variables to force colors and links to be enabled.
|
|
28
|
+
// ref: https://github.com/nodejs/node/issues/26946
|
|
29
|
+
FORCE_COLOR: process.stdin.isTTY ? '1' : '0',
|
|
47
30
|
FORCE_HYPERLINK: terminalLink.isSupported ? '1' : '0',
|
|
31
|
+
...process.env,
|
|
48
32
|
},
|
|
49
33
|
// NOTE: Pass CLI options (--unhandled-rejections=strict, etc.) to the worker
|
|
50
34
|
execArgv: process.execArgv,
|
package/src/core-worker.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { parentPort } from 'node:worker_threads';
|
|
2
2
|
import { expose, proxy } from 'comlink';
|
|
3
3
|
import nodeEndpoint from 'comlink/dist/esm/node-adapter.mjs';
|
|
4
|
-
import type { ESLint } from 'eslint';
|
|
5
|
-
import type { Config } from './config.js';
|
|
6
4
|
import { Core } from './core.js';
|
|
7
|
-
import type {
|
|
5
|
+
import type { Config } from './type.js';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* @file This is a wrapper module for using the Core API with comlink.
|
|
@@ -29,6 +27,11 @@ export class SerializableCore {
|
|
|
29
27
|
formatResultSummary(...args: Parameters<Core['formatResultSummary']>): ReturnType<Core['formatResultSummary']> {
|
|
30
28
|
return this.core.formatResultSummary(...args);
|
|
31
29
|
}
|
|
30
|
+
getSortedRuleIdsInResults(
|
|
31
|
+
...args: Parameters<Core['getSortedRuleIdsInResults']>
|
|
32
|
+
): ReturnType<Core['getSortedRuleIdsInResults']> {
|
|
33
|
+
return this.core.getSortedRuleIdsInResults(...args);
|
|
34
|
+
}
|
|
32
35
|
async formatResultDetails(...args: Parameters<Core['formatResultDetails']>): ReturnType<Core['formatResultDetails']> {
|
|
33
36
|
return this.core.formatResultDetails(...args);
|
|
34
37
|
}
|
|
@@ -46,24 +49,6 @@ export class SerializableCore {
|
|
|
46
49
|
): ReturnType<Core['convertErrorToWarningPerFile']> {
|
|
47
50
|
return proxy(await this.core.convertErrorToWarningPerFile(...args));
|
|
48
51
|
}
|
|
49
|
-
async applySuggestions(
|
|
50
|
-
results: ESLint.LintResult[],
|
|
51
|
-
ruleIds: string[],
|
|
52
|
-
filterScript: string,
|
|
53
|
-
): ReturnType<Core['applySuggestions']> {
|
|
54
|
-
// eslint-disable-next-line no-eval -- TODO: replace with a better solution
|
|
55
|
-
const filter = eval(filterScript) as SuggestionFilter;
|
|
56
|
-
return proxy(await this.core.applySuggestions(results, ruleIds, filter));
|
|
57
|
-
}
|
|
58
|
-
async makeFixableAndFix(
|
|
59
|
-
results: ESLint.LintResult[],
|
|
60
|
-
ruleIds: string[],
|
|
61
|
-
fixableMakerScript: string,
|
|
62
|
-
): ReturnType<Core['makeFixableAndFix']> {
|
|
63
|
-
// eslint-disable-next-line no-eval -- TODO: replace with a better solution
|
|
64
|
-
const fixableMaker = eval(fixableMakerScript) as FixableMaker;
|
|
65
|
-
return proxy(await this.core.makeFixableAndFix(results, ruleIds, fixableMaker));
|
|
66
|
-
}
|
|
67
52
|
}
|
|
68
53
|
|
|
69
54
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/src/core.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Rule } from 'eslint';
|
|
3
|
+
import { ESLint } from 'eslint';
|
|
3
4
|
import type { DescriptionPosition } from './cli/prompt.js';
|
|
4
|
-
import type { Config, NormalizedConfig } from './config.js';
|
|
5
|
-
import { normalizeConfig } from './config.js';
|
|
6
|
-
import { FlatESLint, LegacyESLint } from './eslint/use-at-your-own-risk.js';
|
|
7
5
|
import type { FixableMaker, FixContext, SuggestionFilter } from './fix/index.js';
|
|
8
6
|
import {
|
|
9
7
|
createFixToApplyAutoFixes,
|
|
@@ -14,8 +12,9 @@ import {
|
|
|
14
12
|
createFixToMakeFixableAndFix,
|
|
15
13
|
verifyAndFix,
|
|
16
14
|
} from './fix/index.js';
|
|
17
|
-
import { format } from './formatter/index.js';
|
|
15
|
+
import { format, sortRuleStatistics, takeRuleStatistics } from './formatter/index.js';
|
|
18
16
|
import { plugin } from './plugin.js';
|
|
17
|
+
import type { Config, SortField, SortOrder } from './type.js';
|
|
19
18
|
import { filterResultsByRuleId } from './util/eslint.js';
|
|
20
19
|
|
|
21
20
|
/**
|
|
@@ -37,49 +36,41 @@ export type Undo = () => Promise<void>;
|
|
|
37
36
|
* It uses ESLint's Node.js API to output a summary of problems, fix problems, apply suggestions, etc.
|
|
38
37
|
*/
|
|
39
38
|
export class Core {
|
|
40
|
-
readonly
|
|
41
|
-
readonly
|
|
39
|
+
readonly #cwd: string;
|
|
40
|
+
readonly #patterns: string[];
|
|
41
|
+
readonly #quiet: boolean;
|
|
42
|
+
readonly #formatterName: string | undefined;
|
|
43
|
+
readonly #sort: SortField | undefined;
|
|
44
|
+
readonly #sortOrder: SortOrder | undefined;
|
|
45
|
+
readonly #eslint: ESLint;
|
|
42
46
|
|
|
43
47
|
constructor(config: Config) {
|
|
44
|
-
this
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
this.#cwd = config.cwd ?? process.cwd();
|
|
49
|
+
this.#patterns = config.patterns;
|
|
50
|
+
this.#quiet = config.quiet ?? false;
|
|
51
|
+
this.#formatterName = config.formatterName;
|
|
52
|
+
this.#sort = config.sort;
|
|
53
|
+
this.#sortOrder = config.sortOrder;
|
|
54
|
+
|
|
55
|
+
// NOTE: Passing an option that does not exist to `new ESLint(...)` will throw an error.
|
|
56
|
+
// Therefore, only options supported by ESLint are extracted into the `eslintOptions` variable.
|
|
57
|
+
const { formatterName, patterns, quiet, sort, sortOrder, ...eslintOptions } = config;
|
|
58
|
+
const overrideConfigs =
|
|
59
|
+
Array.isArray(eslintOptions.overrideConfig) ? eslintOptions.overrideConfig
|
|
60
|
+
: eslintOptions.overrideConfig ? [eslintOptions.overrideConfig]
|
|
61
|
+
: [];
|
|
62
|
+
this.#eslint = new ESLint({
|
|
63
|
+
...eslintOptions,
|
|
64
|
+
overrideConfig: [
|
|
65
|
+
...overrideConfigs,
|
|
66
|
+
{
|
|
67
|
+
plugins: { 'eslint-interactive': plugin },
|
|
57
68
|
rules: {
|
|
58
|
-
...rest.overrideConfig?.rules,
|
|
59
69
|
'eslint-interactive/source-code-snatcher': 'error',
|
|
60
70
|
},
|
|
61
71
|
},
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
const { type, ...rest } = eslintOptions;
|
|
65
|
-
const overrideConfigs =
|
|
66
|
-
Array.isArray(rest.overrideConfig) ? rest.overrideConfig
|
|
67
|
-
: rest.overrideConfig ? [rest.overrideConfig]
|
|
68
|
-
: [];
|
|
69
|
-
this.eslint = new FlatESLint({
|
|
70
|
-
...rest,
|
|
71
|
-
overrideConfig: [
|
|
72
|
-
...overrideConfigs,
|
|
73
|
-
{
|
|
74
|
-
...rest.overrideConfig,
|
|
75
|
-
plugins: { 'eslint-interactive': plugin },
|
|
76
|
-
rules: {
|
|
77
|
-
'eslint-interactive/source-code-snatcher': 'error',
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
}
|
|
72
|
+
],
|
|
73
|
+
});
|
|
83
74
|
}
|
|
84
75
|
|
|
85
76
|
/**
|
|
@@ -87,8 +78,8 @@ export class Core {
|
|
|
87
78
|
* @returns The results of linting
|
|
88
79
|
*/
|
|
89
80
|
async lint(): Promise<ESLint.LintResult[]> {
|
|
90
|
-
let results = await this
|
|
91
|
-
if (this
|
|
81
|
+
let results = await this.#eslint.lintFiles(this.#patterns);
|
|
82
|
+
if (this.#quiet) results = ESLint.getErrorResults(results);
|
|
92
83
|
return results;
|
|
93
84
|
}
|
|
94
85
|
|
|
@@ -97,8 +88,20 @@ export class Core {
|
|
|
97
88
|
* @param results The lint results of the project to print summary
|
|
98
89
|
*/
|
|
99
90
|
formatResultSummary(results: ESLint.LintResult[]): string {
|
|
100
|
-
const rulesMeta = this
|
|
101
|
-
return format(results, { rulesMeta, cwd: this
|
|
91
|
+
const rulesMeta = this.#eslint.getRulesMetaForResults(results);
|
|
92
|
+
return format(results, { rulesMeta, cwd: this.#cwd }, { sort: this.#sort, sortOrder: this.#sortOrder });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns ruleIds from lint results, sorted according to the configured sort options.
|
|
97
|
+
* @param results The lint results of the project
|
|
98
|
+
*/
|
|
99
|
+
getSortedRuleIdsInResults(results: ESLint.LintResult[]): string[] {
|
|
100
|
+
let ruleStatistics = takeRuleStatistics(results);
|
|
101
|
+
if (this.#sort) {
|
|
102
|
+
ruleStatistics = sortRuleStatistics(ruleStatistics, this.#sort, this.#sortOrder);
|
|
103
|
+
}
|
|
104
|
+
return ruleStatistics.map((s) => s.ruleId);
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
/**
|
|
@@ -107,8 +110,7 @@ export class Core {
|
|
|
107
110
|
* @param ruleIds The rule ids to print details
|
|
108
111
|
*/
|
|
109
112
|
async formatResultDetails(results: ESLint.LintResult[], ruleIds: (string | null)[]): Promise<string> {
|
|
110
|
-
const
|
|
111
|
-
const formatter = await this.eslint.loadFormatter(formatterName);
|
|
113
|
+
const formatter = await this.#eslint.loadFormatter(this.#formatterName);
|
|
112
114
|
return formatter.format(filterResultsByRuleId(results, ruleIds));
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -207,7 +209,7 @@ export class Core {
|
|
|
207
209
|
if (!source) throw new Error('Source code is required to apply fixes.');
|
|
208
210
|
|
|
209
211
|
// eslint-disable-next-line no-await-in-loop
|
|
210
|
-
const fixedResult = await verifyAndFix(this
|
|
212
|
+
const fixedResult = await verifyAndFix(this.#eslint, source, filePath, ruleIds, fixCreator);
|
|
211
213
|
|
|
212
214
|
// Write the fixed source code to the file
|
|
213
215
|
if (fixedResult.fixed) {
|
|
@@ -218,7 +220,7 @@ export class Core {
|
|
|
218
220
|
|
|
219
221
|
return async () => {
|
|
220
222
|
const resultsToUndo = generateResultsToUndo(filteredResultsOfLint);
|
|
221
|
-
await
|
|
223
|
+
await ESLint.outputFixes(resultsToUndo);
|
|
222
224
|
};
|
|
223
225
|
}
|
|
224
226
|
}
|
package/src/eslint/linter.ts
CHANGED
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
* @author aladdin-add
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { Rule } from 'eslint';
|
|
10
|
+
import type { ESLint, Rule } from 'eslint';
|
|
11
11
|
import type { FixContext } from '../fix/index.js';
|
|
12
12
|
import { getLastSourceCode } from '../plugin.js';
|
|
13
13
|
import { ruleFixer } from './rule-fixer.js';
|
|
14
14
|
import { SourceCodeFixer } from './source-code-fixer.js';
|
|
15
|
-
import type { FlatESLint, LegacyESLint } from './use-at-your-own-risk.js';
|
|
16
15
|
|
|
17
16
|
const MAX_AUTOFIX_PASSES = 10;
|
|
18
17
|
|
|
@@ -27,7 +26,7 @@ type FixedResult = {
|
|
|
27
26
|
*/
|
|
28
27
|
|
|
29
28
|
export async function verifyAndFix(
|
|
30
|
-
eslint:
|
|
29
|
+
eslint: ESLint,
|
|
31
30
|
text: string,
|
|
32
31
|
filePath: string,
|
|
33
32
|
ruleIds: string[],
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import type { Linter, Rule, SourceCode } from 'eslint';
|
|
2
|
-
import { traverse } from 'estraverse';
|
|
3
|
-
import type { Node } from 'estree';
|
|
4
|
-
import { unreachable } from '../util/type-check.js';
|
|
1
|
+
import type { AST, Linter, Rule, SourceCode } from 'eslint';
|
|
5
2
|
import type { FixContext } from './index.js';
|
|
6
3
|
|
|
7
4
|
export type FixableMaker = (
|
|
8
5
|
message: Linter.LintMessage,
|
|
9
|
-
|
|
6
|
+
range: AST.Range,
|
|
10
7
|
context: FixContext,
|
|
11
8
|
) => Rule.Fix | null | undefined;
|
|
12
9
|
|
|
@@ -14,58 +11,29 @@ export type FixToMakeFixableAndFixArgs = {
|
|
|
14
11
|
fixableMaker: FixableMaker;
|
|
15
12
|
};
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
* Check the node is the source of the message.
|
|
19
|
-
*/
|
|
20
|
-
function isMessageSourceNode(sourceCode: SourceCode, node: Node, message: Linter.LintMessage): boolean {
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated -- TODO: Do not use `nodeType` in the future.
|
|
22
|
-
if (message.nodeType === undefined) return false;
|
|
23
|
-
|
|
24
|
-
// In some cases there may be no `endLine` or `endColumn`.
|
|
25
|
-
if (message.endLine === undefined || message.endColumn === undefined) return false;
|
|
26
|
-
// If `nodeType` is exists, `range` must be exists.
|
|
27
|
-
if (node.range === undefined) return unreachable();
|
|
28
|
-
|
|
14
|
+
function getMessageRange(sourceCode: SourceCode, message: Linter.LintMessage): AST.Range {
|
|
29
15
|
const index = sourceCode.getIndexFromLoc({
|
|
30
16
|
line: message.line,
|
|
31
17
|
// NOTE: `column` of `ESLint.LintMessage` is 1-based, but `column` of `ESTree.Position` is 0-based.
|
|
32
18
|
column: message.column - 1,
|
|
33
19
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getMessageToSourceNode(sourceCode: SourceCode, messages: Linter.LintMessage[]): Map<Linter.LintMessage, Node> {
|
|
46
|
-
const result = new Map<Linter.LintMessage, Node>();
|
|
47
|
-
|
|
48
|
-
traverse(sourceCode.ast, {
|
|
49
|
-
// Required to traverse extension nodes such as `JSXElement`.
|
|
50
|
-
fallback: 'iteration',
|
|
51
|
-
enter(node: Node) {
|
|
52
|
-
for (const message of messages) {
|
|
53
|
-
if (isMessageSourceNode(sourceCode, node, message)) {
|
|
54
|
-
result.set(message, node);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
return result;
|
|
20
|
+
if (message.endLine && message.endColumn) {
|
|
21
|
+
const endIndex = sourceCode.getIndexFromLoc({
|
|
22
|
+
line: message.endLine,
|
|
23
|
+
// NOTE: `column` of `ESLint.LintMessage` is 1-based, but `column` of `ESTree.Position` is 0-based.
|
|
24
|
+
column: message.endColumn - 1,
|
|
25
|
+
});
|
|
26
|
+
return [index, endIndex];
|
|
27
|
+
} else {
|
|
28
|
+
return [index, index];
|
|
29
|
+
}
|
|
60
30
|
}
|
|
61
31
|
|
|
62
32
|
function generateFixes(context: FixContext, args: FixToMakeFixableAndFixArgs): Rule.Fix[] {
|
|
63
|
-
const messageToNode = getMessageToSourceNode(context.sourceCode, context.messages);
|
|
64
|
-
|
|
65
33
|
const fixes: Rule.Fix[] = [];
|
|
66
34
|
for (const message of context.messages) {
|
|
67
|
-
const
|
|
68
|
-
const fix = args.fixableMaker(message,
|
|
35
|
+
const range = getMessageRange(context.sourceCode, message);
|
|
36
|
+
const fix = args.fixableMaker(message, range, context);
|
|
69
37
|
if (fix) fixes.push(fix);
|
|
70
38
|
}
|
|
71
39
|
return fixes;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
1
2
|
import { styleText } from 'node:util';
|
|
2
3
|
import type { ESLint } from 'eslint';
|
|
3
4
|
import terminalLink from 'terminal-link';
|
|
5
|
+
import type { SortField, SortOrder } from '../type.js';
|
|
4
6
|
import { ERROR_COLOR } from './colors.js';
|
|
5
7
|
import { formatTable } from './format-table.js';
|
|
8
|
+
import { sortRuleStatistics } from './sort-rule-statistics.js';
|
|
6
9
|
import { takeRuleStatistics } from './take-rule-statistics.js';
|
|
7
10
|
|
|
8
11
|
const headerRow = ['Rule', 'Error', 'Warning', 'is fixable', 'has suggestions'];
|
|
@@ -15,14 +18,26 @@ type Row = [
|
|
|
15
18
|
hasSuggestionsCount: string,
|
|
16
19
|
];
|
|
17
20
|
|
|
21
|
+
export type FormatByRulesSortOptions = {
|
|
22
|
+
sort?: SortField | undefined;
|
|
23
|
+
sortOrder?: SortOrder | undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
18
26
|
function numCell(num: number): string {
|
|
19
27
|
return num > 0 ? styleText([ERROR_COLOR, 'bold'], num.toString()) : num.toString();
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
export function formatByRules(
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
export function formatByRules(
|
|
31
|
+
results: ESLint.LintResult[],
|
|
32
|
+
data?: ESLint.LintResultData,
|
|
33
|
+
sortOptions?: FormatByRulesSortOptions,
|
|
34
|
+
): string {
|
|
35
|
+
let ruleStatistics = takeRuleStatistics(results);
|
|
36
|
+
if (sortOptions?.sort) {
|
|
37
|
+
ruleStatistics = sortRuleStatistics(ruleStatistics, sortOptions.sort, sortOptions.sortOrder);
|
|
38
|
+
}
|
|
25
39
|
|
|
40
|
+
const rows: Row[] = [];
|
|
26
41
|
ruleStatistics.forEach((ruleStatistic) => {
|
|
27
42
|
const { ruleId, errorCount, warningCount, isFixableCount, hasSuggestionsCount } = ruleStatistic;
|
|
28
43
|
const ruleMetaData = data?.rulesMeta[ruleId];
|