@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
@@ -1,22 +1,220 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noAsyncForEach = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const getNodeStart = (node) => node?.range?.[0] ?? Number.POSITIVE_INFINITY;
6
+ const getFunctionDescription = (node, fallbackName) => {
7
+ const declaredName = (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
8
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
9
+ node.id?.name
10
+ ? node.id.name
11
+ : null;
12
+ const isArrowFunction = node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
13
+ const functionName = declaredName ?? (isArrowFunction ? undefined : fallbackName);
14
+ if (functionName) {
15
+ return `function "${functionName}"`;
16
+ }
17
+ if (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
18
+ return fallbackName ? `arrow function "${fallbackName}"` : 'arrow function';
19
+ }
20
+ return 'function expression';
21
+ };
22
+ const findVariableInScope = (scope, name) => {
23
+ let currentScope = scope;
24
+ while (currentScope) {
25
+ const variable = currentScope.set.get(name);
26
+ if (variable) {
27
+ return variable;
28
+ }
29
+ currentScope = currentScope.upper;
30
+ }
31
+ return null;
32
+ };
33
+ /**
34
+ * Function declarations are hoisted across their scope regardless of source order.
35
+ * Returning NEGATIVE_INFINITY makes hoisted declarations always "earlier" than any
36
+ * callback usage when we compare source positions.
37
+ */
38
+ const getDefinitionStart = (definition) => definition.node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
39
+ ? Number.NEGATIVE_INFINITY
40
+ : getNodeStart(definition.node);
41
+ const getReferenceStart = (reference) => getNodeStart(reference.identifier);
42
+ const isWriteReference = (reference) => typeof reference.isWrite === 'function'
43
+ ? reference.isWrite()
44
+ : Boolean(reference.isWrite);
45
+ const isAsyncFunctionExpression = (node) => {
46
+ if (!node || typeof node !== 'object') {
47
+ return false;
48
+ }
49
+ const typedNode = node;
50
+ return ((typedNode.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
51
+ typedNode.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
52
+ typedNode.async === true);
53
+ };
54
+ /**
55
+ * Checks if context has direct sourceCode property
56
+ */
57
+ const hasSourceCodeProperty = (context) => {
58
+ return 'sourceCode' in context && !!context.sourceCode;
59
+ };
60
+ /**
61
+ * Checks if context has getSourceCode method
62
+ */
63
+ const hasGetSourceCodeMethod = (context) => {
64
+ return ('getSourceCode' in context && typeof context.getSourceCode === 'function');
65
+ };
66
+ /**
67
+ * Retrieves source code from an ESLint rule context
68
+ */
69
+ const getSourceCode = (context) => {
70
+ if (hasSourceCodeProperty(context)) {
71
+ return context.sourceCode;
72
+ }
73
+ if (hasGetSourceCodeMethod(context)) {
74
+ return context.getSourceCode();
75
+ }
76
+ throw new Error(`Unable to retrieve source code from context in rule "no-async-foreach". ` +
77
+ `File: ${context.filename ??
78
+ context.getFilename?.() ??
79
+ 'unknown'}. ` +
80
+ `Available properties: sourceCode=${typeof context
81
+ .sourceCode}, ` +
82
+ `getSourceCode=${typeof context.getSourceCode}.`);
83
+ };
84
+ const getScope = (context, sourceCode, node) => {
85
+ const typedSourceCode = sourceCode;
86
+ return typedSourceCode.getScope?.(node) ?? context.getScope?.() ?? null;
87
+ };
88
+ const analyzeInlineCallback = (callback) => {
89
+ if (!callback.async) {
90
+ return null;
91
+ }
92
+ return {
93
+ callbackLabel: getFunctionDescription(callback),
94
+ };
95
+ };
96
+ const getAsyncFunctionDeclarationInfo = (definition, callbackName) => {
97
+ if (definition.node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
98
+ definition.node.async) {
99
+ return {
100
+ callbackLabel: getFunctionDescription(definition.node, definition.node.id?.name ?? callbackName),
101
+ };
102
+ }
103
+ return null;
104
+ };
105
+ const getAsyncVariableDeclaratorInfo = (definition, callbackName) => {
106
+ if (definition.node.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
107
+ return null;
108
+ }
109
+ const initializerExpression = definition.node.init;
110
+ if (!isAsyncFunctionExpression(initializerExpression)) {
111
+ return null;
112
+ }
113
+ const name = (definition.node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
114
+ definition.node.id.name) ||
115
+ callbackName;
116
+ return {
117
+ callbackLabel: getFunctionDescription(initializerExpression, name),
118
+ };
119
+ };
120
+ const analyzeVariableDefinition = (definition, callbackName) => getAsyncFunctionDeclarationInfo(definition, callbackName) ??
121
+ getAsyncVariableDeclaratorInfo(definition, callbackName);
122
+ const getReferenceWriteExpression = (reference) => {
123
+ const parent = reference.identifier.parent;
124
+ if (reference.writeExpr) {
125
+ return reference.writeExpr;
126
+ }
127
+ if (parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression) {
128
+ return parent.right;
129
+ }
130
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator && parent.init) {
131
+ return parent.init;
132
+ }
133
+ return null;
134
+ };
135
+ const analyzeVariableReference = (reference) => {
136
+ if (!isWriteReference(reference)) {
137
+ return null;
138
+ }
139
+ const writeExpr = getReferenceWriteExpression(reference);
140
+ if (!isAsyncFunctionExpression(writeExpr)) {
141
+ return null;
142
+ }
143
+ const name = (writeExpr.type === utils_1.AST_NODE_TYPES.FunctionExpression &&
144
+ writeExpr.id?.name) ||
145
+ reference.identifier.name;
146
+ return {
147
+ callbackLabel: getFunctionDescription(writeExpr, name),
148
+ };
149
+ };
150
+ const analyzeCallbackAsyncStatus = (callback, scope) => {
151
+ if (callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
152
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
153
+ return analyzeInlineCallback(callback);
154
+ }
155
+ if (callback.type !== utils_1.AST_NODE_TYPES.Identifier) {
156
+ return null;
157
+ }
158
+ const variable = findVariableInScope(scope, callback.name);
159
+ if (!variable) {
160
+ return null;
161
+ }
162
+ /**
163
+ * Track every write to the callback identifier with its source position. Only
164
+ * the last write at or before the callback location determines whether the
165
+ * callback is async when it is passed to forEach. If the callback location is
166
+ * unknown, bail out to avoid blaming writes that might occur after the call
167
+ * (e.g., a later reassignment to async that should not retroactively flag an
168
+ * earlier forEach use).
169
+ */
170
+ const callbackStart = callback.range?.[0];
171
+ const writes = [];
172
+ for (const definition of variable.defs) {
173
+ const definitionResult = analyzeVariableDefinition(definition, callback.name);
174
+ writes.push({
175
+ start: getDefinitionStart(definition),
176
+ isAsync: Boolean(definitionResult),
177
+ info: definitionResult,
178
+ });
179
+ }
180
+ for (const reference of variable.references) {
181
+ if (!isWriteReference(reference)) {
182
+ continue;
183
+ }
184
+ const referenceResult = analyzeVariableReference(reference);
185
+ writes.push({
186
+ start: getReferenceStart(reference),
187
+ isAsync: Boolean(referenceResult),
188
+ info: referenceResult,
189
+ });
190
+ }
191
+ const relevantWrites = writes.filter(({ start }) => typeof callbackStart === 'number' && start <= callbackStart);
192
+ if (!relevantWrites.length) {
193
+ return null;
194
+ }
195
+ const lastWrite = relevantWrites.reduce((latest, current) => !latest || current.start > latest.start ? current : latest, null);
196
+ return lastWrite && lastWrite.isAsync ? lastWrite.info : null;
197
+ };
4
198
  exports.noAsyncForEach = {
5
199
  create(context) {
200
+ const sourceCode = getSourceCode(context);
6
201
  return {
7
202
  CallExpression(node) {
8
203
  const callee = node.callee;
9
- if (callee.type === 'MemberExpression' &&
10
- callee.property.type === 'Identifier' &&
204
+ const callback = node.arguments[0];
205
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
206
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
11
207
  callee.property.name === 'forEach' &&
12
- node.arguments[0] &&
13
- (node.arguments[0].type === 'ArrowFunctionExpression' ||
14
- node.arguments[0].type === 'FunctionExpression') &&
15
- node.arguments[0].async) {
16
- context.report({
17
- node,
18
- messageId: 'noAsyncForEach',
19
- });
208
+ callback) {
209
+ const scope = getScope(context, sourceCode, callback);
210
+ const asyncCallbackInfo = analyzeCallbackAsyncStatus(callback, scope);
211
+ if (asyncCallbackInfo) {
212
+ context.report({
213
+ node: callback,
214
+ messageId: 'noAsyncForEach',
215
+ data: asyncCallbackInfo,
216
+ });
217
+ }
20
218
  }
21
219
  },
22
220
  };
@@ -24,11 +222,11 @@ exports.noAsyncForEach = {
24
222
  meta: {
25
223
  type: 'problem',
26
224
  docs: {
27
- description: 'Disallow Array.forEach with an async callback function as it does not wait for promises to resolve. This can lead to race conditions and unexpected behavior. Use a standard for...of loop for sequential execution or Promise.all with map for concurrent execution.',
225
+ description: 'Disallow async callbacks to Array.forEach',
28
226
  recommended: 'error',
29
227
  },
30
228
  messages: {
31
- noAsyncForEach: 'Do not use async function as callback in Array.forEach. Use a standard for loop for sequential execution or Promise.all for concurrent execution.',
229
+ noAsyncForEach: 'Async {{callbackLabel}} passed to Array.forEach runs without awaiting each item. Array.forEach ignores returned promises, so async work executes in parallel and rejections go unhandled. Use a for...of loop to await sequentially or map with Promise.all when you want controlled concurrency.',
32
230
  },
33
231
  schema: [],
34
232
  },
@@ -16,11 +16,21 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
16
16
  },
17
17
  schema: [],
18
18
  messages: {
19
- circularReference: 'Circular reference detected in object. This can cause issues with JSON serialization and memory leaks.',
19
+ circularReference: 'Reference "{{referenceText}}" makes this object point back to itself (directly or through other objects). Circular object graphs throw in `JSON.stringify()` and keep members reachable longer, which causes memory leaks and unexpected mutations. Store a copy or a serialize-safe identifier instead of the original object when assigning.',
20
20
  },
21
21
  },
22
22
  defaultOptions: [],
23
23
  create(context) {
24
+ const sourceCode = context.getSourceCode();
25
+ function reportCircularReference(node, reference) {
26
+ context.report({
27
+ node,
28
+ messageId: 'circularReference',
29
+ data: {
30
+ referenceText: sourceCode.getText(reference),
31
+ },
32
+ });
33
+ }
24
34
  function isObjectExpression(node) {
25
35
  return node.type === utils_1.AST_NODE_TYPES.ObjectExpression;
26
36
  }
@@ -207,10 +217,7 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
207
217
  if (targetInfo) {
208
218
  targetInfo.references.add(reference);
209
219
  if (detectCircularReference(targetObj)) {
210
- context.report({
211
- node: reference,
212
- messageId: 'circularReference',
213
- });
220
+ reportCircularReference(reference, reference);
214
221
  }
215
222
  }
216
223
  }
@@ -502,10 +509,8 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
502
509
  if (isIdentifier(leftObj)) {
503
510
  const leftObjRef = getReferencedObject(leftObj);
504
511
  if (leftObjRef === referencedObj) {
505
- context.report({
506
- node: assignment,
507
- messageId: 'circularReference',
508
- });
512
+ const reference = assignment.right;
513
+ reportCircularReference(assignment, reference);
509
514
  }
510
515
  }
511
516
  }
@@ -523,10 +528,8 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
523
528
  if (isIdentifier(leftObj)) {
524
529
  const leftObjRef = getReferencedObject(leftObj);
525
530
  if (leftObjRef === referencedObj) {
526
- context.report({
527
- node: assignment,
528
- messageId: 'circularReference',
529
- });
531
+ const reference = assignment.right;
532
+ reportCircularReference(assignment, reference);
530
533
  }
531
534
  }
532
535
  }
