@blumintinc/eslint-plugin-blumint 1.20.85 → 1.20.87
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/lib/index.js +1 -1
- package/lib/rules/enforce-assert-safe-object-key.js +7 -9
- package/lib/rules/enforce-memoize-async.js +8 -16
- package/lib/rules/enforce-memoize-getters.js +12 -16
- package/lib/rules/enforce-microdiff.js +14 -1
- package/lib/rules/enforce-querykey-ts.js +23 -7
- package/lib/rules/enforce-safe-stringify.js +7 -3
- package/lib/rules/enforce-snapshot-state-narrowing.js +24 -7
- package/lib/rules/enforce-stable-hash-spread-props.js +14 -3
- package/lib/rules/logical-top-to-bottom-grouping.js +70 -0
- package/lib/rules/no-array-length-in-deps.js +13 -1
- package/lib/rules/no-empty-dependency-use-callbacks.js +36 -0
- package/lib/rules/no-explicit-return-type.js +47 -59
- package/lib/rules/prefer-fragment-component.js +9 -27
- package/lib/rules/prefer-global-router-state-key.js +18 -24
- package/lib/rules/prefer-spread-over-reassembly.js +266 -11
- package/lib/rules/prefer-use-deep-compare-memo.js +14 -18
- package/lib/rules/react-memoize-literals.js +8 -7
- package/lib/rules/require-memo.js +10 -7
- package/lib/rules/require-memoize-jsx-returners.js +8 -13
- package/lib/utils/importInsertion.d.ts +52 -0
- package/lib/utils/importInsertion.js +121 -0
- package/lib/utils/importRemoval.d.ts +57 -0
- package/lib/utils/importRemoval.js +397 -0
- package/package.json +1 -1
- package/release-manifest.json +164 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planOrphanedImportRemoval = exports.importBindingSpecifierOf = exports.removeImportBindingFixes = exports.planImportBindingRemoval = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const disableDirectives_1 = require("./disableDirectives");
|
|
6
|
+
function contains(outer, inner) {
|
|
7
|
+
return inner[0] >= outer[0] && inner[1] <= outer[1];
|
|
8
|
+
}
|
|
9
|
+
function isWithinAny(range, ranges) {
|
|
10
|
+
return ranges.some((candidate) => contains(candidate, range));
|
|
11
|
+
}
|
|
12
|
+
function isNamedSpecifier(specifier) {
|
|
13
|
+
return specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A comment that governs the line directly below it. Deleting the declaration
|
|
17
|
+
* underneath such a comment strands the directive on whatever moves up into
|
|
18
|
+
* its place — an `@ts-expect-error` then becomes a compile error of its own and
|
|
19
|
+
* a disable directive silences an unrelated line.
|
|
20
|
+
*/
|
|
21
|
+
function bindsNextLine(comment) {
|
|
22
|
+
const [directive] = (0, disableDirectives_1.parseDisableDirectives)([comment]);
|
|
23
|
+
if (directive?.kind === 'disable-next-line') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const value = comment.value.trim();
|
|
27
|
+
return value.startsWith('@ts-expect-error') || value.startsWith('@ts-ignore');
|
|
28
|
+
}
|
|
29
|
+
function isComment(token) {
|
|
30
|
+
return (token.type === utils_1.AST_TOKEN_TYPES.Line || token.type === utils_1.AST_TOKEN_TYPES.Block);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The slice to delete for a declaration that loses its last binding. The whole
|
|
34
|
+
* line goes when the declaration owns it, trailing same-line comments included:
|
|
35
|
+
* those describe the import that is going away, and a `// eslint-disable-line`
|
|
36
|
+
* among them would otherwise outlive its subject. A declaration sharing its
|
|
37
|
+
* line with other code gives up only its own characters.
|
|
38
|
+
*/
|
|
39
|
+
function declarationRemovalRange(source, declaration) {
|
|
40
|
+
const commentsBefore = source.getCommentsBefore(declaration);
|
|
41
|
+
const boundComment = commentsBefore[commentsBefore.length - 1];
|
|
42
|
+
if (boundComment &&
|
|
43
|
+
bindsNextLine(boundComment) &&
|
|
44
|
+
boundComment.loc.end.line + 1 === declaration.loc.start.line) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const tokenBefore = source.getTokenBefore(declaration, {
|
|
48
|
+
includeComments: true,
|
|
49
|
+
});
|
|
50
|
+
const ownsLineStart = !tokenBefore || tokenBefore.loc.end.line < declaration.loc.start.line;
|
|
51
|
+
const start = ownsLineStart
|
|
52
|
+
? source.text.lastIndexOf('\n', declaration.range[0] - 1) + 1
|
|
53
|
+
: declaration.range[0];
|
|
54
|
+
let cursor = declaration;
|
|
55
|
+
for (;;) {
|
|
56
|
+
const next = source.getTokenAfter(cursor, { includeComments: true });
|
|
57
|
+
if (!next || next.loc.start.line !== cursor.loc.end.line) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
if (!isComment(next)) {
|
|
61
|
+
// Code shares the line, so the line terminator belongs to that code.
|
|
62
|
+
return [start, declaration.range[1]];
|
|
63
|
+
}
|
|
64
|
+
cursor = next;
|
|
65
|
+
}
|
|
66
|
+
const lineEnd = source.text.indexOf('\n', cursor.range[1]);
|
|
67
|
+
return [start, lineEnd === -1 ? source.text.length : lineEnd + 1];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Ranges covering every removed item together with the separators that would
|
|
71
|
+
* be left dangling. A removed run reaches forward to the next survivor, or
|
|
72
|
+
* backward from the previous one when it ends the list, so exactly one
|
|
73
|
+
* separator per removed item disappears. Returns `null` when every item is
|
|
74
|
+
* removed — that collapse is the caller's to make, since what encloses the
|
|
75
|
+
* list (braces, the declaration itself) must go too.
|
|
76
|
+
*/
|
|
77
|
+
function removalRuns(items) {
|
|
78
|
+
const ranges = [];
|
|
79
|
+
let index = 0;
|
|
80
|
+
while (index < items.length) {
|
|
81
|
+
if (!items[index].removed) {
|
|
82
|
+
index++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
let last = index;
|
|
86
|
+
while (last + 1 < items.length && items[last + 1].removed) {
|
|
87
|
+
last++;
|
|
88
|
+
}
|
|
89
|
+
const following = items[last + 1];
|
|
90
|
+
if (following) {
|
|
91
|
+
ranges.push([items[index].range[0], following.range[0]]);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const preceding = items[index - 1];
|
|
95
|
+
if (!preceding)
|
|
96
|
+
return null;
|
|
97
|
+
ranges.push([preceding.range[1], items[last].range[1]]);
|
|
98
|
+
}
|
|
99
|
+
index = last + 1;
|
|
100
|
+
}
|
|
101
|
+
return ranges;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The declaration a specifier belongs to, found by scanning the program rather
|
|
105
|
+
* than by following `parent`: parent links exist only once ESLint's traversal
|
|
106
|
+
* has installed them, and a helper that reads a `SourceCode` should not depend
|
|
107
|
+
* on having been called from inside a rule visitor.
|
|
108
|
+
*/
|
|
109
|
+
function declarationOf(source, specifier) {
|
|
110
|
+
return source.ast.body.find((statement) => statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
111
|
+
statement.specifiers.includes(specifier));
|
|
112
|
+
}
|
|
113
|
+
function bracesRange(source, named) {
|
|
114
|
+
const open = source.getTokenBefore(named[0], {
|
|
115
|
+
filter: (token) => token.value === '{',
|
|
116
|
+
});
|
|
117
|
+
const close = source.getTokenAfter(named[named.length - 1], {
|
|
118
|
+
filter: (token) => token.value === '}',
|
|
119
|
+
});
|
|
120
|
+
return open && close ? [open.range[0], close.range[1]] : null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The text ranges that unbind `specifiers` from their shared declaration, or
|
|
124
|
+
* `null` when no removal is provably safe. All specifiers of the declaration
|
|
125
|
+
* being listed collapses the declaration itself rather than leaving
|
|
126
|
+
* `import {} from './User';` behind, and losing every *named* specifier next to
|
|
127
|
+
* a surviving default takes the braces with it.
|
|
128
|
+
*
|
|
129
|
+
* Any comment inside the declaration declines the removal outright: the ranges
|
|
130
|
+
* computed here span separators, so a comment nested among the specifiers
|
|
131
|
+
* would be swallowed or stranded depending on where it sits.
|
|
132
|
+
*/
|
|
133
|
+
function planImportBindingRemoval(source, specifiers) {
|
|
134
|
+
const [first] = specifiers;
|
|
135
|
+
if (!first)
|
|
136
|
+
return [];
|
|
137
|
+
const declaration = declarationOf(source, first);
|
|
138
|
+
if (!declaration)
|
|
139
|
+
return null;
|
|
140
|
+
if (specifiers.some((specifier) => !declaration.specifiers.includes(specifier))) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
if (source.getCommentsInside(declaration).length > 0)
|
|
144
|
+
return null;
|
|
145
|
+
const removed = new Set(specifiers);
|
|
146
|
+
if (declaration.specifiers.every((specifier) => removed.has(specifier))) {
|
|
147
|
+
const range = declarationRemovalRange(source, declaration);
|
|
148
|
+
return range ? [range] : null;
|
|
149
|
+
}
|
|
150
|
+
const named = declaration.specifiers.filter(isNamedSpecifier);
|
|
151
|
+
const bracesRemoved = named.length > 0 && named.every((specifier) => removed.has(specifier));
|
|
152
|
+
const clauses = declaration.specifiers
|
|
153
|
+
.filter((specifier) => !isNamedSpecifier(specifier))
|
|
154
|
+
.map((specifier) => ({
|
|
155
|
+
range: specifier.range,
|
|
156
|
+
removed: removed.has(specifier),
|
|
157
|
+
}));
|
|
158
|
+
if (named.length > 0) {
|
|
159
|
+
const braces = bracesRange(source, named);
|
|
160
|
+
if (!braces)
|
|
161
|
+
return null;
|
|
162
|
+
clauses.push({ range: braces, removed: bracesRemoved });
|
|
163
|
+
}
|
|
164
|
+
const ranges = removalRuns(clauses);
|
|
165
|
+
if (!ranges)
|
|
166
|
+
return null;
|
|
167
|
+
if (named.length > 0 && !bracesRemoved) {
|
|
168
|
+
const inner = removalRuns(named.map((specifier) => ({
|
|
169
|
+
range: specifier.range,
|
|
170
|
+
removed: removed.has(specifier),
|
|
171
|
+
})));
|
|
172
|
+
if (!inner)
|
|
173
|
+
return null;
|
|
174
|
+
ranges.push(...inner);
|
|
175
|
+
}
|
|
176
|
+
return [...ranges].sort((left, right) => left[0] - right[0]);
|
|
177
|
+
}
|
|
178
|
+
exports.planImportBindingRemoval = planImportBindingRemoval;
|
|
179
|
+
/**
|
|
180
|
+
* Fixes unbinding `specifiers`, or `null` when the removal is not provably
|
|
181
|
+
* safe. A caller that gets `null` must drop its whole fix: a rewrite that
|
|
182
|
+
* strips a binding's last use while leaving the binding behind trades its own
|
|
183
|
+
* report for an unused-variable one.
|
|
184
|
+
*/
|
|
185
|
+
function removeImportBindingFixes(source, fixer, specifiers) {
|
|
186
|
+
const ranges = planImportBindingRemoval(source, specifiers);
|
|
187
|
+
return (ranges?.map((range) => fixer.removeRange([range[0], range[1]])) ?? null);
|
|
188
|
+
}
|
|
189
|
+
exports.removeImportBindingFixes = removeImportBindingFixes;
|
|
190
|
+
/**
|
|
191
|
+
* The specifier that binds `variable`, when the variable is a named/default/
|
|
192
|
+
* namespace import. `import X = require('y')` binds through a declaration of
|
|
193
|
+
* its own rather than a specifier and is left alone.
|
|
194
|
+
*/
|
|
195
|
+
function importBindingSpecifierOf(variable) {
|
|
196
|
+
if (variable.defs.length !== 1)
|
|
197
|
+
return undefined;
|
|
198
|
+
const [definition] = variable.defs;
|
|
199
|
+
if (definition.type !== utils_1.TSESLint.Scope.DefinitionType.ImportBinding) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
const { node } = definition;
|
|
203
|
+
return node.type === utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration
|
|
204
|
+
? undefined
|
|
205
|
+
: node;
|
|
206
|
+
}
|
|
207
|
+
exports.importBindingSpecifierOf = importBindingSpecifierOf;
|
|
208
|
+
function isImportBindingVariable(variable) {
|
|
209
|
+
return variable.defs.some((definition) => definition.type === utils_1.TSESLint.Scope.DefinitionType.ImportBinding);
|
|
210
|
+
}
|
|
211
|
+
function allVariables(scopeManager) {
|
|
212
|
+
const globalScope = scopeManager?.globalScope;
|
|
213
|
+
if (!globalScope)
|
|
214
|
+
return [];
|
|
215
|
+
const variables = [];
|
|
216
|
+
const stack = [globalScope];
|
|
217
|
+
while (stack.length > 0) {
|
|
218
|
+
const scope = stack.pop();
|
|
219
|
+
variables.push(...scope.variables);
|
|
220
|
+
stack.push(...scope.childScopes);
|
|
221
|
+
}
|
|
222
|
+
return variables;
|
|
223
|
+
}
|
|
224
|
+
function isAstNode(value) {
|
|
225
|
+
return (typeof value === 'object' &&
|
|
226
|
+
value !== null &&
|
|
227
|
+
typeof value.type === 'string');
|
|
228
|
+
}
|
|
229
|
+
function forEachIdentifier(node, parent, key, visit) {
|
|
230
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
231
|
+
node.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
232
|
+
visit(node, parent, key);
|
|
233
|
+
}
|
|
234
|
+
for (const [childKey, value] of Object.entries(node)) {
|
|
235
|
+
if (childKey === 'parent')
|
|
236
|
+
continue;
|
|
237
|
+
const children = Array.isArray(value) ? value : [value];
|
|
238
|
+
for (const child of children) {
|
|
239
|
+
if (isAstNode(child)) {
|
|
240
|
+
forEachIdentifier(child, node, childKey, visit);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* True when the name still occurs in the source outside `ranges`, ignoring the
|
|
247
|
+
* positions where a name is a module's export name rather than a reference to
|
|
248
|
+
* a local binding.
|
|
249
|
+
*
|
|
250
|
+
* Scope analysis already answers "is this binding referenced", and it is what
|
|
251
|
+
* `no-unused-vars` answers with. This coarser second opinion exists to catch
|
|
252
|
+
* the cases where the two could disagree — a shadowing binding, a construct the
|
|
253
|
+
* scope manager does not link — because a removal that turns out to be wrong
|
|
254
|
+
* deletes working code, while a removal declined in error only leaves the
|
|
255
|
+
* original report standing.
|
|
256
|
+
*/
|
|
257
|
+
function nameOccursOutside(source, name, ranges, binding) {
|
|
258
|
+
let found = false;
|
|
259
|
+
forEachIdentifier(source.ast, undefined, undefined, (node, parent, key) => {
|
|
260
|
+
if (found || node.name !== name)
|
|
261
|
+
return;
|
|
262
|
+
if (isWithinAny(node.range, ranges))
|
|
263
|
+
return;
|
|
264
|
+
if (contains(binding.range, node.range))
|
|
265
|
+
return;
|
|
266
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.ImportSpecifier && key === 'imported') {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.ExportSpecifier && key === 'exported') {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
found = true;
|
|
273
|
+
});
|
|
274
|
+
return found;
|
|
275
|
+
}
|
|
276
|
+
function patternIdentifiers(pattern) {
|
|
277
|
+
switch (pattern?.type) {
|
|
278
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
279
|
+
return [pattern];
|
|
280
|
+
case utils_1.AST_NODE_TYPES.ObjectPattern:
|
|
281
|
+
return pattern.properties.flatMap((property) => patternIdentifiers(property.type === utils_1.AST_NODE_TYPES.RestElement
|
|
282
|
+
? property.argument
|
|
283
|
+
: property.value));
|
|
284
|
+
case utils_1.AST_NODE_TYPES.ArrayPattern:
|
|
285
|
+
return pattern.elements.flatMap((element) => patternIdentifiers(element ?? null));
|
|
286
|
+
case utils_1.AST_NODE_TYPES.AssignmentPattern:
|
|
287
|
+
return patternIdentifiers(pattern.left);
|
|
288
|
+
case utils_1.AST_NODE_TYPES.RestElement:
|
|
289
|
+
return patternIdentifiers(pattern.argument);
|
|
290
|
+
default:
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The names the program exports by declaration. A binding among them has a
|
|
296
|
+
* consumer no edit to this file can reach, so no deletion here orphans it.
|
|
297
|
+
*
|
|
298
|
+
* Only the exported name itself counts — a type parameter or a local declared
|
|
299
|
+
* *inside* an exported function is not exported, and treating it as such would
|
|
300
|
+
* wave through the very deletion this guard exists to catch.
|
|
301
|
+
*/
|
|
302
|
+
function exportedBindingKeys(ast) {
|
|
303
|
+
const keys = new Set();
|
|
304
|
+
for (const statement of ast.body) {
|
|
305
|
+
if (statement.type !== utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
|
|
306
|
+
statement.type !== utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const declaration = statement.declaration;
|
|
310
|
+
const identifiers = declaration?.type === utils_1.AST_NODE_TYPES.VariableDeclaration
|
|
311
|
+
? declaration.declarations.flatMap((declarator) => patternIdentifiers(declarator.id))
|
|
312
|
+
: declaration && 'id' in declaration
|
|
313
|
+
? patternIdentifiers(declaration.id ?? null)
|
|
314
|
+
: [];
|
|
315
|
+
for (const identifier of identifiers) {
|
|
316
|
+
keys.add(`${identifier.range[0]},${identifier.range[1]}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return keys;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* The extra ranges a fix must delete so that removing `removed` leaves nothing
|
|
323
|
+
* bound to nothing, or `null` when some binding would be orphaned yet cannot be
|
|
324
|
+
* unbound safely — an import behind a directive comment, a type alias or a type
|
|
325
|
+
* parameter, all of which this helper refuses to guess at.
|
|
326
|
+
*
|
|
327
|
+
* `null` is a demand, not a suggestion: the caller declines its whole fix. The
|
|
328
|
+
* alternative — deleting the last use and keeping the declaration — converts a
|
|
329
|
+
* file that lints clean into one that fails `no-unused-vars`, and since the
|
|
330
|
+
* original report is resolved by the fix, nothing re-reports the debt.
|
|
331
|
+
*
|
|
332
|
+
* `removed` is one fix's own deletion, judged against the file as it stands.
|
|
333
|
+
* Widening it to "everything this `--fix` run will delete" looks like it would
|
|
334
|
+
* catch a binding two edits share, but it is unsound: a rule cannot see
|
|
335
|
+
* `eslint-disable` comments, because suppression is applied to reports after the
|
|
336
|
+
* rule emits them. A suppressed sibling edit never happens, so the widened view
|
|
337
|
+
* deletes an import that a surviving reference still needs — trading an unused
|
|
338
|
+
* import for a dangling type reference, a lint warning for a compile error.
|
|
339
|
+
* There is deliberately no way to ask this helper that question.
|
|
340
|
+
*/
|
|
341
|
+
function planOrphanedImportRemoval(source, removed) {
|
|
342
|
+
const orphaned = [];
|
|
343
|
+
const names = [];
|
|
344
|
+
const exported = exportedBindingKeys(source.ast);
|
|
345
|
+
for (const variable of allVariables(source.scopeManager)) {
|
|
346
|
+
// A global has no declaration in this file, so nothing here can orphan it.
|
|
347
|
+
if (variable.defs.length === 0)
|
|
348
|
+
continue;
|
|
349
|
+
const references = variable.references.map((reference) => reference.identifier.range);
|
|
350
|
+
// Only bindings this deletion touches are its business, and only those it
|
|
351
|
+
// leaves with nothing at all are unbound: a reference that survives this
|
|
352
|
+
// edit keeps the binding alive, whatever another edit might later do to it.
|
|
353
|
+
if (!references.some((range) => isWithinAny(range, removed)))
|
|
354
|
+
continue;
|
|
355
|
+
if (!references.every((range) => isWithinAny(range, removed)))
|
|
356
|
+
continue;
|
|
357
|
+
if (variable.identifiers.some((identifier) => exported.has(`${identifier.range[0]},${identifier.range[1]}`))) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const specifier = isImportBindingVariable(variable)
|
|
361
|
+
? importBindingSpecifierOf(variable)
|
|
362
|
+
: undefined;
|
|
363
|
+
// Orphaned, yet bound by something this helper cannot rewrite.
|
|
364
|
+
if (!specifier)
|
|
365
|
+
return null;
|
|
366
|
+
orphaned.push(specifier);
|
|
367
|
+
names.push(variable.name);
|
|
368
|
+
}
|
|
369
|
+
if (orphaned.length === 0)
|
|
370
|
+
return [];
|
|
371
|
+
if (names.some((name, index) => nameOccursOutside(source, name, removed, orphaned[index]))) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
const byDeclaration = new Map();
|
|
375
|
+
for (const specifier of orphaned) {
|
|
376
|
+
const declaration = declarationOf(source, specifier);
|
|
377
|
+
if (!declaration)
|
|
378
|
+
return null;
|
|
379
|
+
const group = byDeclaration.get(declaration);
|
|
380
|
+
if (group) {
|
|
381
|
+
group.push(specifier);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
byDeclaration.set(declaration, [specifier]);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const ranges = [];
|
|
388
|
+
for (const group of byDeclaration.values()) {
|
|
389
|
+
const plan = planImportBindingRemoval(source, group);
|
|
390
|
+
if (!plan)
|
|
391
|
+
return null;
|
|
392
|
+
ranges.push(...plan);
|
|
393
|
+
}
|
|
394
|
+
return ranges.sort((left, right) => left[0] - right[0]);
|
|
395
|
+
}
|
|
396
|
+
exports.planOrphanedImportRemoval = planOrphanedImportRemoval;
|
|
397
|
+
//# sourceMappingURL=importRemoval.js.map
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,168 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.87",
|
|
4
|
+
"date": "2026-08-03T08:16:33.082Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-assert-safe-object-key",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1648
|
|
11
|
+
],
|
|
12
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "enforce-memoize-async",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1648
|
|
19
|
+
],
|
|
20
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "enforce-memoize-getters",
|
|
24
|
+
"changeType": "fix",
|
|
25
|
+
"issues": [
|
|
26
|
+
1648
|
|
27
|
+
],
|
|
28
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "enforce-microdiff",
|
|
32
|
+
"changeType": "fix",
|
|
33
|
+
"issues": [
|
|
34
|
+
1648
|
|
35
|
+
],
|
|
36
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "enforce-querykey-ts",
|
|
40
|
+
"changeType": "fix",
|
|
41
|
+
"issues": [
|
|
42
|
+
1648
|
|
43
|
+
],
|
|
44
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "enforce-safe-stringify",
|
|
48
|
+
"changeType": "fix",
|
|
49
|
+
"issues": [
|
|
50
|
+
1648
|
|
51
|
+
],
|
|
52
|
+
"summary": "keep the suggestion's added import below the file prologue (#1648)"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "enforce-snapshot-state-narrowing",
|
|
56
|
+
"changeType": "fix",
|
|
57
|
+
"issues": [
|
|
58
|
+
1648
|
|
59
|
+
],
|
|
60
|
+
"summary": "keep the suggestion's added import below the file prologue (#1648)"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "enforce-stable-hash-spread-props",
|
|
64
|
+
"changeType": "fix",
|
|
65
|
+
"issues": [
|
|
66
|
+
1648
|
|
67
|
+
],
|
|
68
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "logical-top-to-bottom-grouping",
|
|
72
|
+
"changeType": "fix",
|
|
73
|
+
"issues": [
|
|
74
|
+
1651
|
|
75
|
+
],
|
|
76
|
+
"summary": "never reorder across a sequential-await run parallelize-async-operations owns (closes #1651)"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "no-array-length-in-deps",
|
|
80
|
+
"changeType": "fix",
|
|
81
|
+
"issues": [
|
|
82
|
+
1648
|
|
83
|
+
],
|
|
84
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "no-empty-dependency-use-callbacks",
|
|
88
|
+
"changeType": "fix",
|
|
89
|
+
"issues": [
|
|
90
|
+
1650
|
|
91
|
+
],
|
|
92
|
+
"summary": "unbind the hook import the hoist empties (closes #1650)"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "no-explicit-return-type",
|
|
96
|
+
"changeType": "fix",
|
|
97
|
+
"issues": [
|
|
98
|
+
1649
|
|
99
|
+
],
|
|
100
|
+
"summary": "drop the import the stripped annotation solely consumed (closes #1649)"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "prefer-fragment-component",
|
|
104
|
+
"changeType": "fix",
|
|
105
|
+
"issues": [
|
|
106
|
+
1648
|
|
107
|
+
],
|
|
108
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "prefer-global-router-state-key",
|
|
112
|
+
"changeType": "fix",
|
|
113
|
+
"issues": [
|
|
114
|
+
1648
|
|
115
|
+
],
|
|
116
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "prefer-use-deep-compare-memo",
|
|
120
|
+
"changeType": "fix",
|
|
121
|
+
"issues": [
|
|
122
|
+
1648
|
|
123
|
+
],
|
|
124
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "react-memoize-literals",
|
|
128
|
+
"changeType": "fix",
|
|
129
|
+
"issues": [
|
|
130
|
+
1648
|
|
131
|
+
],
|
|
132
|
+
"summary": "keep the suggestion's added import below the file prologue (#1648)"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "require-memo",
|
|
136
|
+
"changeType": "fix",
|
|
137
|
+
"issues": [
|
|
138
|
+
1648
|
|
139
|
+
],
|
|
140
|
+
"summary": "keep the autofix's added import below the file prologue (#1648)"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "require-memoize-jsx-returners",
|
|
144
|
+
"changeType": "fix",
|
|
145
|
+
"issues": [
|
|
146
|
+
1648
|
|
147
|
+
],
|
|
148
|
+
"summary": "keep the autofix's added import below the file prologue (closes #1648)"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"version": "1.20.86",
|
|
154
|
+
"date": "2026-08-03T03:52:41.277Z",
|
|
155
|
+
"rules": [
|
|
156
|
+
{
|
|
157
|
+
"name": "prefer-spread-over-reassembly",
|
|
158
|
+
"changeType": "fix",
|
|
159
|
+
"issues": [
|
|
160
|
+
1644
|
|
161
|
+
],
|
|
162
|
+
"summary": "prove narrowing through a relative import (closes #1644)"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
},
|
|
2
166
|
{
|
|
3
167
|
"version": "1.20.85",
|
|
4
168
|
"date": "2026-08-03T03:21:29.711Z",
|