@blumintinc/eslint-plugin-blumint 1.20.92 → 1.20.94
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
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const enforceDateTTime: TSESLint.RuleModule<"enforceDateTTime", [], TSESLint.RuleListener>;
|
|
@@ -27,6 +27,8 @@ 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");
|
|
31
|
+
const importRemoval_1 = require("../utils/importRemoval");
|
|
30
32
|
exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
31
33
|
name: 'enforce-date-ttime',
|
|
32
34
|
meta: {
|
|
@@ -43,7 +45,8 @@ exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
|
43
45
|
},
|
|
44
46
|
defaultOptions: [],
|
|
45
47
|
create(context) {
|
|
46
|
-
const sourceCode = context
|
|
48
|
+
const sourceCode = context
|
|
49
|
+
.sourceCode ?? context.getSourceCode();
|
|
47
50
|
const parserServices = sourceCode?.parserServices ?? context.parserServices;
|
|
48
51
|
const checker = parserServices?.program?.getTypeChecker();
|
|
49
52
|
if (!parserServices || !checker) {
|
|
@@ -90,7 +93,102 @@ exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
|
90
93
|
}
|
|
91
94
|
return false;
|
|
92
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
|
+
}
|
|
93
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
|
+
},
|
|
94
192
|
TSTypeReference(node) {
|
|
95
193
|
const typeNameNode = node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName
|
|
96
194
|
? node.typeName.right
|
|
@@ -114,39 +212,21 @@ exports.enforceDateTTime = (0, createRule_1.createRule)({
|
|
|
114
212
|
const tTimeArg = typeArgs[tTimeIndex];
|
|
115
213
|
const typeName = sourceCode.getText(node.typeName);
|
|
116
214
|
if (!tTimeArg) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
node,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (node.typeParameters) {
|
|
124
|
-
// Already has type parameters, but not enough to cover TTime
|
|
125
|
-
const lastParam = node.typeParameters.params[node.typeParameters.params.length - 1];
|
|
126
|
-
// Check if we can just append ", Date"
|
|
127
|
-
// We can only do this safely if all parameters between existing ones and TTime have defaults.
|
|
128
|
-
// For simplicity and safety, we only fix if tTimeIndex is the next one or if it's already provided.
|
|
129
|
-
if (tTimeIndex === node.typeParameters.params.length) {
|
|
130
|
-
return fixer.insertTextAfter(lastParam, ', Date');
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else if (tTimeIndex === 0) {
|
|
134
|
-
// No type parameters and TTime is the first one
|
|
135
|
-
return fixer.insertTextAfter(node.typeName, '<Date>');
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
},
|
|
215
|
+
sites.push({
|
|
216
|
+
kind: 'omitted',
|
|
217
|
+
reportNode: node,
|
|
218
|
+
reference: node,
|
|
219
|
+
typeName,
|
|
220
|
+
tTimeIndex,
|
|
139
221
|
});
|
|
140
222
|
}
|
|
141
223
|
else if (!isExactDate(tTimeArg)) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return fixer.replaceText(tTimeArg, 'Date');
|
|
149
|
-
},
|
|
224
|
+
sites.push({
|
|
225
|
+
kind: 'nonDate',
|
|
226
|
+
reportNode: tTimeArg,
|
|
227
|
+
reference: node,
|
|
228
|
+
typeName,
|
|
229
|
+
tTimeIndex,
|
|
150
230
|
});
|
|
151
231
|
}
|
|
152
232
|
},
|
|
@@ -29,6 +29,8 @@ 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");
|
|
33
|
+
const importRemoval_1 = require("../utils/importRemoval");
|
|
32
34
|
/**
|
|
33
35
|
* Type string formatting flags chosen to keep comparisons stable and predictable.
|
|
34
36
|
* - NoTruncation avoids `...` elisions that can hide important differences.
|
|
@@ -143,10 +145,15 @@ function findTypeAnnotationStart(typeAnnotation, sourceCode) {
|
|
|
143
145
|
}
|
|
144
146
|
return removalStart;
|
|
145
147
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The slice a fix deletes to drop `typeAnnotation`: the `:` and the whitespace
|
|
150
|
+
* separating it from the declared name go with the type.
|
|
151
|
+
*/
|
|
152
|
+
function annotationRemovalRange(typeAnnotation, sourceCode) {
|
|
153
|
+
return [
|
|
154
|
+
findTypeAnnotationStart(typeAnnotation, sourceCode),
|
|
155
|
+
typeAnnotation.range[1],
|
|
156
|
+
];
|
|
150
157
|
}
|
|
151
158
|
function typeText(type, checker) {
|
|
152
159
|
return checker.typeToString(type, undefined, typeFormatFlags());
|
|
@@ -338,18 +345,79 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
338
345
|
return {};
|
|
339
346
|
}
|
|
340
347
|
const checker = parserServices.program.getTypeChecker();
|
|
341
|
-
|
|
348
|
+
/**
|
|
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.
|
|
356
|
+
*/
|
|
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) {
|
|
342
366
|
const matchingType = haveMatchingTypes(annotation.typeAnnotation, assertion, checker, parserServices);
|
|
343
367
|
if (!matchingType)
|
|
344
368
|
return;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
fix: (fixer) => removeTypeAnnotation(fixer, fixerTarget, sourceCode),
|
|
369
|
+
sites.push({
|
|
370
|
+
reportNode,
|
|
371
|
+
removal: annotationRemovalRange(fixerTarget, sourceCode),
|
|
372
|
+
matchingType,
|
|
350
373
|
});
|
|
351
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
|
+
}
|
|
352
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
|
+
},
|
|
353
421
|
VariableDeclarator(node) {
|
|
354
422
|
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
355
423
|
!node.id.typeAnnotation ||
|
|
@@ -360,7 +428,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
360
428
|
const assertionType = extractAssertionTypeNode(node.init);
|
|
361
429
|
if (!assertionType)
|
|
362
430
|
return;
|
|
363
|
-
|
|
431
|
+
collectIfRedundant(node.id.typeAnnotation, assertionType, node.id, node.id.typeAnnotation);
|
|
364
432
|
},
|
|
365
433
|
PropertyDefinition(node) {
|
|
366
434
|
if (!node.typeAnnotation ||
|
|
@@ -371,7 +439,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
371
439
|
const assertionType = extractAssertionTypeNode(node.value);
|
|
372
440
|
if (!assertionType)
|
|
373
441
|
return;
|
|
374
|
-
|
|
442
|
+
collectIfRedundant(node.typeAnnotation, assertionType, node.key, node.typeAnnotation);
|
|
375
443
|
},
|
|
376
444
|
FunctionDeclaration(node) {
|
|
377
445
|
if (!node.returnType)
|
|
@@ -379,7 +447,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
379
447
|
const assertionType = getReturnAssertion(node);
|
|
380
448
|
if (!assertionType)
|
|
381
449
|
return;
|
|
382
|
-
|
|
450
|
+
collectIfRedundant(node.returnType, assertionType, node.id ?? node, node.returnType);
|
|
383
451
|
},
|
|
384
452
|
FunctionExpression(node) {
|
|
385
453
|
if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
@@ -390,7 +458,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
390
458
|
const assertionType = getReturnAssertion(node);
|
|
391
459
|
if (!assertionType)
|
|
392
460
|
return;
|
|
393
|
-
|
|
461
|
+
collectIfRedundant(node.returnType, assertionType, node, node.returnType);
|
|
394
462
|
},
|
|
395
463
|
ArrowFunctionExpression(node) {
|
|
396
464
|
if (!node.returnType)
|
|
@@ -398,7 +466,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
398
466
|
const assertionType = getReturnAssertion(node);
|
|
399
467
|
if (!assertionType)
|
|
400
468
|
return;
|
|
401
|
-
|
|
469
|
+
collectIfRedundant(node.returnType, assertionType, node, node.returnType);
|
|
402
470
|
},
|
|
403
471
|
MethodDefinition(node) {
|
|
404
472
|
if (!node.value.returnType)
|
|
@@ -406,7 +474,7 @@ exports.noRedundantAnnotationAssertion = (0, createRule_1.createRule)({
|
|
|
406
474
|
const assertionType = getReturnAssertion(node);
|
|
407
475
|
if (!assertionType)
|
|
408
476
|
return;
|
|
409
|
-
|
|
477
|
+
collectIfRedundant(node.value.returnType, assertionType, node.key, node.value.returnType);
|
|
410
478
|
},
|
|
411
479
|
};
|
|
412
480
|
},
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,48 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.94",
|
|
4
|
+
"date": "2026-08-03T22:57:52.839Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-date-ttime",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1669
|
|
11
|
+
],
|
|
12
|
+
"summary": "unbind the import two arguments share (closes #1669)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "no-redundant-annotation-assertion",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1668
|
|
19
|
+
],
|
|
20
|
+
"summary": "unbind the import two annotations share (closes #1668)"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"version": "1.20.93",
|
|
26
|
+
"date": "2026-08-03T22:34:05.616Z",
|
|
27
|
+
"rules": [
|
|
28
|
+
{
|
|
29
|
+
"name": "enforce-date-ttime",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
1667
|
|
33
|
+
],
|
|
34
|
+
"summary": "keep the alias it rewrites away (closes #1667)"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "no-redundant-annotation-assertion",
|
|
38
|
+
"changeType": "fix",
|
|
39
|
+
"issues": [
|
|
40
|
+
1666
|
|
41
|
+
],
|
|
42
|
+
"summary": "keep the type its annotation named (closes #1666)"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
2
46
|
{
|
|
3
47
|
"version": "1.20.92",
|
|
4
48
|
"date": "2026-08-03T19:30:56.799Z",
|