@@ -568,10 +571,8 @@ exports.noCircularReferences = (0, createRule_1.createRule)({
568
571
  parent.right === node) {
569
572
  const leftObj = getObjectFromMemberExpression(parent.left);
570
573
  if (leftObj === referencedObj) {
571
- context.report({
572
- node,
573
- messageId: 'circularReference',
574
- });
574
+ const reference = node;
575
+ reportCircularReference(node, reference);
575
576
  }
576
577
  }
577
578
  }
@@ -14,11 +14,55 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- noClassInstanceDestructuring: 'Avoid destructuring class instances as it can lead to loss of `this` context. Use direct property access instead.',
17
+ noClassInstanceDestructuring: [
18
+ "What's wrong: Destructuring {{members}} from class instance {{instance}} detaches those members from the instance.",
19
+ 'Why it matters: Methods can run with the wrong `this`, and getters become one-time snapshots that go stale when the instance changes.',
20
+ 'How to fix: Access through the instance instead (for example, {{suggestion}}) and bind when you need to pass a method around.',
21
+ ].join('\n'),
18
22
  },
19
23
  },
20
24
  defaultOptions: [],
21
25
  create(context) {
26
+ const sourceCode = context.getSourceCode();
27
+ function describeMember(prop) {
28
+ if (prop.type === utils_1.AST_NODE_TYPES.Property) {
29
+ if (prop.computed) {
30
+ const keyText = sourceCode.getText(prop.key);
31
+ return `[${keyText}]`;
32
+ }
33
+ if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier) {
34
+ return prop.key.name;
35
+ }
36
+ return sourceCode.getText(prop.key);
37
+ }
38
+ if (prop.type === utils_1.AST_NODE_TYPES.RestElement &&
39
+ prop.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
40
+ return `...${prop.argument.name}`;
41
+ }
42
+ return 'member';
43
+ }
44
+ function buildAccessPath(initText, prop) {
45
+ const keyText = sourceCode.getText(prop.key);
46
+ if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier && !prop.computed) {
47
+ return `${initText}.${keyText}`;
48
+ }
49
+ return `${initText}[${keyText}]`;
50
+ }
51
+ function formatMembers(properties) {
52
+ const memberNames = properties.map(describeMember).filter(Boolean);
53
+ if (memberNames.length === 0)
54
+ return '`<members>`';
55
+ return memberNames.map((name) => `\`${name}\``).join(', ');
56
+ }
57
+ function formatAccessExamples(properties, initText) {
58
+ const accessPaths = properties
59
+ .filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property)
60
+ .map((prop) => buildAccessPath(initText, prop));
61
+ if (accessPaths.length === 0) {
62
+ return `\`${initText}.<member>\``;
63
+ }
64
+ return accessPaths.map((path) => `\`${path}\``).join(', ');
65
+ }
22
66
  function isClassInstance(node) {
23
67
  // Check for new expressions
24
68
  if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
@@ -43,11 +87,16 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
43
87
  node.init &&
44
88
  isClassInstance(node.init)) {
45
89
  const objectPattern = node.id;
90
+ const initText = sourceCode.getText(node.init);
46
91
  context.report({
47
92
  node,
48
93
  messageId: 'noClassInstanceDestructuring',
94
+ data: {
95
+ members: formatMembers(objectPattern.properties),
96
+ instance: `\`${initText}\``,
97
+ suggestion: formatAccessExamples(objectPattern.properties, initText),
98
+ },
49
99
  fix(fixer) {
50
- const sourceCode = context.getSourceCode();
51
100
  const properties = objectPattern.properties;
52
101
  // Skip if there's no init expression
53
102
  if (!node.init)
@@ -56,14 +105,11 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
56
105
  if (properties.length === 1) {
57
106
  const prop = properties[0];
58
107
  if (prop.type === utils_1.AST_NODE_TYPES.Property) {
59
- const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
60
- ? prop.key.name
61
- : sourceCode.getText(prop.key);
62
108
  const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
63
109
  ? prop.value.name
64
110
  : sourceCode.getText(prop.value);
65
- const initText = sourceCode.getText(node.init);
66
- return fixer.replaceText(node, `${value} = ${initText}.${key}`);
111
+ const accessPath = buildAccessPath(initText, prop);
112
+ return fixer.replaceText(node, `${value} = ${accessPath}`);
67
113
  }
68
114
  return null;
69
115
  }
@@ -71,14 +117,11 @@ exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
71
117
  const declarations = properties
72
118
  .filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property)
73
119
  .map((prop) => {
74
- const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
75
- ? prop.key.name
76
- : sourceCode.getText(prop.key);
77
120
  const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
78
121
  ? prop.value.name
79
122
  : sourceCode.getText(prop.value);
80
- const initText = sourceCode.getText(node.init);
81
- return `${value} = ${initText}.${key}`;
123
+ const accessPath = buildAccessPath(initText, prop);
124
+ return `${value} = ${accessPath}`;
82
125
  })
83
126
  .join(';\nconst ');
84
127
  // Only apply the fix if we have valid declarations
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noComplexCloudParams = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const PASCAL_CASE_RE = /^[A-Z][a-zA-Z0-9]*$/;
6
7
  exports.noComplexCloudParams = (0, createRule_1.createRule)({
7
8
  name: 'no-complex-cloud-params',
8
9
  meta: {
@@ -13,7 +14,7 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
13
14
  },
14
15
  schema: [],
15
16
  messages: {
16
- noComplexObjects: 'Do not pass complex objects to cloud functions. Complex objects include class instances, objects with methods, non-serializable values (RegExp, BigInt, TypedArray, etc.), or objects with nested complex properties.',
17
+ noComplexObjects: 'Cloud function "{{callee}}" receives a value that is not JSON-serializable. Cloud params must stay plain data; class instances, functions, RegExp/BigInt/TypedArray values, or nested complex properties are dropped or cause runtime errors during transport to Firebase. Send only primitives and plain objects/arrays, or serialize the value first (for example, convert a RegExp to a string or JSON.stringify the payload) before calling "{{callee}}".',
17
18
  },
18
19
  },
19
20
  defaultOptions: [],
@@ -117,8 +118,20 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
117
118
  return isClassInstance(def.node.init);
118
119
  }
119
120
  }
120
- // Check if the identifier starts with a capital letter (potential class instance)
121
- return node.name[0] === node.name[0].toUpperCase();
121
+ // Only consider identifiers as potential class instances if they follow PascalCase naming
122
+ // (not SCREAMING_SNAKE_CASE constants)
123
+ if (node.name[0] === node.name[0].toUpperCase()) {
124
+ // Check if it's a constant (SCREAMING_SNAKE_CASE) - these are not class instances
125
+ if (node.name.includes('_') &&
126
+ node.name === node.name.toUpperCase()) {
127
+ return false;
128
+ }
129
+ // Check if it's PascalCase (potential class instance)
130
+ // PascalCase: starts with capital, has lowercase letters, no underscores
131
+ const isPascalCase = PASCAL_CASE_RE.test(node.name) && /[a-z]/.test(node.name);
132
+ return isPascalCase;
133
+ }
134
+ return false;
122
135
  }
123
136
  return false;
124
137
  }
