@blumintinc/eslint-plugin-blumint 1.12.6 → 1.14.0

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.
Files changed (258) hide show
  1. package/README.md +171 -99
  2. package/lib/index.js +300 -17
  3. package/lib/rules/array-methods-this-context.js +20 -2
  4. package/lib/rules/avoid-utils-directory.js +11 -2
  5. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  6. package/lib/rules/consistent-callback-naming.js +1 -1
  7. package/lib/rules/dynamic-https-errors.d.ts +3 -1
  8. package/lib/rules/dynamic-https-errors.js +48 -11
  9. package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
  10. package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +52 -96
  11. package/lib/rules/enforce-assert-throws.d.ts +3 -1
  12. package/lib/rules/enforce-assert-throws.js +493 -51
  13. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
  14. package/lib/rules/enforce-boolean-naming-prefixes.js +820 -39
  15. package/lib/rules/enforce-callable-types.js +4 -4
  16. package/lib/rules/enforce-callback-memo.js +143 -18
  17. package/lib/rules/enforce-centralized-mock-firestore.js +5 -2
  18. package/lib/rules/enforce-console-error.d.ts +3 -0
  19. package/lib/rules/enforce-console-error.js +278 -0
  20. package/lib/rules/enforce-css-media-queries.js +24 -46
  21. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  22. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  23. package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
  24. package/lib/rules/enforce-dynamic-imports.js +1 -1
  25. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  26. package/lib/rules/enforce-early-destructuring.js +980 -0
  27. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  28. package/lib/rules/enforce-empty-object-check.js +502 -0
  29. package/lib/rules/enforce-exported-function-types.js +4 -4
  30. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
  31. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
  32. package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
  33. package/lib/rules/enforce-firestore-facade.js +349 -157
  34. package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
  35. package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
  36. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  37. package/lib/rules/enforce-global-constants.d.ts +4 -1
  38. package/lib/rules/enforce-global-constants.js +240 -12
  39. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  40. package/lib/rules/enforce-memoize-async.d.ts +2 -1
  41. package/lib/rules/enforce-memoize-async.js +92 -27
  42. package/lib/rules/enforce-memoize-getters.d.ts +3 -0
  43. package/lib/rules/enforce-memoize-getters.js +147 -0
  44. package/lib/rules/enforce-microdiff.js +3 -2
  45. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  46. package/lib/rules/enforce-positive-naming.js +9 -7
  47. package/lib/rules/enforce-props-argument-name.d.ts +2 -7
  48. package/lib/rules/enforce-props-argument-name.js +165 -151
  49. package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
  50. package/lib/rules/enforce-props-naming-consistency.js +145 -0
  51. package/lib/rules/enforce-querykey-ts.d.ts +9 -0
  52. package/lib/rules/enforce-querykey-ts.js +274 -0
  53. package/lib/rules/enforce-safe-stringify.js +1 -1
  54. package/lib/rules/enforce-serializable-params.js +3 -3
  55. package/lib/rules/enforce-singular-type-names.js +10 -6
  56. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  57. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  58. package/lib/rules/enforce-storage-context.d.ts +9 -0
  59. package/lib/rules/enforce-storage-context.js +646 -0
  60. package/lib/rules/enforce-timestamp-now.js +23 -7
  61. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  62. package/lib/rules/enforce-transform-memoization.js +416 -0
  63. package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
  64. package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
  65. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  66. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  67. package/lib/rules/enforce-verb-noun-naming.js +90 -14
  68. package/lib/rules/ensure-pointer-events-none.js +28 -2
  69. package/lib/rules/export-if-in-doubt.js +27 -4
  70. package/lib/rules/extract-global-constants.js +92 -53
  71. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
  72. package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
  73. package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
  74. package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
  75. package/lib/rules/flatten-push-calls.d.ts +2 -0
  76. package/lib/rules/flatten-push-calls.js +428 -0
  77. package/lib/rules/generic-starts-with-t.js +8 -2
  78. package/lib/rules/global-const-style.js +50 -19
  79. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  80. package/lib/rules/jsdoc-above-field.js +208 -0
  81. package/lib/rules/key-only-outermost-element.js +21 -3
  82. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  83. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  84. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  85. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  86. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  87. package/lib/rules/memo-nested-react-components.js +410 -0
  88. package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
  89. package/lib/rules/memoize-root-level-hocs.js +242 -0
  90. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  91. package/lib/rules/no-always-true-false-conditions.js +68 -8
  92. package/lib/rules/no-array-length-in-deps.d.ts +12 -0
  93. package/lib/rules/no-array-length-in-deps.js +314 -0
  94. package/lib/rules/no-async-array-filter.js +5 -2
  95. package/lib/rules/no-async-foreach.js +210 -12
  96. package/lib/rules/no-circular-references.js +18 -17
  97. package/lib/rules/no-class-instance-destructuring.js +55 -12
  98. package/lib/rules/no-complex-cloud-params.js +22 -5
  99. package/lib/rules/no-compositing-layer-props.js +6 -4
  100. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  101. package/lib/rules/no-console-error.d.ts +9 -0
  102. package/lib/rules/no-console-error.js +525 -0
  103. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  104. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  105. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  106. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  107. package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
  108. package/lib/rules/no-entire-object-hook-deps.js +320 -68
  109. package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
  110. package/lib/rules/no-excessive-parent-chain.js +273 -0
  111. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  112. package/lib/rules/no-explicit-return-type.js +215 -26
  113. package/lib/rules/no-filter-without-return.js +5 -1
  114. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  115. package/lib/rules/no-firestore-jest-mock.js +126 -8
  116. package/lib/rules/no-firestore-object-arrays.js +398 -28
  117. package/lib/rules/no-handler-suffix.d.ts +12 -0
  118. package/lib/rules/no-handler-suffix.js +305 -0
  119. package/lib/rules/no-hungarian.js +14 -14
  120. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  121. package/lib/rules/no-inline-component-prop.js +456 -0
  122. package/lib/rules/no-jsx-in-hooks.js +6 -1
  123. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  124. package/lib/rules/no-margin-properties.d.ts +7 -1
  125. package/lib/rules/no-margin-properties.js +22 -9
  126. package/lib/rules/no-memoize-on-static.js +9 -1
  127. package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
  128. package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
  129. package/lib/rules/no-misused-switch-case.js +22 -1
  130. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  131. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  132. package/lib/rules/no-mock-firebase-admin.js +5 -2
  133. package/lib/rules/no-object-values-on-strings.js +10 -2
  134. package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
  135. package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
  136. package/lib/rules/no-passthrough-getters.d.ts +4 -0
  137. package/lib/rules/no-passthrough-getters.js +206 -0
  138. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  139. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  140. package/lib/rules/no-redundant-param-types.js +16 -6
  141. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  142. package/lib/rules/no-redundant-this-params.js +459 -0
  143. package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
  144. package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
  145. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  146. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  147. package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
  148. package/lib/rules/no-restricted-properties-fix.js +173 -0
  149. package/lib/rules/no-separate-loading-state.d.ts +8 -0
  150. package/lib/rules/no-separate-loading-state.js +133 -0
  151. package/lib/rules/no-stale-state-across-await.d.ts +1 -0
  152. package/lib/rules/no-stale-state-across-await.js +185 -0
  153. package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
  154. package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
  155. package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
  156. package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
  157. package/lib/rules/no-type-assertion-returns.js +49 -49
  158. package/lib/rules/no-undefined-null-passthrough.js +217 -40
  159. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  160. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  161. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  162. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  163. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  164. package/lib/rules/no-unnecessary-verb-suffix.js +2 -3
  165. package/lib/rules/no-unpinned-dependencies.js +36 -5
  166. package/lib/rules/no-unsafe-firestore-spread.js +8 -5
  167. package/lib/rules/no-unused-props.d.ts +5 -2
  168. package/lib/rules/no-unused-props.js +451 -112
  169. package/lib/rules/no-unused-usestate.js +6 -2
  170. package/lib/rules/no-useless-fragment.js +28 -2
  171. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  172. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  173. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  174. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  175. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  176. package/lib/rules/omit-index-html.d.ts +2 -1
  177. package/lib/rules/omit-index-html.js +62 -7
  178. package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
  179. package/lib/rules/optimize-object-boolean-conditions.js +236 -0
  180. package/lib/rules/parallelize-async-operations.d.ts +8 -0
  181. package/lib/rules/parallelize-async-operations.js +394 -0
  182. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  183. package/lib/rules/prefer-batch-operations.js +228 -35
  184. package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
  185. package/lib/rules/prefer-clone-deep.js +3 -3
  186. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  187. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  188. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  189. package/lib/rules/prefer-docsetter-setall.js +243 -0
  190. package/lib/rules/prefer-document-flattening.d.ts +4 -0
  191. package/lib/rules/prefer-document-flattening.js +239 -0
  192. package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
  193. package/lib/rules/prefer-field-paths-in-transforms.js +251 -0
  194. package/lib/rules/prefer-fragment-component.js +4 -4
  195. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  196. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  197. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  198. package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
  199. package/lib/rules/prefer-global-router-state-key.js +305 -31
  200. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  201. package/lib/rules/prefer-memoized-props.js +445 -0
  202. package/lib/rules/prefer-next-dynamic.d.ts +9 -0
  203. package/lib/rules/prefer-next-dynamic.js +357 -0
  204. package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
  205. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
  206. package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
  207. package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
  208. package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
  209. package/lib/rules/prefer-params-over-parent-id.js +277 -0
  210. package/lib/rules/prefer-settings-object.js +23 -15
  211. package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
  212. package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
  213. package/lib/rules/prefer-type-over-interface.js +7 -2
  214. package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
  215. package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
  216. package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
  217. package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
  218. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
  219. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
  220. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  221. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  222. package/lib/rules/prevent-children-clobber.js +536 -0
  223. package/lib/rules/react-memoize-literals.d.ts +4 -0
  224. package/lib/rules/react-memoize-literals.js +495 -0
  225. package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
  226. package/lib/rules/react-usememo-should-be-component.js +144 -78
  227. package/lib/rules/require-dynamic-firebase-imports.js +2 -1
  228. package/lib/rules/require-hooks-default-params.js +17 -16
  229. package/lib/rules/require-https-error-cause.d.ts +4 -0
  230. package/lib/rules/require-https-error-cause.js +136 -0
  231. package/lib/rules/require-https-error.js +43 -21
  232. package/lib/rules/require-image-optimized.js +2 -2
  233. package/lib/rules/require-memo.js +12 -2
  234. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  235. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  236. package/lib/rules/require-usememo-object-literals.js +9 -4
  237. package/lib/rules/semantic-function-prefixes.js +38 -18
  238. package/lib/rules/sync-onwrite-name-func.js +5 -1
  239. package/lib/rules/test-file-location-enforcement.d.ts +1 -0
  240. package/lib/rules/test-file-location-enforcement.js +57 -0
  241. package/lib/rules/use-custom-link.js +6 -1
  242. package/lib/rules/use-custom-router.js +6 -1
  243. package/lib/rules/use-latest-callback.d.ts +3 -0
  244. package/lib/rules/use-latest-callback.js +271 -0
  245. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  246. package/lib/rules/vertically-group-related-functions.js +480 -0
  247. package/lib/utils/ASTHelpers.d.ts +13 -1
  248. package/lib/utils/ASTHelpers.js +34 -0
  249. package/lib/utils/createRule.js +1 -1
  250. package/lib/utils/getMethodName.d.ts +27 -0
  251. package/lib/utils/getMethodName.js +35 -0
  252. package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
  253. package/lib/utils/ruleTester.d.ts +1 -0
  254. package/lib/utils/ruleTester.js +4 -1
  255. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  256. package/lib/utils/tsTypeClassifier.js +149 -0
  257. package/package.json +23 -14
  258. package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
