eslint-rule-forge 0.5.0 → 0.6.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 +15 -14
- package/dist/bindings.d.mts +8 -2
- package/dist/bindings.d.mts.map +1 -1
- package/dist/bindings.mjs +33 -2
- package/dist/bindings.mjs.map +1 -1
- package/dist/imports.d.mts +24 -2
- package/dist/imports.d.mts.map +1 -1
- package/dist/imports.mjs +83 -25
- package/dist/imports.mjs.map +1 -1
- package/dist/rules/prefer-array-is-array.d.mts.map +1 -1
- package/dist/rules/prefer-array-is-array.mjs +56 -49
- 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 +13 -8
- 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 +14 -9
- 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 +19 -10
- package/dist/rules/prefer-safe-parse.mjs.map +1 -1
- package/package.json +6 -6
- package/src/bindings.mts +43 -3
- package/src/imports.mts +151 -54
- package/src/rules/prefer-array-is-array.mts +25 -18
- package/src/rules/prefer-is-non-null-object.mts +16 -9
- package/src/rules/prefer-is-record-and-has-key.mts +17 -12
- package/src/rules/prefer-safe-parse.mts +26 -21
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
|
-
import {
|
|
4
|
-
import { bindValueImports } from '../imports.mjs';
|
|
3
|
+
import { bindValueImportsAt } from '../imports.mjs';
|
|
5
4
|
import { getArgumentText } from '../parentheses.mjs';
|
|
6
5
|
import { isIdentifierNamed, skipTypeWrappers, typeHoldsAtEveryLayer, } from '../type-wrappers.mjs';
|
|
7
6
|
|
|
@@ -18,8 +17,8 @@ export const createPreferIsRecordAndHasKey = ({ moduleName, }) => ({
|
|
|
18
17
|
fixable: 'code',
|
|
19
18
|
schema: [],
|
|
20
19
|
messages: {
|
|
21
|
-
useIsRecordAndHasKey: `Replace \`{{original}}\` with \`isRecord({{objName}}) && hasKey({{objName}}, {{keyName}})\` from ${moduleName}.`,
|
|
22
|
-
useHasKey: `Replace \`{{original}}\` with \`hasKey({{objName}}, {{keyName}})\` from ${moduleName}.`,
|
|
20
|
+
useIsRecordAndHasKey: `Replace \`{{original}}\` with \`{{isRecord}}({{objName}}) && {{hasKey}}({{objName}}, {{keyName}})\` from ${moduleName}.`,
|
|
21
|
+
useHasKey: `Replace \`{{original}}\` with \`{{hasKey}}({{objName}}, {{keyName}})\` from ${moduleName}.`,
|
|
23
22
|
},
|
|
24
23
|
defaultOptions: [],
|
|
25
24
|
},
|
|
@@ -99,20 +98,23 @@ export const createPreferIsRecordAndHasKey = ({ moduleName, }) => ({
|
|
|
99
98
|
});
|
|
100
99
|
},
|
|
101
100
|
'Program:exit': () => {
|
|
101
|
+
if (mut_nodesToFix.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
102
104
|
|
|
103
105
|
|
|
104
|
-
const
|
|
106
|
+
const withIsRecordAt = bindValueImportsAt(sourceCode, moduleName, [
|
|
105
107
|
'isRecord',
|
|
106
108
|
'hasKey',
|
|
107
109
|
]);
|
|
108
|
-
const
|
|
110
|
+
const hasKeyOnlyAt = bindValueImportsAt(sourceCode, moduleName, [
|
|
109
111
|
'hasKey',
|
|
110
112
|
]);
|
|
111
113
|
for (const { node, objExpression, keyExpression } of mut_nodesToFix) {
|
|
112
114
|
|
|
113
115
|
|
|
114
116
|
const needsIsRecord = !isKnownRecord(objExpression);
|
|
115
|
-
const binding = needsIsRecord ?
|
|
117
|
+
const binding = (needsIsRecord ? withIsRecordAt : hasKeyOnlyAt)(node);
|
|
116
118
|
const hasKeyName = binding?.localNames.at(-1);
|
|
117
119
|
const isRecordName = needsIsRecord
|
|
118
120
|
? binding?.localNames[0]
|
|
@@ -123,8 +125,7 @@ export const createPreferIsRecordAndHasKey = ({ moduleName, }) => ({
|
|
|
123
125
|
hasKeyName !== undefined &&
|
|
124
126
|
(!needsIsRecord || isRecordName !== undefined) &&
|
|
125
127
|
|
|
126
|
-
(!needsIsRecord || isSideEffectFree(objExpression))
|
|
127
|
-
binding.localNames.every((name) => !hasForeignBinding(sourceCode, node, name, moduleName));
|
|
128
|
+
(!needsIsRecord || isSideEffectFree(objExpression));
|
|
128
129
|
context.report({
|
|
129
130
|
node,
|
|
130
131
|
messageId: needsIsRecord ? 'useIsRecordAndHasKey' : 'useHasKey',
|
|
@@ -132,6 +133,10 @@ export const createPreferIsRecordAndHasKey = ({ moduleName, }) => ({
|
|
|
132
133
|
original: sourceCode.getText(node),
|
|
133
134
|
objName: objText,
|
|
134
135
|
keyName: keyText,
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
isRecord: isRecordName ?? 'isRecord',
|
|
139
|
+
hasKey: hasKeyName ?? 'hasKey',
|
|
135
140
|
},
|
|
136
141
|
fix: fixable
|
|
137
142
|
? (fixer) => [
|
|
@@ -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,EAAE,
|
|
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;AAWlC,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;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,WAC3B,mBAAmB,KAC1B,QAAQ,CAAC,UAAU,CACpB,iBAAiB,GAAG,sBAAsB,EAC1C,SAAS,EAAE,CAuBT,CAAC;AAEL;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,WAC7B,mBAAmB,KAC1B,QAAQ,CAAC,UAAU,CACpB,mBAAmB,GAAG,sBAAsB,EAC5C,SAAS,EAAE,CAWT,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
|
-
import {
|
|
4
|
-
import { bindValueImports } from '../imports.mjs';
|
|
3
|
+
import { bindValueImportsAt } from '../imports.mjs';
|
|
5
4
|
import { getArgumentText } from '../parentheses.mjs';
|
|
6
5
|
import { isIdentifierNamed, skipTypeWrappers, typeHoldsAtEveryLayer, } from '../type-wrappers.mjs';
|
|
7
6
|
|
|
@@ -68,7 +67,7 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
68
67
|
schema: [],
|
|
69
68
|
messages: {
|
|
70
69
|
...messages,
|
|
71
|
-
replaceWithSafeParse: `Replace with \`
|
|
70
|
+
replaceWithSafeParse: `Replace with \`{{resultName}}.unwrapOkOr({{namespaceName}}.${method}({{argName}}), Number.NaN)\`.`,
|
|
72
71
|
},
|
|
73
72
|
defaultOptions: [],
|
|
74
73
|
},
|
|
@@ -99,20 +98,27 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
99
98
|
mut_reports.push({ node, argExpression: firstArg });
|
|
100
99
|
},
|
|
101
100
|
'Program:exit': () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
101
|
+
if (mut_reports.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const bindingAt = bindValueImportsAt(sourceCode, moduleName, requiredImports);
|
|
106
105
|
|
|
107
106
|
|
|
108
107
|
|
|
109
108
|
|
|
110
109
|
|
|
111
110
|
for (const { node, argExpression } of mut_reports) {
|
|
111
|
+
const binding = bindingAt(node);
|
|
112
|
+
const localNameOf = (name) => binding?.localNames[requiredImports.indexOf(name)];
|
|
113
|
+
const namespaceName = localNameOf(namespace);
|
|
114
|
+
const resultName = localNameOf('Result');
|
|
112
115
|
const argText = getArgumentText(sourceCode, argExpression);
|
|
113
116
|
const data = {
|
|
114
117
|
original: sourceCode.getText(node),
|
|
115
118
|
argName: argText,
|
|
119
|
+
|
|
120
|
+
namespaceName: namespaceName ?? namespace,
|
|
121
|
+
resultName: resultName ?? 'Result',
|
|
116
122
|
};
|
|
117
123
|
context.report({
|
|
118
124
|
node,
|
|
@@ -120,8 +126,7 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
120
126
|
data,
|
|
121
127
|
suggest: binding === undefined ||
|
|
122
128
|
namespaceName === undefined ||
|
|
123
|
-
resultName === undefined
|
|
124
|
-
[namespaceName, resultName].some((name) => hasForeignBinding(sourceCode, node, name, moduleName))
|
|
129
|
+
resultName === undefined
|
|
125
130
|
? []
|
|
126
131
|
: [
|
|
127
132
|
{
|
|
@@ -140,7 +145,11 @@ const createSafeParseRule = ({ moduleName, namespace, method }, messageId, { mes
|
|
|
140
145
|
},
|
|
141
146
|
};
|
|
142
147
|
};
|
|
143
|
-
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
const messageText = ({ moduleName, method }) => `Replace \`{{original}}\` with \`{{resultName}}.unwrapOkOr({{namespaceName}}.${method}({{argName}}), Number.NaN)\` from ${moduleName}. It parses some strings differently from the call it replaces, so check the input before applying it.`;
|
|
144
153
|
|
|
145
154
|
const isGlobalCall = (callee, name) => callee.type === AST_NODE_TYPES.Identifier && callee.name === name;
|
|
146
155
|
|
|
@@ -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,EAAE,
|
|
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.6.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,14 +41,14 @@
|
|
|
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": "7.
|
|
45
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
46
|
-
"eslint-plugin-ts-type-forge": "0.8.
|
|
44
|
+
"eslint-config-typed": "7.5.0",
|
|
45
|
+
"eslint-plugin-ts-fortress": "1.3.14",
|
|
46
|
+
"eslint-plugin-ts-type-forge": "0.8.19",
|
|
47
47
|
"prettier": "3.9.10",
|
|
48
48
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
49
49
|
"prettier-plugin-packagejson": "3.0.2",
|
|
50
|
-
"ts-data-forge": "14.
|
|
51
|
-
"ts-repo-utils": "10.7.
|
|
50
|
+
"ts-data-forge": "14.11.0",
|
|
51
|
+
"ts-repo-utils": "10.7.3"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"eslint": ">=9.0.0",
|
package/src/bindings.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AST_NODE_TYPES,
|
|
3
3
|
ASTUtils,
|
|
4
|
-
|
|
4
|
+
TSESLint,
|
|
5
5
|
type TSESTree,
|
|
6
6
|
} from '@typescript-eslint/utils';
|
|
7
7
|
|
|
@@ -33,7 +33,13 @@ export const isLocallyBound = (
|
|
|
33
33
|
* has to give up then, because the name would refer to that binding instead.
|
|
34
34
|
*
|
|
35
35
|
* The innermost binding decides: an import from `moduleName` at the top of the
|
|
36
|
-
* file does not help a name a function shadows around `node`.
|
|
36
|
+
* file does not help a name a function shadows around `node`. A binding with no
|
|
37
|
+
* value meaning inside the file — a type parameter, a local type alias or
|
|
38
|
+
* interface — is passed over, since a type does not shadow a value or the head
|
|
39
|
+
* of a qualified name. At the top of the file it still counts: the import a fix
|
|
40
|
+
* adds there conflicts with it when the export has a type meaning too. A fix
|
|
41
|
+
* that writes the name alone in a type position, where such a binding does
|
|
42
|
+
* capture it, has to check for one itself.
|
|
37
43
|
*/
|
|
38
44
|
export const hasForeignBinding = (
|
|
39
45
|
sourceCode: TSESLint.SourceCode,
|
|
@@ -41,11 +47,45 @@ export const hasForeignBinding = (
|
|
|
41
47
|
name: string,
|
|
42
48
|
moduleName: string,
|
|
43
49
|
): boolean => {
|
|
44
|
-
const variable =
|
|
50
|
+
const variable = findShadowingVariable(sourceCode.getScope(node), name);
|
|
45
51
|
|
|
46
52
|
return variable?.defs.some((def) => !isImportFrom(def, moduleName)) ?? false;
|
|
47
53
|
};
|
|
48
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Whether every definition of `variable` is a type parameter, type alias or
|
|
57
|
+
* interface. Read from `def.type` rather than `isValueVariable`, which ESLint's
|
|
58
|
+
* own scope manager, used with its default parser, does not have.
|
|
59
|
+
*/
|
|
60
|
+
const isTypeOnly = (variable: TSESLint.Scope.Variable): boolean =>
|
|
61
|
+
variable.defs.length > 0 &&
|
|
62
|
+
variable.defs.every((def) => def.type === TSESLint.Scope.DefinitionType.Type);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether `scope` is the file's own, where an added import lands: the module or
|
|
66
|
+
* global scope, or the function scope a CommonJS file wraps around them.
|
|
67
|
+
*/
|
|
68
|
+
const isTopLevel = (scope: TSESLint.Scope.Scope): boolean =>
|
|
69
|
+
scope.block.type === AST_NODE_TYPES.Program;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The innermost variable named `name` from `scope` outwards that a value of
|
|
73
|
+
* that name written in `scope` would meet: one with a value meaning, or any at
|
|
74
|
+
* the top of the file.
|
|
75
|
+
*/
|
|
76
|
+
const findShadowingVariable = (
|
|
77
|
+
scope: TSESLint.Scope.Scope,
|
|
78
|
+
name: string,
|
|
79
|
+
): TSESLint.Scope.Variable | undefined => {
|
|
80
|
+
const variable = scope.set.get(name);
|
|
81
|
+
|
|
82
|
+
return variable !== undefined && (!isTypeOnly(variable) || isTopLevel(scope))
|
|
83
|
+
? variable
|
|
84
|
+
: scope.upper === null
|
|
85
|
+
? undefined
|
|
86
|
+
: findShadowingVariable(scope.upper, name);
|
|
87
|
+
};
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Whether `def` is a binding an `import … from '<moduleName>'` declaration
|
|
51
91
|
* introduces.
|
package/src/imports.mts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
6
|
import type { DeepReadonly } from 'ts-type-forge';
|
|
7
|
+
import { hasForeignBinding } from './bindings.mjs';
|
|
7
8
|
import { isTypeWrapper, skipTypeWrappers } from './type-wrappers.mjs';
|
|
8
9
|
|
|
9
10
|
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
@@ -224,47 +225,60 @@ export const bindValueImports = (
|
|
|
224
225
|
sourceCode: TSESLint.SourceCode,
|
|
225
226
|
moduleName: string,
|
|
226
227
|
exportNames: readonly string[],
|
|
227
|
-
): ValueImportBinding | undefined =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const allImports = program.body.filter(
|
|
231
|
-
(node): node is TSESTree.ImportDeclaration =>
|
|
232
|
-
node.type === AST_NODE_TYPES.ImportDeclaration,
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
const declarations = getImportDeclarations(program, moduleName);
|
|
236
|
-
|
|
237
|
-
const named = declarations.flatMap((declaration) =>
|
|
238
|
-
declaration.specifiers.flatMap((specifier) =>
|
|
239
|
-
specifier.type === AST_NODE_TYPES.ImportSpecifier
|
|
240
|
-
? [{ declaration, specifier }]
|
|
241
|
-
: [],
|
|
242
|
-
),
|
|
243
|
-
);
|
|
228
|
+
): ValueImportBinding | undefined =>
|
|
229
|
+
bindNames(sourceCode, moduleName, exportNames, undefined);
|
|
244
230
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
231
|
+
/**
|
|
232
|
+
* {@link bindValueImports} for the code at a node, which writes an export
|
|
233
|
+
* through a namespace import of the module (`ns.f` after
|
|
234
|
+
* `import * as ns from '<moduleName>'`) rather than importing it by name beside
|
|
235
|
+
* the namespace. Gives, for a node, what a fix there writes, or `undefined`
|
|
236
|
+
* when no fix can: an import binds a name to something else, or something other
|
|
237
|
+
* than an import from the module takes a name the fix writes where the node is
|
|
238
|
+
* ({@link hasForeignBinding}).
|
|
239
|
+
*
|
|
240
|
+
* - An export imported as a value is written by its local name, as
|
|
241
|
+
* {@link bindValueImports} writes it.
|
|
242
|
+
* - Any other, one imported only by `import { type x }` included, goes through
|
|
243
|
+
* the first namespace import that is not `import type * as`, with no edit.
|
|
244
|
+
* - Where a local binding shadows the namespace's name, every export is bound
|
|
245
|
+
* as {@link bindValueImports} binds it instead.
|
|
246
|
+
*
|
|
247
|
+
* Each choice is worked out once, however many nodes ask.
|
|
248
|
+
*/
|
|
249
|
+
export const bindValueImportsAt = (
|
|
250
|
+
sourceCode: TSESLint.SourceCode,
|
|
251
|
+
moduleName: string,
|
|
252
|
+
exportNames: readonly string[],
|
|
253
|
+
): ((node: TSESTree.Node) => ValueImportBinding | undefined) => {
|
|
254
|
+
const namespaceName = getImportDeclarations(sourceCode.ast, moduleName)
|
|
255
|
+
.filter((declaration) => !isTypeOnlyDeclaration(declaration))
|
|
256
|
+
.flatMap((declaration) => declaration.specifiers)
|
|
257
|
+
.find(
|
|
258
|
+
(specifier) => specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier,
|
|
259
|
+
)?.local.name;
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
const viaNamespace =
|
|
262
|
+
namespaceName === undefined
|
|
263
|
+
? undefined
|
|
264
|
+
: bindNames(sourceCode, moduleName, exportNames, namespaceName);
|
|
265
|
+
|
|
266
|
+
const byName = bindNames(sourceCode, moduleName, exportNames, undefined);
|
|
267
|
+
|
|
268
|
+
const holdsAt = (
|
|
269
|
+
binding: BoundNames | undefined,
|
|
270
|
+
node: TSESTree.Node,
|
|
271
|
+
): binding is BoundNames =>
|
|
272
|
+
binding?.boundNames.every(
|
|
273
|
+
(name) => !hasForeignBinding(sourceCode, node, name, moduleName),
|
|
274
|
+
) ?? false;
|
|
275
|
+
|
|
276
|
+
return (node) =>
|
|
277
|
+
holdsAt(viaNamespace, node)
|
|
278
|
+
? viaNamespace
|
|
279
|
+
: holdsAt(byName, node)
|
|
280
|
+
? byName
|
|
281
|
+
: undefined;
|
|
268
282
|
};
|
|
269
283
|
|
|
270
284
|
/**
|
|
@@ -400,20 +414,6 @@ const addToFirstHost = (
|
|
|
400
414
|
: buildNamedImportFix(fixer, sourceCode.ast, moduleName, specifiers);
|
|
401
415
|
};
|
|
402
416
|
|
|
403
|
-
/** What {@link bindValueImports} gives. */
|
|
404
|
-
export type ValueImportBinding = Readonly<{
|
|
405
|
-
/** The names to write, in the order of the export names asked for. */
|
|
406
|
-
localNames: readonly string[];
|
|
407
|
-
|
|
408
|
-
/** The edits that bring every one of them into scope. */
|
|
409
|
-
edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
|
|
410
|
-
}>;
|
|
411
|
-
|
|
412
|
-
type NamedSpecifier = Readonly<{
|
|
413
|
-
declaration: TSESTree.ImportDeclaration;
|
|
414
|
-
specifier: TSESTree.ImportSpecifier;
|
|
415
|
-
}>;
|
|
416
|
-
|
|
417
417
|
/** `import type …`. */
|
|
418
418
|
const isTypeOnlyDeclaration = (
|
|
419
419
|
declaration: TSESTree.ImportDeclaration,
|
|
@@ -460,9 +460,11 @@ const bindingOf = (
|
|
|
460
460
|
allImports: readonly TSESTree.ImportDeclaration[],
|
|
461
461
|
named: readonly NamedSpecifier[],
|
|
462
462
|
exportName: string,
|
|
463
|
+
namespaceName: string | undefined,
|
|
463
464
|
):
|
|
464
465
|
| Readonly<{
|
|
465
466
|
localName: string;
|
|
467
|
+
boundName: string;
|
|
466
468
|
isAdded: boolean;
|
|
467
469
|
edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
|
|
468
470
|
}>
|
|
@@ -476,6 +478,16 @@ const bindingOf = (
|
|
|
476
478
|
if (value !== undefined) {
|
|
477
479
|
return {
|
|
478
480
|
localName: value.specifier.local.name,
|
|
481
|
+
boundName: value.specifier.local.name,
|
|
482
|
+
isAdded: false,
|
|
483
|
+
edits: () => [],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (namespaceName !== undefined) {
|
|
488
|
+
return {
|
|
489
|
+
localName: `${namespaceName}.${exportName}`,
|
|
490
|
+
boundName: namespaceName,
|
|
479
491
|
isAdded: false,
|
|
480
492
|
edits: () => [],
|
|
481
493
|
};
|
|
@@ -497,6 +509,7 @@ const bindingOf = (
|
|
|
497
509
|
? undefined
|
|
498
510
|
: {
|
|
499
511
|
localName: inlineType.local.name,
|
|
512
|
+
boundName: inlineType.local.name,
|
|
500
513
|
isAdded: false,
|
|
501
514
|
edits: (fixer) => [
|
|
502
515
|
fixer.removeRange([typeKeyword.range[0], next.range[0]]),
|
|
@@ -512,5 +525,89 @@ const bindingOf = (
|
|
|
512
525
|
|
|
513
526
|
return isTaken
|
|
514
527
|
? undefined
|
|
515
|
-
: {
|
|
528
|
+
: {
|
|
529
|
+
localName: exportName,
|
|
530
|
+
boundName: exportName,
|
|
531
|
+
isAdded: true,
|
|
532
|
+
edits: () => [],
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* {@link bindValueImports}, writing an export no value import binds through the
|
|
538
|
+
* namespace import `namespaceName` when there is one. `boundNames` are the
|
|
539
|
+
* names the written text starts with, which something at the node must not
|
|
540
|
+
* shadow.
|
|
541
|
+
*/
|
|
542
|
+
const bindNames = (
|
|
543
|
+
sourceCode: TSESLint.SourceCode,
|
|
544
|
+
moduleName: string,
|
|
545
|
+
exportNames: readonly string[],
|
|
546
|
+
namespaceName: string | undefined,
|
|
547
|
+
): BoundNames | undefined => {
|
|
548
|
+
const program = sourceCode.ast;
|
|
549
|
+
|
|
550
|
+
const allImports = program.body.filter(
|
|
551
|
+
(node): node is TSESTree.ImportDeclaration =>
|
|
552
|
+
node.type === AST_NODE_TYPES.ImportDeclaration,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
const declarations = getImportDeclarations(program, moduleName);
|
|
556
|
+
|
|
557
|
+
const named = declarations.flatMap((declaration) =>
|
|
558
|
+
declaration.specifiers.flatMap((specifier) =>
|
|
559
|
+
specifier.type === AST_NODE_TYPES.ImportSpecifier
|
|
560
|
+
? [{ declaration, specifier }]
|
|
561
|
+
: [],
|
|
562
|
+
),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
const bindings = exportNames.map((exportName) =>
|
|
566
|
+
bindingOf(sourceCode, allImports, named, exportName, namespaceName),
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
if (bindings.includes(undefined)) {
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const defined = bindings.filter((b) => b !== undefined);
|
|
574
|
+
|
|
575
|
+
const toAdd = defined.flatMap((b) => (b.isAdded ? [b.localName] : []));
|
|
576
|
+
|
|
577
|
+
const hosts = hostsFor(declarations);
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
localNames: defined.map((b) => b.localName),
|
|
581
|
+
boundNames: defined.map((b) => b.boundName),
|
|
582
|
+
edits: (fixer) => [
|
|
583
|
+
...defined.flatMap((b) => b.edits(fixer)),
|
|
584
|
+
...(toAdd.length === 0
|
|
585
|
+
? []
|
|
586
|
+
: addToFirstHost(fixer, sourceCode, moduleName, hosts, toAdd)),
|
|
587
|
+
],
|
|
588
|
+
};
|
|
516
589
|
};
|
|
590
|
+
|
|
591
|
+
/** What {@link bindValueImports} and {@link bindValueImportsAt} give. */
|
|
592
|
+
export type ValueImportBinding = Readonly<{
|
|
593
|
+
/**
|
|
594
|
+
* What to write for each export, in the order of the export names asked for:
|
|
595
|
+
* a local name, or `ns.x` through a namespace import.
|
|
596
|
+
*/
|
|
597
|
+
localNames: readonly string[];
|
|
598
|
+
|
|
599
|
+
/** The edits that bring every one of them into scope. */
|
|
600
|
+
edits: (fixer: TSESLint.RuleFixer) => readonly TSESLint.RuleFix[];
|
|
601
|
+
}>;
|
|
602
|
+
|
|
603
|
+
/** What {@link bindNames} gives. */
|
|
604
|
+
type BoundNames = ValueImportBinding &
|
|
605
|
+
Readonly<{
|
|
606
|
+
/** The name each of `localNames` starts with. */
|
|
607
|
+
boundNames: readonly string[];
|
|
608
|
+
}>;
|
|
609
|
+
|
|
610
|
+
type NamedSpecifier = Readonly<{
|
|
611
|
+
declaration: TSESTree.ImportDeclaration;
|
|
612
|
+
specifier: TSESTree.ImportSpecifier;
|
|
613
|
+
}>;
|
|
@@ -3,8 +3,7 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import {
|
|
7
|
-
import { bindValueImports } from '../imports.mjs';
|
|
6
|
+
import { bindValueImportsAt } from '../imports.mjs';
|
|
8
7
|
import { getRangeWithParens } from '../parentheses.mjs';
|
|
9
8
|
import { isIdentifierNamed, skipTypeWrappers } from '../type-wrappers.mjs';
|
|
10
9
|
|
|
@@ -22,19 +21,18 @@ export const createPreferArrayIsArray = ({
|
|
|
22
21
|
|
|
23
22
|
/** The exported namespace whose `isArray` replaces `Array.isArray`. */
|
|
24
23
|
namespace: string;
|
|
25
|
-
}>): TSESLint.RuleModule<'useIsArray', readonly []> =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const text =
|
|
29
|
-
`Replace \`Array.isArray\` with \`${replacement}\` from ${moduleName}.` as const;
|
|
30
|
-
|
|
31
|
-
return {
|
|
24
|
+
}>): TSESLint.RuleModule<'useIsArray', readonly []> =>
|
|
25
|
+
({
|
|
32
26
|
meta: {
|
|
33
27
|
type: 'suggestion',
|
|
34
|
-
docs: {
|
|
28
|
+
docs: {
|
|
29
|
+
description: `Replace \`Array.isArray\` with \`${namespace}.isArray\` from ${moduleName}.`,
|
|
30
|
+
},
|
|
35
31
|
fixable: 'code',
|
|
36
32
|
schema: [],
|
|
37
|
-
messages: {
|
|
33
|
+
messages: {
|
|
34
|
+
useIsArray: `Replace \`Array.isArray\` with \`{{replacement}}\` from ${moduleName}.`,
|
|
35
|
+
},
|
|
38
36
|
defaultOptions: [],
|
|
39
37
|
},
|
|
40
38
|
|
|
@@ -52,20 +50,30 @@ export const createPreferArrayIsArray = ({
|
|
|
52
50
|
mut_nodesToFix.push(node);
|
|
53
51
|
},
|
|
54
52
|
'Program:exit': () => {
|
|
55
|
-
|
|
53
|
+
if (mut_nodesToFix.length === 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
56
|
|
|
57
|
-
const
|
|
57
|
+
const bindingAt = bindValueImportsAt(sourceCode, moduleName, [
|
|
58
|
+
namespace,
|
|
59
|
+
]);
|
|
58
60
|
|
|
59
61
|
// Every fix brings the namespace into scope itself, so that one
|
|
60
62
|
// applied alone (an editor's quick fix) is complete.
|
|
61
63
|
for (const node of mut_nodesToFix) {
|
|
64
|
+
const binding = bindingAt(node);
|
|
65
|
+
|
|
66
|
+
const localName = binding?.localNames[0];
|
|
67
|
+
|
|
62
68
|
context.report({
|
|
63
69
|
node,
|
|
64
70
|
messageId: 'useIsArray',
|
|
71
|
+
data: {
|
|
72
|
+
// The name the fix writes; with no fix, the export's own.
|
|
73
|
+
replacement: `${localName ?? namespace}.isArray`,
|
|
74
|
+
},
|
|
65
75
|
fix:
|
|
66
|
-
binding === undefined ||
|
|
67
|
-
localName === undefined ||
|
|
68
|
-
hasForeignBinding(sourceCode, node, localName, moduleName)
|
|
76
|
+
binding === undefined || localName === undefined
|
|
69
77
|
? null
|
|
70
78
|
: (fixer) => [
|
|
71
79
|
...binding.edits(fixer),
|
|
@@ -82,8 +90,7 @@ export const createPreferArrayIsArray = ({
|
|
|
82
90
|
},
|
|
83
91
|
};
|
|
84
92
|
},
|
|
85
|
-
} as const;
|
|
86
|
-
};
|
|
93
|
+
}) as const;
|
|
87
94
|
|
|
88
95
|
/**
|
|
89
96
|
* Whether `node` calls `Array.isArray`, the callee and `Array` both read
|