eslint-interactive 11.0.2 → 11.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/bin/eslint-interactive.js +1 -11
- package/dist/action/apply-suggestions.js.map +1 -1
- package/dist/action/disable-per-file.js.map +1 -1
- package/dist/action/disable-per-line.js.map +1 -1
- package/dist/action/make-fixable-and-fix.js.map +1 -1
- package/dist/action/print-result-details.d.ts.map +1 -1
- package/dist/action/print-result-details.js +2 -2
- package/dist/action/print-result-details.js.map +1 -1
- package/dist/cli/pager.js.map +1 -1
- package/dist/cli/parse-argv.js +1 -1
- package/dist/cli/parse-argv.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +2 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core.d.ts +8 -9
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +36 -21
- package/dist/core.js.map +1 -1
- package/dist/eslint/linter.d.ts +2 -2
- package/dist/eslint/linter.d.ts.map +1 -1
- package/dist/eslint/linter.js +9 -4
- package/dist/eslint/linter.js.map +1 -1
- package/dist/eslint/report-translator.js.map +1 -1
- package/dist/eslint/rule-fixer.d.ts.map +1 -1
- package/dist/eslint/source-code-fixer.d.ts.map +1 -1
- package/dist/eslint/source-code-fixer.js.map +1 -1
- package/dist/eslint/use-at-your-own-risk.d.ts +4 -0
- package/dist/eslint/use-at-your-own-risk.d.ts.map +1 -0
- package/dist/eslint/use-at-your-own-risk.js +6 -0
- package/dist/eslint/use-at-your-own-risk.js.map +1 -0
- package/dist/fix/apply-suggestions.js.map +1 -1
- package/dist/fix/disable-per-file.d.ts.map +1 -1
- package/dist/fix/disable-per-file.js +6 -6
- package/dist/fix/disable-per-file.js.map +1 -1
- package/dist/fix/disable-per-line.d.ts.map +1 -1
- package/dist/fix/disable-per-line.js +18 -7
- package/dist/fix/disable-per-line.js.map +1 -1
- package/dist/fix/make-fixable-and-fix.js.map +1 -1
- package/dist/formatter/format-by-files.js.map +1 -1
- package/dist/formatter/format-by-rules.d.ts.map +1 -1
- package/dist/formatter/format-by-rules.js +1 -3
- package/dist/formatter/format-by-rules.js.map +1 -1
- package/dist/formatter/take-rule-statistics.js.map +1 -1
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +73 -0
- package/dist/plugin.js.map +1 -0
- package/dist/scene/check-results.js.map +1 -1
- package/dist/scene/lint.js.map +1 -1
- package/dist/scene/select-action.js.map +1 -1
- package/dist/util/array.js.map +1 -1
- package/dist/util/eslint.d.ts +4 -0
- package/dist/util/eslint.d.ts.map +1 -1
- package/dist/util/eslint.js +23 -4
- package/dist/util/eslint.js.map +1 -1
- package/package.json +19 -25
- package/src/action/print-result-details.ts +2 -2
- package/src/cli/parse-argv.ts +1 -1
- package/src/cli/run.ts +2 -4
- package/src/config.ts +5 -2
- package/src/core.ts +49 -45
- package/src/eslint/linter.ts +11 -8
- package/src/eslint/use-at-your-own-risk.d.ts +62 -0
- package/src/eslint/use-at-your-own-risk.js +6 -0
- package/src/fix/disable-per-file.ts +8 -6
- package/src/fix/disable-per-line.ts +19 -5
- package/src/formatter/format-by-rules.ts +2 -3
- package/src/plugin.ts +71 -0
- package/src/util/eslint.ts +27 -4
package/src/config.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ESLint } from 'eslint';
|
|
2
2
|
import { cliOptionsDefaults, ParsedCLIOptions } from './cli/parse-argv.js';
|
|
3
|
+
import { FlatESLint } from './eslint/use-at-your-own-risk.js';
|
|
3
4
|
import { DeepPartial } from './util/type-check.js';
|
|
4
5
|
type LegacyESLintOptions = { type: 'eslintrc' } & Pick<
|
|
5
|
-
|
|
6
|
+
ESLint.Options,
|
|
6
7
|
| 'useEslintrc'
|
|
7
8
|
| 'overrideConfigFile'
|
|
8
9
|
| 'extensions'
|
|
@@ -13,6 +14,7 @@ type LegacyESLintOptions = { type: 'eslintrc' } & Pick<
|
|
|
13
14
|
| 'overrideConfig'
|
|
14
15
|
| 'cwd'
|
|
15
16
|
| 'resolvePluginsRelativeTo'
|
|
17
|
+
| 'plugins'
|
|
16
18
|
>;
|
|
17
19
|
type FlatESLintOptions = { type: 'flat' } & Pick<
|
|
18
20
|
FlatESLint.Options,
|
|
@@ -110,6 +112,7 @@ export function normalizeConfig(config: Config): NormalizedConfig {
|
|
|
110
112
|
cwd,
|
|
111
113
|
resolvePluginsRelativeTo:
|
|
112
114
|
config.eslintOptions.resolvePluginsRelativeTo ?? configDefaults.eslintOptions.resolvePluginsRelativeTo,
|
|
115
|
+
plugins: config.eslintOptions.plugins,
|
|
113
116
|
};
|
|
114
117
|
} else {
|
|
115
118
|
eslintOptions = {
|
package/src/core.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
3
|
-
import { ESLint, Linter, Rule } from 'eslint';
|
|
4
|
-
import eslintPkg, { LegacyESLint as LegacyESLintNS } from 'eslint/use-at-your-own-risk';
|
|
5
|
-
import isInstalledGlobally from 'is-installed-globally';
|
|
2
|
+
import { ESLint, Rule } from 'eslint';
|
|
6
3
|
import { DescriptionPosition } from './cli/prompt.js';
|
|
7
4
|
import { Config, NormalizedConfig, normalizeConfig } from './config.js';
|
|
5
|
+
import { LegacyESLint, FlatESLint } from './eslint/use-at-your-own-risk.js';
|
|
8
6
|
import {
|
|
9
7
|
createFixToApplyAutoFixes,
|
|
10
8
|
createFixToApplySuggestions,
|
|
@@ -18,16 +16,15 @@ import {
|
|
|
18
16
|
verifyAndFix,
|
|
19
17
|
} from './fix/index.js';
|
|
20
18
|
import { format } from './formatter/index.js';
|
|
19
|
+
import { plugin } from './plugin.js';
|
|
21
20
|
import { filterResultsByRuleId } from './util/eslint.js';
|
|
22
21
|
|
|
23
|
-
const { LegacyESLint, FlatESLint } = eslintPkg;
|
|
24
|
-
|
|
25
22
|
/**
|
|
26
23
|
* Generate results to undo.
|
|
27
24
|
* @param resultsOfLint The results of lint.
|
|
28
25
|
* @returns The results to undo.
|
|
29
26
|
*/
|
|
30
|
-
function generateResultsToUndo(resultsOfLint:
|
|
27
|
+
function generateResultsToUndo(resultsOfLint: ESLint.LintResult[]): ESLint.LintResult[] {
|
|
31
28
|
return resultsOfLint.map((resultOfLint) => {
|
|
32
29
|
// NOTE: THIS IS HACK.
|
|
33
30
|
return { ...resultOfLint, output: resultOfLint.source };
|
|
@@ -42,17 +39,47 @@ export type Undo = () => Promise<void>;
|
|
|
42
39
|
*/
|
|
43
40
|
export class Core {
|
|
44
41
|
readonly config: NormalizedConfig;
|
|
45
|
-
readonly eslint:
|
|
42
|
+
readonly eslint: ESLint;
|
|
46
43
|
|
|
47
44
|
constructor(config: Config) {
|
|
48
45
|
this.config = normalizeConfig(config);
|
|
49
46
|
const eslintOptions = this.config.eslintOptions;
|
|
50
47
|
if (eslintOptions.type === 'eslintrc') {
|
|
51
48
|
const { type, ...rest } = eslintOptions;
|
|
52
|
-
this.eslint = new LegacyESLint(
|
|
49
|
+
this.eslint = new LegacyESLint({
|
|
50
|
+
...rest,
|
|
51
|
+
plugins: {
|
|
52
|
+
...rest.plugins,
|
|
53
|
+
'eslint-interactive': plugin,
|
|
54
|
+
},
|
|
55
|
+
overrideConfig: {
|
|
56
|
+
...rest.overrideConfig,
|
|
57
|
+
plugins: [...(rest.overrideConfig?.plugins ?? []), 'eslint-interactive'],
|
|
58
|
+
rules: {
|
|
59
|
+
...rest.overrideConfig?.rules,
|
|
60
|
+
'eslint-interactive/source-code-snatcher': 'error',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
53
64
|
} else {
|
|
54
65
|
const { type, ...rest } = eslintOptions;
|
|
55
|
-
|
|
66
|
+
const overrideConfigs =
|
|
67
|
+
Array.isArray(rest.overrideConfig) ? rest.overrideConfig
|
|
68
|
+
: rest.overrideConfig ? [rest.overrideConfig]
|
|
69
|
+
: [];
|
|
70
|
+
this.eslint = new FlatESLint({
|
|
71
|
+
...rest,
|
|
72
|
+
overrideConfig: [
|
|
73
|
+
...overrideConfigs,
|
|
74
|
+
{
|
|
75
|
+
...rest.overrideConfig,
|
|
76
|
+
plugins: { 'eslint-interactive': plugin },
|
|
77
|
+
rules: {
|
|
78
|
+
'eslint-interactive/source-code-snatcher': 'error',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
|
|
@@ -80,17 +107,9 @@ export class Core {
|
|
|
80
107
|
* @param results The lint results of the project to print summary
|
|
81
108
|
* @param ruleIds The rule ids to print details
|
|
82
109
|
*/
|
|
83
|
-
async formatResultDetails(results:
|
|
110
|
+
async formatResultDetails(results: ESLint.LintResult[], ruleIds: (string | null)[]): Promise<string> {
|
|
84
111
|
const formatterName = this.config.formatterName;
|
|
85
|
-
|
|
86
|
-
// When eslint-interactive is installed globally, eslint-formatter-codeframe will also be installed globally.
|
|
87
|
-
// On the other hand, `eslint.loadFormatter` cannot load the globally installed formatter by name. So here it loads them by path.
|
|
88
|
-
const resolvedFormatterNameOrPath =
|
|
89
|
-
isInstalledGlobally && formatterName === 'codeframe'
|
|
90
|
-
? fileURLToPath(import.meta.resolve('eslint-formatter-codeframe', import.meta.resolve('eslint-interactive')))
|
|
91
|
-
: formatterName;
|
|
92
|
-
|
|
93
|
-
const formatter = await this.eslint.loadFormatter(resolvedFormatterNameOrPath);
|
|
112
|
+
const formatter = await this.eslint.loadFormatter(formatterName);
|
|
94
113
|
return formatter.format(filterResultsByRuleId(results, ruleIds));
|
|
95
114
|
}
|
|
96
115
|
|
|
@@ -98,7 +117,7 @@ export class Core {
|
|
|
98
117
|
* Run `eslint --fix`.
|
|
99
118
|
* @param ruleIds The rule ids to fix
|
|
100
119
|
*/
|
|
101
|
-
async applyAutoFixes(results:
|
|
120
|
+
async applyAutoFixes(results: ESLint.LintResult[], ruleIds: string[]): Promise<Undo> {
|
|
102
121
|
return this.fix(results, ruleIds, (context) => createFixToApplyAutoFixes(context, {}));
|
|
103
122
|
}
|
|
104
123
|
|
|
@@ -110,7 +129,7 @@ export class Core {
|
|
|
110
129
|
* @param descriptionPosition The position of the description
|
|
111
130
|
*/
|
|
112
131
|
async disablePerLine(
|
|
113
|
-
results:
|
|
132
|
+
results: ESLint.LintResult[],
|
|
114
133
|
ruleIds: string[],
|
|
115
134
|
description?: string,
|
|
116
135
|
descriptionPosition?: DescriptionPosition,
|
|
@@ -128,7 +147,7 @@ export class Core {
|
|
|
128
147
|
* @param descriptionPosition The position of the description
|
|
129
148
|
*/
|
|
130
149
|
async disablePerFile(
|
|
131
|
-
results:
|
|
150
|
+
results: ESLint.LintResult[],
|
|
132
151
|
ruleIds: string[],
|
|
133
152
|
description?: string,
|
|
134
153
|
descriptionPosition?: DescriptionPosition,
|
|
@@ -145,7 +164,7 @@ export class Core {
|
|
|
145
164
|
* @param description The comment explaining the reason for converting
|
|
146
165
|
*/
|
|
147
166
|
async convertErrorToWarningPerFile(
|
|
148
|
-
results:
|
|
167
|
+
results: ESLint.LintResult[],
|
|
149
168
|
ruleIds: string[],
|
|
150
169
|
description?: string,
|
|
151
170
|
): Promise<Undo> {
|
|
@@ -158,11 +177,7 @@ export class Core {
|
|
|
158
177
|
* @param ruleIds The rule ids to apply suggestions
|
|
159
178
|
* @param filter The script to filter suggestions
|
|
160
179
|
*/
|
|
161
|
-
async applySuggestions(
|
|
162
|
-
results: LegacyESLintNS.LintResult[],
|
|
163
|
-
ruleIds: string[],
|
|
164
|
-
filter: SuggestionFilter,
|
|
165
|
-
): Promise<Undo> {
|
|
180
|
+
async applySuggestions(results: ESLint.LintResult[], ruleIds: string[], filter: SuggestionFilter): Promise<Undo> {
|
|
166
181
|
return this.fix(results, ruleIds, (context) => createFixToApplySuggestions(context, { filter }));
|
|
167
182
|
}
|
|
168
183
|
|
|
@@ -172,11 +187,7 @@ export class Core {
|
|
|
172
187
|
* @param ruleIds The rule ids to apply suggestions
|
|
173
188
|
* @param fixableMaker The function to make `Linter.LintMessage` forcibly fixable.
|
|
174
189
|
*/
|
|
175
|
-
async makeFixableAndFix(
|
|
176
|
-
results: LegacyESLintNS.LintResult[],
|
|
177
|
-
ruleIds: string[],
|
|
178
|
-
fixableMaker: FixableMaker,
|
|
179
|
-
): Promise<Undo> {
|
|
190
|
+
async makeFixableAndFix(results: ESLint.LintResult[], ruleIds: string[], fixableMaker: FixableMaker): Promise<Undo> {
|
|
180
191
|
return this.fix(results, ruleIds, (context) => createFixToMakeFixableAndFix(context, { fixableMaker }));
|
|
181
192
|
}
|
|
182
193
|
|
|
@@ -185,26 +196,19 @@ export class Core {
|
|
|
185
196
|
* @param fix The fix information to do.
|
|
186
197
|
*/
|
|
187
198
|
private async fix(
|
|
188
|
-
resultsOfLint:
|
|
199
|
+
resultsOfLint: ESLint.LintResult[],
|
|
189
200
|
ruleIds: string[],
|
|
190
201
|
fixCreator: (context: FixContext) => Rule.Fix[],
|
|
191
202
|
): Promise<Undo> {
|
|
192
203
|
// NOTE: Extract only necessary results and files for performance
|
|
193
204
|
const filteredResultsOfLint = filterResultsByRuleId(resultsOfLint, ruleIds);
|
|
194
|
-
const linter = new Linter({ configType: this.config.eslintOptions.type });
|
|
195
205
|
|
|
196
206
|
// eslint-disable-next-line prefer-const
|
|
197
207
|
for (let { filePath, source } of filteredResultsOfLint) {
|
|
198
208
|
if (!source) throw new Error('Source code is required to apply fixes.');
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
await this.eslint.calculateConfigForFile(filePath)
|
|
203
|
-
: // NOTE: For some reason, if files is not specified, it will not match .jsx
|
|
204
|
-
// eslint-disable-next-line no-await-in-loop
|
|
205
|
-
[{ ...(await this.eslint.calculateConfigForFile(filePath)), files: ['**/*.*', '**/*'] }];
|
|
206
|
-
|
|
207
|
-
const fixedResult = verifyAndFix(linter, source, config, filePath, ruleIds, fixCreator);
|
|
209
|
+
|
|
210
|
+
// eslint-disable-next-line no-await-in-loop
|
|
211
|
+
const fixedResult = await verifyAndFix(this.eslint, source, filePath, ruleIds, fixCreator);
|
|
208
212
|
|
|
209
213
|
// Write the fixed source code to the file
|
|
210
214
|
if (fixedResult.fixed) {
|
package/src/eslint/linter.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* @author aladdin-add
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { ESLint, Rule } from 'eslint';
|
|
11
11
|
import { FixContext } from '../fix/index.js';
|
|
12
|
+
import { getLastSourceCode } from '../plugin.js';
|
|
12
13
|
import { ruleFixer } from './rule-fixer.js';
|
|
13
14
|
import { SourceCodeFixer } from './source-code-fixer.js';
|
|
14
15
|
|
|
@@ -24,14 +25,13 @@ type FixedResult = {
|
|
|
24
25
|
* @param linter
|
|
25
26
|
*/
|
|
26
27
|
// eslint-disable-next-line max-params
|
|
27
|
-
export function verifyAndFix(
|
|
28
|
-
|
|
28
|
+
export async function verifyAndFix(
|
|
29
|
+
eslint: ESLint,
|
|
29
30
|
text: string,
|
|
30
|
-
config: Linter.Config | Linter.FlatConfig[],
|
|
31
31
|
filePath: string,
|
|
32
32
|
ruleIds: string[],
|
|
33
33
|
fixCreator: (context: FixContext) => Rule.Fix[],
|
|
34
|
-
): FixedResult {
|
|
34
|
+
): Promise<FixedResult> {
|
|
35
35
|
let fixedResult: FixedResult;
|
|
36
36
|
let fixed = false;
|
|
37
37
|
let passNumber = 0;
|
|
@@ -49,10 +49,13 @@ export function verifyAndFix(
|
|
|
49
49
|
do {
|
|
50
50
|
passNumber++;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// eslint-disable-next-line no-await-in-loop
|
|
53
|
+
const results = await eslint.lintText(currentText, { filePath });
|
|
54
|
+
const messages = results
|
|
55
|
+
.flatMap((result) => result.messages)
|
|
54
56
|
.filter((message) => message.ruleId && ruleIds.includes(message.ruleId));
|
|
55
|
-
const sourceCode =
|
|
57
|
+
const sourceCode = getLastSourceCode();
|
|
58
|
+
if (!sourceCode) throw new Error('Failed to get the last source code.');
|
|
56
59
|
|
|
57
60
|
// Create `Rule.Fix[]`
|
|
58
61
|
const fixContext: FixContext = {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ESLint, Linter, Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
/** @deprecated */
|
|
4
|
+
export const LegacyESLint: typeof ESLint;
|
|
5
|
+
|
|
6
|
+
/** @deprecated */
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
8
|
+
export namespace FlatESLint {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
interface Options {
|
|
11
|
+
// File enumeration
|
|
12
|
+
cwd?: string | undefined;
|
|
13
|
+
errorOnUnmatchedPattern?: boolean | undefined;
|
|
14
|
+
globInputPaths?: boolean | undefined;
|
|
15
|
+
ignore?: boolean | undefined;
|
|
16
|
+
|
|
17
|
+
// Linting
|
|
18
|
+
allowInlineConfig?: boolean | undefined;
|
|
19
|
+
baseConfig?: Linter.FlatConfig | Linter.FlatConfig[] | undefined;
|
|
20
|
+
overrideConfig?: Linter.FlatConfig | Linter.FlatConfig[] | undefined;
|
|
21
|
+
overrideConfigFile?: boolean | string | undefined;
|
|
22
|
+
reportUnusedDisableDirectives?: Linter.StringSeverity | undefined;
|
|
23
|
+
|
|
24
|
+
// Autofix
|
|
25
|
+
fix?: boolean | ((message: Linter.LintMessage) => boolean) | undefined;
|
|
26
|
+
fixTypes?: Rule.RuleMetaData['type'][] | undefined;
|
|
27
|
+
|
|
28
|
+
// Cache-related
|
|
29
|
+
cache?: boolean | undefined;
|
|
30
|
+
cacheLocation?: string | undefined;
|
|
31
|
+
cacheStrategy?: 'content' | 'metadata' | undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** @deprecated */
|
|
35
|
+
export declare class FlatESLint {
|
|
36
|
+
constructor(options?: FlatESLint.Options);
|
|
37
|
+
|
|
38
|
+
static get version(): string;
|
|
39
|
+
|
|
40
|
+
static outputFixes(results: ESLint.LintResult[]): Promise<void>;
|
|
41
|
+
|
|
42
|
+
static getErrorResults(results: ESLint.LintResult[]): ESLint.LintResult[];
|
|
43
|
+
|
|
44
|
+
getRulesMetaForResults(results: ESLint.LintResult[]): Record<string, Rule.RuleMetaData>;
|
|
45
|
+
|
|
46
|
+
lintFiles(patterns: string | string[]): Promise<ESLint.LintResult[]>;
|
|
47
|
+
|
|
48
|
+
lintText(
|
|
49
|
+
code: string,
|
|
50
|
+
options?: { filePath?: string | undefined; warnIgnored?: boolean | undefined },
|
|
51
|
+
): Promise<ESLint.LintResult[]>;
|
|
52
|
+
|
|
53
|
+
loadFormatter(name?: string): Promise<ESLint.Formatter>;
|
|
54
|
+
|
|
55
|
+
calculateConfigForFile(filePath: string): Promise<Linter.FlatConfig | undefined>;
|
|
56
|
+
|
|
57
|
+
findConfigFile(): Promise<string | undefined>;
|
|
58
|
+
|
|
59
|
+
isPathIgnored(filePath: string): Promise<boolean>;
|
|
60
|
+
}
|
|
61
|
+
/** @deprecated */
|
|
62
|
+
export function shouldUseFlatConfig(): Promise<boolean>;
|
|
@@ -39,10 +39,9 @@ function generateFix(
|
|
|
39
39
|
|
|
40
40
|
// if shebang exists, insert comment after shebang
|
|
41
41
|
const shebang = findShebang(context.sourceCode.text);
|
|
42
|
-
const lineToInsert =
|
|
43
|
-
? disableCommentPerFile.loc.start.line
|
|
44
|
-
: shebang
|
|
45
|
-
? sourceCode.getLocFromIndex(shebang.range[0]).line + 1
|
|
42
|
+
const lineToInsert =
|
|
43
|
+
disableCommentPerFile ? disableCommentPerFile.loc.start.line
|
|
44
|
+
: shebang ? sourceCode.getLocFromIndex(shebang.range[0]).line + 1
|
|
46
45
|
: 1;
|
|
47
46
|
|
|
48
47
|
const fixes: Rule.Fix[] = [];
|
|
@@ -54,6 +53,7 @@ function generateFix(
|
|
|
54
53
|
fixer,
|
|
55
54
|
sourceCode,
|
|
56
55
|
line: lineToInsert,
|
|
56
|
+
column: 0,
|
|
57
57
|
description,
|
|
58
58
|
}),
|
|
59
59
|
);
|
|
@@ -65,8 +65,9 @@ function generateFix(
|
|
|
65
65
|
fixer,
|
|
66
66
|
disableComment: disableCommentPerFile,
|
|
67
67
|
newRules: mergeRuleIds(disableCommentPerFile.ruleIds, ruleIdsToDisable),
|
|
68
|
-
newDescription:
|
|
69
|
-
?
|
|
68
|
+
newDescription:
|
|
69
|
+
isPreviousLine ?
|
|
70
|
+
disableCommentPerFile.description
|
|
70
71
|
: mergeDescription(disableCommentPerFile.description, description),
|
|
71
72
|
}),
|
|
72
73
|
);
|
|
@@ -76,6 +77,7 @@ function generateFix(
|
|
|
76
77
|
fixer,
|
|
77
78
|
sourceCode,
|
|
78
79
|
line: lineToInsert,
|
|
80
|
+
column: 0,
|
|
79
81
|
scope: 'file',
|
|
80
82
|
ruleIds: ruleIdsToDisable,
|
|
81
83
|
description: isPreviousLine ? undefined : description,
|
|
@@ -4,8 +4,10 @@ import { mergeFixes } from '../eslint/report-translator.js';
|
|
|
4
4
|
import { groupBy, unique } from '../util/array.js';
|
|
5
5
|
import {
|
|
6
6
|
DisableComment,
|
|
7
|
+
getStartColumnOfTemplateExpression,
|
|
7
8
|
insertDescriptionCommentStatementBeforeLine,
|
|
8
9
|
insertDisableCommentStatementBeforeLine,
|
|
10
|
+
isLineInTemplateLiteral,
|
|
9
11
|
mergeDescription,
|
|
10
12
|
mergeRuleIds,
|
|
11
13
|
parseDisableComment,
|
|
@@ -30,6 +32,7 @@ function generateFixesPerLine(
|
|
|
30
32
|
description: string | undefined,
|
|
31
33
|
descriptionPosition: DescriptionPosition | undefined,
|
|
32
34
|
line: number,
|
|
35
|
+
column: number,
|
|
33
36
|
messagesInLine: Linter.LintMessage[],
|
|
34
37
|
): Rule.Fix | null {
|
|
35
38
|
const { fixer, sourceCode } = context;
|
|
@@ -48,6 +51,7 @@ function generateFixesPerLine(
|
|
|
48
51
|
fixer,
|
|
49
52
|
sourceCode,
|
|
50
53
|
line: disableCommentPerLine ? disableCommentPerLine.loc.start.line : line,
|
|
54
|
+
column,
|
|
51
55
|
description,
|
|
52
56
|
}),
|
|
53
57
|
);
|
|
@@ -58,8 +62,9 @@ function generateFixesPerLine(
|
|
|
58
62
|
fixer,
|
|
59
63
|
disableComment: disableCommentPerLine,
|
|
60
64
|
newRules: mergeRuleIds(disableCommentPerLine.ruleIds, ruleIdsToDisable),
|
|
61
|
-
newDescription:
|
|
62
|
-
?
|
|
65
|
+
newDescription:
|
|
66
|
+
isPreviousLine ?
|
|
67
|
+
disableCommentPerLine.description
|
|
63
68
|
: mergeDescription(disableCommentPerLine.description, description),
|
|
64
69
|
}),
|
|
65
70
|
);
|
|
@@ -69,6 +74,7 @@ function generateFixesPerLine(
|
|
|
69
74
|
fixer,
|
|
70
75
|
sourceCode,
|
|
71
76
|
line,
|
|
77
|
+
column,
|
|
72
78
|
scope: 'next-line',
|
|
73
79
|
ruleIds: ruleIdsToDisable,
|
|
74
80
|
description: isPreviousLine ? undefined : description,
|
|
@@ -82,10 +88,18 @@ function generateFixesPerLine(
|
|
|
82
88
|
* Create fix to add disable comment per line.
|
|
83
89
|
*/
|
|
84
90
|
export function createFixToDisablePerLine(context: FixContext, args: FixToDisablePerLineArgs): Rule.Fix[] {
|
|
85
|
-
const
|
|
91
|
+
const groupedMessages = groupBy(context.messages, (message) => {
|
|
92
|
+
if (isLineInTemplateLiteral(context.sourceCode, message.line)) {
|
|
93
|
+
const column = getStartColumnOfTemplateExpression(context.sourceCode, message);
|
|
94
|
+
return `${message.line}:${column}`;
|
|
95
|
+
} else {
|
|
96
|
+
return `${message.line}:0`;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
86
99
|
const fixes: Rule.Fix[] = [];
|
|
87
|
-
for (const [
|
|
88
|
-
const
|
|
100
|
+
for (const [lineAndColumn, messagesInLine] of groupedMessages) {
|
|
101
|
+
const [line, column] = lineAndColumn.split(':').map(Number) as [number, number];
|
|
102
|
+
const fix = generateFixesPerLine(context, args.description, args.descriptionPosition, line, column, messagesInLine);
|
|
89
103
|
if (fix) fixes.push(fix);
|
|
90
104
|
}
|
|
91
105
|
return fixes;
|
|
@@ -44,9 +44,8 @@ export function formatByRules(results: ESLint.LintResult[], data?: ESLint.LintRe
|
|
|
44
44
|
ruleStatistics.forEach((ruleStatistic) => {
|
|
45
45
|
const { ruleId } = ruleStatistic;
|
|
46
46
|
const ruleMetaData = data?.rulesMeta[ruleId];
|
|
47
|
-
const ruleCell =
|
|
48
|
-
? terminalLink(ruleId, ruleMetaData?.docs.url, { fallback: false })
|
|
49
|
-
: ruleId;
|
|
47
|
+
const ruleCell =
|
|
48
|
+
ruleMetaData?.docs?.url ? terminalLink(ruleId, ruleMetaData?.docs.url, { fallback: false }) : ruleId;
|
|
50
49
|
result = result.replace(` ${ruleId} `, ` ${ruleCell} `);
|
|
51
50
|
});
|
|
52
51
|
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ESLint, Rule, SourceCode } from 'eslint';
|
|
2
|
+
|
|
3
|
+
let lastSourceCode: SourceCode | null = null;
|
|
4
|
+
|
|
5
|
+
export function getLastSourceCode() {
|
|
6
|
+
return lastSourceCode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const plugin: ESLint.Plugin = {
|
|
10
|
+
rules: {
|
|
11
|
+
/**
|
|
12
|
+
* This is a rule for getting a `SourceCode` instance.
|
|
13
|
+
* `ESLint` class does not provide a method for getting a `SourceCode` instance. As an alternative, we have prepared this custom rule.
|
|
14
|
+
*/
|
|
15
|
+
'source-code-snatcher': {
|
|
16
|
+
create(context) {
|
|
17
|
+
lastSourceCode = context.sourceCode;
|
|
18
|
+
return {};
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
/** This is a rule for testing purposes. */
|
|
22
|
+
'prefer-addition-shorthand': {
|
|
23
|
+
meta: {
|
|
24
|
+
type: 'suggestion',
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
hasSuggestions: true,
|
|
27
|
+
},
|
|
28
|
+
create(context: Rule.RuleContext) {
|
|
29
|
+
return {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
31
|
+
AssignmentExpression: (node) => {
|
|
32
|
+
if (node.left.type !== 'Identifier') return;
|
|
33
|
+
const leftIdentifier = node.left;
|
|
34
|
+
if (node.right.type !== 'BinaryExpression') return;
|
|
35
|
+
const rightBinaryExpression = node.right;
|
|
36
|
+
if (rightBinaryExpression.operator !== '+') return;
|
|
37
|
+
if (rightBinaryExpression.left.type !== 'Identifier') return;
|
|
38
|
+
const rightIdentifier = rightBinaryExpression.left;
|
|
39
|
+
if (leftIdentifier.name !== rightIdentifier.name) return;
|
|
40
|
+
if (rightBinaryExpression.right.type !== 'Literal' || rightBinaryExpression.right.value !== 1) return;
|
|
41
|
+
|
|
42
|
+
context.report({
|
|
43
|
+
node,
|
|
44
|
+
message: 'The addition method is redundant.',
|
|
45
|
+
suggest: [
|
|
46
|
+
{
|
|
47
|
+
desc: 'Use `val += 1` instead.',
|
|
48
|
+
fix(fixer) {
|
|
49
|
+
return fixer.replaceText(node, `${leftIdentifier.name} += 1`);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
desc: 'Use `val++` instead.',
|
|
54
|
+
fix(fixer) {
|
|
55
|
+
return fixer.replaceText(node, `${leftIdentifier.name}++`);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
desc: 'Use `++val` instead.',
|
|
60
|
+
fix(fixer) {
|
|
61
|
+
return fixer.replaceText(node, `++${leftIdentifier.name}`);
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
package/src/util/eslint.ts
CHANGED
|
@@ -109,6 +109,27 @@ function isLineInJSXText(sourceCode: SourceCode, line: number): boolean {
|
|
|
109
109
|
return headNode?.type === 'JSXText';
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
export function isLineInTemplateLiteral(sourceCode: SourceCode, line: number): boolean {
|
|
113
|
+
const headNodeIndex = sourceCode.getIndexFromLoc({ line, column: 0 });
|
|
114
|
+
const headNode = sourceCode.getNodeByRangeIndex(headNodeIndex);
|
|
115
|
+
return headNode?.type === 'TemplateElement';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getStartColumnOfTemplateExpression(sourceCode: SourceCode, message: Linter.LintMessage): number {
|
|
119
|
+
for (let i = message.column; i >= 1; i--) {
|
|
120
|
+
const index = sourceCode.getIndexFromLoc({
|
|
121
|
+
line: message.line,
|
|
122
|
+
// Convert 1-indexed to 0-indexed
|
|
123
|
+
column: i - 1,
|
|
124
|
+
});
|
|
125
|
+
const node = sourceCode.getNodeByRangeIndex(index);
|
|
126
|
+
if (node?.type === 'TemplateElement') {
|
|
127
|
+
return i;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`unreachable: The line ${message.line} does not have a template element.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
112
133
|
/**
|
|
113
134
|
* Merge the ruleIds of the disable comments.
|
|
114
135
|
* @param a The ruleIds of first disable comment
|
|
@@ -136,11 +157,12 @@ export function insertDescriptionCommentStatementBeforeLine(args: {
|
|
|
136
157
|
fixer: Rule.RuleFixer;
|
|
137
158
|
sourceCode: SourceCode;
|
|
138
159
|
line: number;
|
|
160
|
+
column: number;
|
|
139
161
|
description: string;
|
|
140
162
|
}): Rule.Fix {
|
|
141
|
-
const { fixer, sourceCode, line, description } = args;
|
|
163
|
+
const { fixer, sourceCode, line, column, description } = args;
|
|
142
164
|
const indent = getIndentFromLine(sourceCode, line);
|
|
143
|
-
const headNodeIndex = sourceCode.getIndexFromLoc({ line, column
|
|
165
|
+
const headNodeIndex = sourceCode.getIndexFromLoc({ line, column });
|
|
144
166
|
|
|
145
167
|
if (isLineInJSXText(sourceCode, line)) {
|
|
146
168
|
const commentText = toCommentText({ type: 'Block', text: description });
|
|
@@ -175,13 +197,14 @@ export function insertDisableCommentStatementBeforeLine(args: {
|
|
|
175
197
|
fixer: Rule.RuleFixer;
|
|
176
198
|
sourceCode: SourceCode;
|
|
177
199
|
line: number;
|
|
200
|
+
column: number;
|
|
178
201
|
scope: 'file' | 'next-line';
|
|
179
202
|
ruleIds: string[];
|
|
180
203
|
description: string | undefined;
|
|
181
204
|
}) {
|
|
182
|
-
const { fixer, sourceCode, line, scope, ruleIds, description } = args;
|
|
205
|
+
const { fixer, sourceCode, line, column, scope, ruleIds, description } = args;
|
|
183
206
|
const indent = getIndentFromLine(sourceCode, line);
|
|
184
|
-
const headNodeIndex = sourceCode.getIndexFromLoc({ line, column
|
|
207
|
+
const headNodeIndex = sourceCode.getIndexFromLoc({ line, column });
|
|
185
208
|
const isInJSXText = isLineInJSXText(sourceCode, line);
|
|
186
209
|
const type = isInJSXText || scope === 'file' ? 'Block' : 'Line';
|
|
187
210
|
const disableCommentText = toDisableCommentText({
|