eslint-rule-forge 0.3.2 → 0.4.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/README.md +30 -31
- package/dist/comments.d.mts +10 -0
- package/dist/comments.d.mts.map +1 -0
- package/dist/comments.mjs +11 -0
- package/dist/comments.mjs.map +1 -0
- package/dist/imports.d.mts +47 -6
- package/dist/imports.d.mts.map +1 -1
- package/dist/imports.mjs +123 -6
- package/dist/imports.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/rules/prefer-array-is-array.d.mts.map +1 -1
- package/dist/rules/prefer-array-is-array.mjs +13 -19
- package/dist/rules/prefer-array-is-array.mjs.map +1 -1
- package/dist/rules/prefer-is-non-null-object.d.mts.map +1 -1
- package/dist/rules/prefer-is-non-null-object.mjs +24 -25
- package/dist/rules/prefer-is-non-null-object.mjs.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.d.mts.map +1 -1
- package/dist/rules/prefer-is-record-and-has-key.mjs +32 -44
- package/dist/rules/prefer-is-record-and-has-key.mjs.map +1 -1
- package/dist/rules/prefer-safe-parse.d.mts.map +1 -1
- package/dist/rules/prefer-safe-parse.mjs +16 -15
- package/dist/rules/prefer-safe-parse.mjs.map +1 -1
- package/package.json +4 -4
- package/src/comments.mts +23 -0
- package/src/imports.mts +217 -17
- package/src/index.mts +1 -0
- package/src/rules/prefer-array-is-array.mts +23 -36
- package/src/rules/prefer-is-non-null-object.mts +37 -42
- package/src/rules/prefer-is-record-and-has-key.mts +42 -66
- package/src/rules/prefer-safe-parse.mts +31 -38
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
|
-
import {
|
|
3
|
+
import { hasForeignBinding } from '../bindings.mjs';
|
|
4
|
+
import { bindValueImports } from '../imports.mjs';
|
|
4
5
|
import { getArgumentText } from '../parentheses.mjs';
|
|
5
6
|
import { isIdentifierNamed, skipTypeWrappers, typeHoldsAtEveryLayer, } from '../type-wrappers.mjs';
|
|
6
7
|
|
|
@@ -24,8 +25,6 @@ export const createPreferIsRecordAndHasKey = ({ moduleName, }) => ({
|
|
|
24
25
|
},
|
|
25
26
|
create: (context) => {
|
|
26
27
|
const sourceCode = context.sourceCode;
|
|
27
|
-
const program = sourceCode.ast;
|
|
28
|
-
const moduleImports = getImportDeclarations(program, moduleName);
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
|
@@ -100,59 +99,48 @@ export const createPreferIsRecordAndHasKey = ({ moduleName, }) => ({
|
|
|
100
99
|
});
|
|
101
100
|
},
|
|
102
101
|
'Program:exit': () => {
|
|
103
|
-
const namedImports = getNamedImports(moduleImports);
|
|
104
|
-
const rewrites = mut_nodesToFix.map((entry) => {
|
|
105
102
|
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
const withIsRecord = bindValueImports(sourceCode, moduleName, [
|
|
105
|
+
'isRecord',
|
|
106
|
+
'hasKey',
|
|
107
|
+
]);
|
|
108
|
+
const hasKeyOnly = bindValueImports(sourceCode, moduleName, [
|
|
109
|
+
'hasKey',
|
|
110
|
+
]);
|
|
111
|
+
for (const { node, objExpression, keyExpression } of mut_nodesToFix) {
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...(fixableRewrites.some(({ needsIsRecord }) => needsIsRecord) && !namedImports.includes('isRecord')
|
|
120
|
-
? ['isRecord']
|
|
121
|
-
: []),
|
|
122
|
-
...(namedImports.includes('hasKey') ? [] : ['hasKey']),
|
|
123
|
-
];
|
|
124
|
-
const importCarrier = fixableRewrites[0];
|
|
125
|
-
for (const rewrite of rewrites) {
|
|
126
|
-
const { node, objExpression, keyExpression, needsIsRecord } = rewrite;
|
|
113
|
+
|
|
114
|
+
const needsIsRecord = !isKnownRecord(objExpression);
|
|
115
|
+
const binding = needsIsRecord ? withIsRecord : hasKeyOnly;
|
|
116
|
+
const hasKeyName = binding?.localNames.at(-1);
|
|
117
|
+
const isRecordName = needsIsRecord
|
|
118
|
+
? binding?.localNames[0]
|
|
119
|
+
: undefined;
|
|
127
120
|
const objText = getArgumentText(sourceCode, objExpression);
|
|
128
121
|
const keyText = getArgumentText(sourceCode, keyExpression);
|
|
129
|
-
const
|
|
122
|
+
const fixable = binding !== undefined &&
|
|
123
|
+
hasKeyName !== undefined &&
|
|
124
|
+
(!needsIsRecord || isRecordName !== undefined) &&
|
|
125
|
+
|
|
126
|
+
(!needsIsRecord || isSideEffectFree(objExpression)) &&
|
|
127
|
+
binding.localNames.every((name) => !hasForeignBinding(sourceCode, node, name, moduleName));
|
|
130
128
|
context.report({
|
|
131
129
|
node,
|
|
132
130
|
messageId: needsIsRecord ? 'useIsRecordAndHasKey' : 'useHasKey',
|
|
133
131
|
data: {
|
|
134
|
-
original:
|
|
132
|
+
original: sourceCode.getText(node),
|
|
135
133
|
objName: objText,
|
|
136
134
|
keyName: keyText,
|
|
137
135
|
},
|
|
138
|
-
fix:
|
|
139
|
-
?
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
? `(
|
|
143
|
-
:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const importFixes = rewrite === importCarrier && importsToAdd.length > 0
|
|
149
|
-
? buildNamedImportFix(fixer, program, moduleName, importsToAdd)
|
|
150
|
-
: [];
|
|
151
|
-
return [
|
|
152
|
-
...importFixes,
|
|
153
|
-
fixer.replaceText(node, replacement),
|
|
154
|
-
];
|
|
155
|
-
},
|
|
136
|
+
fix: fixable
|
|
137
|
+
? (fixer) => [
|
|
138
|
+
...binding.edits(fixer),
|
|
139
|
+
fixer.replaceText(node, needsIsRecord
|
|
140
|
+
? `(${isRecordName}(${objText}) && ${hasKeyName}(${objText}, ${keyText}))`
|
|
141
|
+
: `${hasKeyName}(${objText}, ${keyText})`),
|
|
142
|
+
]
|
|
143
|
+
: null,
|
|
156
144
|
});
|
|
157
145
|
}
|
|
158
146
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-is-record-and-has-key.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"prefer-is-record-and-has-key.mjs","sourceRoot":"","sources":["../../src/rules/prefer-is-record-and-has-key.mts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-parse.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-parse.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"prefer-safe-parse.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-safe-parse.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAYlC,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IAEnB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,WAC3B,mBAAmB,KAC1B,QAAQ,CAAC,UAAU,CAAC,iBAAiB,EAAE,SAAS,EAAE,CAsBjD,CAAC;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,WAC7B,mBAAmB,KAC1B,QAAQ,CAAC,UAAU,CAAC,mBAAmB,EAAE,SAAS,EAAE,CAUnD,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
|
-
import {
|
|
3
|
+
import { hasForeignBinding } from '../bindings.mjs';
|
|
4
|
+
import { bindValueImports } from '../imports.mjs';
|
|
4
5
|
import { getArgumentText } from '../parentheses.mjs';
|
|
5
6
|
import { isIdentifierNamed, skipTypeWrappers, typeHoldsAtEveryLayer, } from '../type-wrappers.mjs';
|
|
6
7
|
|
|
@@ -44,7 +45,6 @@ export const createPreferSafeParseFloat = (config) => createSafeParseRule(config
|
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { messages, description, isTarget, }) => {
|
|
47
|
-
const parseFunction = `${namespace}.${method}`;
|
|
48
48
|
|
|
49
49
|
const requiredImports = [namespace, 'Result'].toSorted();
|
|
50
50
|
return {
|
|
@@ -58,8 +58,6 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
58
58
|
},
|
|
59
59
|
create: (context) => {
|
|
60
60
|
const sourceCode = context.sourceCode;
|
|
61
|
-
const program = sourceCode.ast;
|
|
62
|
-
const moduleImports = getImportDeclarations(program, moduleName);
|
|
63
61
|
const mut_nodesToFix = [];
|
|
64
62
|
return {
|
|
65
63
|
CallExpression: (node) => {
|
|
@@ -86,12 +84,13 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
86
84
|
mut_nodesToFix.push({ node, argExpression: firstArg });
|
|
87
85
|
},
|
|
88
86
|
'Program:exit': () => {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
87
|
+
const binding = bindValueImports(sourceCode, moduleName, requiredImports);
|
|
88
|
+
const localNameOf = (name) => binding?.localNames[requiredImports.indexOf(name)];
|
|
89
|
+
const namespaceName = localNameOf(namespace);
|
|
90
|
+
const resultName = localNameOf('Result');
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
for (const [index, { node, argExpression },] of mut_nodesToFix.entries()) {
|
|
93
|
+
for (const { node, argExpression } of mut_nodesToFix) {
|
|
95
94
|
const argText = getArgumentText(sourceCode, argExpression);
|
|
96
95
|
context.report({
|
|
97
96
|
node,
|
|
@@ -100,13 +99,15 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
100
99
|
original: sourceCode.getText(node),
|
|
101
100
|
argName: argText,
|
|
102
101
|
},
|
|
103
|
-
fix:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
fix: binding === undefined ||
|
|
103
|
+
namespaceName === undefined ||
|
|
104
|
+
resultName === undefined ||
|
|
105
|
+
[namespaceName, resultName].some((name) => hasForeignBinding(sourceCode, node, name, moduleName))
|
|
106
|
+
? null
|
|
107
|
+
: (fixer) => [
|
|
108
|
+
...binding.edits(fixer),
|
|
109
|
+
fixer.replaceText(node, `${resultName}.unwrapOkOr(${namespaceName}.${method}(${argText}), Number.NaN)`),
|
|
110
|
+
],
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
113
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-safe-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-parse.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"prefer-safe-parse.mjs","sourceRoot":"","sources":["../../src/rules/prefer-safe-parse.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-rule-forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Helpers for writing typescript-eslint rules that see through type wrappers and parentheses, and resolve what a file imports.",
|
|
6
6
|
"keywords": [
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"@typescript-eslint/rule-tester": "8.72.0",
|
|
42
42
|
"dedent": "1.7.2",
|
|
43
43
|
"eslint": "10.13.0",
|
|
44
|
-
"eslint-config-typed": "
|
|
45
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
46
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
44
|
+
"eslint-config-typed": "7.1.0",
|
|
45
|
+
"eslint-plugin-ts-fortress": "1.3.10",
|
|
46
|
+
"eslint-plugin-ts-type-forge": "0.8.15",
|
|
47
47
|
"prettier": "3.9.10",
|
|
48
48
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
49
49
|
"prettier-plugin-packagejson": "3.0.2",
|
package/src/comments.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
import type { DeepReadonly } from 'ts-type-forge';
|
|
3
|
+
|
|
4
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Whether a comment inside `node` lies within one of `ranges`, which a fix
|
|
8
|
+
* would delete along with it. Such a fix is dropped instead of the comment.
|
|
9
|
+
* Only comments inside `node` are looked at, so each range has to lie within
|
|
10
|
+
* it.
|
|
11
|
+
*/
|
|
12
|
+
export const hasCommentWithin = (
|
|
13
|
+
sourceCode: TSESLint.SourceCode,
|
|
14
|
+
node: TSESTree.Node,
|
|
15
|
+
ranges: DeepReadonly<TSESTree.Range[]>,
|
|
16
|
+
): boolean =>
|
|
17
|
+
sourceCode
|
|
18
|
+
.getCommentsInside(node)
|
|
19
|
+
.some((comment) =>
|
|
20
|
+
ranges.some(
|
|
21
|
+
([start, end]) => start <= comment.range[0] && comment.range[1] <= end,
|
|
22
|
+
),
|
|
23
|
+
);
|
package/src/imports.mts
CHANGED
|
@@ -140,33 +140,233 @@ export const buildCalleeResolver = (
|
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
142
|
* A fix that inserts `import { <specifiers> } from '<moduleName>';` at the top
|
|
143
|
-
* of the file,
|
|
143
|
+
* of the file, after its directive prologue (`'use client';` stays a
|
|
144
|
+
* directive), or no fix when `specifiers` is empty. Each specifier is written
|
|
144
145
|
* as given, so `type T` imports a type.
|
|
145
146
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
147
|
+
* Use it only when the file does not import from `moduleName` by name;
|
|
148
|
+
* {@link bindValueImports} adds to an existing declaration instead, which
|
|
149
|
+
* keeps `import-x/no-duplicates` satisfied, and falls back to this. An import
|
|
150
|
+
* sorter orders the declaration afterwards. Put every name one report needs
|
|
151
|
+
* into a single call: ESLint merges the fixes of one report into one edit,
|
|
152
|
+
* and two fixes inserting at the same place conflict, so that one of them is
|
|
153
|
+
* dropped.
|
|
151
154
|
*/
|
|
152
155
|
export const buildNamedImportFix = (
|
|
153
156
|
fixer: TSESLint.RuleFixer,
|
|
154
157
|
program: TSESTree.Program,
|
|
155
158
|
moduleName: string,
|
|
156
159
|
specifiers: readonly string[],
|
|
157
|
-
): readonly TSESLint.RuleFix[] =>
|
|
158
|
-
specifiers.length === 0
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
): readonly TSESLint.RuleFix[] => {
|
|
161
|
+
if (specifiers.length === 0) {
|
|
162
|
+
return [] as const;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const declaration =
|
|
166
|
+
`import { ${specifiers.join(', ')} } from '${moduleName}';` as const;
|
|
167
|
+
|
|
168
|
+
const lastDirective = program.body
|
|
169
|
+
.filter(
|
|
170
|
+
(statement): statement is TSESTree.ExpressionStatement =>
|
|
171
|
+
statement.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
172
|
+
statement.directive !== undefined,
|
|
173
|
+
)
|
|
174
|
+
.at(-1);
|
|
175
|
+
|
|
176
|
+
if (lastDirective === undefined) {
|
|
177
|
+
return [fixer.insertTextBefore(program, `${declaration}\n`)] as const;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// After the comments that start on the directive's last line as well: one
|
|
181
|
+
// such as `// eslint-disable-line` is about that line.
|
|
182
|
+
const anchor =
|
|
183
|
+
(program.comments ?? [])
|
|
184
|
+
.filter(
|
|
185
|
+
(comment) =>
|
|
186
|
+
comment.range[0] >= lastDirective.range[1] &&
|
|
187
|
+
comment.loc.start.line === lastDirective.loc.end.line,
|
|
188
|
+
)
|
|
189
|
+
.at(-1) ?? lastDirective;
|
|
190
|
+
|
|
191
|
+
return [fixer.insertTextAfter(anchor, `\n${declaration}`)] as const;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The local names `moduleName`'s exports `exportNames` are bound to as values
|
|
196
|
+
* once `edits` are made, in the same order, or `undefined` when one of them
|
|
197
|
+
* cannot be bound.
|
|
198
|
+
*
|
|
199
|
+
* - Imported as a value: that local name, and no edit.
|
|
200
|
+
* - Imported by `import { type x }`: the edit drops `type`, keeping a comment
|
|
201
|
+
* after it.
|
|
202
|
+
* - Not imported: the edit adds it to the first named import from the module
|
|
203
|
+
* that has a value specifier, or else to any named import from it that is
|
|
204
|
+
* not `import type`. Adding it there rather than in a new declaration keeps
|
|
205
|
+
* `import-x/no-duplicates` satisfied. Failing that, it goes into braces
|
|
206
|
+
* after a lone default import (`import D, { x }`), and when the module has
|
|
207
|
+
* neither, a new declaration goes after the directive prologue
|
|
208
|
+
* ({@link buildNamedImportFix}).
|
|
209
|
+
* - Refused when an import already binds the name to something else
|
|
210
|
+
* (`import type { x }`, `import { y as x }`, `import { x } from 'other'`),
|
|
211
|
+
* where adding it would be a duplicate binding.
|
|
212
|
+
*
|
|
213
|
+
* ESLint's default parser leaves `importKind` out, which counts as a value
|
|
214
|
+
* import. Every name a fix writes is bound by that fix's own edits rather than
|
|
215
|
+
* left to another report's: an editor applies one fix at a time, and a fix
|
|
216
|
+
* that loses a conflict with another in the same pass is dropped. Fixes that
|
|
217
|
+
* all add a name overlap, so `--fix` applies one per pass, and the rest in the
|
|
218
|
+
* next, by when the file imports it. Whether something other than an import
|
|
219
|
+
* binds the name where it is written is the caller's to check
|
|
220
|
+
* ({@link hasForeignBinding}).
|
|
221
|
+
*/
|
|
222
|
+
export const bindValueImports = (
|
|
223
|
+
sourceCode: TSESLint.SourceCode,
|
|
224
|
+
moduleName: string,
|
|
225
|
+
exportNames: readonly string[],
|
|
226
|
+
): ValueImportBinding | undefined => {
|
|
227
|
+
const program = sourceCode.ast;
|
|
228
|
+
|
|
229
|
+
const allImports = program.body.filter(
|
|
230
|
+
(node): node is TSESTree.ImportDeclaration =>
|
|
231
|
+
node.type === AST_NODE_TYPES.ImportDeclaration,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const declarations = getImportDeclarations(program, moduleName);
|
|
235
|
+
|
|
236
|
+
const named = declarations.flatMap((declaration) =>
|
|
237
|
+
declaration.specifiers.flatMap((specifier) =>
|
|
238
|
+
specifier.type === AST_NODE_TYPES.ImportSpecifier
|
|
239
|
+
? [{ declaration, specifier }]
|
|
240
|
+
: [],
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const bindings = exportNames.map((exportName) =>
|
|
245
|
+
bindingOf(sourceCode, allImports, named, exportName),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
if (bindings.includes(undefined)) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const defined = bindings.filter((b) => b !== undefined);
|
|
253
|
+
|
|
254
|
+
const toAdd = defined.flatMap((b) => (b.isAdded ? [b.localName] : []));
|
|
255
|
+
|
|
256
|
+
const host = (
|
|
257
|
+
named.find(isValueImport) ?? named.find(isNotTypeOnly)
|
|
258
|
+
)?.declaration.specifiers.at(-1);
|
|
259
|
+
|
|
260
|
+
// `import D from 'm'` takes `, { x }`: a second declaration beside it would
|
|
261
|
+
// be a duplicate.
|
|
262
|
+
const defaultHost = declarations.find(
|
|
263
|
+
(declaration) =>
|
|
264
|
+
declaration.importKind !== 'type' &&
|
|
265
|
+
declaration.specifiers.length === 1 &&
|
|
266
|
+
declaration.specifiers[0]?.type === AST_NODE_TYPES.ImportDefaultSpecifier,
|
|
267
|
+
)?.specifiers[0];
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
localNames: defined.map((b) => b.localName),
|
|
271
|
+
edits: (fixer) => [
|
|
272
|
+
...defined.flatMap((b) => b.edits(fixer)),
|
|
273
|
+
...(toAdd.length === 0
|
|
274
|
+
? []
|
|
275
|
+
: host !== undefined
|
|
276
|
+
? [fixer.insertTextAfter(host, `, ${toAdd.join(', ')}`)]
|
|
277
|
+
: defaultHost !== undefined
|
|
278
|
+
? [fixer.insertTextAfter(defaultHost, `, { ${toAdd.join(', ')} }`)]
|
|
279
|
+
: buildNamedImportFix(fixer, program, moduleName, toAdd)),
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/** The exported name `specifier` imports: `x` for `import { x as y }`. */
|
|
285
|
+
export const importedNameOf = (
|
|
168
286
|
specifier: DeepReadonly<TSESTree.ImportSpecifier>,
|
|
169
287
|
): string =>
|
|
170
288
|
specifier.imported.type === AST_NODE_TYPES.Identifier
|
|
171
289
|
? specifier.imported.name
|
|
172
290
|
: specifier.imported.value;
|
|
291
|
+
|
|
292
|
+
/** What {@link bindValueImports} gives. */
|
|
293
|
+
export type ValueImportBinding = Readonly<{
|
|
294
|
+
/** The names to write, in the order of the export names asked for. */
|
|
295
|
+
localNames: readonly string[];
|
|
296
|
+
|
|
297
|
+
/** The edits that bring every one of them into scope. */
|
|
298
|
+
edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
|
|
299
|
+
}>;
|
|
300
|
+
|
|
301
|
+
type NamedSpecifier = Readonly<{
|
|
302
|
+
declaration: TSESTree.ImportDeclaration;
|
|
303
|
+
specifier: TSESTree.ImportSpecifier;
|
|
304
|
+
}>;
|
|
305
|
+
|
|
306
|
+
/** Neither `specifier` nor its declaration says `type`. */
|
|
307
|
+
const isValueImport = ({ declaration, specifier }: NamedSpecifier): boolean =>
|
|
308
|
+
isNotTypeOnly({ declaration, specifier }) && specifier.importKind !== 'type';
|
|
309
|
+
|
|
310
|
+
/** The declaration is not `import type`. */
|
|
311
|
+
const isNotTypeOnly = ({ declaration }: NamedSpecifier): boolean =>
|
|
312
|
+
declaration.importKind !== 'type';
|
|
313
|
+
|
|
314
|
+
const bindingOf = (
|
|
315
|
+
sourceCode: TSESLint.SourceCode,
|
|
316
|
+
allImports: readonly TSESTree.ImportDeclaration[],
|
|
317
|
+
named: readonly NamedSpecifier[],
|
|
318
|
+
exportName: string,
|
|
319
|
+
):
|
|
320
|
+
| Readonly<{
|
|
321
|
+
localName: string;
|
|
322
|
+
isAdded: boolean;
|
|
323
|
+
edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
|
|
324
|
+
}>
|
|
325
|
+
| undefined => {
|
|
326
|
+
const matching = named.filter(
|
|
327
|
+
({ specifier }) => importedNameOf(specifier) === exportName,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const value = matching.find(isValueImport);
|
|
331
|
+
|
|
332
|
+
if (value !== undefined) {
|
|
333
|
+
return {
|
|
334
|
+
localName: value.specifier.local.name,
|
|
335
|
+
isAdded: false,
|
|
336
|
+
edits: () => [],
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const inlineType = matching.find(isNotTypeOnly)?.specifier;
|
|
341
|
+
|
|
342
|
+
if (inlineType !== undefined) {
|
|
343
|
+
const typeKeyword = sourceCode.getFirstToken(inlineType);
|
|
344
|
+
|
|
345
|
+
// Up to whatever follows `type`, a comment included, so that the comment
|
|
346
|
+
// stays.
|
|
347
|
+
const next =
|
|
348
|
+
typeKeyword === null
|
|
349
|
+
? null
|
|
350
|
+
: sourceCode.getTokenAfter(typeKeyword, { includeComments: true });
|
|
351
|
+
|
|
352
|
+
return typeKeyword === null || next === null
|
|
353
|
+
? undefined
|
|
354
|
+
: {
|
|
355
|
+
localName: inlineType.local.name,
|
|
356
|
+
isAdded: false,
|
|
357
|
+
edits: (fixer) => [
|
|
358
|
+
fixer.removeRange([typeKeyword.range[0], next.range[0]]),
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const isTaken = allImports.some((declaration) =>
|
|
364
|
+
declaration.specifiers.some(
|
|
365
|
+
(specifier) => specifier.local.name === exportName,
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
return isTaken
|
|
370
|
+
? undefined
|
|
371
|
+
: { localName: exportName, isAdded: true, edits: () => [] };
|
|
372
|
+
};
|
package/src/index.mts
CHANGED
|
@@ -3,11 +3,8 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
getImportDeclarations,
|
|
9
|
-
getNamedImports,
|
|
10
|
-
} from '../imports.mjs';
|
|
6
|
+
import { hasForeignBinding } from '../bindings.mjs';
|
|
7
|
+
import { bindValueImports } from '../imports.mjs';
|
|
11
8
|
import { getRangeWithParens } from '../parentheses.mjs';
|
|
12
9
|
import { isIdentifierNamed, skipTypeWrappers } from '../type-wrappers.mjs';
|
|
13
10
|
|
|
@@ -44,10 +41,6 @@ export const createPreferArrayIsArray = ({
|
|
|
44
41
|
create: (context) => {
|
|
45
42
|
const sourceCode = context.sourceCode;
|
|
46
43
|
|
|
47
|
-
const program = sourceCode.ast;
|
|
48
|
-
|
|
49
|
-
const moduleImports = getImportDeclarations(program, moduleName);
|
|
50
|
-
|
|
51
44
|
const mut_nodesToFix: TSESTree.CallExpression[] = [];
|
|
52
45
|
|
|
53
46
|
return {
|
|
@@ -59,37 +52,31 @@ export const createPreferArrayIsArray = ({
|
|
|
59
52
|
mut_nodesToFix.push(node);
|
|
60
53
|
},
|
|
61
54
|
'Program:exit': () => {
|
|
62
|
-
const
|
|
63
|
-
getNamedImports(moduleImports).includes(namespace);
|
|
55
|
+
const binding = bindValueImports(sourceCode, moduleName, [namespace]);
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
for (const [index, node] of mut_nodesToFix.entries()) {
|
|
57
|
+
const localName = binding?.localNames[0];
|
|
58
|
+
|
|
59
|
+
// Every fix brings the namespace into scope itself, so that one
|
|
60
|
+
// applied alone (an editor's quick fix) is complete.
|
|
61
|
+
for (const node of mut_nodesToFix) {
|
|
71
62
|
context.report({
|
|
72
63
|
node,
|
|
73
64
|
messageId: 'useIsArray',
|
|
74
|
-
fix:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
replacement,
|
|
90
|
-
),
|
|
91
|
-
];
|
|
92
|
-
},
|
|
65
|
+
fix:
|
|
66
|
+
binding === undefined ||
|
|
67
|
+
localName === undefined ||
|
|
68
|
+
hasForeignBinding(sourceCode, node, localName, moduleName)
|
|
69
|
+
? null
|
|
70
|
+
: (fixer) => [
|
|
71
|
+
...binding.edits(fixer),
|
|
72
|
+
// Only the callee is replaced, type wrappers included,
|
|
73
|
+
// along with the parentheses around it, which its range
|
|
74
|
+
// leaves out; the arguments stay as written.
|
|
75
|
+
fixer.replaceTextRange(
|
|
76
|
+
getRangeWithParens(sourceCode, node.callee),
|
|
77
|
+
`${localName}.isArray`,
|
|
78
|
+
),
|
|
79
|
+
],
|
|
93
80
|
});
|
|
94
81
|
}
|
|
95
82
|
},
|