@@ -4,81 +4,355 @@ exports.preferGlobalRouterStateKey = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
6
  /**
7
- * Rule to enforce best practices for the `key` parameter in `useRouterState` hook calls.
8
- * Encourages type safety and maintainability by using global constants or type-safe functions.
7
+ * Rule to enforce the use of centralized router state key constants imported from
8
+ * `src/util/routing/queryKeys.ts` instead of arbitrary string literals when calling
9
+ * router methods that accept key parameters.
9
10
  */
10
11
  exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
11
12
  name: 'prefer-global-router-state-key',
12
13
  meta: {
13
- type: 'suggestion',
14
+ type: 'problem',
14
15
  docs: {
15
- description: 'Enforce using global constants or type-safe functions for useRouterState key parameter',
16
- recommended: 'warn',
16
+ description: 'Enforce using centralized router state key constants from queryKeys.ts for useRouterState key parameter',
17
+ recommended: 'error',
17
18
  },
19
+ fixable: 'code',
18
20
  schema: [],
19
21
  messages: {
20
- preferGlobalRouterStateKey: 'Prefer using a global constant or type-safe function for useRouterState key parameter instead of string literals',
22
+ preferGlobalRouterStateKey: 'Router state key {{keyValue}} is a string literal. String literals bypass the shared queryKeys.ts QUERY_KEY_* constants, which leads to duplicate router cache entries and makes allowed keys hard to discover. Import the corresponding QUERY_KEY_* constant from "@/util/routing/queryKeys" (or its approved re-export) and pass that to useRouterState instead.',
23
+ invalidQueryKeySource: 'Router state key variable "{{variableName}}" is not sourced from queryKeys.ts. useRouterState keys must come from QUERY_KEY_* exports so routing cache keys stay stable and traceable. Import the matching constant from "@/util/routing/queryKeys" (or its approved re-export) and use that value here instead of {{variableName}}.',
21
24
  },
22
25
  },
