@blumintinc/eslint-plugin-blumint 1.19.27 → 1.19.29
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
|
@@ -27,6 +27,21 @@ exports.enforceIdCapitalization = (0, createRule_1.createRule)({
|
|
|
27
27
|
// Regular expression to match standalone "id" surrounded by whitespace or punctuation
|
|
28
28
|
// This ensures we only match "id" as a word, not as part of another word
|
|
29
29
|
const idRegex = /(^|\s|[.,;:!?'"()\[\]{}])id(\s|$|[.,;:!?'"()\[\]{}])/g;
|
|
30
|
+
// DOM / Testing-Library APIs whose first argument is an attribute NAME
|
|
31
|
+
// (code), not user-facing text. A literal like 'id' passed here is a DOM
|
|
32
|
+
// attribute name; flagging or rewriting it to 'ID' breaks the call.
|
|
33
|
+
const ATTRIBUTE_NAME_METHODS = new Set([
|
|
34
|
+
'getAttribute',
|
|
35
|
+
'setAttribute',
|
|
36
|
+
'hasAttribute',
|
|
37
|
+
'removeAttribute',
|
|
38
|
+
'getAttributeNode',
|
|
39
|
+
'getAttributeNS',
|
|
40
|
+
'setAttributeNS',
|
|
41
|
+
'hasAttributeNS',
|
|
42
|
+
'removeAttributeNS',
|
|
43
|
+
'toHaveAttribute',
|
|
44
|
+
]);
|
|
30
45
|
/**
|
|
31
46
|
* Check if a node is in a context that should be excluded from the rule
|
|
32
47
|
* (e.g., parameter names, property names, type definitions)
|
|
@@ -82,6 +97,24 @@ exports.enforceIdCapitalization = (0, createRule_1.createRule)({
|
|
|
82
97
|
if (node.parent && node.parent.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
83
98
|
return true;
|
|
84
99
|
}
|
|
100
|
+
// Check if the node is the attribute-name argument of a DOM / jest-dom
|
|
101
|
+
// attribute API call, e.g. element.getAttribute('id') or
|
|
102
|
+
// expect(el).toHaveAttribute('id', ...). The attribute name is code, not
|
|
103
|
+
// user-facing text. For the *NS variants the name is the second argument
|
|
104
|
+
// (the first is the namespace URI); otherwise it is the first argument.
|
|
105
|
+
if (node.parent &&
|
|
106
|
+
node.parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
107
|
+
node.parent.callee &&
|
|
108
|
+
node.parent.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
109
|
+
node.parent.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
110
|
+
ATTRIBUTE_NAME_METHODS.has(node.parent.callee.property.name)) {
|
|
111
|
+
const nameArgIndex = node.parent.callee.property.name.endsWith('NS')
|
|
112
|
+
? 1
|
|
113
|
+
: 0;
|
|
114
|
+
if (node.parent.arguments[nameArgIndex] === node) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
85
118
|
// Check if the node is a string literal used for property access
|
|
86
119
|
// This handles cases like obj['id'] or OverwolfGame['id']
|
|
87
120
|
if (node.parent &&
|
|
@@ -339,6 +339,35 @@ const returnExpressionIsJsx = (expression) => {
|
|
|
339
339
|
return (unwrapped.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
340
340
|
unwrapped.type === utils_1.AST_NODE_TYPES.JSXFragment);
|
|
341
341
|
};
|
|
342
|
+
/**
|
|
343
|
+
* True when the expression is a `memo(...)` / `forwardRef(...)` call. Returned
|
|
344
|
+
* from a useMemo/useDeepCompareMemo callback, this is the complete, sanctioned
|
|
345
|
+
* stabilization pattern: the memo hook stabilizes the component *identity*
|
|
346
|
+
* across re-renders (a new identity only on a deps change), so the inner
|
|
347
|
+
* component never remounts on an ordinary re-render—the exact harm this rule
|
|
348
|
+
* exists to prevent does not occur.
|
|
349
|
+
*/
|
|
350
|
+
const returnExpressionIsMemoOrForwardRefCall = (expression, reactImports) => {
|
|
351
|
+
if (!expression) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
const unwrapped = unwrapExpression(expression);
|
|
355
|
+
return (unwrapped.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
356
|
+
(isMemoCall(unwrapped, reactImports) ||
|
|
357
|
+
isForwardRefCall(unwrapped, reactImports)));
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* True when a useMemo/useDeepCompareMemo callback directly returns a
|
|
361
|
+
* memo()/forwardRef()-wrapped component. The memo hook stabilizes the returned
|
|
362
|
+
* component's identity, so such a binding does not remount on re-render and
|
|
363
|
+
* must not be flagged.
|
|
364
|
+
*/
|
|
365
|
+
const memoCallbackReturnsMemoizedComponent = (callback, reactImports) => {
|
|
366
|
+
if (!isFunctionExpression(callback)) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
return collectDirectReturnExpressions(callback).some((expression) => returnExpressionIsMemoOrForwardRefCall(expression, reactImports));
|
|
370
|
+
};
|
|
342
371
|
/**
|
|
343
372
|
* Direct return-statement arguments of a function, traversing control flow
|
|
344
373
|
* (if/switch/try/loops) but NOT descending into nested function definitions—
|
|
@@ -486,6 +515,12 @@ See: https://react.dev/learn/your-first-component#nesting-and-organizing-compone
|
|
|
486
515
|
// Returns JSX directly, so it's an element, not a component
|
|
487
516
|
return;
|
|
488
517
|
}
|
|
518
|
+
// A memo()/forwardRef()-wrapped return is identity-stabilized by the
|
|
519
|
+
// memo hook, so it does not remount on re-render and must not be
|
|
520
|
+
// flagged (see #1336). Bare inner components stay flagged.
|
|
521
|
+
if (memoCallbackReturnsMemoizedComponent(callback, reactImports)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
489
524
|
}
|
|
490
525
|
const variableName = getVariableName(node);
|
|
491
526
|
// A non-PascalCase binding (e.g. renderHit) is a render callback used
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.19.29",
|
|
4
|
+
"date": "2026-07-23T21:23:38.334Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-id-capitalization",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1337
|
|
11
|
+
],
|
|
12
|
+
"summary": "exempt DOM attribute-name arguments (closes #1337)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.19.28",
|
|
18
|
+
"date": "2026-07-23T17:25:39.744Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "memo-nested-react-components",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
1336
|
|
25
|
+
],
|
|
26
|
+
"summary": "exempt memo/forwardRef components returned from useMemo/useDeepCompareMemo callbacks (closes #1336)"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.19.27",
|
|
4
32
|
"date": "2026-07-23T04:26:05.419Z",
|