@blumintinc/eslint-plugin-blumint 1.19.22 → 1.19.24
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
|
@@ -86,6 +86,21 @@ const ITERATION_METHODS = new Set([
|
|
|
86
86
|
'flatMap',
|
|
87
87
|
'sort',
|
|
88
88
|
]);
|
|
89
|
+
/**
|
|
90
|
+
* Comparison operators whose result is a boolean primitive. A value produced by
|
|
91
|
+
* one of these can never carry a referential identity, so a call whose result
|
|
92
|
+
* feeds such a comparison never lets its argument's identity escape.
|
|
93
|
+
*/
|
|
94
|
+
const COMPARISON_OPERATORS = new Set([
|
|
95
|
+
'===',
|
|
96
|
+
'!==',
|
|
97
|
+
'==',
|
|
98
|
+
'!=',
|
|
99
|
+
'<',
|
|
100
|
+
'>',
|
|
101
|
+
'<=',
|
|
102
|
+
'>=',
|
|
103
|
+
]);
|
|
89
104
|
const MEMOIZATION_DEPS_TODO_PLACEHOLDER = '__TODO_MEMOIZATION_DEPENDENCIES__';
|
|
90
105
|
const TODO_DEPS_COMMENT = `/* ${MEMOIZATION_DEPS_TODO_PLACEHOLDER} */`;
|
|
91
106
|
const PARENTHESIZED_EXPRESSION_TYPE = utils_1.AST_NODE_TYPES.ParenthesizedExpression ??
|
|
@@ -847,6 +862,108 @@ exports.reactMemoizeLiterals = (0, createRule_1.createRule)({
|
|
|
847
862
|
}
|
|
848
863
|
return usages.every((ref) => isStyleJSXAttributeValue(ref.identifier));
|
|
849
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* True when `expr` — a plain function call's result, or a reference to the
|
|
867
|
+
* variable holding that result — is consumed only for its primitive value,
|
|
868
|
+
* never by reference.
|
|
869
|
+
*
|
|
870
|
+
* Referential stability of a call's ARGUMENT matters only if the call's
|
|
871
|
+
* RESULT reaches a memoization boundary (a hook dependency, a memoized
|
|
872
|
+
* child's prop, an effect capture) where identities are reference-compared.
|
|
873
|
+
* A result that lands in a boolean-test position — a ternary/if/while/for
|
|
874
|
+
* test, a `!`, a comparison, or a logical chain that itself ends in such a
|
|
875
|
+
* position — is coerced to (or already is) a primitive, so the argument's
|
|
876
|
+
* identity provably never crosses such a boundary regardless of what the
|
|
877
|
+
* callee returns. Reasoning from how the CALLER consumes the result (not the
|
|
878
|
+
* callee's body) keeps the analysis purely syntactic and type-free.
|
|
879
|
+
*
|
|
880
|
+
* The whitelist is deliberately tight: any position not proven primitive
|
|
881
|
+
* (JSX attribute value, hook dependency element, argument to another call,
|
|
882
|
+
* spread, return, object/array member) yields false, so the exemption never
|
|
883
|
+
* silences a literal whose identity could still escape.
|
|
884
|
+
*/
|
|
885
|
+
function isPrimitivelyConsumed(expr) {
|
|
886
|
+
const parent = expr.parent;
|
|
887
|
+
if (!parent) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
// TS-assertion/parenthesized wrappers are transparent to consumption:
|
|
891
|
+
// analyze how the wrapped value is ultimately used.
|
|
892
|
+
if (isExpressionWrapper(parent)) {
|
|
893
|
+
const wrapper = parent;
|
|
894
|
+
return wrapper.expression === expr && isPrimitivelyConsumed(parent);
|
|
895
|
+
}
|
|
896
|
+
switch (parent.type) {
|
|
897
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
898
|
+
return parent.test === expr;
|
|
899
|
+
case utils_1.AST_NODE_TYPES.IfStatement:
|
|
900
|
+
case utils_1.AST_NODE_TYPES.WhileStatement:
|
|
901
|
+
case utils_1.AST_NODE_TYPES.DoWhileStatement:
|
|
902
|
+
case utils_1.AST_NODE_TYPES.ForStatement:
|
|
903
|
+
return parent.test === expr;
|
|
904
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
905
|
+
return parent.operator === '!';
|
|
906
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
907
|
+
return COMPARISON_OPERATORS.has(parent.operator);
|
|
908
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
909
|
+
// A logical operand inherits the consumption of the whole expression:
|
|
910
|
+
// safe only if that ultimately lands in a primitive position too.
|
|
911
|
+
return isPrimitivelyConsumed(parent);
|
|
912
|
+
case utils_1.AST_NODE_TYPES.VariableDeclarator: {
|
|
913
|
+
if (parent.init !== expr ||
|
|
914
|
+
parent.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
const variables = ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, parent);
|
|
918
|
+
if (variables.length === 0) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
const usages = variables[0].references.filter((ref) => !ref.init);
|
|
922
|
+
// No usages (dead code): can't prove the result stays primitive, so
|
|
923
|
+
// keep the literal reported (mirrors isStyleVariableInitializer).
|
|
924
|
+
if (usages.length === 0) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
return usages.every((ref) => isPrimitivelyConsumed(ref.identifier));
|
|
928
|
+
}
|
|
929
|
+
default:
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* True when the literal is a direct argument of a plain function call whose
|
|
935
|
+
* result is only ever consumed primitively (see isPrimitivelyConsumed). In
|
|
936
|
+
* that case the literal's identity provably never reaches a memoization
|
|
937
|
+
* boundary — it is neither a JSX prop, nor a hook dependency, nor captured
|
|
938
|
+
* by an effect — so re-creating it each render costs nothing and memoizing
|
|
939
|
+
* it buys nothing. The callee must be a plain Identifier: member calls
|
|
940
|
+
* (`obj.method({...})`) are excluded because the receiver could retain the
|
|
941
|
+
* reference, and this keeps the guard to plain, non-hook synchronous calls.
|
|
942
|
+
*/
|
|
943
|
+
function isPrimitiveConsumedCallArgument(node) {
|
|
944
|
+
// Walk up through transparent wrappers to the position the literal
|
|
945
|
+
// effectively occupies as a call argument.
|
|
946
|
+
let effective = node;
|
|
947
|
+
let parent = effective.parent;
|
|
948
|
+
while (parent &&
|
|
949
|
+
isExpressionWrapper(parent) &&
|
|
950
|
+
parent
|
|
951
|
+
.expression === effective) {
|
|
952
|
+
effective = parent;
|
|
953
|
+
parent = effective.parent;
|
|
954
|
+
}
|
|
955
|
+
if (!parent || parent.type !== utils_1.AST_NODE_TYPES.CallExpression) {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
const isDirectArgument = parent.arguments.some((arg) => arg === effective);
|
|
959
|
+
if (!isDirectArgument) {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
if (parent.callee.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
return isPrimitivelyConsumed(parent);
|
|
966
|
+
}
|
|
850
967
|
function reportLiteral(node) {
|
|
851
968
|
const descriptor = getLiteralDescriptor(node);
|
|
852
969
|
if (!descriptor)
|
|
@@ -937,6 +1054,14 @@ exports.reactMemoizeLiterals = (0, createRule_1.createRule)({
|
|
|
937
1054
|
});
|
|
938
1055
|
return;
|
|
939
1056
|
}
|
|
1057
|
+
// A literal passed only as an argument to a plain synchronous call whose
|
|
1058
|
+
// result is consumed primitively never carries its identity to a
|
|
1059
|
+
// memoization boundary, so memoizing it is pointless (issue #1329). Placed
|
|
1060
|
+
// last so hook-argument and hook-return handling run first and are
|
|
1061
|
+
// unaffected.
|
|
1062
|
+
if (isPrimitiveConsumedCallArgument(node)) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
940
1065
|
const contextLabel = formatContextLabel(owner);
|
|
941
1066
|
// Only emit auto-fix suggestions for simple variable initializers; other
|
|
942
1067
|
// contexts (returns, JSX props, nested expressions) risk unsafe rewrites.
|
|
@@ -605,9 +605,18 @@ exports.verticallyGroupRelatedFunctions = (0, createRule_1.createRule)({
|
|
|
605
605
|
if (misplacedIndex === -1) {
|
|
606
606
|
return;
|
|
607
607
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
608
|
+
// Report the function that BELONGS in the first mismatched slot
|
|
609
|
+
// (expected[i]), not the wrong occupant currently sitting there
|
|
610
|
+
// (actual[i]). Telling the reader to move expected[i] up to right after
|
|
611
|
+
// expected[i-1] fills the hole and extends the already-correct prefix
|
|
612
|
+
// by one, so obeying each emitted instruction verbatim strictly
|
|
613
|
+
// converges on the fixed point. Naming the displaced occupant instead
|
|
614
|
+
// sends it off to its own final home without filling this slot — the
|
|
615
|
+
// prefix stays mismatched and the next pass emits a different
|
|
616
|
+
// instruction anchored on a different sibling, the non-converging
|
|
617
|
+
// "anchor chase" on diamond/shared-leaf graphs (#1330).
|
|
618
|
+
const misplacedInfo = expectedOrderInfos[misplacedIndex];
|
|
619
|
+
const anchorName = misplacedIndex > 0 ? expectedNames[misplacedIndex - 1] : null;
|
|
611
620
|
const dependencyReason = misplacedInfo.dependencies.length > 0
|
|
612
621
|
? normalizedOptions.dependencyDirection === 'callees-first'
|
|
613
622
|
? `it calls ${misplacedInfo.dependencies.join(', ')} and helpers should precede the callers that depend on them when dependencyDirection is "callees-first"`
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.19.24",
|
|
4
|
+
"date": "2026-07-22T21:30:58.024Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-positive-naming",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1331
|
|
11
|
+
],
|
|
12
|
+
"summary": "exempt ingest word family from in- prefix heuristic (closes #1331)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "vertically-group-related-functions",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1330
|
|
19
|
+
],
|
|
20
|
+
"summary": "fill the mismatched slot instead of evicting its occupant (closes #1330)"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"version": "1.19.23",
|
|
26
|
+
"date": "2026-07-22T15:32:41.825Z",
|
|
27
|
+
"rules": [
|
|
28
|
+
{
|
|
29
|
+
"name": "react-memoize-literals",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
1329
|
|
33
|
+
],
|
|
34
|
+
"summary": "exempt literal args to primitively-consumed plain calls (closes #1329)"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
2
38
|
{
|
|
3
39
|
"version": "1.19.22",
|
|
4
40
|
"date": "2026-07-22T09:39:07.911Z",
|