23
26
  defaultOptions: [],
24
27
  create(context) {
28
+ const sourceCode = context.sourceCode;
29
+ // Prevent duplicate import insertions when multiple fixes target the same query key constant.
30
+ const scheduledQueryKeyNamedImports = new Set();
31
+ // Track imports from queryKeys.ts
32
+ const queryKeyImports = new Map();
33
+ // Track namespace imports (import * as QueryKeys from ...)
34
+ const namespaceImports = new Map();
35
+ // Track default imports (import queryKeys from ...)
36
+ const defaultImports = new Map();
37
+ // Track re-exports and variable assignments
38
+ const variableAssignments = new Map();
39
+ const validQueryKeySources = new Set([
40
+ 'util/routing/queryKeys',
41
+ 'constants',
42
+ 'constants/index',
43
+ ]);
25
44
  /**
26
- * Checks if a node contains a string literal that should be reported
45
+ * Check if a source path refers to queryKeys.ts or re-exports from it
27
46
  */
28
- function containsStringLiteral(node) {
47
+ function isQueryKeysSource(source) {
48
+ const normalized = source
49
+ .replace(/^@\/|^src\//, '')
50
+ .replace(/^(\.\/|\.\.\/)+/, '');
51
+ return (validQueryKeySources.has(normalized) ||
52
+ normalized.endsWith('util/routing/queryKeys'));
53
+ }
54
+ /**
55
+ * Check if an identifier is a valid QUERY_KEY constant
56
+ */
57
+ function isValidQueryKeyConstant(name) {
58
+ return name.startsWith('QUERY_KEY_');
59
+ }
60
+ /**
61
+ * Check if a node represents a valid query key usage
62
+ */
63
+ function isValidQueryKeyUsage(node) {
64
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
65
+ // Check direct imports
66
+ const importInfo = queryKeyImports.get(node.name);
67
+ if (importInfo && isQueryKeysSource(importInfo.source)) {
68
+ return isValidQueryKeyConstant(importInfo.imported);
69
+ }
70
+ // Check if it's a variable derived from a query key constant
71
+ const assignment = variableAssignments.get(node.name);
72
+ if (assignment) {
73
+ return isValidQueryKeyUsage(assignment);
74
+ }
75
+ }
76
+ // Allow member expressions accessing query key constants
77
+ if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
78
+ if (node.object.type === utils_1.AST_NODE_TYPES.Identifier) {
79
+ // Check namespace imports (QueryKeys.QUERY_KEY_USER)
80
+ const namespaceSource = namespaceImports.get(node.object.name);
81
+ if (namespaceSource && isQueryKeysSource(namespaceSource)) {
82
+ if (node.property.type === utils_1.AST_NODE_TYPES.Identifier) {
83
+ return isValidQueryKeyConstant(node.property.name);
84
+ }
85
+ return false; // Invalid property access on namespace import
86
+ }
87
+ // Check default imports (queryKeys.QUERY_KEY_USER)
88
+ const defaultSource = defaultImports.get(node.object.name);
89
+ if (defaultSource && isQueryKeysSource(defaultSource)) {
90
+ return true; // Allow any property access on default imports
91
+ }
92
+ // Check regular imports
93
+ const importInfo = queryKeyImports.get(node.object.name);
94
+ if (importInfo && isQueryKeysSource(importInfo.source)) {
95
+ return true;
96
+ }
97
+ }
98
+ }
99
+ // Allow template literals if they use valid query keys
100
+ if (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
101
+ if (node.expressions.length > 0) {
102
+ return node.expressions.some((expr) => isValidQueryKeyUsage(expr));
103
+ }
104
+ // Template literals with no expressions are just static strings
105
+ return false;
106
+ }
107
+ // Allow binary expressions if they use valid query keys
108
+ if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
109
+ node.operator === '+') {
110
+ return (isValidQueryKeyUsage(node.left) || isValidQueryKeyUsage(node.right));
111
+ }
112
+ // Allow conditional expressions if both branches use valid query keys
113
+ if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
114
+ return (isValidQueryKeyUsage(node.consequent) &&
115
+ isValidQueryKeyUsage(node.alternate));
116
+ }
117
+ // Allow function calls that might return query keys
118
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
119
+ return true; // Permissive for function calls
120
+ }
121
+ // Allow type assertions (key as const)
122
+ if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
123
+ return isValidQueryKeyUsage(node.expression);
124
+ }
125
+ if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
126
+ return false;
127
+ }
128
+ return false;
129
+ }
130
+ /**
131
+ * Check if a node contains string literals that should be reported
132
+ */
133
+ function containsInvalidStringLiteral(node) {
29
134
  // Direct string literal
30
135
  if (node.type === utils_1.AST_NODE_TYPES.Literal &&
31
136
  typeof node.value === 'string') {
32
137
  return true;
33
138
  }
34
- // String concatenation with + operator
139
+ // String concatenation with + operator containing literals
35
140
  if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
36
- node.operator === '+' &&
37
- (containsStringLiteral(node.left) || containsStringLiteral(node.right))) {
38
- return true;
141
+ node.operator === '+') {
142
+ return (containsInvalidStringLiteral(node.left) ||
143
+ containsInvalidStringLiteral(node.right));
39
144
  }
