@blumintinc/eslint-plugin-blumint 1.20.149 → 1.20.150

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
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.149',
226
+ version: '1.20.150',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -748,35 +748,6 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
748
748
  return;
749
749
  checkComponentProps(component.fn);
750
750
  },
751
- // Skip type checking for React components since we handle them separately
752
- 'FunctionDeclaration[id.name=/^[A-Z]/] > TSTypeAnnotation'() { },
753
- 'FunctionDeclaration[id.name=/^[A-Z]/] > TSTypeAnnotation > TSTypeReference'() { },
754
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > TSTypeAnnotation'() { },
755
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > TSTypeAnnotation > TSTypeReference'() { },
756
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation'() { },
757
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation'() { },
758
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference'() { },
759
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference'() { },
760
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > Identifier'() { },
761
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > Identifier'() { },
762
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName'() { },
763
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName'() { },
764
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > Identifier'() { },
765
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > Identifier'() { },
766
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName'() { },
767
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName'() { },
768
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > Identifier'() { },
769
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > Identifier'() { },
770
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
771
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
772
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
773
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
774
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
775
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
776
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
777
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
778
- 'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
779
- 'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
780
751
  };
781
752
  },
782
753
  });
@@ -385,6 +385,16 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
385
385
  * Track variable assignments to detect variables derived from query key constants
386
386
  */
387
387
  const variableAssignments = new Map();
388
+ /**
389
+ * Names whose declaration leaves them holding `undefined` — `let key;` and
390
+ * `let key = undefined;` alike. Both falsy and nullish, which is what makes
391
+ * a later `||=`/`??=` onto one of them assign unconditionally.
392
+ */
393
+ const declaredUndefined = new Set();
394
+ /** Declarations and assignments seen per name, to bound the above to the provable case. */
395
+ const declarationCounts = new Map();
396
+ const assignmentCounts = new Map();
397
+ const isUndefinedIdentifier = (node) => node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === 'undefined';
388
398
  /**
389
399
  * Check if a node represents a valid query key usage
390
400
  */
@@ -631,13 +641,45 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
631
641
  },
632
642
  // Track variable declarations that might derive from query key constants
633
643
  VariableDeclarator(node) {
634
- if (node.id.type === utils_1.AST_NODE_TYPES.Identifier && node.init) {
635
- variableAssignments.set(node.id.name, node.init);
644
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
645
+ return;
646
+ const { name } = node.id;
647
+ declarationCounts.set(name, (declarationCounts.get(name) ?? 0) + 1);
648
+ if (node.init) {
649
+ variableAssignments.set(name, node.init);
650
+ }
651
+ if (!node.init || isUndefinedIdentifier(node.init)) {
652
+ declaredUndefined.add(name);
636
653
  }
637
654
  },
638
655
  AssignmentExpression(node) {
639
- if (node.left.type === utils_1.AST_NODE_TYPES.Identifier && node.right) {
640
- variableAssignments.set(node.left.name, node.right);
656
+ if (node.left.type !== utils_1.AST_NODE_TYPES.Identifier)
657
+ return;
658
+ const { name } = node.left;
659
+ const assignmentCount = (assignmentCounts.get(name) ?? 0) + 1;
660
+ assignmentCounts.set(name, assignmentCount);
661
+ // Only a plain `=` makes the right-hand side the variable's value in
662
+ // general. A compound assignment leaves the prior value reachable
663
+ // (`key ||= K` is the old key OR K), so recording the operand would
664
+ // launder an unapproved key into an approved one.
665
+ if (node.operator === '=') {
666
+ variableAssignments.set(name, node.right);
667
+ return;
668
+ }
669
+ // The exception is a `||=`/`??=` onto a variable still holding
670
+ // `undefined`, which is both falsy and nullish: the assignment always
671
+ // happens, so the operand IS the value. A single declaration and a
672
+ // single assignment are what keep that provable — a second assignment
673
+ // anywhere puts a prior value back in play. `+=` is excluded because it
674
+ // concatenates onto `undefined` and yields neither operand.
675
+ //
676
+ // The operand is recorded rather than exempted, so it still has to be
677
+ // an approved constant to pass.
678
+ if ((node.operator === '||=' || node.operator === '??=') &&
679
+ declaredUndefined.has(name) &&
680
+ declarationCounts.get(name) === 1 &&
681
+ assignmentCount === 1) {
682
+ variableAssignments.set(name, node.right);
641
683
  }
642
684
  },
643
685
  // Check useRouterState calls
@@ -352,10 +352,6 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
352
352
  checkFunctionReturnType(node);
353
353
  }
354
354
  },
355
- // Check for array methods with type assertions
356
- CallExpression() {
357
- // Skip this check as we'll handle it in the TSAsExpression and TSTypeAssertion handlers
358
- },
359
355
  // Check for type assertions in expressions
