@blumintinc/eslint-plugin-blumint 1.21.12 → 1.21.13
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 +103 -28
- package/package.json +1 -1
- package/release-manifest.json +14 -0
package/lib/index.js
CHANGED
|
@@ -588,8 +588,10 @@ const isInferenceSite = (identifier) => {
|
|
|
588
588
|
* second: a walk keyed on the first parameter would enrol a binding typed from
|
|
589
589
|
* the seed value and miss the one typed from the constant (Issue #2338).
|
|
590
590
|
*
|
|
591
|
-
*
|
|
592
|
-
* whatever the receiver holds, so nothing the assertion
|
|
591
|
+
* The INDEX parameter is absent from every map here, and from nothing else:
|
|
592
|
+
* it is a `number` whatever the receiver holds, so nothing the assertion
|
|
593
|
+
* changes reaches it. The parameter AFTER the index is a different matter —
|
|
594
|
+
* see `ARRAY_PARAMETER_INDEX_BY_METHOD`.
|
|
593
595
|
*/
|
|
594
596
|
const ELEMENT_PARAMETER_INDEX_BY_METHOD = new Map([
|
|
595
597
|
['forEach', 0],
|
|
@@ -605,6 +607,45 @@ const ELEMENT_PARAMETER_INDEX_BY_METHOD = new Map([
|
|
|
605
607
|
['reduce', 1],
|
|
606
608
|
['reduceRight', 1],
|
|
607
609
|
]);
|
|
610
|
+
/**
|
|
611
|
+
* The same methods, mapped to the position the RECEIVER ARRAY arrives in.
|
|
612
|
+
*
|
|
613
|
+
* That parameter is a second name for the iterated value itself, so a mutating
|
|
614
|
+
* call through it writes to the constant:
|
|
615
|
+
* `ITEMS.forEach((item, index, arr) => { arr.push(2); })` is TS2339 once
|
|
616
|
+
* `ITEMS` is frozen, for an input that compiled — the identical call written
|
|
617
|
+
* directly as `ITEMS.push(2)` is one the rule already declines for, so only the
|
|
618
|
+
* handed-node spelling escapes it (Issue #2339).
|
|
619
|
+
*
|
|
620
|
+
* The position is carried apart from the element's because the two are enrolled
|
|
621
|
+
* on different terms rather than because they differ by one: an element keeps
|
|
622
|
+
* the constant's type through every derivation the iteration walk follows,
|
|
623
|
+
* while the receiver is the constant only when the iteration reads the
|
|
624
|
+
* constant's own value or member path — see `iterationBindingsOf`.
|
|
625
|
+
* `reduce`/`reduceRight` push it to fourth, having spent the first position on
|
|
626
|
+
* the accumulator.
|
|
627
|
+
*
|
|
628
|
+
* `flatMap` is listed even though its lib signature declares the parameter
|
|
629
|
+
* `T[]` where every sibling declares it `readonly T[]` — measured against
|
|
630
|
+
* `lib.es2020`, so a mutating METHOD through it survives the assertion. Its
|
|
631
|
+
* ELEMENTS are frozen regardless, so `arr[0].n = 2` inside a `flatMap` callback
|
|
632
|
+
* is TS2540 for an input that compiled, and the walk's write check reaches it
|
|
633
|
+
* only once the parameter is enrolled.
|
|
634
|
+
*/
|
|
635
|
+
const ARRAY_PARAMETER_INDEX_BY_METHOD = new Map([
|
|
636
|
+
['forEach', 2],
|
|
637
|
+
['map', 2],
|
|
638
|
+
['filter', 2],
|
|
639
|
+
['find', 2],
|
|
640
|
+
['findIndex', 2],
|
|
641
|
+
['findLast', 2],
|
|
642
|
+
['findLastIndex', 2],
|
|
643
|
+
['some', 2],
|
|
644
|
+
['every', 2],
|
|
645
|
+
['flatMap', 2],
|
|
646
|
+
['reduce', 3],
|
|
647
|
+
['reduceRight', 3],
|
|
648
|
+
]);
|
|
608
649
|
/**
|
|
609
650
|
* The `Object.values(X)` / `Object.entries(X)` call this value feeds — a fresh
|
|
610
651
|
* array whose ELEMENTS are the constant's own property values, so freezing the
|
|
@@ -630,9 +671,10 @@ const elementProjectionCallOf = (node) => {
|
|
|
630
671
|
: null;
|
|
631
672
|
};
|
|
632
673
|
/**
|
|
633
|
-
* The bindings a construct that ITERATES `iterable` introduces
|
|
634
|
-
*
|
|
635
|
-
*
|
|
674
|
+
* The bindings a construct that ITERATES `iterable` introduces: the head of a
|
|
675
|
+
* `for…of` over it, the parameter an array method hands each element to, and —
|
|
676
|
+
* when the iterated expression is the constant itself — the parameter that
|
|
677
|
+
* method hands the RECEIVER ARRAY to.
|
|
636
678
|
*
|
|
637
679
|
* A `for…of` head is accepted in all three binding spellings, on the same terms
|
|
638
680
|
* as `aliasDeclaratorOf` accepts all three declarator spellings — every name a
|
|
@@ -645,8 +687,14 @@ const elementProjectionCallOf = (node) => {
|
|
|
645
687
|
* A callback parameter is reached only through a function LITERAL: a callback
|
|
646
688
|
* passed by name is declared elsewhere, where its parameter carries whatever
|
|
647
689
|
* type that declaration gives it rather than one read off the constant.
|
|
690
|
+
*
|
|
691
|
+
* `iteratesConstantValue` says whether `iterable` is the constant's own value
|
|
692
|
+
* or member path rather than something derived from it. It gates the receiver
|
|
693
|
+
* ARRAY parameter alone: the element parameter is typed from the constant
|
|
694
|
+
* either way, while the array parameter names the constant only in the first
|
|
695
|
+
* case — see `iterationBindingsOf`.
|
|
648
696
|
*/
|
|
649
|
-
const
|
|
697
|
+
const bindingsOfIterationOver = (iterable, declaredVariablesOf, iteratesConstantValue) => {
|
|
650
698
|
// The member path is resolved first because the iterated expression is
|
|
651
699
|
// routinely a PROPERTY of the constant (`for (const x of CONFIG.list)`),
|
|
652
700
|
// which the alias walk refuses precisely because it arrives through a member
|
|
@@ -666,7 +714,10 @@ const elementBindingsOfIteration = (iterable, declaredVariablesOf) => {
|
|
|
666
714
|
return [];
|
|
667
715
|
}
|
|
668
716
|
const method = accessedPropertyName(path);
|
|
669
|
-
|
|
717
|
+
if (method === null) {
|
|
718
|
+
return [];
|
|
719
|
+
}
|
|
720
|
+
const elementIndex = ELEMENT_PARAMETER_INDEX_BY_METHOD.get(method);
|
|
670
721
|
if (elementIndex === undefined ||
|
|
671
722
|
parent.type !== utils_1.AST_NODE_TYPES.CallExpression ||
|
|
672
723
|
parent.callee !== value) {
|
|
@@ -676,25 +727,35 @@ const elementBindingsOfIteration = (iterable, declaredVariablesOf) => {
|
|
|
676
727
|
if (!callback || !isFunctionValue(callback)) {
|
|
677
728
|
return [];
|
|
678
729
|
}
|
|
679
|
-
const
|
|
680
|
-
|
|
730
|
+
const arrayIndex = iteratesConstantValue
|
|
731
|
+
? ARRAY_PARAMETER_INDEX_BY_METHOD.get(method)
|
|
732
|
+
: undefined;
|
|
733
|
+
// A callback routinely declares fewer parameters than the method passes, so
|
|
734
|
+
// each position is taken only where the signature actually spells it.
|
|
735
|
+
const enrolled = [
|
|
736
|
+
callback.params[elementIndex],
|
|
737
|
+
arrayIndex === undefined ? undefined : callback.params[arrayIndex],
|
|
738
|
+
].filter((param) => param !== undefined);
|
|
739
|
+
if (enrolled.length === 0) {
|
|
681
740
|
return [];
|
|
682
741
|
}
|
|
683
742
|
// The scope manager answers for the WHOLE function — every parameter, and a
|
|
684
|
-
// function expression's own name — so the
|
|
685
|
-
// picked out by the
|
|
686
|
-
// whole would enrol the accumulator of a `reduce`,
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
743
|
+
// function expression's own name — so the enrolled parameters' bindings are
|
|
744
|
+
// picked out by the spans they are declared in. Taking the function's list
|
|
745
|
+
// whole would enrol the accumulator of a `reduce`, typed from the seed value
|
|
746
|
+
// rather than from the constant, and the index, which the assertion cannot
|
|
747
|
+
// reach.
|
|
748
|
+
return declaredVariablesOf(callback).filter((variable) => variable.defs.some((def) => enrolled.some((param) => def.name.range[0] >= param.range[0] &&
|
|
749
|
+
def.name.range[1] <= param.range[1])));
|
|
690
750
|
};
|
|
691
751
|
/**
|
|
692
752
|
* The bindings ITERATING this reference introduces, directly or through a value
|
|
693
753
|
* derived from it that keeps its element types.
|
|
694
754
|
*
|
|
695
755
|
* Such a binding is typed from the constant exactly as a destructured copy is —
|
|
696
|
-
* it carries the ELEMENT type
|
|
697
|
-
* it breaks on the assertion
|
|
756
|
+
* it carries the ELEMENT type, or for the receiver parameter the whole value —
|
|
757
|
+
* so a write through it breaks on the assertion as a write through an alias
|
|
758
|
+
* does:
|
|
698
759
|
* `for (const item of ITEMS) { item.label = 'b'; }` is TS2540 once `ITEMS` is
|
|
699
760
|
* frozen, for an input that compiled (Issue #2338). Enrolling the binding is
|
|
700
761
|
* therefore the whole remedy; the walk's existing write, mutating-method and
|
|
@@ -707,15 +768,26 @@ const elementBindingsOfIteration = (iterable, declaredVariablesOf) => {
|
|
|
707
768
|
* builds a fresh OUTER value whose elements are still the frozen ones, so the
|
|
708
769
|
* element binding breaks identically. One derivation step is followed, matching
|
|
709
770
|
* the depth the alias walk already follows a copy to.
|
|
771
|
+
*
|
|
772
|
+
* The receiver ARRAY parameter is enrolled for the constant's own value or
|
|
773
|
+
* member path ALONE, which is the one iterable of the three that hands the
|
|
774
|
+
* callback the constant itself. A derivation hands it the fresh outer value it
|
|
775
|
+
* built, and mutating that is no readonly violation:
|
|
776
|
+
* `[...ITEMS].forEach((item, index, arr) => { arr.push(3); })` does break after
|
|
777
|
+
* the fix, but as TS2345 — the spread narrows the element type, so `3` is not
|
|
778
|
+
* assignable — which belongs to the literal-narrowing family filed as #2330 and
|
|
779
|
+
* needs the type checker. Enrolling it here would withhold the assertion for a
|
|
780
|
+
* reason this arm cannot justify, so it is an over-decline (Issue #2339).
|
|
710
781
|
*/
|
|
711
782
|
const iterationBindingsOf = (identifier, declaredVariablesOf) => {
|
|
712
783
|
const value = outermostValueOf(identifier);
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
784
|
+
const derivations = [copyExpressionOf(value), elementProjectionCallOf(value)];
|
|
785
|
+
return [
|
|
786
|
+
...bindingsOfIterationOver(value, declaredVariablesOf, true),
|
|
787
|
+
...derivations.flatMap((iterable) => iterable
|
|
788
|
+
? bindingsOfIterationOver(iterable, declaredVariablesOf, false)
|
|
789
|
+
: []),
|
|
717
790
|
];
|
|
718
|
-
return iterables.flatMap((iterable) => iterable ? elementBindingsOfIteration(iterable, declaredVariablesOf) : []);
|
|
719
791
|
};
|
|
720
792
|
/**
|
|
721
793
|
* Whether anything in the file stops this binding taking `as const`, under its
|
|
@@ -748,13 +820,16 @@ const iterationBindingsOf = (identifier, declaredVariablesOf) => {
|
|
|
748
820
|
* the one value — and `visited` keeps a chain that leads back on itself, which
|
|
749
821
|
* a redeclared `var` can build, from looping forever.
|
|
750
822
|
*
|
|
751
|
-
* Iteration is followed on the same reasoning, keyed on the
|
|
752
|
-
*
|
|
753
|
-
* second names for the constant's contents, so `for (const item of ITEMS) {
|
|
823
|
+
* Iteration is followed on the same reasoning, keyed on what the construct
|
|
824
|
+
* HANDS its body: a `for…of` head and an iteration callback's element parameter
|
|
825
|
+
* are second names for the constant's contents, so `for (const item of ITEMS) {
|
|
754
826
|
* item.label = 'b'; }` is TS2540 once `ITEMS` is frozen while `ITEMS`'s own
|
|
755
|
-
* references show nothing but a read (Issue #2338)
|
|
756
|
-
*
|
|
757
|
-
*
|
|
827
|
+
* references show nothing but a read (Issue #2338); the parameter after the
|
|
828
|
+
* index is a second name for the constant ITSELF, so `arr.push(2)` inside the
|
|
829
|
+
* callback is the TS2339 the rule already declines for when the same call is
|
|
830
|
+
* written directly (Issue #2339). Enrolling the binding is all it takes — the
|
|
831
|
+
* checks above then decide, so a callback that only reads what it is handed
|
|
832
|
+
* keeps the assertion.
|
|
758
833
|
*
|
|
759
834
|
* The declaring KEYWORD is deliberately not screened. `as const` types the
|
|
760
835
|
* value `readonly`, and a binding takes its declared type from its initializer,
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.21.13",
|
|
4
|
+
"date": "2026-09-05T22:39:56.997Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "global-const-style",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2339
|
|
11
|
+
],
|
|
12
|
+
"summary": "enrol the receiver array parameter of an iteration over the constant (closes #2339)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "1.21.12",
|
|
4
18
|
"date": "2026-09-05T18:30:20.453Z",
|