40
145
  // Conditional (ternary) expression with string literals
41
- if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
42
- (containsStringLiteral(node.consequent) ||
43
- containsStringLiteral(node.alternate))) {
44
- return true;
146
+ if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
147
+ return (containsInvalidStringLiteral(node.consequent) ||
148
+ containsInvalidStringLiteral(node.alternate));
45
149
  }
46
- // Template literal with static parts
150
+ // Template literal with static parts (but allow if it uses query key variables)
47
151
  if (node.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
48
- // Only report if there's a meaningful static part in the template
49
- // This allows dynamic templates like `${prefix}-${id}` but catches `static-${id}`
152
+ // If no expressions, it's just a static template literal
153
+ if (node.expressions.length === 0) {
154
+ return true;
155
+ }
50
156
  const hasSignificantStaticPart = node.quasis.some((quasi) => {
51
157
  const content = quasi.value.raw.trim();
52
- // Allow common separators like '-', '_', ':', '/' as they're just joining dynamic parts
53
158
  return content.length > 0 && !/^[-_:/.]+$/.test(content);
54
159
  });
55
- return hasSignificantStaticPart;
160
+ if (hasSignificantStaticPart) {
161
+ return !node.expressions.every((expr) => isValidQueryKeyUsage(expr));
162
+ }
56
163
  }