360
356
  TSAsExpression(node) {
361
357
  // Allow 'as const' assertions if configured
@@ -122,6 +122,16 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
122
122
  const defaultImports = new Map();
123
123
  // Track re-exports and variable assignments
124
124
  const variableAssignments = new Map();
125
+ /**
126
+ * Names whose declaration leaves them holding `undefined` — `let key;` and
127
+ * `let key = undefined;` alike. Both falsy and nullish, which is what makes
128
+ * a later `||=`/`??=` onto one of them assign unconditionally.
129
+ */
130
+ const declaredUndefined = new Set();
131
+ /** Declarations and assignments seen per name, to bound the above to the provable case. */
132
+ const declarationCounts = new Map();
133
+ const assignmentCounts = new Map();
134
+ const isUndefinedIdentifier = (node) => node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === 'undefined';
125
135
  const validQueryKeySources = new Set([
126
136
  'util/routing/queryKeys',
127
137
  'constants',
@@ -444,15 +454,46 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
444
454
  },
445
455
  // Track variable declarations that might derive from query key constants
446
456
  VariableDeclarator(node) {
447
- if (node.id.type === utils_1.AST_NODE_TYPES.Identifier && node.init) {
448
- variableAssignments.set(node.id.name, node.init);
457
+ if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
458
+ return;
459
+ const { name } = node.id;
460
+ declarationCounts.set(name, (declarationCounts.get(name) ?? 0) + 1);
461
+ if (node.init) {
462
+ variableAssignments.set(name, node.init);
463
+ }
464
+ if (!node.init || isUndefinedIdentifier(node.init)) {
465
+ declaredUndefined.add(name);
449
466
  }
450
467
  },
451
468
  // Track assignment expressions
452
469
  AssignmentExpression(node) {
453
- if (node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
454
- node.operator === '=') {
455
- variableAssignments.set(node.left.name, node.right);
470
+ if (node.left.type !== utils_1.AST_NODE_TYPES.Identifier)
471
+ return;
472
+ const { name } = node.left;
473
+ const assignmentCount = (assignmentCounts.get(name) ?? 0) + 1;
474
+ assignmentCounts.set(name, assignmentCount);
475
+ // Only a plain `=` makes the right-hand side the variable's value in
476
+ // general. A compound assignment leaves the prior value reachable
477
+ // (`key ||= K` is the old key OR K), so recording the operand would
478
+ // launder an unapproved key into an approved one.
479
+ if (node.operator === '=') {
480
+ variableAssignments.set(name, node.right);
481
+ return;
482
+ }
483
+ // The exception is a `||=`/`??=` onto a variable still holding
484
+ // `undefined`, which is both falsy and nullish: the assignment always
485
+ // happens, so the operand IS the value. A single declaration and a
486
+ // single assignment are what keep that provable — a second assignment
487
+ // anywhere puts a prior value back in play. `+=` is excluded because it
488
+ // concatenates onto `undefined` and yields neither operand.
489
+ //
490
+ // The operand is recorded rather than exempted, so it still has to be
491
+ // an approved constant to pass.
492
+ if ((node.operator === '||=' || node.operator === '??=') &&
493
+ declaredUndefined.has(name) &&
494
+ declarationCounts.get(name) === 1 &&
495
+ assignmentCount === 1) {
496
+ variableAssignments.set(name, node.right);
456
497
  }
457
498
  },
458
499
  // Check useRouterState calls
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.149",
3
+ "version": "1.20.150",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,43 @@
1
1
  [
2
+ {
3
+ "version": "1.20.150",
4
+ "date": "2026-08-13T19:12:01.840Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-exported-function-types",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2000
11
+ ],
12
+ "summary": "drop 28 inert empty-bodied selectors (closes #2000)"
13
+ },
14
+ {
15
+ "name": "enforce-querykey-ts",
16
+ "changeType": "fix",
17
+ "issues": [
18
+ 1999,
19
+ 2001
20
+ ],
21
+ "summary": "follow a `||=`/`??=` onto an undefined key (#2001); guard variable tracking on a plain `=` assignment (closes #1999)"
22
+ },
23
+ {
24
+ "name": "no-type-assertion-returns",
25
+ "changeType": "fix",
26
+ "issues": [
27
+ 2002
28
+ ],
29
+ "summary": "drop the inert empty CallExpression handler (closes #2002)"
30
+ },
31
+ {
32
+ "name": "prefer-global-router-state-key",
33
+ "changeType": "fix",
34
+ "issues": [
35
+ 2001
36
+ ],
37
+ "summary": "follow a `||=`/`??=` onto an undefined key (closes #2001)"
38
+ }
39
+ ]
40
+ },
2
41
  {
3
42
  "version": "1.20.149",
4
43
  "date": "2026-08-13T16:22:27.196Z",