@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
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceFirestoreSetMerge = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
7
  exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
7
8
  name: 'enforce-firestore-set-merge',
8
9
  meta: {
@@ -93,9 +94,7 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
93
94
  return true;
94
95
  }
95
96
  // Check if it's a Firestore document reference by looking at imports
96
- const program = context
97
- .getAncestors()
98
- .find((node) => node.type === utils_1.AST_NODE_TYPES.Program);
97
+ const program = ASTHelpers_1.ASTHelpers.getAncestors(context, node).find((node) => node.type === utils_1.AST_NODE_TYPES.Program);
99
98
  if (program) {
100
99
  for (const node of program.body) {
101
100
  if (node.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
@@ -212,7 +211,7 @@ exports.enforceFirestoreSetMerge = (0, createRule_1.createRule)({
212
211
  node,
213
212
  messageId: 'preferSetMerge',
214
213
  fix(fixer) {
215
- const newText = convertUpdateToSetMerge(node, context.getSourceCode());
214
+ const newText = convertUpdateToSetMerge(node, context.sourceCode);
216
215
  return fixer.replaceText(node, newText);
217
216
  },
218
217
  });
@@ -1 +1,4 @@
1
- export declare const enforceGlobalConstants: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useGlobalConstant", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'useGlobalConstant' | 'extractDefaultToGlobalConstant';
3
+ export declare const enforceGlobalConstants: TSESLint.RuleModule<MessageIds, [], TSESLint.RuleListener>;
4
+ export {};
@@ -3,48 +3,250 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceGlobalConstants = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const ASTHelpers_1 = require("../utils/ASTHelpers");
6
7
  exports.enforceGlobalConstants = (0, createRule_1.createRule)({
7
8
  name: 'enforce-global-constants',
8
9
  meta: {
9
10
  type: 'suggestion',
10
11
  docs: {
11
- description: 'Enforce using global static constants instead of useMemo with empty dependency arrays for object literals',
12
+ description: 'Enforce using global static constants instead of useMemo with empty dependency arrays for object literals, and extract inline destructuring defaults in React components/hooks to global constants',
12
13
  recommended: 'error',
13
14
  },
15
+ fixable: 'code',
14
16
  schema: [],
15
17
  messages: {
16
18
  useGlobalConstant: 'Use a global static constant instead of useMemo with an empty dependency array for object literals',
19
+ extractDefaultToGlobalConstant: 'Extract inline default value to a module-level constant for stable reference',
17
20
  },
18
21
  },
19
22
  defaultOptions: [],
20
23
  create(context) {
24
+ const sourceCode = context.sourceCode;
25
+ function isHookName(name) {
26
+ return /^use[A-Z]/.test(name);
27
+ }
28
+ function isComponentOrHookFunction(fn) {
29
+ if (fn.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
30
+ const n = fn.id?.name ?? '';
31
+ return /^[A-Z]/.test(n) || isHookName(n);
32
+ }
33
+ const parent = fn.parent;
34
+ if (parent &&
35
+ parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
36
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
37
+ const n = parent.id.name;
38
+ return /^[A-Z]/.test(n) || isHookName(n);
39
+ }
40
+ return false;
41
+ }
42
+ function getEnclosingFunction(node) {
43
+ let current = node;
44
+ while (current) {
45
+ if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
46
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
47
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
48
+ return current;
49
+ }
50
+ current = current.parent;
51
+ }
52
+ return null;
53
+ }
54
+ function toUpperSnakeCase(name) {
55
+ return name
56
+ .replace(/([A-Z])/g, '_$1')
57
+ .toUpperCase()
58
+ .replace(/^_/, '');
59
+ }
60
+ function collectAssignmentDefaultsFromPattern(pattern) {
61
+ const results = [];
62
+ const visitPattern = (p) => {
63
+ if (p.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
64
+ for (const prop of p.properties) {
65
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
66
+ const value = prop.value;
67
+ if (value &&
68
+ value.type ===
69
+ utils_1.AST_NODE_TYPES.AssignmentPattern) {
70
+ const assign = value;
71
+ const left = assign.left;
72
+ if (left.type === utils_1.AST_NODE_TYPES.Identifier) {
73
+ results.push({ assignment: assign, localName: left.name });
74
+ }
75
+ if (left.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
76
+ left.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
77
+ // Nested pattern on the left of an assignment; uncommon, ignore naming
78
+ }
79
+ }
80
+ else if (value &&
81
+ value
82
+ .type &&
83
+ (value
84
+ .type === utils_1.AST_NODE_TYPES.ObjectPattern ||
85
+ value
86
+ .type === utils_1.AST_NODE_TYPES.ArrayPattern)) {
87
+ visitPattern(value);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ else if (p.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
93
+ for (const elem of p.elements) {
94
+ if (!elem)
95
+ continue;
96
+ if (elem.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
97
+ const left = elem.left;
98
+ if (left.type === utils_1.AST_NODE_TYPES.Identifier) {
99
+ results.push({ assignment: elem, localName: left.name });
100
+ }
101
+ }
102
+ else if (elem.type === utils_1.AST_NODE_TYPES.ArrayPattern ||
103
+ elem.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
104
+ visitPattern(elem);
105
+ }
106
+ }
107
+ }
108
+ else if (p.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
109
+ const left = p.left;
110
+ if (left.type === utils_1.AST_NODE_TYPES.Identifier) {
111
+ results.push({ assignment: p, localName: left.name });
112
+ }
113
+ }
114
+ };
115
+ visitPattern(pattern);
116
+ return results;
117
+ }
118
+ function hasIdentifiers(node) {
119
+ return !!node && ASTHelpers_1.ASTHelpers.declarationIncludesIdentifier(node);
120
+ }
121
+ function alreadyHasConst(program, constName) {
122
+ for (const stmt of program.body) {
123
+ if (stmt.type === utils_1.AST_NODE_TYPES.VariableDeclaration &&
124
+ stmt.kind === 'const') {
125
+ for (const d of stmt.declarations) {
126
+ if (d.id.type === utils_1.AST_NODE_TYPES.Identifier &&
127
+ d.id.name === constName) {
128
+ return true;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+ function buildConstDeclarationLine(constName, initText) {
136
+ const needsAsConst = /^(?:true|false|-?\d|\[|\{|[`'"])/.test(initText) &&
137
+ !/\bas const\b/.test(initText);
138
+ const initializer = needsAsConst ? `${initText} as const` : initText;
139
+ return `const ${constName} = ${initializer};`;
140
+ }
141
+ function reportStaticDefaults(patterns, enclosingFn, nodeForReport) {
142
+ if (!enclosingFn || !isComponentOrHookFunction(enclosingFn))
143
+ return;
144
+ const defaults = [];
145
+ for (const pattern of patterns) {
146
+ defaults.push(...collectAssignmentDefaultsFromPattern(pattern));
147
+ }
148
+ if (defaults.length === 0)
149
+ return;
150
+ const staticDefaults = defaults.filter((def) => {
151
+ const right = def.assignment.right;
152
+ return right && !hasIdentifiers(right);
153
+ });
154
+ if (staticDefaults.length === 0)
155
+ return;
156
+ context.report({
157
+ node: nodeForReport,
158
+ messageId: 'extractDefaultToGlobalConstant',
159
+ fix(fixer) {
160
+ const fixes = [];
161
+ const programNode = sourceCode.ast;
162
+ const declLines = [];
163
+ for (const def of staticDefaults) {
164
+ const { assignment, localName } = def;
165
+ const right = assignment.right;
166
+ const rightText = sourceCode.getText(right);
167
+ const constName = `DEFAULT_${toUpperSnakeCase(localName)}`;
168
+ if (!alreadyHasConst(programNode, constName)) {
169
+ declLines.push(buildConstDeclarationLine(constName, rightText));
170
+ }
171
+ fixes.push(fixer.replaceText(right, constName));
172
+ }
173
+ if (declLines.length > 0) {
174
+ const program = sourceCode.ast;
175
+ const constSection = declLines.length === 1
176
+ ? declLines[0]
177
+ : `${declLines[0]}\n\n${declLines.slice(1).join('\n')}`;
178
+ const text = sourceCode.text;
179
+ const findNextNonWhitespace = (start) => {
180
+ let idx = start;
181
+ while (idx < text.length && /\s/.test(text[idx])) {
182
+ idx += 1;
183
+ }
184
+ return idx;
185
+ };
186
+ const buildBlock = (extraSpacing, insertPos, nextPos) => {
187
+ const whitespace = text.slice(insertPos, nextPos);
188
+ const lastNewline = whitespace.lastIndexOf('\n');
189
+ const nextIndentRaw = lastNewline === -1
190
+ ? ''
191
+ : whitespace.slice(lastNewline + 1).replace(/[^\t ]/g, '');
192
+ const separator = extraSpacing ? '\n\n\n' : '\n\n';
193
+ const nextIndent = extraSpacing ? nextIndentRaw : '';
194
+ return `\n${constSection}${separator}${nextIndent}`;
195
+ };
196
+ const imports = program.body.filter((s) => s.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
197
+ if (imports.length > 0) {
198
+ const lastImport = imports[imports.length - 1];
199
+ const insertPos = lastImport.range[1];
200
+ const nextPos = findNextNonWhitespace(insertPos);
201
+ fixes.push(fixer.replaceTextRange([insertPos, nextPos], buildBlock(false, insertPos, nextPos)));
202
+ }
203
+ else {
204
+ const body = program.body;
205
+ let insertPos = 0;
206
+ let afterDirectiveIdx = -1;
207
+ for (let i = 0; i < body.length; i++) {
208
+ const stmt = body[i];
209
+ if (stmt.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
210
+ stmt.expression.type === utils_1.AST_NODE_TYPES.Literal &&
211
+ typeof stmt.expression.value === 'string') {
212
+ afterDirectiveIdx = i;
213
+ }
214
+ else {
215
+ break;
216
+ }
217
+ }
218
+ if (afterDirectiveIdx >= 0) {
219
+ insertPos = body[afterDirectiveIdx].range[1];
220
+ }
221
+ const nextPos = findNextNonWhitespace(insertPos);
222
+ fixes.push(fixer.replaceTextRange([insertPos, nextPos], buildBlock(afterDirectiveIdx < 0, insertPos, nextPos)));
223
+ }
224
+ }
225
+ return fixes;
226
+ },
227
+ });
228
+ }
21
229
  return {
22
230
  CallExpression(node) {
23
- // Check if it's a useMemo call
24
231
  if (node.callee.type !== utils_1.AST_NODE_TYPES.Identifier ||
25
232
  node.callee.name !== 'useMemo') {
26
233
  return;
27
234
  }
28
- // Check if it has exactly two arguments
29
235
  if (node.arguments.length !== 2) {
30
236
  return;
31
237
  }
32
- // Check if the second argument is an empty array
33
238
  const depsArray = node.arguments[1];
34
239
  if (depsArray.type !== utils_1.AST_NODE_TYPES.ArrayExpression ||
35
240
  depsArray.elements.length !== 0) {
36
241
  return;
37
242
  }
38
- // Check if the first argument is an arrow function
39
243
  const callback = node.arguments[0];
40
- if (callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
244
+ if (callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
245
+ callback.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
41
246
  return;
42
247
  }
43
- // Check if the arrow function body is a block statement with a return statement
44
- // or a direct expression (implicit return)
45
248
  let returnValue = null;
46
249
  if (callback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
47
- // If it's a block, find the return statement
48
250
  const returnStatement = callback.body.body.find((stmt) => stmt.type === utils_1.AST_NODE_TYPES.ReturnStatement);
49
251
  if (!returnStatement || !returnStatement.argument) {
50
252
  return;
@@ -52,15 +254,12 @@ exports.enforceGlobalConstants = (0, createRule_1.createRule)({
52
254
  returnValue = returnStatement.argument;
53
255
  }
54
256
  else {
55
- // If it's an expression (implicit return)
56
257
  returnValue = callback.body;
57
258
  }
58
- // Handle 'as const' type assertions
59
259
  let actualReturnValue = returnValue;
60
260
  if (returnValue.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
61
261
  actualReturnValue = returnValue.expression;
62
262
  }
63
- // Check if the return value is an object literal or an array of object literals
64
263
  if (actualReturnValue.type === utils_1.AST_NODE_TYPES.ObjectExpression ||
65
264
  (actualReturnValue.type === utils_1.AST_NODE_TYPES.ArrayExpression &&
66
265
  actualReturnValue.elements.some((element) => element !== null &&
@@ -71,6 +270,35 @@ exports.enforceGlobalConstants = (0, createRule_1.createRule)({
71
270
  });
72
271
  }
73
272
  },
273
+ VariableDeclaration(node) {
274
+ const relevantDeclarators = node.declarations.filter((d) => d.id.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
275
+ d.id.type === utils_1.AST_NODE_TYPES.ArrayPattern);
276
+ if (relevantDeclarators.length === 0)
277
+ return;
278
+ const enclosingFn = getEnclosingFunction(node);
279
+ reportStaticDefaults(relevantDeclarators.map((d) => d.id), enclosingFn, node);
280
+ },
281
+ FunctionDeclaration(node) {
282
+ const patterns = node.params.filter((p) => p.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
283
+ p.type === utils_1.AST_NODE_TYPES.ArrayPattern);
284
+ if (patterns.length === 0)
285
+ return;
286
+ reportStaticDefaults(patterns, node, node);
287
+ },
288
+ FunctionExpression(node) {
289
+ const patterns = node.params.filter((p) => p.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
290
+ p.type === utils_1.AST_NODE_TYPES.ArrayPattern);
291
+ if (patterns.length === 0)
292
+ return;
293
+ reportStaticDefaults(patterns, node, node);
294
+ },
295
+ ArrowFunctionExpression(node) {
296
+ const patterns = node.params.filter((p) => p.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
297
+ p.type === utils_1.AST_NODE_TYPES.ArrayPattern);
298
+ if (patterns.length === 0)
299
+ return;
300
+ reportStaticDefaults(patterns, node, node);
301
+ },
74
302
  };
75
303
  },
76
304
  });
@@ -5,8 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.enforceIdentifiableFirestoreType = void 0;
7
7
  const utils_1 = require("@typescript-eslint/utils");
8
- const createRule_1 = require("../utils/createRule");
9
8
  const path_1 = __importDefault(require("path"));
9
+ const createRule_1 = require("../utils/createRule");
10
+ const TRANSPARENT_TYPE_NAMES = new Set(['Readonly', 'Resolve']);
10
11
  exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
11
12
  name: 'enforce-identifiable-firestore-type',
12
13
  meta: {
@@ -63,94 +64,143 @@ exports.enforceIdentifiableFirestoreType = (0, createRule_1.createRule)({
63
64
  TSTypeAliasDeclaration(node) {
64
65
  if (node.id.name === folderName) {
65
66
  hasExpectedType = true;
66
- // Find Identifiable in type's dependencies
67
+ const findTypeAliasAnnotation = (typeName) => {
68
+ let scope = context.getScope();
69
+ while (scope) {
70
+ const variable = scope.variables.find((variableNode) => variableNode.name === typeName);
71
+ if (variable) {
72
+ const definition = variable.defs.find((definition) => definition.node.type ===
73
+ utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
74
+ if (definition &&
75
+ definition.node.type ===
76
+ utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration &&
77
+ definition.node.typeAnnotation) {
78
+ return definition.node.typeAnnotation;
79
+ }
80
+ }
81
+ scope = scope.upper;
82
+ }
83
+ return null;
84
+ };
85
+ const isParenthesizedType = (node) => node?.type === 'TSParenthesizedType';
86
+ const isReadonlyTypeOperator = (node) => node?.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
87
+ node.operator === 'readonly';
88
+ const unwrapTransparentType = (typeNode) => {
89
+ let current = typeNode;
90
+ while (current) {
91
+ if (isParenthesizedType(current)) {
92
+ const parenthesized = current;
93
+ current = parenthesized.typeAnnotation ?? null;
94
+ continue;
95
+ }
96
+ if (isReadonlyTypeOperator(current)) {
97
+ current = current.typeAnnotation ?? null;
98
+ continue;
99
+ }
100
+ break;
101
+ }
102
+ return current ?? null;
103
+ };
67
104
  const findIdentifiable = (type, checkedTypes = new Set()) => {
68
- if (!type)
105
+ const resolvedType = unwrapTransparentType(type);
106
+ if (!resolvedType) {
69
107
  return false;
70
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
71
- type.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
72
- const typeName = type.typeName.name;
108
+ }
109
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
110
+ resolvedType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
111
+ const typeName = resolvedType.typeName.name;
73
112
  if (typeName === 'Identifiable') {
74
113
  return true;
75
114
  }
76
- if (!checkedTypes.has(typeName)) {
77
- checkedTypes.add(typeName);
78
- // Look for the type in all scopes
79
- const scope = context.getScope();
80
- const variable = scope.variables.find((v) => v.name === typeName);
81
- if (variable) {
82
- const def = variable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
83
- if (def &&
84
- 'typeAnnotation' in def.node &&
85
- def.node.typeAnnotation) {
86
- return findIdentifiable(def.node.typeAnnotation, checkedTypes);
87
- }
88
- }
89
- // Try looking in the parent scope
90
- if (scope.upper) {
91
- const parentVariable = scope.upper.variables.find((v) => v.name === typeName);
92
- if (parentVariable) {
93
- const def = parentVariable.defs.find((d) => d.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration);
94
- if (def &&
95
- 'typeAnnotation' in def.node &&
96
- def.node.typeAnnotation) {
97
- return findIdentifiable(def.node.typeAnnotation, checkedTypes);
98
- }
99
- }
100
- }
115
+ if (TRANSPARENT_TYPE_NAMES.has(typeName) &&
116
+ resolvedType.typeParameters?.params?.some((param) => findIdentifiable(param, checkedTypes))) {
117
+ return true;
118
+ }
119
+ if (checkedTypes.has(typeName)) {
120
+ return false;
101
121
  }
122
+ checkedTypes.add(typeName);
123
+ const aliasAnnotation = findTypeAliasAnnotation(typeName);
124
+ return findIdentifiable(aliasAnnotation, checkedTypes);
102
125
  }
103
- else if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
104
- // For intersection types, check each part
105
- return type.types.some((part) => findIdentifiable(part, checkedTypes));
126
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
127
+ return resolvedType.types.some((part) => findIdentifiable(part, new Set(checkedTypes)));
106
128
  }
107
129
  return false;
108
130
  };
109
- // Check if type extends Identifiable
110
- const checkIdentifiableExtension = (type) => {
111
- if (!type)
112
- return false;
113
- return findIdentifiable(type);
114
- };
115
131
  // Check if type has id: string field
116
- const checkIdField = (type) => {
117
- // Check for id: string field in type literal
118
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
119
- return type.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
132
+ const checkIdField = (type, visitedTypes = new Set()) => {
133
+ const resolvedType = unwrapTransparentType(type);
134
+ if (!resolvedType) {
135
+ return false;
136
+ }
137
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
138
+ return resolvedType.members.some((member) => member.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
120
139
  member.key.type === utils_1.AST_NODE_TYPES.Identifier &&
121
140
  member.key.name === 'id' &&
122
141
  member.typeAnnotation?.typeAnnotation.type ===
123
142
  utils_1.AST_NODE_TYPES.TSStringKeyword);
124
143
  }
125
- // Check intersection types
126
- if (type.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
127
- return type.types.some(checkIdField);
144
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
145
+ return resolvedType.types.some((part) => checkIdField(part, new Set(visitedTypes)));
146
+ }
147
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
148
+ if (resolvedType.typeName.type !== utils_1.AST_NODE_TYPES.Identifier) {
149
+ return false;
150
+ }
151
+ const typeName = resolvedType.typeName.name;
152
+ if (typeName === 'Identifiable') {
153
+ return true;
154
+ }
155
+ if (TRANSPARENT_TYPE_NAMES.has(typeName) &&
156
+ resolvedType.typeParameters?.params?.some((param) => checkIdField(param, visitedTypes))) {
157
+ return true;
158
+ }
159
+ if (visitedTypes.has(typeName)) {
160
+ return false;
161
+ }
162
+ visitedTypes.add(typeName);
163
+ const referencedType = findTypeAliasAnnotation(typeName);
164
+ return checkIdField(referencedType, visitedTypes);
128
165
  }
129
166
  return false;
130
167
  };
131
168
  // Check if type is wrapped in a utility type
132
169
  const isUtilityType = (type) => {
170
+ if (!type) {
171
+ return false;
172
+ }
133
173
  return (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
134
174
  type.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
135
175
  type.typeName.name === 'Resolve');
136
176
  };
137
177
  // Recursively check the type and its parameters
138
- const checkType = (type) => {
139
- // Check if type extends Identifiable
140
- if (checkIdentifiableExtension(type)) {
178
+ const checkType = (type, visitedTypes = new Set()) => {
179
+ const resolvedType = unwrapTransparentType(type);
180
+ if (!resolvedType) {
181
+ return false;
182
+ }
183
+ if (findIdentifiable(resolvedType)) {
141
184
  return true;
142
185
  }
143
- // Check if type has id: string field (only for utility types)
144
- if (isUtilityType(type) &&
145
- checkIdField(type.typeParameters.params[0])) {
186
+ if (isUtilityType(resolvedType) &&
187
+ resolvedType.typeParameters?.params?.[0] &&
188
+ checkIdField(resolvedType.typeParameters.params[0])) {
146
189
  return true;
147
190
  }
148
- // Check if type is wrapped in a utility type
149
- if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
150
- type.typeParameters?.params?.[0]) {
151
- return checkType(type.typeParameters.params[0]);
191
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
192
+ resolvedType.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
193
+ const typeName = resolvedType.typeName.name;
194
+ if (visitedTypes.has(typeName)) {
195
+ return false;
196
+ }
197
+ visitedTypes.add(typeName);
198
+ const aliasAnnotation = findTypeAliasAnnotation(typeName);
199
+ return checkType(aliasAnnotation, visitedTypes);
200
+ }
201
+ if (resolvedType.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
202
+ return resolvedType.types.some((part) => checkType(part, new Set(visitedTypes)));
152
203
  }
153
- // For direct type definitions, require extending Identifiable
154
204
  return false;
155
205
  };
156
206
  typeHasIdentifiable = checkType(node.typeAnnotation);
@@ -1 +1,2 @@
1
- export declare const enforceMemoizeAsync: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"requireMemoize", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const enforceMemoizeAsync: TSESLint.RuleModule<"requireMemoize", [], TSESLint.RuleListener>;