@blumintinc/eslint-plugin-blumint 1.19.19 → 1.19.21

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
@@ -222,7 +222,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
222
222
  module.exports = {
223
223
  meta: {
224
224
  name: '@blumintinc/eslint-plugin-blumint',
225
- version: '1.19.19',
225
+ version: '1.19.21',
226
226
  },
227
227
  parseOptions: {
228
228
  ecmaVersion: 2020,
@@ -40,14 +40,20 @@ exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
40
40
  return false;
41
41
  }
42
42
  /**
43
- * Checks if an array contains object literals that might be used as component props
43
+ * Checks if the (unwrapped) return argument is an array literal.
44
+ *
45
+ * Arrays returned from React hook callbacks represent memoized data/prop
46
+ * lists (component props, hit lists, tab labels, etc.). Freezing them into
47
+ * readonly tuples via `as const` fights the mutable/`readonly`-array types
48
+ * they flow into downstream, so any array returned from a hook is exempt —
49
+ * regardless of whether its elements are inline object literals (`[{...}]`),
50
+ * identifier/member references to objects (`[ANY_GAME_HIT]`,
51
+ * `[constants.HIT]`), or primitives. The rule has no type information, so it
52
+ * cannot narrow this further without reintroducing the false positives
53
+ * issues #511 and #1324 document.
44
54
  */
45
- function isArrayWithObjectLiterals(node) {
46
- if (node.type !== 'ArrayExpression') {
47
- return false;
48
- }
49
- // Check if any element in the array is an object literal
50
- return node.elements.some((elem) => elem !== null && elem.type === 'ObjectExpression');
55
+ function isArrayLiteral(node) {
56
+ return node.type === 'ArrayExpression';
51
57
  }
52
58
  return {
53
59
  ReturnStatement(node) {
@@ -96,9 +102,10 @@ exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
96
102
  argument.elements.some((elem) => elem !== null && elem.type === 'SpreadElement'))) {
97
103
  return;
98
104
  }
99
- // Skip arrays with object literals inside React hooks (likely component props)
105
+ // Skip arrays returned from React hooks (memoized data/prop lists that
106
+ // must not be frozen into readonly tuples — see #511 and #1324)
100
107
  if (isInsideReactHook(ancestors) &&
101
- isArrayWithObjectLiterals(argument.type === 'TSAsExpression'
108
+ isArrayLiteral(argument.type === 'TSAsExpression'
102
109
  ? argument.expression
103
110
  : argument)) {
104
111
  return;
@@ -177,7 +177,11 @@ exports.preferMapOverConditionalDispatch = (0, createRule_1.createRule)({
177
177
  }
178
178
  let text;
179
179
  try {
180
- text = checker.typeToString(widened);
180
+ // Pass the expression's TS node as the enclosing declaration so the
181
+ // printer qualifies namespaced symbols to their scoped name (e.g.
182
+ // `JSX.Element`, not the DOM global `Element`) at the fix site.
183
+ const enclosing = esTreeNodeToTSNodeMap.get(expr);
184
+ text = checker.typeToString(widened, enclosing);
181
185
  }
182
186
  catch {
183
187
  return null;
@@ -192,9 +196,13 @@ exports.preferMapOverConditionalDispatch = (0, createRule_1.createRule)({
192
196
  }
193
197
  return parts.length > 0 ? parts.join(' | ') : null;
194
198
  }
195
- function discriminantTypeText(type) {
199
+ function discriminantTypeText(type, discriminant) {
196
200
  try {
197
- const text = checker.typeToString(type);
201
+ // Pass the discriminant's TS node as the enclosing declaration so the
202
+ // printer qualifies namespaced symbols to their scoped name at the fix
203
+ // site rather than printing an ambiguous short name.
204
+ const enclosing = esTreeNodeToTSNodeMap.get(discriminant);
205
+ const text = checker.typeToString(type, enclosing);
198
206
  return text && text !== 'error' ? text : null;
199
207
  }
200
208
  catch {
@@ -542,7 +550,7 @@ exports.preferMapOverConditionalDispatch = (0, createRule_1.createRule)({
542
550
  if (literalKeys.length === 0 || hasOther) {
543
551
  return null;
544
552
  }
545
- const dText = discriminantTypeText(type);
553
+ const dText = discriminantTypeText(type, discriminant);
546
554
  if (!dText) {
547
555
  return null;
548
556
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.19.19",
3
+ "version": "1.19.21",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,32 @@
1
1
  [
2
+ {
3
+ "version": "1.19.21",
4
+ "date": "2026-07-21T21:24:23.464Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-object-literal-as-const",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1324
11
+ ],
12
+ "summary": "exempt identifier/member array elements returned from hooks (closes #1324)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.19.20",
18
+ "date": "2026-07-18T20:26:20.204Z",
19
+ "rules": [
20
+ {
21
+ "name": "prefer-map-over-conditional-dispatch",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1322
25
+ ],
26
+ "summary": "qualify JSX value/discriminant type via enclosingDeclaration (closes #1322)"
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.19.19",
4
32
  "date": "2026-07-18T17:27:38.611Z",