57
164
  return false;
58
165
  }
166
+ /**
167
+ * Generate auto-fix suggestion for string literals
168
+ */
169
+ function generateAutoFix(keyValue) {
170
+ const normalizedKey = keyValue
171
+ .toUpperCase()
172
+ .replace(/[^A-Z0-9]/g, '_')
173
+ .replace(/_+/g, '_')
174
+ .replace(/^_|_$/g, '');
175
+ return `QUERY_KEY_${normalizedKey}`;
176
+ }
177
+ /**
178
+ * Helper to find a key in an import map based on a predicate
179
+ */
180
+ function findImportKey(importMap, predicate) {
181
+ return Array.from(importMap.entries()).find(([, value]) => predicate(value))?.[0];
182
+ }
183
+ /**
184
+ * Helper to find the last directive prologue in a statement list
185
+ */
186
+ function findLastDirective(body) {
187
+ let lastDirective;
188
+ for (const stmt of body) {
189
+ if (stmt.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
190
+ stmt.expression.type === utils_1.AST_NODE_TYPES.Literal &&
191
+ typeof stmt.expression.value === 'string' &&
192
+ typeof stmt.directive === 'string') {
193
+ lastDirective = stmt;
194
+ continue;
195
+ }
196
+ break;
197
+ }
198
+ return lastDirective;
199
+ }
59
200
  return {
201
+ // Track imports from queryKeys.ts
202
+ ImportDeclaration(node) {
203
+ if (node.importKind === 'type')
204
+ return;
205
+ if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
206
+ typeof node.source.value === 'string') {
207
+ const source = node.source.value;
208
+ if (isQueryKeysSource(source)) {
209
+ node.specifiers.forEach((spec) => {
210
+ if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
211
+ if (spec.importKind === 'type')
212
+ return;
213
+ const imported = spec.imported.name;
214
+ const local = spec.local.name;
215
+ queryKeyImports.set(local, { source, imported });
216
+ }
217
+ else if (spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
218
+ namespaceImports.set(spec.local.name, source);
219
+ }
220
+ else if (spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
221
+ defaultImports.set(spec.local.name, source);
222
+ }
223
+ });
224
+ }
225
+ }
226
+ },
227
+ // Track variable declarations that might derive from query key constants
228
+ VariableDeclarator(node) {
229
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier && node.init) {
230
+ variableAssignments.set(node.id.name, node.init);
231
+ }
232
+ },
233
+ // Track assignment expressions
234
+ AssignmentExpression(node) {
235
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
236
+ node.operator === '=') {
237
+ variableAssignments.set(node.left.name, node.right);
238
+ }
239
+ },
240
+ // Check useRouterState calls
60
241
  CallExpression(node) {
61
242
  // Check if this is a call to useRouterState
62
243
  if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
63
244
  node.callee.name === 'useRouterState') {
64
- // Check if there are arguments
65
245
  if (node.arguments.length > 0) {
66
246
  const firstArg = node.arguments[0];
67
- // Check if the first argument is an object expression
68
247
  if (firstArg.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
69
- // Find the key property in the object
70
248
  const keyProperty = firstArg.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
71
249
  prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
72
250
  prop.key.name === 'key');
73
- // If key property exists, check its value
74
251
  if (keyProperty && keyProperty.value) {
75
252
  const keyValue = keyProperty.value;
76
- // Check for string literals in various contexts
77
- if (containsStringLiteral(keyValue)) {
78
- context.report({
79
- node: keyValue,
80
- messageId: 'preferGlobalRouterStateKey',
81
- });
253
+ if (!isValidQueryKeyUsage(keyValue)) {
254
+ if (containsInvalidStringLiteral(keyValue)) {
255
+ context.report({
256
+ node: keyValue,
257
+ messageId: 'preferGlobalRouterStateKey',
258
+ data: {
259
+ keyValue: sourceCode.getText(keyValue),
260
+ },
261
+ fix(fixer) {
262
+ if (keyValue.type === utils_1.AST_NODE_TYPES.Literal &&
263
+ typeof keyValue.value === 'string') {
264
+ const suggestedConstant = generateAutoFix(keyValue.value);
265
+ if (suggestedConstant) {
266
+ const fixes = [];
267
+ const namespaceAlias = findImportKey(namespaceImports, isQueryKeysSource);
268
+ const defaultAlias = findImportKey(defaultImports, isQueryKeysSource);
269
+ // Check if the constant is already imported (possibly with an alias)
270
+ const existingNamedImport = findImportKey(queryKeyImports, (info) => isQueryKeysSource(info.source) &&
271
+ info.imported === suggestedConstant);
272
+ const localName = existingNamedImport;
273
+ const importAlias = namespaceAlias ?? defaultAlias;
274
+ const formatConstantReference = (alias, constant) => alias ? `${alias}.${constant}` : constant;
275
+ const replacementText = localName
276
+ ? localName
277
+ : formatConstantReference(importAlias, suggestedConstant);
278
+ // 1) Replace the literal with the constant (qualify if alias exists)
279
+ fixes.push(fixer.replaceText(keyValue, replacementText));
280
+ // 2) Ensure an import exists for the suggested constant
281
+ const hasNamespaceOrDefault = Boolean(importAlias);
282
+ if (!existingNamedImport &&
283
+ !hasNamespaceOrDefault) {
284
+ if (scheduledQueryKeyNamedImports.has(suggestedConstant)) {
285
+ return fixes;
286
+ }
287
+ const importText = `import { ${suggestedConstant} } from '@/util/routing/queryKeys';\n`;
288
+ const queryKeysNamedImport = sourceCode.ast.body.find((n) => n.type ===
289
+ utils_1.AST_NODE_TYPES.ImportDeclaration &&
290
+ n.importKind !== 'type' &&
291
+ n.source.type === utils_1.AST_NODE_TYPES.Literal &&
292
+ typeof n.source.value === 'string' &&
293
+ isQueryKeysSource(n.source.value) &&
294
+ n.specifiers.some((s) => s.type ===
295
+ utils_1.AST_NODE_TYPES.ImportSpecifier));
296
+ const sideEffectImport = sourceCode.ast.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
297
+ n.source.type === utils_1.AST_NODE_TYPES.Literal &&
298
+ typeof n.source.value === 'string' &&
299
+ isQueryKeysSource(n.source.value) &&
300
+ n.specifiers.length === 0);
301
+ if (queryKeysNamedImport) {
302
+ const importSpecifiers = queryKeysNamedImport.specifiers.filter((spec) => spec.type ===
303
+ utils_1.AST_NODE_TYPES.ImportSpecifier);
304
+ const lastSpecifier = importSpecifiers[importSpecifiers.length - 1];
305
+ fixes.push(fixer.insertTextAfter(lastSpecifier, `, ${suggestedConstant}`));
306
+ }
307
+ else if (sideEffectImport) {
308
+ fixes.push(fixer.replaceText(sideEffectImport, importText.trimEnd()));
309
+ }
310
+ else {
311
+ const firstImport = sourceCode.ast.body.find((n) => n.type ===
312
+ utils_1.AST_NODE_TYPES.ImportDeclaration &&
313
+ n.importKind !== 'type') ||
314
+ sourceCode.ast.body.find((n) => n.type ===
315
+ utils_1.AST_NODE_TYPES.ImportDeclaration);
316
+ if (firstImport) {
317
+ fixes.push(fixer.insertTextBefore(firstImport, importText));
318
+ }
319
+ else {
320
+ const lastDirective = findLastDirective(sourceCode.ast.body);
321
+ if (lastDirective) {
322
+ fixes.push(fixer.insertTextAfter(lastDirective, `\n${importText}`));
323
+ }
324
+ else {
325
+ fixes.push(fixer.insertTextBeforeRange([0, 0], importText));
326
+ }
327
+ }
328
+ }
329
+ scheduledQueryKeyNamedImports.add(suggestedConstant);
330
+ }
331
+ return fixes;
332
+ }
333
+ }
334
+ return null;
335
+ },
336
+ });
337
+ }
338
+ else if (keyValue.type === utils_1.AST_NODE_TYPES.Identifier) {
339
+ context.report({
340
+ node: keyValue,
341
+ messageId: 'invalidQueryKeySource',
342
+ data: {
343
+ variableName: keyValue.name,
344
+ },
345
+ });
346
+ }
347
+ else if (keyValue.type === utils_1.AST_NODE_TYPES.MemberExpression) {
348
+ context.report({
349
+ node: keyValue,
350
+ messageId: 'invalidQueryKeySource',
351
+ data: {
352
+ variableName: sourceCode.getText(keyValue),
353
+ },
354
+ });
355
+ }
82
356
  }
83
357
  }
84
358
  }
@@ -0,0 +1,3 @@
1
+ type MessageIds = 'memoizeReferenceProp' | 'avoidPrimitiveMemo';
2
+ export declare const preferMemoizedProps: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
3
+ export {};