@blumintinc/eslint-plugin-blumint 1.18.14 → 1.18.16
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
|
@@ -92,11 +92,39 @@ const isHookCall = (callee) => {
|
|
|
92
92
|
}
|
|
93
93
|
return null;
|
|
94
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Recognizes a callee named exactly `name`, whether a bare identifier (`memo`)
|
|
97
|
+
* or a non-computed member access (`X.memo`), regardless of import source.
|
|
98
|
+
*
|
|
99
|
+
* memo/forwardRef are commonly re-exported from project-local wrapper modules
|
|
100
|
+
* (e.g. `import { memo } from 'src/util/memo'`, which the sibling
|
|
101
|
+
* `use-custom-memo` rule actively enforces). Resolving these purely against
|
|
102
|
+
* react's import table would miss the wrapper form and misclassify an HOC
|
|
103
|
+
* factory as a render body. By-name recognition is safe here because callers
|
|
104
|
+
* are already gated by PascalCase naming and the surrounding factory analysis.
|
|
105
|
+
*
|
|
106
|
+
* Note: this deliberately does NOT apply to `createElement`, which must stay
|
|
107
|
+
* bound to a react import so unrelated factories (e.g. `Factory.createElement`)
|
|
108
|
+
* are not misread as element creation.
|
|
109
|
+
*/
|
|
110
|
+
const calleeMatchesName = (callee, name) => {
|
|
111
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
112
|
+
return callee.name === name;
|
|
113
|
+
}
|
|
114
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
115
|
+
!callee.computed &&
|
|
116
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
117
|
+
return callee.property.name === name;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
};
|
|
95
121
|
const isForwardRefCall = (node, reactImports) => {
|
|
96
|
-
return calleeMatchesReactMember(node.callee, reactImports, 'forwardRef')
|
|
122
|
+
return (calleeMatchesReactMember(node.callee, reactImports, 'forwardRef') ||
|
|
123
|
+
calleeMatchesName(node.callee, 'forwardRef'));
|
|
97
124
|
};
|
|
98
125
|
const isMemoCall = (node, reactImports) => {
|
|
99
|
-
return calleeMatchesReactMember(node.callee, reactImports, 'memo')
|
|
126
|
+
return (calleeMatchesReactMember(node.callee, reactImports, 'memo') ||
|
|
127
|
+
calleeMatchesName(node.callee, 'memo'));
|
|
100
128
|
};
|
|
101
129
|
const filterPresentResults = (results) => {
|
|
102
130
|
return results.filter((result) => Boolean(result));
|
|
@@ -298,6 +298,15 @@ function isDomainNumberCompound(name) {
|
|
|
298
298
|
const lastSegment = segments[segments.length - 1];
|
|
299
299
|
return (!!lastSegment && DOMAIN_NUMBER_HEAD_NOUNS.has(lastSegment.toLowerCase()));
|
|
300
300
|
}
|
|
301
|
+
// Rebuild a SCREAMING_SNAKE_CASE identifier's segments into a PascalCase compound
|
|
302
|
+
// (["MATCH","NUMBER"] -> "MatchNumber") so the snake-case branch can reuse the
|
|
303
|
+
// camelCase isDomainNumberCompound / DOMAIN_NUMBER_HEAD_NOUNS exemption verbatim,
|
|
304
|
+
// keeping MATCH_NUMBER and matchNumber on a single code path (#1294).
|
|
305
|
+
function screamingSnakePartsToPascalCase(parts) {
|
|
306
|
+
return parts
|
|
307
|
+
.map((part) => part.charAt(0) + part.slice(1).toLowerCase())
|
|
308
|
+
.join('');
|
|
309
|
+
}
|
|
301
310
|
exports.noHungarian = (0, createRule_1.createRule)({
|
|
302
311
|
name: 'no-hungarian',
|
|
303
312
|
meta: {
|
|
@@ -387,12 +396,31 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
387
396
|
if (isAbbreviation) {
|
|
388
397
|
return true;
|
|
389
398
|
}
|
|
390
|
-
// A FULL type word tags the entity's type only
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
399
|
+
// A FULL type word tags the entity's runtime type only as a genuine
|
|
400
|
+
// leading prefix (index 0) or trailing head-noun (last segment).
|
|
401
|
+
// Mirror the camelCase/PascalCase branch, which never flags a
|
|
402
|
+
// full-type-word in a MIDDLE segment: an interior NUMBER/STRING is a
|
|
403
|
+
// domain modifier describing a variant (CADENCE_NUMBER_EDITORS —
|
|
404
|
+
// "editors of a numeric cadence"), not a redundant type tag. The
|
|
405
|
+
// previous `index === lastIndex - 1` allowance produced a casing
|
|
406
|
+
// asymmetry — CadenceNumberEditor was exempt (#1250) but
|
|
407
|
+
// CADENCE_NUMBER_EDITORS fired (#1294).
|
|
408
|
+
if (index !== 0 && index !== lastIndex) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
// A trailing "..._NUMBER" whose preceding head noun is a domain
|
|
412
|
+
// entity (MATCH_NUMBER, ISSUE_NUMBER, CURRENT_LINE_NUMBER) is a
|
|
413
|
+
// domain compound, not a Hungarian type tag — route through the same
|
|
414
|
+
// isDomainNumberCompound carve-out used for camelCase matchNumber
|
|
415
|
+
// (#1277), so numeric-quantity heads (COUNT_NUMBER, MAX_RETRY_NUMBER)
|
|
416
|
+
// still fire because those heads are absent from
|
|
417
|
+
// DOMAIN_NUMBER_HEAD_NOUNS.
|
|
418
|
+
if (normalizedMarker === 'number' &&
|
|
419
|
+
index === lastIndex &&
|
|
420
|
+
isDomainNumberCompound(screamingSnakePartsToPascalCase(parts))) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
return true;
|
|
396
424
|
});
|
|
397
425
|
});
|
|
398
426
|
}
|
|
@@ -100,11 +100,18 @@ function isHookName(name) {
|
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* Detects PascalCase identifiers commonly used for React components.
|
|
103
|
+
*
|
|
104
|
+
* SCREAMING_SNAKE_CASE names (`CHANNEL_OPTIONS`, `MAX_COUNT`, `SPACING`) are
|
|
105
|
+
* constants by convention, not components, so they are excluded even though
|
|
106
|
+
* they begin with an uppercase letter. Treating them as components would
|
|
107
|
+
* misclassify a module-scope constant's `.map`/`.filter` callback body as a
|
|
108
|
+
* render body and flag literals that are only ever built once at import.
|
|
103
109
|
* @param name Candidate identifier name.
|
|
104
|
-
* @returns True when the name
|
|
110
|
+
* @returns True when the name is PascalCase (starts uppercase and contains a
|
|
111
|
+
* lowercase letter), false for all-caps constants.
|
|
105
112
|
*/
|
|
106
113
|
function isComponentName(name) {
|
|
107
|
-
return !!name && /^[A-Z]/.test(name);
|
|
114
|
+
return !!name && /^[A-Z]/.test(name) && !/^[A-Z][A-Z0-9_]*$/.test(name);
|
|
108
115
|
}
|
|
109
116
|
/**
|
|
110
117
|
* True when `node` is a function literal passed directly as an argument to an
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.18.16",
|
|
4
|
+
"date": "2026-07-13T17:31:09.661Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "memo-nested-react-components",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1293
|
|
11
|
+
],
|
|
12
|
+
"summary": "recognize project-local memo/forwardRef re-exports in HOC-factory escape hatch (closes #1293)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "no-hungarian",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1294
|
|
19
|
+
],
|
|
20
|
+
"summary": "exempt interior type-word segments in SCREAMING_SNAKE_CASE constants (closes #1294)"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"version": "1.18.15",
|
|
26
|
+
"date": "2026-07-13T15:25:03.959Z",
|
|
27
|
+
"rules": [
|
|
28
|
+
{
|
|
29
|
+
"name": "react-memoize-literals",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
1292
|
|
33
|
+
],
|
|
34
|
+
"summary": "exclude SCREAMING_SNAKE_CASE constants from component detection (closes #1292)"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
2
38
|
{
|
|
3
39
|
"version": "1.18.14",
|
|
4
40
|
"date": "2026-07-13T08:30:23.444Z",
|