@blumintinc/eslint-plugin-blumint 1.20.93 → 1.20.95
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
CHANGED
|
@@ -27,6 +27,7 @@ exports.enforceDateTTime = void 0;
|
|
|
27
27
|
const utils_1 = require("@typescript-eslint/utils");
|
|
28
28
|
const ts = __importStar(require("typescript"));
|
|
29
29
|
const createRule_1 = require("../utils/createRule");
|
|
30
|
+
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
30
31
|
const importRemoval_1 = require("../utils/importRemoval");
|
|
31
32
|
exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
32
33
|
name: 'enforce-date-ttime',
|
|
@@ -92,7 +93,102 @@ exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
|
92
93
|
}
|
|
93
94
|
return false;
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Reporting is deferred to `Program:exit` because an import is unbound only
|
|
98
|
+
* once no reference to it survives the fix, and a file where two arguments
|
|
99
|
+
* name the same imported alias overwrites both in a single pass. Judging
|
|
100
|
+
* each rewrite alone sees the sibling argument still standing, concludes the
|
|
101
|
+
* binding is alive, and leaves the import stranded with no later pass to
|
|
102
|
+
* notice — this rule's own reports are resolved by the fix, so nothing
|
|
103
|
+
* re-reports the debt.
|
|
104
|
+
*/
|
|
105
|
+
const sites = [];
|
|
106
|
+
/**
|
|
107
|
+
* Suppression is applied to reports after a rule emits them, so a suppressed
|
|
108
|
+
* site keeps its argument while losing its fix. Counting its range toward
|
|
109
|
+
* orphanhood would unbind an import the surviving text still spells, trading
|
|
110
|
+
* an unused import for a dangling type.
|
|
111
|
+
*/
|
|
112
|
+
const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
|
|
113
|
+
/**
|
|
114
|
+
* Supplies a TTime that was left out entirely. Declines whenever the
|
|
115
|
+
* argument cannot be appended positionally — a gap before TTime would need
|
|
116
|
+
* every intervening parameter spelled out, and this rule does not invent
|
|
117
|
+
* them.
|
|
118
|
+
*/
|
|
119
|
+
function fixOmittedArgument(site, fixer) {
|
|
120
|
+
const { reference, tTimeIndex } = site;
|
|
121
|
+
if (reference.typeParameters) {
|
|
122
|
+
const { params } = reference.typeParameters;
|
|
123
|
+
if (tTimeIndex === params.length) {
|
|
124
|
+
return fixer.insertTextAfter(params[params.length - 1], ', Date');
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (tTimeIndex === 0) {
|
|
129
|
+
return fixer.insertTextAfter(reference.typeName, '<Date>');
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The rewrites that actually ship. A site is excluded when its report will
|
|
135
|
+
* be suppressed, or when its own range holds the last reference to something
|
|
136
|
+
* the helper cannot rewrite — a locally declared alias, or the enclosing
|
|
137
|
+
* declaration's own type parameter. Deleting a declaration is a materially
|
|
138
|
+
* riskier edit than dropping an import specifier, and a type parameter left
|
|
139
|
+
* unread by its own body fails `no-unused-vars` and `noUnusedParameters`
|
|
140
|
+
* exactly as an orphaned alias does, on top of turning a pass-through
|
|
141
|
+
* generic into one whose argument no longer means anything.
|
|
142
|
+
*
|
|
143
|
+
* Screening individually before batching keeps one unfixable site from
|
|
144
|
+
* vetoing the rest: orphanhood grows monotonically with the overwritten set,
|
|
145
|
+
* so a site that cannot be planned alone can only ever poison the batch.
|
|
146
|
+
*/
|
|
147
|
+
function selectRewritableSites() {
|
|
148
|
+
return sites.filter((site) => site.kind === 'nonDate' &&
|
|
149
|
+
!isReportSuppressed(site.reportNode) &&
|
|
150
|
+
(0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, [site.reportNode.range]) !==
|
|
151
|
+
null);
|
|
152
|
+
}
|
|
95
153
|
return {
|
|
154
|
+
'Program:exit'() {
|
|
155
|
+
if (sites.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
const rewrites = selectRewritableSites();
|
|
158
|
+
const ranges = rewrites.map((site) => site.reportNode.range);
|
|
159
|
+
// One plan over every surviving rewrite: an import referenced solely by
|
|
160
|
+
// arguments that all go in this pass is orphaned by their union, even
|
|
161
|
+
// though no single one of them orphans it.
|
|
162
|
+
const importRanges = ranges.length > 0
|
|
163
|
+
? (0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, ranges)
|
|
164
|
+
: null;
|
|
165
|
+
// The whole batch ships as one fix, so no rewrite can land without the
|
|
166
|
+
// others that the import's orphanhood was judged against. The rest
|
|
167
|
+
// report without a fixer; the carrier's pass already resolves them.
|
|
168
|
+
const carrier = importRanges ? rewrites[0] : undefined;
|
|
169
|
+
for (const site of sites) {
|
|
170
|
+
if (site.kind === 'omitted') {
|
|
171
|
+
context.report({
|
|
172
|
+
node: site.reportNode,
|
|
173
|
+
messageId: 'enforceDateTTime',
|
|
174
|
+
data: { typeName: site.typeName },
|
|
175
|
+
fix: (fixer) => fixOmittedArgument(site, fixer),
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
context.report({
|
|
180
|
+
node: site.reportNode,
|
|
181
|
+
messageId: 'enforceDateTTime',
|
|
182
|
+
data: { typeName: site.typeName },
|
|
183
|
+
fix: site === carrier && importRanges
|
|
184
|
+
? (fixer) => [
|
|
185
|
+
...rewrites.map((rewrite) => fixer.replaceText(rewrite.reportNode, 'Date')),
|
|
186
|
+
...importRanges.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
187
|
+
]
|
|
188
|
+
: null,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
},
|
|
96
192
|
TSTypeReference(node) {
|
|
97
193
|
const typeNameNode = node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName
|
|
98
194
|
? node.typeName.right
|
|
@@ -116,71 +212,21 @@ exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
|
116
212
|
const tTimeArg = typeArgs[tTimeIndex];
|
|
117
213
|
const typeName = sourceCode.getText(node.typeName);
|
|
118
214
|
if (!tTimeArg) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
node,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (node.typeParameters) {
|
|
126
|
-
// Already has type parameters, but not enough to cover TTime
|
|
127
|
-
const lastParam = node.typeParameters.params[node.typeParameters.params.length - 1];
|
|
128
|
-
// Check if we can just append ", Date"
|
|
129
|
-
// We can only do this safely if all parameters between existing ones and TTime have defaults.
|
|
130
|
-
// For simplicity and safety, we only fix if tTimeIndex is the next one or if it's already provided.
|
|
131
|
-
if (tTimeIndex === node.typeParameters.params.length) {
|
|
132
|
-
return fixer.insertTextAfter(lastParam, ', Date');
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else if (tTimeIndex === 0) {
|
|
136
|
-
// No type parameters and TTime is the first one
|
|
137
|
-
return fixer.insertTextAfter(node.typeName, '<Date>');
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
},
|
|
215
|
+
sites.push({
|
|
216
|
+
kind: 'omitted',
|
|
217
|
+
reportNode: node,
|
|
218
|
+
reference: node,
|
|
219
|
+
typeName,
|
|
220
|
+
tTimeIndex,
|
|
141
221
|
});
|
|
142
222
|
}
|
|
143
223
|
else if (!isExactDate(tTimeArg)) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
//
|
|
151
|
-
// Orphanhood is judged against this one argument's own range and the
|
|
152
|
-
// file as it stands, never against what the rest of the `--fix` run
|
|
153
|
-
// might also overwrite. A sibling argument naming the same alias may
|
|
154
|
-
// be `eslint-disable`d — which a rule cannot see, since suppression is
|
|
155
|
-
// applied to reports after they are emitted — so an edit assuming its
|
|
156
|
-
// sibling will also go unbinds an import the survivor still
|
|
157
|
-
// references, trading an unused import for a dangling type. Judging
|
|
158
|
-
// one edit at a time is suppression-safe by construction.
|
|
159
|
-
const importRanges = (0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, [
|
|
160
|
-
tTimeArg.range,
|
|
161
|
-
]);
|
|
162
|
-
context.report({
|
|
163
|
-
node: tTimeArg,
|
|
164
|
-
messageId: 'enforceDateTTime',
|
|
165
|
-
data: { typeName },
|
|
166
|
-
// No plan means the argument holds the last reference to something
|
|
167
|
-
// the helper cannot rewrite — a locally declared alias, or the
|
|
168
|
-
// enclosing declaration's own type parameter — so the argument
|
|
169
|
-
// stays as written: the report without a fixer is the lesser
|
|
170
|
-
// damage. Deleting a declaration is a materially riskier edit than
|
|
171
|
-
// dropping an import specifier, and a type parameter left unread by
|
|
172
|
-
// its own body fails `no-unused-vars` and `noUnusedParameters`
|
|
173
|
-
// exactly as an orphaned alias does, on top of turning a
|
|
174
|
-
// pass-through generic into one whose argument no longer means
|
|
175
|
-
// anything.
|
|
176
|
-
...(importRanges
|
|
177
|
-
? {
|
|
178
|
-
fix: (fixer) => [
|
|
179
|
-
fixer.replaceText(tTimeArg, 'Date'),
|
|
180
|
-
...importRanges.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
181
|
-
],
|
|
182
|
-
}
|
|
183
|
-
: {}),
|
|
224
|
+
sites.push({
|
|
225
|
+
kind: 'nonDate',
|
|
226
|
+
reportNode: tTimeArg,
|
|
227
|
+
reference: node,
|
|
228
|
+
typeName,
|
|
229
|
+
tTimeIndex,
|
|
184
230
|
});
|
|
185
231
|
}
|
|
186
232
|
},
|
|
@@ -29,6 +29,7 @@ const visitor_keys_1 = require("@typescript-eslint/visitor-keys");
|
|
|
29
29
|
const ts = __importStar(require("typescript"));
|
|
30
30
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
31
31
|
const createRule_1 = require("../utils/createRule");
|
|
32
|
+
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
32
33
|
const importRemoval_1 = require("../utils/importRemoval");
|
|
33
34
|
/**
|
|
34
35
|
* Type string formatting flags chosen to keep comparisons stable and predictable.
|
|
@@ -345,51 +346,78 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
345
346
|
}
|
|
346
347
|
const checker = parserServices.program.getTypeChecker();
|
|
347
348
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* the
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* file as it stands, never against what the rest of the `--fix` run might
|
|
356
|
-
* also delete. A sibling annotation naming the same type may be
|
|
357
|
-
* `eslint-disable`d — which a rule cannot see, since suppression is applied
|
|
358
|
-
* to reports after they are emitted — so an edit that assumes its sibling
|
|
359
|
-
* will also go deletes an import the surviving annotation still references,
|
|
360
|
-
* trading an unused import for a dangling type. Judging one edit at a time
|
|
361
|
-
* is suppression-safe by construction: a suppressed report's fix never
|
|
362
|
-
* applies, so it can never have been depended on.
|
|
363
|
-
*
|
|
364
|
-
* A type the annotation names but the helper cannot rewrite — a local alias,
|
|
365
|
-
* an interface, a type parameter — declines the fix outright. Deleting a
|
|
366
|
-
* declaration is a materially riskier edit than dropping an import
|
|
367
|
-
* specifier, and the author is better placed to decide whether the type
|
|
368
|
-
* should go or be used elsewhere.
|
|
349
|
+
* Reporting is deferred to `Program:exit` because an import is unbound only
|
|
350
|
+
* once no reference to it survives the fix, and a file where two annotations
|
|
351
|
+
* name the same imported type strips both in a single pass. Judging each
|
|
352
|
+
* removal alone sees the sibling annotation still standing, concludes the
|
|
353
|
+
* binding is alive, and leaves the import stranded with no later pass to
|
|
354
|
+
* notice — this rule's own reports are resolved by the fix, so nothing
|
|
355
|
+
* re-reports the debt.
|
|
369
356
|
*/
|
|
370
|
-
|
|
357
|
+
const sites = [];
|
|
358
|
+
/**
|
|
359
|
+
* Suppression is applied to reports after a rule emits them, so a suppressed
|
|
360
|
+
* site keeps its annotation while losing its fix. Counting its removal
|
|
361
|
+
* toward orphanhood would unbind an import the surviving text still spells,
|
|
362
|
+
* trading an unused import for a dangling type.
|
|
363
|
+
*/
|
|
364
|
+
const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
|
|
365
|
+
function collectIfRedundant(annotation, assertion, reportNode, fixerTarget) {
|
|
371
366
|
const matchingType = haveMatchingTypes(annotation.typeAnnotation, assertion, checker, parserServices);
|
|
372
367
|
if (!matchingType)
|
|
373
368
|
return;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
messageId: 'redundantAnnotationAndAssertion',
|
|
379
|
-
data: { type: matchingType },
|
|
380
|
-
// No plan means no binding can be unbound safely, so the annotation
|
|
381
|
-
// stays too: the report without a fixer is the lesser damage.
|
|
382
|
-
...(importRanges
|
|
383
|
-
? {
|
|
384
|
-
fix: (fixer) => [
|
|
385
|
-
fixer.removeRange([removal[0], removal[1]]),
|
|
386
|
-
...importRanges.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
387
|
-
],
|
|
388
|
-
}
|
|
389
|
-
: {}),
|
|
369
|
+
sites.push({
|
|
370
|
+
reportNode,
|
|
371
|
+
removal: annotationRemovalRange(fixerTarget, sourceCode),
|
|
372
|
+
matchingType,
|
|
390
373
|
});
|
|
391
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* The sites whose fixes actually ship. A site is excluded when its report
|
|
377
|
+
* will be suppressed, or when its own removal orphans something the helper
|
|
378
|
+
* cannot rewrite — a local alias, an interface, a type parameter. Deleting a
|
|
379
|
+
* declaration is a materially riskier edit than dropping an import
|
|
380
|
+
* specifier, and the author is better placed to decide whether the type
|
|
381
|
+
* should go or be used elsewhere.
|
|
382
|
+
*
|
|
383
|
+
* Screening individually before batching keeps one unfixable site from
|
|
384
|
+
* vetoing the rest: orphanhood grows monotonically with the removed set, so
|
|
385
|
+
* a site that cannot be planned alone can only ever poison the batch.
|
|
386
|
+
*/
|
|
387
|
+
function selectFixableSites() {
|
|
388
|
+
return sites.filter((site) => !isReportSuppressed(site.reportNode) &&
|
|
389
|
+
(0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, [site.removal]) !== null);
|
|
390
|
+
}
|
|
392
391
|
return {
|
|
392
|
+
'Program:exit'() {
|
|
393
|
+
if (sites.length === 0)
|
|
394
|
+
return;
|
|
395
|
+
const fixable = selectFixableSites();
|
|
396
|
+
const removals = fixable.map((site) => site.removal);
|
|
397
|
+
// One plan over every surviving removal: an import referenced solely by
|
|
398
|
+
// annotations that all go in this pass is orphaned by their union, even
|
|
399
|
+
// though no single one of them orphans it.
|
|
400
|
+
const importRanges = removals.length > 0
|
|
401
|
+
? (0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, removals)
|
|
402
|
+
: null;
|
|
403
|
+
// The whole batch ships as one fix, so no removal can land without the
|
|
404
|
+
// others that the import's orphanhood was judged against. The rest
|
|
405
|
+
// report without a fixer; the carrier's pass already resolves them.
|
|
406
|
+
const carrier = importRanges ? fixable[0] : undefined;
|
|
407
|
+
for (const site of sites) {
|
|
408
|
+
context.report({
|
|
409
|
+
node: site.reportNode,
|
|
410
|
+
messageId: 'redundantAnnotationAndAssertion',
|
|
411
|
+
data: { type: site.matchingType },
|
|
412
|
+
fix: site === carrier && importRanges
|
|
413
|
+
? (fixer) => [
|
|
414
|
+
...removals.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
415
|
+
...importRanges.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
416
|
+
]
|
|
417
|
+
: null,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
},
|
|
393
421
|
VariableDeclarator(node) {
|
|
394
422
|
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
395
423
|
!node.id.typeAnnotation ||
|
|
@@ -400,7 +428,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
400
428
|
const assertionType = extractAssertionTypeNode(node.init);
|
|
401
429
|
if (!assertionType)
|
|
402
430
|
return;
|
|
403
|
-
|
|
431
|
+
collectIfRedundant(node.id.typeAnnotation, assertionType, node.id, node.id.typeAnnotation);
|
|
404
432
|
},
|
|
405
433
|
PropertyDefinition(node) {
|
|
406
434
|
if (!node.typeAnnotation ||
|
|
@@ -411,7 +439,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
411
439
|
const assertionType = extractAssertionTypeNode(node.value);
|
|
412
440
|
if (!assertionType)
|
|
413
441
|
return;
|
|
414
|
-
|
|
442
|
+
collectIfRedundant(node.typeAnnotation, assertionType, node.key, node.typeAnnotation);
|
|
415
443
|
},
|
|
416
444
|
FunctionDeclaration(node) {
|
|
417
445
|
if (!node.returnType)
|
|
@@ -419,7 +447,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
419
447
|
const assertionType = getReturnAssertion(node);
|
|
420
448
|
if (!assertionType)
|
|
421
449
|
return;
|
|
422
|
-
|
|
450
|
+
collectIfRedundant(node.returnType, assertionType, node.id ?? node, node.returnType);
|
|
423
451
|
},
|
|
424
452
|
FunctionExpression(node) {
|
|
425
453
|
if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
@@ -430,7 +458,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
430
458
|
const assertionType = getReturnAssertion(node);
|
|
431
459
|
if (!assertionType)
|
|
432
460
|
return;
|
|
433
|
-
|
|
461
|
+
collectIfRedundant(node.returnType, assertionType, node, node.returnType);
|
|
434
462
|
},
|
|
435
463
|
ArrowFunctionExpression(node) {
|
|
436
464
|
if (!node.returnType)
|
|
@@ -438,7 +466,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
438
466
|
const assertionType = getReturnAssertion(node);
|
|
439
467
|
if (!assertionType)
|
|
440
468
|
return;
|
|
441
|
-
|
|
469
|
+
collectIfRedundant(node.returnType, assertionType, node, node.returnType);
|
|
442
470
|
},
|
|
443
471
|
MethodDefinition(node) {
|
|
444
472
|
if (!node.value.returnType)
|
|
@@ -446,7 +474,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
446
474
|
const assertionType = getReturnAssertion(node);
|
|
447
475
|
if (!assertionType)
|
|
448
476
|
return;
|
|
449
|
-
|
|
477
|
+
collectIfRedundant(node.value.returnType, assertionType, node.key, node.value.returnType);
|
|
450
478
|
},
|
|
451
479
|
};
|
|
452
480
|
},
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noRedundantParamTypes = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
6
7
|
const importRemoval_1 = require("../utils/importRemoval");
|
|
7
8
|
/**
|
|
8
9
|
* The annotation a parameter carries, or `undefined` when it has none. A
|
|
@@ -68,57 +69,88 @@ exports.noRedundantParamTypes = (0, createRule_1.createRule)({
|
|
|
68
69
|
create(context) {
|
|
69
70
|
const sourceCode = context.getSourceCode();
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
+
* An annotation is stripped together with any import it was the only
|
|
72
73
|
* consumer of. The two are one fix: applying either half alone leaves the
|
|
73
74
|
* file worse than applying neither — a stripped annotation with its import
|
|
74
75
|
* left behind fails `no-unused-vars`, and since this rule's own report is
|
|
75
76
|
* resolved by the fix, nothing re-reports the debt.
|
|
76
77
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
* Reporting is therefore deferred to `Program:exit`: an import is unbound
|
|
79
|
+
* only once no reference to it survives the fix, and a file where two
|
|
80
|
+
* annotations name the same imported type strips both in a single pass.
|
|
81
|
+
* Judging each removal alone sees the sibling annotation still standing,
|
|
82
|
+
* concludes the binding is alive, and leaves the import stranded with no
|
|
83
|
+
* later pass to notice.
|
|
84
|
+
*/
|
|
85
|
+
const sites = [];
|
|
86
|
+
/**
|
|
87
|
+
* Suppression is applied to reports after a rule emits them, so a suppressed
|
|
88
|
+
* site keeps its annotation while losing its fix. Counting its removal
|
|
89
|
+
* toward orphanhood would unbind an import the surviving text still spells,
|
|
90
|
+
* trading an unused import for a dangling type.
|
|
91
|
+
*/
|
|
92
|
+
const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
|
|
93
|
+
/**
|
|
94
|
+
* The strips that actually ship. A site is excluded when its report will be
|
|
95
|
+
* suppressed, or when its own removal orphans something the helper cannot
|
|
96
|
+
* rewrite — a local alias, an interface, a type parameter. Deleting a
|
|
97
|
+
* declaration is a materially riskier edit than dropping an import
|
|
98
|
+
* specifier, and the author is better placed to decide whether the type
|
|
99
|
+
* should go or be used elsewhere.
|
|
86
100
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* the import.
|
|
101
|
+
* Screening individually before batching keeps one unfixable site from
|
|
102
|
+
* vetoing the rest: orphanhood grows monotonically with the removed set, so
|
|
103
|
+
* a site that cannot be planned alone can only ever poison the batch.
|
|
91
104
|
*/
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
context.report({
|
|
96
|
-
node: param,
|
|
97
|
-
messageId: 'redundantParamType',
|
|
98
|
-
data: {
|
|
99
|
-
paramText: sourceCode.getText(param).replace(/\s+/g, ' ').trim(),
|
|
100
|
-
},
|
|
101
|
-
// No plan means no binding can be unbound safely, so the annotation
|
|
102
|
-
// stays too: the report without a fixer is the lesser damage.
|
|
103
|
-
...(importRanges
|
|
104
|
-
? {
|
|
105
|
-
fix: (fixer) => [
|
|
106
|
-
fixer.removeRange([removal[0], removal[1]]),
|
|
107
|
-
...importRanges.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
108
|
-
],
|
|
109
|
-
}
|
|
110
|
-
: {}),
|
|
111
|
-
});
|
|
105
|
+
function selectFixableSites() {
|
|
106
|
+
return sites.filter((site) => !isReportSuppressed(site.param) &&
|
|
107
|
+
(0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, [site.removal]) !== null);
|
|
112
108
|
}
|
|
113
109
|
return {
|
|
110
|
+
'Program:exit'() {
|
|
111
|
+
if (sites.length === 0)
|
|
112
|
+
return;
|
|
113
|
+
const fixable = selectFixableSites();
|
|
114
|
+
const removals = fixable.map((site) => site.removal);
|
|
115
|
+
// One plan over every surviving strip: an import referenced solely by
|
|
116
|
+
// annotations that all go in this pass is orphaned by their union, even
|
|
117
|
+
// though no single one of them orphans it.
|
|
118
|
+
const importRanges = removals.length > 0
|
|
119
|
+
? (0, importRemoval_1.planOrphanedImportRemoval)(sourceCode, removals)
|
|
120
|
+
: null;
|
|
121
|
+
// The whole batch ships as one fix, so no strip can land without the
|
|
122
|
+
// others that the import's orphanhood was judged against. The rest
|
|
123
|
+
// report without a fixer; the carrier's pass already resolves them.
|
|
124
|
+
//
|
|
125
|
+
// No plan at all means no binding can be unbound safely, so every
|
|
126
|
+
// annotation stays: reports without a fixer are the lesser damage.
|
|
127
|
+
const carrier = importRanges ? fixable[0] : undefined;
|
|
128
|
+
for (const site of sites) {
|
|
129
|
+
context.report({
|
|
130
|
+
node: site.param,
|
|
131
|
+
messageId: 'redundantParamType',
|
|
132
|
+
data: { paramText: site.paramText },
|
|
133
|
+
fix: site === carrier && importRanges
|
|
134
|
+
? (fixer) => [
|
|
135
|
+
...removals.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
136
|
+
...importRanges.map((range) => fixer.removeRange([range[0], range[1]])),
|
|
137
|
+
]
|
|
138
|
+
: null,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
},
|
|
114
142
|
ArrowFunctionExpression(node) {
|
|
115
143
|
if (!hasRedundantTypeAnnotation(node))
|
|
116
144
|
return;
|
|
117
145
|
node.params.forEach((param) => {
|
|
118
146
|
const typeAnnotation = annotationOf(param);
|
|
119
|
-
if (typeAnnotation)
|
|
120
|
-
|
|
121
|
-
|
|
147
|
+
if (!typeAnnotation)
|
|
148
|
+
return;
|
|
149
|
+
sites.push({
|
|
150
|
+
param,
|
|
151
|
+
removal: annotationRemovalRange(typeAnnotation, sourceCode),
|
|
152
|
+
paramText: sourceCode.getText(param).replace(/\s+/g, ' ').trim(),
|
|
153
|
+
});
|
|
122
154
|
});
|
|
123
155
|
},
|
|
124
156
|
};
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.95",
|
|
4
|
+
"date": "2026-08-03T23:44:33.733Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "no-redundant-param-types",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1670
|
|
11
|
+
],
|
|
12
|
+
"summary": "unbind the import two annotations share (closes #1670)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.20.94",
|
|
18
|
+
"date": "2026-08-03T22:57:52.839Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "enforce-date-ttime",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
1669
|
|
25
|
+
],
|
|
26
|
+
"summary": "unbind the import two arguments share (closes #1669)"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "no-redundant-annotation-assertion",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
1668
|
|
33
|
+
],
|
|
34
|
+
"summary": "unbind the import two annotations share (closes #1668)"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
2
38
|
{
|
|
3
39
|
"version": "1.20.93",
|
|
4
40
|
"date": "2026-08-03T22:34:05.616Z",
|