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.
Files changed (214) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +147 -0
  3. package/bin/eslint-interactive.js +10 -0
  4. package/dist/action/apply-suggestions.d.ts +6 -0
  5. package/dist/action/apply-suggestions.d.ts.map +1 -0
  6. package/dist/action/apply-suggestions.js +28 -0
  7. package/dist/action/apply-suggestions.js.map +1 -0
  8. package/dist/action/disable-per-file.d.ts +6 -0
  9. package/dist/action/disable-per-file.d.ts.map +1 -0
  10. package/dist/action/disable-per-file.js +8 -0
  11. package/dist/action/disable-per-file.js.map +1 -0
  12. package/dist/action/disable-per-line.d.ts +6 -0
  13. package/dist/action/disable-per-line.d.ts.map +1 -0
  14. package/dist/action/disable-per-line.js +8 -0
  15. package/dist/action/disable-per-line.js.map +1 -0
  16. package/dist/action/fix.d.ts +6 -0
  17. package/dist/action/fix.d.ts.map +1 -0
  18. package/dist/action/fix.js +6 -0
  19. package/dist/action/fix.js.map +1 -0
  20. package/dist/action/index.d.ts +7 -0
  21. package/dist/action/index.d.ts.map +1 -0
  22. package/dist/action/index.js +7 -0
  23. package/dist/action/index.js.map +1 -0
  24. package/dist/action/make-fixable-and-fix.d.ts +6 -0
  25. package/dist/action/make-fixable-and-fix.d.ts.map +1 -0
  26. package/dist/action/make-fixable-and-fix.js +28 -0
  27. package/dist/action/make-fixable-and-fix.js.map +1 -0
  28. package/dist/action/print-result-details.d.ts +5 -0
  29. package/dist/action/print-result-details.d.ts.map +1 -0
  30. package/dist/action/print-result-details.js +12 -0
  31. package/dist/action/print-result-details.js.map +1 -0
  32. package/dist/cli/log.d.ts +6 -0
  33. package/dist/cli/log.d.ts.map +1 -0
  34. package/dist/cli/log.js +9 -0
  35. package/dist/cli/log.js.map +1 -0
  36. package/dist/cli/ora.d.ts +4 -0
  37. package/dist/cli/ora.d.ts.map +1 -0
  38. package/dist/cli/ora.js +23 -0
  39. package/dist/cli/ora.js.map +1 -0
  40. package/dist/cli/package.d.ts +2 -0
  41. package/dist/cli/package.d.ts.map +1 -0
  42. package/dist/cli/package.js +6 -0
  43. package/dist/cli/package.js.map +1 -0
  44. package/dist/cli/parse-argv.d.ts +4 -0
  45. package/dist/cli/parse-argv.d.ts.map +1 -0
  46. package/dist/cli/parse-argv.js +50 -0
  47. package/dist/cli/parse-argv.js.map +1 -0
  48. package/dist/cli/prompt.d.ts +53 -0
  49. package/dist/cli/prompt.d.ts.map +1 -0
  50. package/dist/cli/prompt.js +154 -0
  51. package/dist/cli/prompt.js.map +1 -0
  52. package/dist/cli/run.d.ts +8 -0
  53. package/dist/cli/run.d.ts.map +1 -0
  54. package/dist/cli/run.js +46 -0
  55. package/dist/cli/run.js.map +1 -0
  56. package/dist/core-worker.d.ts +21 -0
  57. package/dist/core-worker.d.ts.map +1 -0
  58. package/dist/core-worker.js +52 -0
  59. package/dist/core-worker.js.map +1 -0
  60. package/dist/core.d.ts +84 -0
  61. package/dist/core.d.ts.map +1 -0
  62. package/dist/core.js +196 -0
  63. package/dist/core.js.map +1 -0
  64. package/dist/formatter/colors.d.ts +4 -0
  65. package/dist/formatter/colors.d.ts.map +1 -0
  66. package/dist/formatter/colors.js +5 -0
  67. package/dist/formatter/colors.js.map +1 -0
  68. package/dist/formatter/format-by-files.d.ts +3 -0
  69. package/dist/formatter/format-by-files.d.ts.map +1 -0
  70. package/dist/formatter/format-by-files.js +41 -0
  71. package/dist/formatter/format-by-files.js.map +1 -0
  72. package/dist/formatter/format-by-rules.d.ts +3 -0
  73. package/dist/formatter/format-by-rules.d.ts.map +1 -0
  74. package/dist/formatter/format-by-rules.js +34 -0
  75. package/dist/formatter/format-by-rules.js.map +1 -0
  76. package/dist/formatter/index.d.ts +4 -0
  77. package/dist/formatter/index.d.ts.map +1 -0
  78. package/dist/formatter/index.js +7 -0
  79. package/dist/formatter/index.js.map +1 -0
  80. package/dist/formatter/take-rule-statistics.d.ts +18 -0
  81. package/dist/formatter/take-rule-statistics.d.ts.map +1 -0
  82. package/dist/formatter/take-rule-statistics.js +51 -0
  83. package/dist/formatter/take-rule-statistics.js.map +1 -0
  84. package/dist/index.d.ts +5 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +4 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/plugin/fix/apply-auto-fixes.d.ts +8 -0
  89. package/dist/plugin/fix/apply-auto-fixes.d.ts.map +1 -0
  90. package/dist/plugin/fix/apply-auto-fixes.js +8 -0
  91. package/dist/plugin/fix/apply-auto-fixes.js.map +1 -0
  92. package/dist/plugin/fix/apply-suggestions.d.ts +11 -0
  93. package/dist/plugin/fix/apply-suggestions.d.ts.map +1 -0
  94. package/dist/plugin/fix/apply-suggestions.js +25 -0
  95. package/dist/plugin/fix/apply-suggestions.js.map +1 -0
  96. package/dist/plugin/fix/disable-per-file.d.ts +10 -0
  97. package/dist/plugin/fix/disable-per-file.d.ts.map +1 -0
  98. package/dist/plugin/fix/disable-per-file.js +39 -0
  99. package/dist/plugin/fix/disable-per-file.js.map +1 -0
  100. package/dist/plugin/fix/disable-per-line.d.ts +10 -0
  101. package/dist/plugin/fix/disable-per-line.d.ts.map +1 -0
  102. package/dist/plugin/fix/disable-per-line.js +54 -0
  103. package/dist/plugin/fix/disable-per-line.js.map +1 -0
  104. package/dist/plugin/fix/index.d.ts +6 -0
  105. package/dist/plugin/fix/index.d.ts.map +1 -0
  106. package/dist/plugin/fix/index.js +6 -0
  107. package/dist/plugin/fix/index.js.map +1 -0
  108. package/dist/plugin/fix/make-fixable-and-fix.d.ts +12 -0
  109. package/dist/plugin/fix/make-fixable-and-fix.d.ts.map +1 -0
  110. package/dist/plugin/fix/make-fixable-and-fix.js +61 -0
  111. package/dist/plugin/fix/make-fixable-and-fix.js.map +1 -0
  112. package/dist/plugin/fix-rule.d.ts +10 -0
  113. package/dist/plugin/fix-rule.d.ts.map +1 -0
  114. package/dist/plugin/fix-rule.js +124 -0
  115. package/dist/plugin/fix-rule.js.map +1 -0
  116. package/dist/plugin/index.d.ts +49 -0
  117. package/dist/plugin/index.d.ts.map +1 -0
  118. package/dist/plugin/index.js +11 -0
  119. package/dist/plugin/index.js.map +1 -0
  120. package/dist/plugin/prefer-addition-shorthand-rule.d.ts +7 -0
  121. package/dist/plugin/prefer-addition-shorthand-rule.d.ts.map +1 -0
  122. package/dist/plugin/prefer-addition-shorthand-rule.js +54 -0
  123. package/dist/plugin/prefer-addition-shorthand-rule.js.map +1 -0
  124. package/dist/plugin/rule-fixer.d.ts +80 -0
  125. package/dist/plugin/rule-fixer.d.ts.map +1 -0
  126. package/dist/plugin/rule-fixer.js +118 -0
  127. package/dist/plugin/rule-fixer.js.map +1 -0
  128. package/dist/scene/check-results.d.ts +21 -0
  129. package/dist/scene/check-results.d.ts.map +1 -0
  130. package/dist/scene/check-results.js +22 -0
  131. package/dist/scene/check-results.js.map +1 -0
  132. package/dist/scene/index.d.ts +25 -0
  133. package/dist/scene/index.d.ts.map +1 -0
  134. package/dist/scene/index.js +6 -0
  135. package/dist/scene/index.js.map +1 -0
  136. package/dist/scene/lint.d.ts +8 -0
  137. package/dist/scene/lint.d.ts.map +1 -0
  138. package/dist/scene/lint.js +31 -0
  139. package/dist/scene/lint.js.map +1 -0
  140. package/dist/scene/select-action.d.ts +20 -0
  141. package/dist/scene/select-action.d.ts.map +1 -0
  142. package/dist/scene/select-action.js +46 -0
  143. package/dist/scene/select-action.js.map +1 -0
  144. package/dist/scene/select-rule-ids.d.ts +15 -0
  145. package/dist/scene/select-rule-ids.d.ts.map +1 -0
  146. package/dist/scene/select-rule-ids.js +10 -0
  147. package/dist/scene/select-rule-ids.js.map +1 -0
  148. package/dist/tsconfig.src.tsbuildinfo +1 -0
  149. package/dist/util/array.d.ts +3 -0
  150. package/dist/util/array.d.ts.map +1 -0
  151. package/dist/util/array.js +14 -0
  152. package/dist/util/array.js.map +1 -0
  153. package/dist/util/cache.d.ts +5 -0
  154. package/dist/util/cache.d.ts.map +1 -0
  155. package/dist/util/cache.js +13 -0
  156. package/dist/util/cache.js.map +1 -0
  157. package/dist/util/eslint.d.ts +68 -0
  158. package/dist/util/eslint.d.ts.map +1 -0
  159. package/dist/util/eslint.js +147 -0
  160. package/dist/util/eslint.js.map +1 -0
  161. package/dist/util/filter-script.d.ts +6 -0
  162. package/dist/util/filter-script.d.ts.map +1 -0
  163. package/dist/util/filter-script.js +39 -0
  164. package/dist/util/filter-script.js.map +1 -0
  165. package/dist/util/type-check.d.ts +3 -0
  166. package/dist/util/type-check.d.ts.map +1 -0
  167. package/dist/util/type-check.js +8 -0
  168. package/dist/util/type-check.js.map +1 -0
  169. package/package.json +93 -0
  170. package/src/action/apply-suggestions.ts +40 -0
  171. package/src/action/disable-per-file.ts +16 -0
  172. package/src/action/disable-per-line.ts +16 -0
  173. package/src/action/fix.ts +14 -0
  174. package/src/action/index.ts +6 -0
  175. package/src/action/make-fixable-and-fix.ts +40 -0
  176. package/src/action/print-result-details.ts +18 -0
  177. package/src/cli/log.ts +11 -0
  178. package/src/cli/ora.ts +25 -0
  179. package/src/cli/package.ts +9 -0
  180. package/src/cli/parse-argv.ts +52 -0
  181. package/src/cli/prompt.ts +205 -0
  182. package/src/cli/run.ts +50 -0
  183. package/src/core-worker.ts +66 -0
  184. package/src/core.ts +240 -0
  185. package/src/formatter/colors.ts +5 -0
  186. package/src/formatter/format-by-files.ts +48 -0
  187. package/src/formatter/format-by-rules.ts +37 -0
  188. package/src/formatter/index.ts +9 -0
  189. package/src/formatter/take-rule-statistics.ts +66 -0
  190. package/src/index.ts +4 -0
  191. package/src/plugin/fix/apply-auto-fixes.ts +13 -0
  192. package/src/plugin/fix/apply-suggestions.ts +44 -0
  193. package/src/plugin/fix/disable-per-file.ts +53 -0
  194. package/src/plugin/fix/disable-per-line.ts +65 -0
  195. package/src/plugin/fix/index.ts +13 -0
  196. package/src/plugin/fix/make-fixable-and-fix.ts +77 -0
  197. package/src/plugin/fix-rule.ts +142 -0
  198. package/src/plugin/index.ts +66 -0
  199. package/src/plugin/prefer-addition-shorthand-rule.ts +56 -0
  200. package/src/plugin/rule-fixer.ts +147 -0
  201. package/src/scene/check-results.ts +43 -0
  202. package/src/scene/index.ts +18 -0
  203. package/src/scene/lint.ts +41 -0
  204. package/src/scene/select-action.ts +70 -0
  205. package/src/scene/select-rule-ids.ts +24 -0
  206. package/src/typings/cachedir.d.ts +5 -0
  207. package/src/typings/node-pager.d.ts +4 -0
  208. package/src/util/array.ts +16 -0
  209. package/src/util/cache.ts +11 -0
  210. package/src/util/eslint.ts +162 -0
  211. package/src/util/filter-script.ts +45 -0
  212. package/src/util/type-check.ts +8 -0
  213. package/static/example-filter-script.js +49 -0
  214. 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,5 @@
1
+ declare module 'cachedir' {
2
+ // eslint-disable-next-line import/no-default-export
3
+ function cachedir(name: string): string;
4
+ export = cachedir;
5
+ }
@@ -0,0 +1,4 @@
1
+ declare module 'node-pager' {
2
+ // eslint-disable-next-line import/no-default-export
3
+ export default function pager(str: string): Promise<void>;
4
+ }
@@ -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
+ }