@blumintinc/eslint-plugin-blumint 1.18.15 → 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
@@ -218,7 +218,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
218
218
  module.exports = {
219
219
  meta: {
220
220
  name: '@blumintinc/eslint-plugin-blumint',
221
- version: '1.18.15',
221
+ version: '1.18.16',
222
222
  },
223
223
  parseOptions: {
224
224
  ecmaVersion: 2020,
@@ -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 at the start (prefix),
391
- // the end (suffix), or directly before the final noun
392
- // (..._STRING_NAME). Buried deeper (EDITABLE_WRAPPER_NUMBER_PROPS_…)
393
- // it qualifies an intermediate segment a descriptive variant, not a
394
- // type tag.
395
- return (index === 0 || index === lastIndex || index === lastIndex - 1);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.18.15",
3
+ "version": "1.18.16",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,26 @@
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
+ },
2
24
  {
3
25
  "version": "1.18.15",
4
26
  "date": "2026-07-13T15:25:03.959Z",