@@ -311,8 +324,9 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
311
324
  return isComplexValue(node);
312
325
  }
313
326
  function checkCloudFunctionCall(node) {
314
- if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
315
- cloudFunctions.has(node.callee.name)) {
327
+ const callee = node.callee;
328
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
329
+ cloudFunctions.has(callee.name)) {
316
330
  // Check each argument for complex objects
317
331
  node.arguments.forEach((arg) => {
318
332
  if (hasComplexProperties(arg) && !reportedNodes.has(node)) {
@@ -320,6 +334,9 @@ exports.noComplexCloudParams = (0, createRule_1.createRule)({
320
334
  context.report({
321
335
  node,
322
336
  messageId: 'noComplexObjects',
337
+ data: {
338
+ callee: callee.name,
339
+ },
323
340
  });
324
341
  }
325
342
  });
@@ -37,12 +37,12 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
37
37
  meta: {
38
38
  type: 'suggestion',
39
39
  docs: {
40
- description: 'Warn when using CSS properties that trigger compositing layers, which can impact performance. Properties like transform, opacity, filter, and will-change create new GPU layers. While sometimes beneficial for animations, excessive layer creation can increase memory usage and hurt performance. This rule checks both regular style objects and MUI sx props. Consider alternatives or explicitly document intentional layer promotion.',
40
+ description: 'Discourage CSS properties that force GPU compositing layers (e.g., transform, filter, will-change). Extra layers consume GPU memory and split rendering work, which slows scrolling and animation when sprinkled across a page. The rule inspects inline style objects and MUI sx props so layer promotion stays intentional rather than incidental.',
41
41
  recommended: 'error',
42
42
  },
43
43
  schema: [],
44
44
  messages: {
45
- compositingLayer: '{{property}} may trigger a new compositing layer which can impact performance. Consider using alternative properties or add an eslint-disable comment if the layer promotion is intentional.',
45
+ compositingLayer: 'CSS property "{{property}}" promotes this element to its own GPU compositing layer. Extra layers allocate GPU memory and isolate painting, which slows scrolling and animation when used broadly. Remove "{{property}}" or keep it only when the layer promotion is intentional and documented (e.g., eslint-disable with a comment).',
46
46
  },
47
47
  },
48
48
  defaultOptions: [],
@@ -79,7 +79,8 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
79
79
  // Check for JSX style attribute
80
80
  if (current.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
81
81
  current.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
82
- (current.parent.name.name === 'style' || current.parent.name.name === 'sx')) {
82
+ (current.parent.name.name === 'style' ||
83
+ current.parent.name.name === 'sx')) {
83
84
  return true;
84
85
  }
85
86
  // Check for style-related variable names or properties
@@ -91,7 +92,8 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
91
92
  // Check for style-related object property assignments
92
93
  if (current.parent.type === utils_1.AST_NODE_TYPES.Property &&
93
94
  current.parent.key.type === utils_1.AST_NODE_TYPES.Identifier &&
94
- (/style/i.test(current.parent.key.name) || current.parent.key.name === 'sx')) {
95
+ (/style/i.test(current.parent.key.name) ||
96
+ current.parent.key.name === 'sx')) {
95
97
  return true;
96
98
  }
97
99
  // Skip if we're in a TypeScript type definition
@@ -9,12 +9,14 @@ exports.noConditionalLiteralsInJsx = (0, createRule_1.createRule)({
9
9
  meta: {
10
10
  type: 'problem',
11
11
  docs: {
12
- description: 'Disallow use of conditional literals in JSX code',
12
+ description: 'Disallow conditional string literals beside other JSX text to avoid fragmented text nodes, translation issues, and hydration mismatches.',
13
13
  recommended: 'error',
14
14
  },
15
15
  schema: [],
16
16
  messages: {
17
- unexpected: 'Conditional text literals must be wrapped in a container element when next to other text. Instead of `<div>text {condition && "more text"}</div>`, use `<div>text <span>{condition && "more text"}</span></div>` to prevent React hydration issues.',
17
+ unexpected: 'Conditional text literal {{literal}} is rendered next to other JSX text or expressions under condition {{condition}}. ' +
18
+ 'This fragments text nodes, confusing translation/i18n tools and potentially causing React hydration mismatches when server and client group the text differently. ' +
19
+ 'Wrap the conditional expression in its own element (for example, <span>{ {{expression}} }</span>) or move the entire sentence inside the conditional so it renders as a single text node.',
18
20
  },
19
21
  },
20
22
  defaultOptions: [],
@@ -42,17 +44,40 @@ exports.noConditionalLiteralsInJsx = (0, createRule_1.createRule)({
42
44
  'expression' in n &&
43
45
  (n.expression.type === 'Identifier' ||
44
46
  n.expression.type === 'MemberExpression'));
45
- // Operands of {conditional && 'string'} -- the conditional and the
46
- // literal. We want to make sure we have a text literal, otherwise we'd
47
- // trigger this rule on the (safe) {conditional && <div>string</div>}.
48
- const expressionOperandTypes = [
49
- node.expression.left.type,
50
- node.expression.right.type,
51
- ];
52
- if (siblingTextNodes.concat(siblingExpressionNodes).length > 0 &&
53
- expressionOperandTypes.includes(utils_1.TSESTree.AST_NODE_TYPES.Literal)) {
54
- context.report({ node, messageId: 'unexpected' });
47
+ const hasSiblingContent = siblingTextNodes.concat(siblingExpressionNodes).length > 0;
48
+ if (!hasSiblingContent) {
49
+ return;
50
+ }
51
+ const logicalExpression = node.expression;
52
+ const literalNode = logicalExpression.right;
53
+ const conditionalNode = logicalExpression.left;
54
+ // Only enforce when the literal is the expression's return value.
55
+ if (literalNode.type !== utils_1.TSESTree.AST_NODE_TYPES.Literal) {
56
+ return;
57
+ }
58
+ // Only enforce for string literals to avoid misleading messages for
59
+ // numeric or boolean literals rendered conditionally.
60
+ if (typeof literalNode.value !== 'string') {
61
+ return;
62
+ }
63
+ /**
64
+ * Ignore logical expressions that do not actually render the literal
65
+ * conditionally (e.g., literal && condition or literal || condition)
66
+ * and expressions with two literals.
67
+ */
68
+ if (conditionalNode.type === utils_1.TSESTree.AST_NODE_TYPES.Literal) {
69
+ return;
55
70
  }
71
+ const sourceCode = context.getSourceCode();
72
+ context.report({
73
+ node,
74
+ messageId: 'unexpected',
75
+ data: {
76
+ literal: sourceCode.getText(literalNode),
77
+ condition: sourceCode.getText(conditionalNode),
78
+ expression: sourceCode.getText(logicalExpression),
79
+ },
80
+ });
56
81
  },
57
82
  };
58
83
  },
@@ -0,0 +1,9 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ ignorePatterns?: string[];
5
+ allowWithUseAlertDialog?: boolean;
6
+ }
7
+ ];
8
+ export declare const noConsoleError: TSESLint.RuleModule<"noConsoleError", Options, TSESLint.RuleListener>;
9
+ export {};