@blumintinc/eslint-plugin-blumint 1.19.22 → 1.19.23
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/react-memoize-literals.js +125 -0
- package/package.json +1 -1
- package/release-manifest.json +14 -0
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.
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.19.23",
|
|
4
|
+
"date": "2026-07-22T15:32:41.825Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "react-memoize-literals",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1329
|
|
11
|
+
],
|
|
12
|
+
"summary": "exempt literal args to primitively-consumed plain calls (closes #1329)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "1.19.22",
|
|
4
18
|
"date": "2026-07-22T09:39:07.911Z",
|