@blumintinc/eslint-plugin-blumint 1.21.7 → 1.21.8
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/global-const-style.js +96 -5
- package/package.json +1 -1
- package/release-manifest.json +14 -0
package/lib/index.js
CHANGED
|
@@ -369,10 +369,90 @@ const aliasDeclaratorOf = (identifier) => {
|
|
|
369
369
|
value = outermostValueOf(container);
|
|
370
370
|
}
|
|
371
371
|
};
|
|
372
|
+
/** Pattern nodes a parameter's binding can be nested inside. */
|
|
373
|
+
const PATTERN_CONTAINERS = new Set([
|
|
374
|
+
utils_1.AST_NODE_TYPES.AssignmentPattern,
|
|
375
|
+
utils_1.AST_NODE_TYPES.Property,
|
|
376
|
+
utils_1.AST_NODE_TYPES.ObjectPattern,
|
|
377
|
+
utils_1.AST_NODE_TYPES.ArrayPattern,
|
|
378
|
+
utils_1.AST_NODE_TYPES.RestElement,
|
|
379
|
+
]);
|
|
380
|
+
const FUNCTION_TYPES = new Set([
|
|
381
|
+
utils_1.AST_NODE_TYPES.FunctionDeclaration,
|
|
382
|
+
utils_1.AST_NODE_TYPES.FunctionExpression,
|
|
383
|
+
utils_1.AST_NODE_TYPES.ArrowFunctionExpression,
|
|
384
|
+
utils_1.AST_NODE_TYPES.TSDeclareFunction,
|
|
385
|
+
]);
|
|
386
|
+
/**
|
|
387
|
+
* Whether a default value is what a PARAMETER's type is inferred FROM.
|
|
388
|
+
*
|
|
389
|
+
* Answered false in the two cases where freezing the default cannot change a
|
|
390
|
+
* signature: the parameter carries a type annotation, so its type is declared
|
|
391
|
+
* rather than inferred — looked for up the whole pattern, since a destructured
|
|
392
|
+
* parameter carries it on the pattern (`({ distance = DEFAULT }: Props)`) and
|
|
393
|
+
* a plain one on its binding (`(model: ModelName = DEFAULT)`) — or the default
|
|
394
|
+
* belongs to a destructuring declaration rather than a parameter list, which
|
|
395
|
+
* declares no signature at all.
|
|
396
|
+
*/
|
|
397
|
+
const isInferredParameterDefault = (pattern) => {
|
|
398
|
+
let current = pattern;
|
|
399
|
+
for (;;) {
|
|
400
|
+
if (current.typeAnnotation) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
const parent = current.parent;
|
|
404
|
+
if (!parent) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
if (FUNCTION_TYPES.has(parent.type)) {
|
|
408
|
+
return parent.params.includes(current);
|
|
409
|
+
}
|
|
410
|
+
if (!PATTERN_CONTAINERS.has(parent.type)) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
current = parent;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
372
416
|
/**
|
|
373
|
-
* Whether
|
|
374
|
-
*
|
|
375
|
-
*
|
|
417
|
+
* Whether a reference sits where TypeScript INFERS a type from it — the value
|
|
418
|
+
* of a default parameter, reached directly or through a composite literal it
|
|
419
|
+
* is stored into.
|
|
420
|
+
*
|
|
421
|
+
* `as const` does not only freeze: it makes the literal type NON-WIDENING, and
|
|
422
|
+
* an inference site that widened `'ready'` to `string` then keeps the literal.
|
|
423
|
+
* A parameter defaulted from the constant therefore narrows to that one value,
|
|
424
|
+
* and every call passing a different one stops compiling (TS2345) for an input
|
|
425
|
+
* that compiled. The mutation walk cannot see this: nothing is written, the
|
|
426
|
+
* signature is simply inferred from a value the assertion changes.
|
|
427
|
+
*/
|
|
428
|
+
const isInferenceSite = (identifier) => {
|
|
429
|
+
let value = outermostValueOf(identifier);
|
|
430
|
+
for (;;) {
|
|
431
|
+
const parent = value.parent;
|
|
432
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
433
|
+
parent.right === value) {
|
|
434
|
+
return isInferredParameterDefault(parent.left);
|
|
435
|
+
}
|
|
436
|
+
const container = storageContainerOf(value);
|
|
437
|
+
if (!container) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
value = outermostValueOf(container);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Whether anything in the file stops this binding taking `as const`, under its
|
|
445
|
+
* own name or through an alias of it.
|
|
446
|
+
*
|
|
447
|
+
* Two things disqualify it, because `as const` does two things. It freezes the
|
|
448
|
+
* value, so a WRITE — through the binding (`X.push(1)`), or to a binding that
|
|
449
|
+
* aliases it (`other = X`) — becomes TS2339/TS2540. And it makes the literal
|
|
450
|
+
* type NON-WIDENING, so an INFERENCE site that read the widened type keeps the
|
|
451
|
+
* literal instead, which rewrites a signature the assertion was never asked to
|
|
452
|
+
* touch.
|
|
453
|
+
*
|
|
454
|
+
* Answered from the scope manager's reference list rather than a textual
|
|
455
|
+
* search for the name, so a same-named binding in
|
|
376
456
|
* another scope (`const arr` shadowed inside a callback) contributes nothing,
|
|
377
457
|
* and a same-named method on an unrelated receiver (`other.push(1)`) is never
|
|
378
458
|
* even visited.
|
|
@@ -394,13 +474,24 @@ const aliasDeclaratorOf = (identifier) => {
|
|
|
394
474
|
* check keyed on `const` would leave the `let` spelling breaking builds under
|
|
395
475
|
* `--fix`.
|
|
396
476
|
*/
|
|
397
|
-
const
|
|
477
|
+
const blocksAsConstAssertion = (variable, declaredVariablesOf) => {
|
|
398
478
|
// Grown in place and walked by index: an alias found mid-walk is appended and
|
|
399
479
|
// reached by the same loop, so the traversal needs no recursion of its own.
|
|
400
480
|
const pending = [variable];
|
|
401
481
|
const visited = new Set(pending);
|
|
402
482
|
for (let index = 0; index < pending.length; index += 1) {
|
|
403
483
|
for (const reference of pending[index].references) {
|
|
484
|
+
// Reassigning an alias is as disqualifying as writing through one. A
|
|
485
|
+
// binding that takes its type from the constant narrows to the frozen
|
|
486
|
+
// literal, so `let stage = DEFAULT; stage = 'live';` becomes TS2322 for
|
|
487
|
+
// an input that compiled. `init` excludes the declaration's own write,
|
|
488
|
+
// which is how the alias was established rather than a change to it.
|
|
489
|
+
if (reference.isWrite() && !reference.init) {
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
if (isInferenceSite(reference.identifier)) {
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
404
495
|
const path = accessPathOf(reference.identifier);
|
|
405
496
|
if (path !== null) {
|
|
406
497
|
if (isMutatingMethodCall(path) || isWriteTarget(path)) {
|
|
@@ -837,7 +928,7 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
837
928
|
.getDeclaredVariables(declaration)
|
|
838
929
|
.find((variable) => variable.name === name);
|
|
839
930
|
return (!declaredVariable ||
|
|
840
|
-
!
|
|
931
|
+
!blocksAsConstAssertion(declaredVariable, declaredVariablesOf));
|
|
841
932
|
};
|
|
842
933
|
if (shouldHaveAsConst(init)) {
|
|
843
934
|
context.report({
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.21.8",
|
|
4
|
+
"date": "2026-09-05T03:15:57.605Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "global-const-style",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2329
|
|
11
|
+
],
|
|
12
|
+
"summary": "withhold the assertion where a type is inferred from the constant (closes #2329)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "1.21.7",
|
|
4
18
|
"date": "2026-09-04T21:52:00.765Z",
|