eslint-interactive 8.1.0
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/LICENSE +25 -0
- package/README.md +147 -0
- package/bin/eslint-interactive.js +10 -0
- package/dist/action/apply-suggestions.d.ts +6 -0
- package/dist/action/apply-suggestions.d.ts.map +1 -0
- package/dist/action/apply-suggestions.js +28 -0
- package/dist/action/apply-suggestions.js.map +1 -0
- package/dist/action/disable-per-file.d.ts +6 -0
- package/dist/action/disable-per-file.d.ts.map +1 -0
- package/dist/action/disable-per-file.js +8 -0
- package/dist/action/disable-per-file.js.map +1 -0
- package/dist/action/disable-per-line.d.ts +6 -0
- package/dist/action/disable-per-line.d.ts.map +1 -0
- package/dist/action/disable-per-line.js +8 -0
- package/dist/action/disable-per-line.js.map +1 -0
- package/dist/action/fix.d.ts +6 -0
- package/dist/action/fix.d.ts.map +1 -0
- package/dist/action/fix.js +6 -0
- package/dist/action/fix.js.map +1 -0
- package/dist/action/index.d.ts +7 -0
- package/dist/action/index.d.ts.map +1 -0
- package/dist/action/index.js +7 -0
- package/dist/action/index.js.map +1 -0
- package/dist/action/make-fixable-and-fix.d.ts +6 -0
- package/dist/action/make-fixable-and-fix.d.ts.map +1 -0
- package/dist/action/make-fixable-and-fix.js +28 -0
- package/dist/action/make-fixable-and-fix.js.map +1 -0
- package/dist/action/print-result-details.d.ts +5 -0
- package/dist/action/print-result-details.d.ts.map +1 -0
- package/dist/action/print-result-details.js +12 -0
- package/dist/action/print-result-details.js.map +1 -0
- package/dist/cli/log.d.ts +6 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +9 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/ora.d.ts +4 -0
- package/dist/cli/ora.d.ts.map +1 -0
- package/dist/cli/ora.js +23 -0
- package/dist/cli/ora.js.map +1 -0
- package/dist/cli/package.d.ts +2 -0
- package/dist/cli/package.d.ts.map +1 -0
- package/dist/cli/package.js +6 -0
- package/dist/cli/package.js.map +1 -0
- package/dist/cli/parse-argv.d.ts +4 -0
- package/dist/cli/parse-argv.d.ts.map +1 -0
- package/dist/cli/parse-argv.js +50 -0
- package/dist/cli/parse-argv.js.map +1 -0
- package/dist/cli/prompt.d.ts +53 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +154 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/run.d.ts +8 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +46 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/core-worker.d.ts +21 -0
- package/dist/core-worker.d.ts.map +1 -0
- package/dist/core-worker.js +52 -0
- package/dist/core-worker.js.map +1 -0
- package/dist/core.d.ts +84 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +196 -0
- package/dist/core.js.map +1 -0
- package/dist/formatter/colors.d.ts +4 -0
- package/dist/formatter/colors.d.ts.map +1 -0
- package/dist/formatter/colors.js +5 -0
- package/dist/formatter/colors.js.map +1 -0
- package/dist/formatter/format-by-files.d.ts +3 -0
- package/dist/formatter/format-by-files.d.ts.map +1 -0
- package/dist/formatter/format-by-files.js +41 -0
- package/dist/formatter/format-by-files.js.map +1 -0
- package/dist/formatter/format-by-rules.d.ts +3 -0
- package/dist/formatter/format-by-rules.d.ts.map +1 -0
- package/dist/formatter/format-by-rules.js +34 -0
- package/dist/formatter/format-by-rules.js.map +1 -0
- package/dist/formatter/index.d.ts +4 -0
- package/dist/formatter/index.d.ts.map +1 -0
- package/dist/formatter/index.js +7 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/take-rule-statistics.d.ts +18 -0
- package/dist/formatter/take-rule-statistics.d.ts.map +1 -0
- package/dist/formatter/take-rule-statistics.js +51 -0
- package/dist/formatter/take-rule-statistics.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/fix/apply-auto-fixes.d.ts +8 -0
- package/dist/plugin/fix/apply-auto-fixes.d.ts.map +1 -0
- package/dist/plugin/fix/apply-auto-fixes.js +8 -0
- package/dist/plugin/fix/apply-auto-fixes.js.map +1 -0
- package/dist/plugin/fix/apply-suggestions.d.ts +11 -0
- package/dist/plugin/fix/apply-suggestions.d.ts.map +1 -0
- package/dist/plugin/fix/apply-suggestions.js +25 -0
- package/dist/plugin/fix/apply-suggestions.js.map +1 -0
- package/dist/plugin/fix/disable-per-file.d.ts +10 -0
- package/dist/plugin/fix/disable-per-file.d.ts.map +1 -0
- package/dist/plugin/fix/disable-per-file.js +39 -0
- package/dist/plugin/fix/disable-per-file.js.map +1 -0
- package/dist/plugin/fix/disable-per-line.d.ts +10 -0
- package/dist/plugin/fix/disable-per-line.d.ts.map +1 -0
- package/dist/plugin/fix/disable-per-line.js +54 -0
- package/dist/plugin/fix/disable-per-line.js.map +1 -0
- package/dist/plugin/fix/index.d.ts +6 -0
- package/dist/plugin/fix/index.d.ts.map +1 -0
- package/dist/plugin/fix/index.js +6 -0
- package/dist/plugin/fix/index.js.map +1 -0
- package/dist/plugin/fix/make-fixable-and-fix.d.ts +12 -0
- package/dist/plugin/fix/make-fixable-and-fix.d.ts.map +1 -0
- package/dist/plugin/fix/make-fixable-and-fix.js +61 -0
- package/dist/plugin/fix/make-fixable-and-fix.js.map +1 -0
- package/dist/plugin/fix-rule.d.ts +10 -0
- package/dist/plugin/fix-rule.d.ts.map +1 -0
- package/dist/plugin/fix-rule.js +124 -0
- package/dist/plugin/fix-rule.js.map +1 -0
- package/dist/plugin/index.d.ts +49 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +11 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/prefer-addition-shorthand-rule.d.ts +7 -0
- package/dist/plugin/prefer-addition-shorthand-rule.d.ts.map +1 -0
- package/dist/plugin/prefer-addition-shorthand-rule.js +54 -0
- package/dist/plugin/prefer-addition-shorthand-rule.js.map +1 -0
- package/dist/plugin/rule-fixer.d.ts +80 -0
- package/dist/plugin/rule-fixer.d.ts.map +1 -0
- package/dist/plugin/rule-fixer.js +118 -0
- package/dist/plugin/rule-fixer.js.map +1 -0
- package/dist/scene/check-results.d.ts +21 -0
- package/dist/scene/check-results.d.ts.map +1 -0
- package/dist/scene/check-results.js +22 -0
- package/dist/scene/check-results.js.map +1 -0
- package/dist/scene/index.d.ts +25 -0
- package/dist/scene/index.d.ts.map +1 -0
- package/dist/scene/index.js +6 -0
- package/dist/scene/index.js.map +1 -0
- package/dist/scene/lint.d.ts +8 -0
- package/dist/scene/lint.d.ts.map +1 -0
- package/dist/scene/lint.js +31 -0
- package/dist/scene/lint.js.map +1 -0
- package/dist/scene/select-action.d.ts +20 -0
- package/dist/scene/select-action.d.ts.map +1 -0
- package/dist/scene/select-action.js +46 -0
- package/dist/scene/select-action.js.map +1 -0
- package/dist/scene/select-rule-ids.d.ts +15 -0
- package/dist/scene/select-rule-ids.d.ts.map +1 -0
- package/dist/scene/select-rule-ids.js +10 -0
- package/dist/scene/select-rule-ids.js.map +1 -0
- package/dist/tsconfig.src.tsbuildinfo +1 -0
- package/dist/util/array.d.ts +3 -0
- package/dist/util/array.d.ts.map +1 -0
- package/dist/util/array.js +14 -0
- package/dist/util/array.js.map +1 -0
- package/dist/util/cache.d.ts +5 -0
- package/dist/util/cache.d.ts.map +1 -0
- package/dist/util/cache.js +13 -0
- package/dist/util/cache.js.map +1 -0
- package/dist/util/eslint.d.ts +68 -0
- package/dist/util/eslint.d.ts.map +1 -0
- package/dist/util/eslint.js +147 -0
- package/dist/util/eslint.js.map +1 -0
- package/dist/util/filter-script.d.ts +6 -0
- package/dist/util/filter-script.d.ts.map +1 -0
- package/dist/util/filter-script.js +39 -0
- package/dist/util/filter-script.js.map +1 -0
- package/dist/util/type-check.d.ts +3 -0
- package/dist/util/type-check.d.ts.map +1 -0
- package/dist/util/type-check.js +8 -0
- package/dist/util/type-check.js.map +1 -0
- package/package.json +93 -0
- package/src/action/apply-suggestions.ts +40 -0
- package/src/action/disable-per-file.ts +16 -0
- package/src/action/disable-per-line.ts +16 -0
- package/src/action/fix.ts +14 -0
- package/src/action/index.ts +6 -0
- package/src/action/make-fixable-and-fix.ts +40 -0
- package/src/action/print-result-details.ts +18 -0
- package/src/cli/log.ts +11 -0
- package/src/cli/ora.ts +25 -0
- package/src/cli/package.ts +9 -0
- package/src/cli/parse-argv.ts +52 -0
- package/src/cli/prompt.ts +205 -0
- package/src/cli/run.ts +50 -0
- package/src/core-worker.ts +66 -0
- package/src/core.ts +240 -0
- package/src/formatter/colors.ts +5 -0
- package/src/formatter/format-by-files.ts +48 -0
- package/src/formatter/format-by-rules.ts +37 -0
- package/src/formatter/index.ts +9 -0
- package/src/formatter/take-rule-statistics.ts +66 -0
- package/src/index.ts +4 -0
- package/src/plugin/fix/apply-auto-fixes.ts +13 -0
- package/src/plugin/fix/apply-suggestions.ts +44 -0
- package/src/plugin/fix/disable-per-file.ts +53 -0
- package/src/plugin/fix/disable-per-line.ts +65 -0
- package/src/plugin/fix/index.ts +13 -0
- package/src/plugin/fix/make-fixable-and-fix.ts +77 -0
- package/src/plugin/fix-rule.ts +142 -0
- package/src/plugin/index.ts +66 -0
- package/src/plugin/prefer-addition-shorthand-rule.ts +56 -0
- package/src/plugin/rule-fixer.ts +147 -0
- package/src/scene/check-results.ts +43 -0
- package/src/scene/index.ts +18 -0
- package/src/scene/lint.ts +41 -0
- package/src/scene/select-action.ts +70 -0
- package/src/scene/select-rule-ids.ts +24 -0
- package/src/typings/cachedir.d.ts +5 -0
- package/src/typings/node-pager.d.ts +4 -0
- package/src/util/array.ts +16 -0
- package/src/util/cache.ts +11 -0
- package/src/util/eslint.ts +162 -0
- package/src/util/filter-script.ts +45 -0
- package/src/util/type-check.ts +8 -0
- package/static/example-filter-script.js +49 -0
- package/static/example-fixable-maker-script.js +47 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ESLint, Rule } from 'eslint';
|
|
2
|
+
import {
|
|
3
|
+
createFixToApplyAutoFixes,
|
|
4
|
+
createFixToApplySuggestions,
|
|
5
|
+
createFixToDisablePerFile,
|
|
6
|
+
createFixToDisablePerLine,
|
|
7
|
+
createFixToMakeFixableAndFix,
|
|
8
|
+
} from './fix/index.js';
|
|
9
|
+
import { ruleFixer } from './rule-fixer.js';
|
|
10
|
+
import { Fix, FixContext } from './index.js';
|
|
11
|
+
|
|
12
|
+
export const OVERLAPPED_PROBLEM_MESSAGE = 'overlapped';
|
|
13
|
+
|
|
14
|
+
// from: https://github.com/eslint/eslint/blob/58840ac844a61c72eabb603ecfb761812b82a7ed/lib/linter/report-translator.js#L136
|
|
15
|
+
function compareFixesByRange(a: Rule.Fix, b: Rule.Fix): number {
|
|
16
|
+
return a.range[0] - b.range[0] || a.range[1] - b.range[1];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @file The rule to do the fix.
|
|
21
|
+
* The fix function returns the `Rule.Fix` that describes how to fix the code.
|
|
22
|
+
* To apply the fix to your code, you need to use ESLint's API to apply the `Rule.Fix`.
|
|
23
|
+
*
|
|
24
|
+
* However, there is no dedicated API in ESLint to apply `Rule.Fix` (there is an internal API
|
|
25
|
+
* called `SourceCodeFixer`,but it is not exposed to the public). For now, the only way
|
|
26
|
+
* to apply `Rule.Fix` is to report a fixable problem from a rule and fix it
|
|
27
|
+
* with `ESLint.outputFixes`.
|
|
28
|
+
*
|
|
29
|
+
* This module is a rule that executes a fix function and converts the return value
|
|
30
|
+
* to a fixable problem.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const fileStatusMap = new Map<string, { isAlreadyFixed: boolean; hasOverlappedProblem: boolean }>();
|
|
34
|
+
|
|
35
|
+
function createFixes(context: Rule.RuleContext, ruleOption: FixRuleOption, fixer: Rule.RuleFixer): Rule.Fix[] | null {
|
|
36
|
+
const { fix, results, ruleIds } = ruleOption;
|
|
37
|
+
const result = results.find((result) => result.filePath === context.getFilename());
|
|
38
|
+
if (!result) return null;
|
|
39
|
+
const messages = result.messages.filter((message) => message.ruleId && ruleIds.includes(message.ruleId));
|
|
40
|
+
|
|
41
|
+
const fixContext: FixContext = {
|
|
42
|
+
filename: context.getFilename(),
|
|
43
|
+
sourceCode: context.getSourceCode(),
|
|
44
|
+
messages,
|
|
45
|
+
ruleIds,
|
|
46
|
+
fixer,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let fixes: Rule.Fix[] = [];
|
|
50
|
+
if (fix.name === 'applyAutoFixes') {
|
|
51
|
+
fixes = createFixToApplyAutoFixes(fixContext, fix.args);
|
|
52
|
+
} else if (fix.name === 'disablePerLine') {
|
|
53
|
+
fixes = createFixToDisablePerLine(fixContext, fix.args);
|
|
54
|
+
} else if (fix.name === 'disablePerFile') {
|
|
55
|
+
fixes = createFixToDisablePerFile(fixContext, fix.args);
|
|
56
|
+
} else if (fix.name === 'applySuggestions') {
|
|
57
|
+
fixes = createFixToApplySuggestions(fixContext, fix.args);
|
|
58
|
+
} else if (fix.name === 'makeFixableAndFix') {
|
|
59
|
+
fixes = createFixToMakeFixableAndFix(fixContext, fix.args);
|
|
60
|
+
} else {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-explicit-any
|
|
62
|
+
throw new Error(`Unknown fix: ${(fix as any).name}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (fixes.length === 0) return null;
|
|
66
|
+
return fixes;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type FixRuleOption = {
|
|
70
|
+
ruleIds: string[];
|
|
71
|
+
results: ESLint.LintResult[];
|
|
72
|
+
fix: Fix;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const fixRule: Rule.RuleModule = {
|
|
76
|
+
meta: {
|
|
77
|
+
fixable: 'code',
|
|
78
|
+
},
|
|
79
|
+
create(context: Rule.RuleContext) {
|
|
80
|
+
// TODO: refactor
|
|
81
|
+
return {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
83
|
+
Program: () => {
|
|
84
|
+
const filename = context.getFilename();
|
|
85
|
+
|
|
86
|
+
// 🤯🤯🤯 THIS IS SUPER HACK!!! 🤯🤯🤯
|
|
87
|
+
// fix するとコードが変わり、また別の lint エラーが発生する可能性があるため、eslint は `context.report` で
|
|
88
|
+
// 報告されたエラーの fix がすべて終わったら、再び create を呼び出し、また `context.report` で fix 可能なエラーが
|
|
89
|
+
// 報告されないかを確認する仕様になっている (これは `context.report` で fix 可能なものがなくなるまで続く)。
|
|
90
|
+
// そのため、ここでは2回目以降 create が呼び出された時に、誤って再び fix してしまわないよう、fix 済み
|
|
91
|
+
// であれば early return するようにしている。
|
|
92
|
+
const status = fileStatusMap.get(filename) ?? { isAlreadyFixed: false, hasOverlappedProblem: false };
|
|
93
|
+
if (status.isAlreadyFixed) {
|
|
94
|
+
fileStatusMap.delete(filename); // Reset just in case.
|
|
95
|
+
if (status.hasOverlappedProblem) {
|
|
96
|
+
context.report({
|
|
97
|
+
loc: {
|
|
98
|
+
// The location is required, so set dummy values.
|
|
99
|
+
line: 0,
|
|
100
|
+
column: 0,
|
|
101
|
+
},
|
|
102
|
+
message: OVERLAPPED_PROBLEM_MESSAGE,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ruleOption = context.options[0] as FixRuleOption;
|
|
109
|
+
const newStatus = {
|
|
110
|
+
isAlreadyFixed: true,
|
|
111
|
+
hasOverlappedProblem: false,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const fixes = createFixes(context, ruleOption, ruleFixer);
|
|
115
|
+
if (!fixes) return;
|
|
116
|
+
fixes.sort(compareFixesByRange);
|
|
117
|
+
|
|
118
|
+
let lastPos = 0;
|
|
119
|
+
const fixesToReport: Rule.Fix[] = [];
|
|
120
|
+
for (const fix of fixes) {
|
|
121
|
+
if (fix.range[0] < lastPos) {
|
|
122
|
+
newStatus.hasOverlappedProblem = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
fixesToReport.push(fix);
|
|
126
|
+
lastPos = fix.range[1];
|
|
127
|
+
}
|
|
128
|
+
fileStatusMap.set(filename, newStatus);
|
|
129
|
+
|
|
130
|
+
context.report({
|
|
131
|
+
loc: {
|
|
132
|
+
// The location is required, so set dummy values.
|
|
133
|
+
line: 0,
|
|
134
|
+
column: 0,
|
|
135
|
+
},
|
|
136
|
+
message: `fix`,
|
|
137
|
+
fix: () => fixesToReport,
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Linter, Rule, SourceCode } from 'eslint';
|
|
2
|
+
import { fixRule, type FixRuleOption } from './fix-rule.js';
|
|
3
|
+
import {
|
|
4
|
+
type FixableMaker,
|
|
5
|
+
type SuggestionFilter,
|
|
6
|
+
type FixToApplySuggestionsArgs,
|
|
7
|
+
type FixToDisablePerFileArgs,
|
|
8
|
+
type FixToDisablePerLineArgs,
|
|
9
|
+
type FixToMakeFixableAndFixArgs,
|
|
10
|
+
type FixToApplyAutoFixesArgs,
|
|
11
|
+
} from './fix/index.js';
|
|
12
|
+
import { preferAdditionShorthandRule } from './prefer-addition-shorthand-rule.js';
|
|
13
|
+
|
|
14
|
+
export { OVERLAPPED_PROBLEM_MESSAGE } from './fix-rule.js';
|
|
15
|
+
|
|
16
|
+
export { FixRuleOption, type FixableMaker, type SuggestionFilter };
|
|
17
|
+
|
|
18
|
+
export const eslintInteractivePlugin = {
|
|
19
|
+
rules: {
|
|
20
|
+
'fix': fixRule,
|
|
21
|
+
// for test
|
|
22
|
+
'prefer-addition-shorthand': preferAdditionShorthandRule,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The type representing the fix to do.
|
|
28
|
+
*/
|
|
29
|
+
export type Fix =
|
|
30
|
+
| { name: 'applyAutoFixes'; args: FixArg<'applyAutoFixes'> }
|
|
31
|
+
| { name: 'disablePerLine'; args: FixArg<'disablePerLine'> }
|
|
32
|
+
| { name: 'disablePerFile'; args: FixArg<'disablePerFile'> }
|
|
33
|
+
| { name: 'applySuggestions'; args: FixArg<'applySuggestions'> }
|
|
34
|
+
| { name: 'makeFixableAndFix'; args: FixArg<'makeFixableAndFix'> };
|
|
35
|
+
|
|
36
|
+
/** For test */
|
|
37
|
+
export type FixName = 'applyAutoFixes' | 'disablePerLine' | 'disablePerFile' | 'applySuggestions' | 'makeFixableAndFix';
|
|
38
|
+
|
|
39
|
+
/** For test */
|
|
40
|
+
export type FixArg<T extends FixName> = T extends 'applyAutoFixes'
|
|
41
|
+
? FixToApplyAutoFixesArgs
|
|
42
|
+
: T extends 'disablePerLine'
|
|
43
|
+
? FixToDisablePerLineArgs
|
|
44
|
+
: T extends 'disablePerFile'
|
|
45
|
+
? FixToDisablePerFileArgs
|
|
46
|
+
: T extends 'applySuggestions'
|
|
47
|
+
? FixToApplySuggestionsArgs
|
|
48
|
+
: T extends 'makeFixableAndFix'
|
|
49
|
+
? FixToMakeFixableAndFixArgs
|
|
50
|
+
: never;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The type representing the additional information for the fix.
|
|
54
|
+
*/
|
|
55
|
+
export type FixContext = {
|
|
56
|
+
filename: string;
|
|
57
|
+
sourceCode: SourceCode;
|
|
58
|
+
messages: Linter.LintMessage[];
|
|
59
|
+
ruleIds: string[];
|
|
60
|
+
fixer: Rule.RuleFixer;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The type representing the fix function.
|
|
65
|
+
*/
|
|
66
|
+
export type FixFunction<T> = (context: FixContext, args: T) => Rule.Fix[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file This is a rule for testing purposes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ApplyFixesRuleOption = Rule.Fix[];
|
|
8
|
+
|
|
9
|
+
export const preferAdditionShorthandRule: Rule.RuleModule = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
hasSuggestions: true,
|
|
14
|
+
},
|
|
15
|
+
create(context: Rule.RuleContext) {
|
|
16
|
+
return {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
18
|
+
AssignmentExpression: (node) => {
|
|
19
|
+
if (node.left.type !== 'Identifier') return;
|
|
20
|
+
const leftIdentifier = node.left;
|
|
21
|
+
if (node.right.type !== 'BinaryExpression') return;
|
|
22
|
+
const rightBinaryExpression = node.right;
|
|
23
|
+
if (rightBinaryExpression.operator !== '+') return;
|
|
24
|
+
if (rightBinaryExpression.left.type !== 'Identifier') return;
|
|
25
|
+
const rightIdentifier = rightBinaryExpression.left;
|
|
26
|
+
if (leftIdentifier.name !== rightIdentifier.name) return;
|
|
27
|
+
if (rightBinaryExpression.right.type !== 'Literal' || rightBinaryExpression.right.value !== 1) return;
|
|
28
|
+
|
|
29
|
+
context.report({
|
|
30
|
+
node,
|
|
31
|
+
message: 'The addition method is redundant.',
|
|
32
|
+
suggest: [
|
|
33
|
+
{
|
|
34
|
+
desc: 'Use `val += 1` instead.',
|
|
35
|
+
fix: function (fixer) {
|
|
36
|
+
return fixer.replaceText(node, `${leftIdentifier.name} += 1`);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
desc: 'Use `val++` instead.',
|
|
41
|
+
fix: function (fixer) {
|
|
42
|
+
return fixer.replaceText(node, `${leftIdentifier.name}++`);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
desc: 'Use `++val` instead.',
|
|
47
|
+
fix: function (fixer) {
|
|
48
|
+
return fixer.replaceText(node, `++${leftIdentifier.name}`);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// copy from: https://github.com/eslint/eslint/blob/219aecb78bc646d44bad27dc775a9b3d3dc58232/lib/linter/rule-fixer.js
|
|
2
|
+
// ESLint does not export the RuleFixer. So, I've copied the implementation.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview An object that creates fix commands for rules.
|
|
6
|
+
* @author Nicholas C. Zakas
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
//------------------------------------------------------------------------------
|
|
10
|
+
// Requirements
|
|
11
|
+
//------------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
// none!
|
|
14
|
+
|
|
15
|
+
//------------------------------------------------------------------------------
|
|
16
|
+
// Helpers
|
|
17
|
+
//------------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
import { AST, Rule } from 'eslint';
|
|
20
|
+
import type { Node } from 'estree';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a fix command that inserts text at the specified index in the source text.
|
|
24
|
+
* @param {int} index The 0-based index at which to insert the new text.
|
|
25
|
+
* @param {string} text The text to insert.
|
|
26
|
+
* @returns {Object} The fix command.
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
function insertTextAt(index: number, text: string): Rule.Fix {
|
|
30
|
+
return {
|
|
31
|
+
range: [index, index],
|
|
32
|
+
text,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//------------------------------------------------------------------------------
|
|
37
|
+
// Public Interface
|
|
38
|
+
//------------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates code fixing commands for rules.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** @type {import('eslint').Rule.RuleFixer} */
|
|
45
|
+
const ruleFixer = Object.freeze({
|
|
46
|
+
/**
|
|
47
|
+
* Creates a fix command that inserts text after the given node or token.
|
|
48
|
+
* The fix is not applied until applyFixes() is called.
|
|
49
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to insert after.
|
|
50
|
+
* @param {string} text The text to insert.
|
|
51
|
+
* @returns {Object} The fix command.
|
|
52
|
+
*/
|
|
53
|
+
insertTextAfter(nodeOrToken: Node | AST.Token, text: string): Rule.Fix {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
55
|
+
return this.insertTextAfterRange(nodeOrToken.range!, text);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a fix command that inserts text after the specified range in the source text.
|
|
60
|
+
* The fix is not applied until applyFixes() is called.
|
|
61
|
+
* @param {int[]} range The range to replace, first item is start of range, second
|
|
62
|
+
* is end of range.
|
|
63
|
+
* @param {string} text The text to insert.
|
|
64
|
+
* @returns {Object} The fix command.
|
|
65
|
+
*/
|
|
66
|
+
insertTextAfterRange(range: AST.Range, text: string): Rule.Fix {
|
|
67
|
+
return insertTextAt(range[1], text);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a fix command that inserts text before the given node or token.
|
|
72
|
+
* The fix is not applied until applyFixes() is called.
|
|
73
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to insert before.
|
|
74
|
+
* @param {string} text The text to insert.
|
|
75
|
+
* @returns {Object} The fix command.
|
|
76
|
+
*/
|
|
77
|
+
insertTextBefore(nodeOrToken: Node | AST.Token, text: string): Rule.Fix {
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
79
|
+
return this.insertTextBeforeRange(nodeOrToken.range!, text);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a fix command that inserts text before the specified range in the source text.
|
|
84
|
+
* The fix is not applied until applyFixes() is called.
|
|
85
|
+
* @param {int[]} range The range to replace, first item is start of range, second
|
|
86
|
+
* is end of range.
|
|
87
|
+
* @param {string} text The text to insert.
|
|
88
|
+
* @returns {Object} The fix command.
|
|
89
|
+
*/
|
|
90
|
+
insertTextBeforeRange(range: AST.Range, text: string): Rule.Fix {
|
|
91
|
+
return insertTextAt(range[0], text);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates a fix command that replaces text at the node or token.
|
|
96
|
+
* The fix is not applied until applyFixes() is called.
|
|
97
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
|
|
98
|
+
* @param {string} text The text to insert.
|
|
99
|
+
* @returns {Object} The fix command.
|
|
100
|
+
*/
|
|
101
|
+
replaceText(nodeOrToken: Node | AST.Token, text: string): Rule.Fix {
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
103
|
+
return this.replaceTextRange(nodeOrToken.range!, text);
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Creates a fix command that replaces text at the specified range in the source text.
|
|
108
|
+
* The fix is not applied until applyFixes() is called.
|
|
109
|
+
* @param {int[]} range The range to replace, first item is start of range, second
|
|
110
|
+
* is end of range.
|
|
111
|
+
* @param {string} text The text to insert.
|
|
112
|
+
* @returns {Object} The fix command.
|
|
113
|
+
*/
|
|
114
|
+
replaceTextRange(range: AST.Range, text: string): Rule.Fix {
|
|
115
|
+
return {
|
|
116
|
+
range,
|
|
117
|
+
text,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Creates a fix command that removes the node or token from the source.
|
|
123
|
+
* The fix is not applied until applyFixes() is called.
|
|
124
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
|
|
125
|
+
* @returns {Object} The fix command.
|
|
126
|
+
*/
|
|
127
|
+
remove(nodeOrToken: Node | AST.Token): Rule.Fix {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
129
|
+
return this.removeRange(nodeOrToken.range!);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Creates a fix command that removes the specified range of text from the source.
|
|
134
|
+
* The fix is not applied until applyFixes() is called.
|
|
135
|
+
* @param {int[]} range The range to remove, first item is start of range, second
|
|
136
|
+
* is end of range.
|
|
137
|
+
* @returns {Object} The fix command.
|
|
138
|
+
*/
|
|
139
|
+
removeRange(range: AST.Range): Rule.Fix {
|
|
140
|
+
return {
|
|
141
|
+
range,
|
|
142
|
+
text: '',
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export { ruleFixer };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ESLint } from 'eslint';
|
|
2
|
+
import { undoingSpinner } from '../cli/ora.js';
|
|
3
|
+
import { Action, promptToInputWhatToDoNext } from '../cli/prompt.js';
|
|
4
|
+
import { Undo } from '../core.js';
|
|
5
|
+
import { NextScene } from './index.js';
|
|
6
|
+
|
|
7
|
+
export type CheckResultsArgs = {
|
|
8
|
+
/** The lint results of the project */
|
|
9
|
+
results: ESLint.LintResult[];
|
|
10
|
+
/** The rule ids that are in the `results`. */
|
|
11
|
+
ruleIdsInResults: string[];
|
|
12
|
+
/** The rule ids to perform the action. */
|
|
13
|
+
selectedRuleIds: string[];
|
|
14
|
+
/** The function to execute undo. */
|
|
15
|
+
undo: Undo;
|
|
16
|
+
/** The selected actions. */
|
|
17
|
+
selectedAction: Action;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run the scene where a user check the fix results.
|
|
22
|
+
*/
|
|
23
|
+
export async function checkResults({
|
|
24
|
+
results,
|
|
25
|
+
ruleIdsInResults,
|
|
26
|
+
selectedRuleIds,
|
|
27
|
+
undo,
|
|
28
|
+
selectedAction,
|
|
29
|
+
}: CheckResultsArgs): Promise<NextScene> {
|
|
30
|
+
const nextStep = await promptToInputWhatToDoNext();
|
|
31
|
+
if (nextStep === 'exit') return { name: 'exit' };
|
|
32
|
+
if (nextStep === 'undoTheFix') {
|
|
33
|
+
await undoingSpinner(async () => undo());
|
|
34
|
+
return {
|
|
35
|
+
name: 'selectAction',
|
|
36
|
+
args: { results, ruleIdsInResults, selectedRuleIds, initialAction: selectedAction },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
console.log();
|
|
40
|
+
console.log('─'.repeat(process.stdout.columns));
|
|
41
|
+
console.log();
|
|
42
|
+
return { name: 'lint' };
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CheckResultsArgs } from './check-results.js';
|
|
2
|
+
import { selectAction, type SelectActionArgs } from './select-action.js';
|
|
3
|
+
import { selectRuleIds, type SelectRuleIdsArgs } from './select-rule-ids.js';
|
|
4
|
+
|
|
5
|
+
export { selectAction, type SelectActionArgs, selectRuleIds, type SelectRuleIdsArgs };
|
|
6
|
+
export { lint } from './lint.js';
|
|
7
|
+
export { checkResults } from './check-results.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The return type when calling a scene function.
|
|
11
|
+
* Indicates which scene to jump to next.
|
|
12
|
+
*/
|
|
13
|
+
export type NextScene =
|
|
14
|
+
| { name: 'lint' }
|
|
15
|
+
| { name: 'selectRuleIds'; args: SelectRuleIdsArgs }
|
|
16
|
+
| { name: 'selectAction'; args: SelectActionArgs }
|
|
17
|
+
| { name: 'checkResults'; args: CheckResultsArgs }
|
|
18
|
+
| { name: 'exit' };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Remote } from 'comlink';
|
|
2
|
+
import { warn } from '../cli/log.js';
|
|
3
|
+
import { lintingSpinner } from '../cli/ora.js';
|
|
4
|
+
import { SerializableCore } from '../core-worker.js';
|
|
5
|
+
import { unique } from '../util/array.js';
|
|
6
|
+
import { notEmpty } from '../util/type-check.js';
|
|
7
|
+
import { NextScene } from './index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Run the scene to lint.
|
|
11
|
+
*/
|
|
12
|
+
export async function lint(core: Remote<SerializableCore>): Promise<NextScene> {
|
|
13
|
+
const results = await lintingSpinner(async () => core.lint());
|
|
14
|
+
console.log();
|
|
15
|
+
|
|
16
|
+
const ruleIdsInResults = unique(
|
|
17
|
+
results
|
|
18
|
+
.flatMap((result) => result.messages)
|
|
19
|
+
.flatMap((message) => message.ruleId)
|
|
20
|
+
.filter(notEmpty),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (ruleIdsInResults.length === 0) {
|
|
24
|
+
console.log('💚 No error found.');
|
|
25
|
+
return { name: 'exit' };
|
|
26
|
+
}
|
|
27
|
+
console.log(await core.formatResultSummary(results));
|
|
28
|
+
|
|
29
|
+
const hasESLintCoreProblems = results.flatMap((result) => result.messages).some((message) => message.ruleId === null);
|
|
30
|
+
if (hasESLintCoreProblems) {
|
|
31
|
+
warn(
|
|
32
|
+
'ESLint Core Problems are found. ' +
|
|
33
|
+
'The problems cannot be fixed by eslint-interactive. ' +
|
|
34
|
+
'Check the details of the problem and fix it. ' +
|
|
35
|
+
'This is usually caused by the invalid eslintrc or the invalid syntax of the linted code.',
|
|
36
|
+
);
|
|
37
|
+
console.log(await core.formatResultDetails(results, [null]));
|
|
38
|
+
}
|
|
39
|
+
console.log();
|
|
40
|
+
return { name: 'selectRuleIds', args: { results, ruleIdsInResults } };
|
|
41
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Remote } from 'comlink';
|
|
2
|
+
import { ESLint } from 'eslint';
|
|
3
|
+
import {
|
|
4
|
+
doApplySuggestionsAction,
|
|
5
|
+
doDisablePerFileAction,
|
|
6
|
+
doDisablePerLineAction,
|
|
7
|
+
doFixAction,
|
|
8
|
+
doMakeFixableAndFixAction,
|
|
9
|
+
doPrintResultDetailsAction,
|
|
10
|
+
} from '../action/index.js';
|
|
11
|
+
import { Action, promptToInputAction } from '../cli/prompt.js';
|
|
12
|
+
import { SerializableCore } from '../core-worker.js';
|
|
13
|
+
import { Undo } from '../core.js';
|
|
14
|
+
import { unreachable } from '../util/type-check.js';
|
|
15
|
+
import { NextScene } from './index.js';
|
|
16
|
+
|
|
17
|
+
export type SelectActionArgs = {
|
|
18
|
+
/** The lint results of the project */
|
|
19
|
+
results: ESLint.LintResult[];
|
|
20
|
+
/** The rule ids that are in the `results`. */
|
|
21
|
+
ruleIdsInResults: string[];
|
|
22
|
+
/** The rule ids to perform the action. */
|
|
23
|
+
selectedRuleIds: string[];
|
|
24
|
+
/** The action to be initially selected. */
|
|
25
|
+
initialAction?: Action;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run the scene where a user select the action to be performed for the problems of selected rules.
|
|
30
|
+
*/
|
|
31
|
+
export async function selectAction(
|
|
32
|
+
core: Remote<SerializableCore>,
|
|
33
|
+
{ results, ruleIdsInResults, selectedRuleIds, initialAction }: SelectActionArgs,
|
|
34
|
+
): Promise<NextScene> {
|
|
35
|
+
const selectedAction = await promptToInputAction(results, selectedRuleIds, initialAction);
|
|
36
|
+
|
|
37
|
+
const selectRuleIdsScene: NextScene = { name: 'selectRuleIds', args: { results, ruleIdsInResults } };
|
|
38
|
+
const selectActionScene: NextScene = { name: 'selectAction', args: { results, ruleIdsInResults, selectedRuleIds } };
|
|
39
|
+
|
|
40
|
+
function createCheckResultsScene(undo: Undo): NextScene {
|
|
41
|
+
return {
|
|
42
|
+
name: 'checkResults',
|
|
43
|
+
args: { results, ruleIdsInResults, selectedRuleIds, undo, selectedAction },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (selectedAction === 'reselectRules') return selectRuleIdsScene;
|
|
48
|
+
|
|
49
|
+
if (selectedAction === 'printResultDetails') {
|
|
50
|
+
await doPrintResultDetailsAction(core, results, selectedRuleIds);
|
|
51
|
+
return selectActionScene;
|
|
52
|
+
} else if (selectedAction === 'applyAutoFixes') {
|
|
53
|
+
const undo = await doFixAction(core, results, selectedRuleIds);
|
|
54
|
+
return createCheckResultsScene(undo);
|
|
55
|
+
} else if (selectedAction === 'disablePerLine') {
|
|
56
|
+
const undo = await doDisablePerLineAction(core, results, selectedRuleIds);
|
|
57
|
+
return createCheckResultsScene(undo);
|
|
58
|
+
} else if (selectedAction === 'disablePerFile') {
|
|
59
|
+
const undo = await doDisablePerFileAction(core, results, selectedRuleIds);
|
|
60
|
+
return createCheckResultsScene(undo);
|
|
61
|
+
} else if (selectedAction === 'applySuggestions') {
|
|
62
|
+
const undo = await doApplySuggestionsAction(core, results, selectedRuleIds);
|
|
63
|
+
return createCheckResultsScene(undo);
|
|
64
|
+
} else if (selectedAction === 'makeFixableAndFix') {
|
|
65
|
+
const undo = await doMakeFixableAndFixAction(core, results, selectedRuleIds);
|
|
66
|
+
return createCheckResultsScene(undo);
|
|
67
|
+
}
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
69
|
+
return unreachable(`unknown action: ${selectedAction}`);
|
|
70
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Remote } from 'comlink';
|
|
2
|
+
import { ESLint } from 'eslint';
|
|
3
|
+
import { promptToInputRuleIds } from '../cli/prompt.js';
|
|
4
|
+
import { SerializableCore } from '../core-worker.js';
|
|
5
|
+
import { selectAction } from './select-action.js';
|
|
6
|
+
import { NextScene } from './index.js';
|
|
7
|
+
|
|
8
|
+
export type SelectRuleIdsArgs = {
|
|
9
|
+
/** The lint results of the project */
|
|
10
|
+
results: ESLint.LintResult[];
|
|
11
|
+
/** The rule ids that are in the `results`. */
|
|
12
|
+
ruleIdsInResults: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run the scene where a user select rule ids.
|
|
17
|
+
*/
|
|
18
|
+
export async function selectRuleIds(
|
|
19
|
+
core: Remote<SerializableCore>,
|
|
20
|
+
{ results, ruleIdsInResults }: SelectRuleIdsArgs,
|
|
21
|
+
): Promise<NextScene> {
|
|
22
|
+
const selectedRuleIds = await promptToInputRuleIds(ruleIdsInResults);
|
|
23
|
+
return await selectAction(core, { results, ruleIdsInResults, selectedRuleIds });
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function unique<T>(array: T[]): T[] {
|
|
2
|
+
return [...new Set(array)];
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function groupBy<T, K>(array: T[], toKey: (item: T) => K): Map<K, T[]> {
|
|
6
|
+
const map = new Map<K, T[]>();
|
|
7
|
+
|
|
8
|
+
for (const item of array) {
|
|
9
|
+
const key = toKey(item);
|
|
10
|
+
const oldValue = map.get(key);
|
|
11
|
+
const newValue = oldValue ? [...oldValue, item] : [item];
|
|
12
|
+
map.set(key, newValue);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return map;
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
3
|
+
import cachedir = require('cachedir');
|
|
4
|
+
import { VERSION } from '../cli/package.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get the path of cache directory for eslint-interactive.
|
|
8
|
+
*/
|
|
9
|
+
export function getCacheDir(): string {
|
|
10
|
+
return join(cachedir('eslint-interactive'), VERSION);
|
|
11
|
+
}
|