@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.noUuidv4Base62AsKey = 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.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
7
8
  name: 'no-uuidv4-base62-as-key',
8
9
  meta: {
@@ -13,13 +14,13 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
13
14
  },
14
15
  schema: [],
15
16
  messages: {
16
- noUuidv4Base62AsKey: 'Do not use uuidv4Base62() to generate keys for elements in a list or loop. ' +
17
- 'These keys are not stable across renders and can cause performance issues. ' +
18
- 'Use a stable identifier from your data instead.',
17
+ noUuidv4Base62AsKey: 'Key "{{keyExpression}}" comes from uuidv4Base62(), which regenerates on every render so React cannot reconcile list items and may remount components, drop local state, or reorder DOM nodes. ' +
18
+ 'Use a stable identifier from your data (for example a database id, slug, or memoized array of ids) instead of generating a new UUID inside render.',
19
19
  },
20
20
  },
21
21
  defaultOptions: [],
22
22
  create(context) {
23
+ const sourceCode = context.sourceCode;
23
24
  // Track imported uuidv4Base62 identifiers
24
25
  const importedUuidv4Base62 = new Set();
25
26
  // Track elements that we've already reported to avoid duplicate reports
@@ -31,14 +32,18 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
31
32
  // Flag to indicate we've seen the itemKeys pattern
32
33
  let hasPreGeneratedKeys = false;
33
34
  // Helper function to report a rule violation while avoiding duplicates
34
- function reportViolation(node, messageId = 'noUuidv4Base62AsKey') {
35
+ function reportViolation(node, keyExpression, messageId = 'noUuidv4Base62AsKey') {
35
36
  // Skip if we've already reported this element
36
37
  if (reportedElements.has(node))
37
38
  return;
38
39
  reportedElements.add(node);
40
+ const keyExpressionText = keyExpression
41
+ ? sourceCode.getText(keyExpression).trim()
42
+ : 'uuidv4Base62()';
39
43
  context.report({
40
44
  node,
41
45
  messageId,
46
+ data: { keyExpression: keyExpressionText },
42
47
  });
43
48
  }
44
49
  // Helper to check if a node is a call to uuidv4Base62()
@@ -117,7 +122,7 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
117
122
  const { expression } = attr.value;
118
123
  // Direct uuidv4Base62() call in key
119
124
  if (containsUuidV4Base62Call(expression)) {
120
- reportViolation(jsxElement);
125
+ reportViolation(jsxElement, expression);
121
126
  return;
122
127
  }
123
128
  // Check for member expressions (item.key pattern)
@@ -128,12 +133,12 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
128
133
  const objName = expression.object.name;
129
134
  // Check if this variable has been marked as containing keys with uuidv4Base62
130
135
  if (variablesWithUuidv4Base62Keys.has(objName)) {
131
- reportViolation(jsxElement);
136
+ reportViolation(jsxElement, expression);
132
137
  return;
133
138
  }
134
139
  // Special case for the failing 'itemKeys' test
135
140
  if (hasPreGeneratedKeys) {
136
- const ancestors = context.getAncestors();
141
+ const ancestors = ASTHelpers_1.ASTHelpers.getAncestors(context, jsxElement);
137
142
  // Check if we're inside a map callback
138
143
  for (let i = ancestors.length - 1; i >= 0; i--) {
139
144
  const ancestor = ancestors[i];
@@ -144,7 +149,7 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
144
149
  ancestor.callee.object.type === utils_1.AST_NODE_TYPES.Identifier) {
145
150
  // If we're mapping over a variable with pre-generated keys
146
151
  if (variablesWithUuidv4Base62Keys.has(ancestor.callee.object.name)) {
147
- reportViolation(jsxElement);
152
+ reportViolation(jsxElement, expression);
148
153
  return;
149
154
  }
150
155
  }
@@ -155,8 +160,8 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
155
160
  }
156
161
  }
157
162
  // Helper to check for react mapping functions
158
- function isInsideMapCallback() {
159
- const ancestors = context.getAncestors();
163
+ function isInsideMapCallback(node) {
164
+ const ancestors = ASTHelpers_1.ASTHelpers.getAncestors(context, node);
160
165
  for (let i = ancestors.length - 1; i >= 0; i--) {
161
166
  const ancestor = ancestors[i];
162
167
  if (ancestor.type === utils_1.AST_NODE_TYPES.CallExpression &&
@@ -276,7 +281,7 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
276
281
  },
277
282
  // Check JSX elements for uuidv4Base62 keys
278
283
  JSXElement(node) {
279
- if (!isInsideMapCallback())
284
+ if (!isInsideMapCallback(node))
280
285
  return;
281
286
  if (node.openingElement.attributes) {
282
287
  checkJSXAttributesForUuidv4Base62(node.openingElement.attributes, node);
@@ -284,7 +289,7 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
284
289
  },
285
290
  // Check JSX fragments for children with uuidv4Base62 keys
286
291
  JSXFragment(node) {
287
- if (!isInsideMapCallback())
292
+ if (!isInsideMapCallback(node))
288
293
  return;
289
294
  for (const child of node.children) {
290
295
  if (child.type === utils_1.AST_NODE_TYPES.JSXElement &&
@@ -336,7 +341,7 @@ exports.noUuidv4Base62AsKey = (0, createRule_1.createRule)({
336
341
  utils_1.AST_NODE_TYPES.Identifier &&
337
342
  attr.value.expression.property.name === 'key') {
338
343
  // The test case - directly report this element
339
- reportViolation(returnExpr);
344
+ reportViolation(returnExpr, attr.value.expression);
340
345
  break;
341
346
  }
342
347
  }
@@ -1,7 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
1
2
  type Options = [
2
3
  {
3
4
  allowWithQueryOrHash?: boolean;
4
5
  }
5
6
  ];
6
- export declare const omitIndexHtml: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"omitIndexHtml", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
7
+ export declare const omitIndexHtml: TSESLint.RuleModule<"omitIndexHtml", Options, TSESLint.RuleListener>;
7
8
  export {};
@@ -25,8 +25,26 @@ function hasQueryOrHash(url) {
25
25
  * Removes 'index.html' from a URL and ensures proper trailing slash
26
26
  */
27
27
  function fixUrl(url) {
28
- // Replace /index.html with /
29
- return url.replace(/\/index\.html($|[?#])/, '/$1');
28
+ // Replace /index.html/ with / and /index.html with / at the end or before ?/#/${
29
+ return url
30
+ .replace(/\/index\.html\//g, '/')
31
+ .replace(/\/index\.html(?=(?:$|[?#]|\$\{))/g, '/');
32
+ }
33
+ /**
34
+ * Reconstructs a template literal using cooked values so static checks and
35
+ * reporting align with what runtime sees (escape sequences are resolved).
36
+ */
37
+ function describeTemplateLiteral(node, sourceCode) {
38
+ const parts = [];
39
+ node.quasis.forEach((quasi, index) => {
40
+ const text = quasi.value.cooked ?? quasi.value.raw;
41
+ parts.push(text);
42
+ const expression = node.expressions[index];
43
+ if (expression) {
44
+ parts.push(`\${${sourceCode.getText(expression)}}`);
45
+ }
46
+ });
47
+ return parts.join('');
30
48
  }
31
49
  exports.omitIndexHtml = (0, createRule_1.createRule)({
32
50
  name: 'omit-index-html',
@@ -49,11 +67,12 @@ exports.omitIndexHtml = (0, createRule_1.createRule)({
49
67
  },
50
68
  ],
51
69
  messages: {
52
- omitIndexHtml: 'Avoid using "index.html" in URLs. Many web servers automatically resolve directory requests to index.html.',
70
+ omitIndexHtml: 'URL "{{url}}" includes "index.html", which servers already serve implicitly for directory paths. Keeping the file name creates duplicate URLs, breaks canonical links, and can make caches treat the same page as different assets. {{fixHint}}',
53
71
  },
54
72
  },
55
73
  defaultOptions: [{ allowWithQueryOrHash: true }],
56
74
  create(context, [options]) {
75
+ const sourceCode = context.getSourceCode();
57
76
  const allowWithQueryOrHash = options.allowWithQueryOrHash !== false;
58
77
  return {
59
78
  Literal(node) {
@@ -65,22 +84,58 @@ exports.omitIndexHtml = (0, createRule_1.createRule)({
65
84
  // Skip if it has query parameters or hash fragments and we're allowing those
66
85
  if (allowWithQueryOrHash && hasQueryOrHash(value))
67
86
  return;
87
+ const suggestedUrl = fixUrl(value);
88
+ const fixHint = `Replace it with the directory path (e.g., "${suggestedUrl}").`;
68
89
  context.report({
69
90
  node,
70
91
  messageId: 'omitIndexHtml',
92
+ data: {
93
+ url: value,
94
+ suggestedUrl,
95
+ fixHint,
96
+ },
71
97
  fix: (fixer) => {
72
- return fixer.replaceText(node, `"${fixUrl(value)}"`);
98
+ const quote = sourceCode.getText(node).startsWith("'")
99
+ ? "'"
100
+ : '"';
101
+ const escapedUrl = suggestedUrl.split(quote).join(`\\${quote}`);
102
+ return fixer.replaceText(node, `${quote}${escapedUrl}${quote}`);
73
103
  },
74
104
  });
75
105
  }
76
106
  },
77
107
  TemplateLiteral(node) {
78
- // For template literals, we can only check if the static parts contain index.html
79
- const value = node.quasis.map((q) => q.value.raw).join('');
80
- if (value.includes('/index.html')) {
108
+ const hasIndexHtmlInStatic = node.quasis.some((quasi) => {
109
+ const text = quasi.value.cooked ?? quasi.value.raw;
110
+ return text.includes('/index.html');
111
+ });
112
+ if (!hasIndexHtmlInStatic) {
113
+ return;
114
+ }
115
+ const value = describeTemplateLiteral(node, sourceCode);
116
+ // Check if it's a URL and contains index.html
117
+ if (isLikelyUrl(value) && value.includes('/index.html')) {
118
+ // Skip if it has query parameters or hash fragments and we're allowing those
119
+ if (allowWithQueryOrHash && hasQueryOrHash(value))
120
+ return;
121
+ const hasDynamicParts = node.expressions.length > 0;
122
+ const suggestedUrlRaw = fixUrl(value);
123
+ const suggestedUrl = hasDynamicParts
124
+ ? `\`${suggestedUrlRaw
125
+ .replace(/\\/g, '\\\\')
126
+ .replace(/`/g, '\\`')}\``
127
+ : suggestedUrlRaw;
128
+ const fixHint = hasDynamicParts
129
+ ? `Remove "index.html" from the static portion of the template so it resolves to the directory path (e.g., ${suggestedUrl}).`
130
+ : `Replace it with the directory path (e.g., "${suggestedUrl}").`;
81
131
  context.report({
82
132
  node,
83
133
  messageId: 'omitIndexHtml',
134
+ data: {
135
+ url: value,
136
+ suggestedUrl,
137
+ fixHint,
138
+ },
84
139
  // No automatic fix for template literals as they may contain dynamic parts
85
140
  });
86
141
  }
@@ -0,0 +1 @@
1
+ export declare const optimizeObjectBooleanConditions: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"extractBooleanCondition", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.optimizeObjectBooleanConditions = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const HOOK_NAMES = new Set(['useEffect', 'useCallback', 'useMemo']);
7
+ function isHookCall(node) {
8
+ const callee = node.callee;
9
+ return ((callee.type === utils_1.AST_NODE_TYPES.Identifier &&
10
+ HOOK_NAMES.has(callee.name)) ||
11
+ (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
12
+ !callee.computed &&
13
+ callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
14
+ callee.object.name === 'React' &&
15
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
16
+ HOOK_NAMES.has(callee.property.name)));
17
+ }
18
+ function generateBooleanVariableName(objectName, conditionType, isNegated) {
19
+ const base = capitalize(objectName);
20
+ if (conditionType === 'existence') {
21
+ return isNegated ? `is${base}Missing` : `has${base}`;
22
+ }
23
+ if (conditionType === 'keyCount') {
24
+ return isNegated ? `is${base}Empty` : `has${base}`;
25
+ }
26
+ // complex: default to hasX to avoid over-guessing semantics
27
+ return `has${base}`;
28
+ }
29
+ function capitalize(str) {
30
+ return str.charAt(0).toUpperCase() + str.slice(1);
31
+ }
32
+ function isObjectExistenceCheck(node) {
33
+ // Check for patterns like !obj
34
+ if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
35
+ // !obj
36
+ if (node.operator === '!' &&
37
+ node.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
38
+ return true;
39
+ }
40
+ // !!obj
41
+ if (node.operator === '!' &&
42
+ node.argument.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
43
+ node.argument.operator === '!' &&
44
+ node.argument.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ function isObjectKeyCountCheck(node) {
51
+ // Check for patterns like Object.keys(obj).length === 0 or Object.keys(obj).length > 0
52
+ if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
53
+ const { left, operator, right } = node;
54
+ // Check if left side is Object.keys(obj).length
55
+ if (left.type === utils_1.AST_NODE_TYPES.MemberExpression &&
56
+ left.property.type === utils_1.AST_NODE_TYPES.Identifier &&
57
+ left.property.name === 'length' &&
58
+ left.object.type === utils_1.AST_NODE_TYPES.CallExpression) {
59
+ const callExpr = left.object;
60
+ if (callExpr.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
61
+ callExpr.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
62
+ callExpr.callee.object.name === 'Object' &&
63
+ callExpr.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
64
+ callExpr.callee.property.name === 'keys' &&
65
+ callExpr.arguments.length === 1 &&
66
+ callExpr.arguments[0].type === utils_1.AST_NODE_TYPES.Identifier) {
67
+ // Check if right side is a number
68
+ if (right.type === utils_1.AST_NODE_TYPES.Literal &&
69
+ typeof right.value === 'number' &&
70
+ ['===', '!==', '>', '<', '>=', '<='].includes(operator)) {
71
+ return true;
72
+ }
73
+ }
74
+ }
75
+ }
76
+ return false;
77
+ }
78
+ function isComplexBooleanExpression(node) {
79
+ // Check for complex expressions involving objects
80
+ if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
81
+ return (containsObjectCondition(node.left) || containsObjectCondition(node.right));
82
+ }
83
+ return false;
84
+ }
85
+ function containsObjectCondition(node) {
86
+ return (isObjectExistenceCheck(node) ||
87
+ isObjectKeyCountCheck(node) ||
88
+ isComplexBooleanExpression(node));
89
+ }
90
+ function extractObjectName(node) {
91
+ if (node.type === utils_1.AST_NODE_TYPES.UnaryExpression) {
92
+ if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
93
+ return node.argument.name;
94
+ }
95
+ }
96
+ else if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
97
+ const { left } = node;
98
+ if (left.type === utils_1.AST_NODE_TYPES.MemberExpression &&
99
+ left.object.type === utils_1.AST_NODE_TYPES.CallExpression &&
100
+ left.object.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
101
+ left.object.arguments.length === 1 &&
102
+ left.object.arguments[0].type === utils_1.AST_NODE_TYPES.Identifier) {
103
+ return left.object.arguments[0].name;
104
+ }
105
+ }
106
+ else if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
107
+ // For complex expressions, try to extract from the first object condition found
108
+ const leftObj = extractObjectName(node.left);
109
+ if (leftObj)
110
+ return leftObj;
111
+ return extractObjectName(node.right);
112
+ }
113
+ return null;
114
+ }
115
+ function analyzeBooleanCondition(node, context) {
116
+ const sourceCode = context.sourceCode;
117
+ const expression = sourceCode.getText(node);
118
+ if (isObjectExistenceCheck(node)) {
119
+ const objectName = extractObjectName(node);
120
+ if (objectName) {
121
+ const isNegated = node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
122
+ node.operator === '!' &&
123
+ !(node.argument.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
124
+ node.argument.operator === '!');
125
+ return {
126
+ type: 'existence',
127
+ objectName,
128
+ expression,
129
+ suggestedName: generateBooleanVariableName(objectName, 'existence', isNegated),
130
+ node,
131
+ };
132
+ }
133
+ }
134
+ else if (isObjectKeyCountCheck(node)) {
135
+ const objectName = extractObjectName(node);
136
+ if (objectName) {
137
+ const isNegated = expression.includes('=== 0') || expression.includes('<= 0');
138
+ return {
139
+ type: 'keyCount',
140
+ objectName,
141
+ expression,
142
+ suggestedName: generateBooleanVariableName(objectName, 'keyCount', isNegated),
143
+ node,
144
+ };
145
+ }
146
+ }
147
+ else if (isComplexBooleanExpression(node)) {
148
+ const objectName = extractObjectName(node);
149
+ if (objectName) {
150
+ return {
151
+ type: 'complex',
152
+ objectName,
153
+ expression,
154
+ suggestedName: generateBooleanVariableName(objectName, 'complex', false),
155
+ node,
156
+ };
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+ function findBooleanConditionsInDependencies(depsArray, context) {
162
+ const patterns = [];
163
+ for (const element of depsArray.elements) {
164
+ if (!element)
165
+ continue; // Skip holes in array
166
+ const pattern = analyzeBooleanCondition(element, context);
167
+ if (pattern) {
168
+ patterns.push(pattern);
169
+ }
170
+ }
171
+ return patterns;
172
+ }
173
+ function isAlreadyBooleanVariable(node) {
174
+ // Check if the dependency is already a boolean variable (starts with is, has, can, etc.)
175
+ if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
176
+ const name = node.name;
177
+ const booleanPrefixes = [
178
+ 'is',
179
+ 'has',
180
+ 'can',
181
+ 'should',
182
+ 'will',
183
+ 'was',
184
+ 'were',
185
+ ];
186
+ return booleanPrefixes.some((prefix) => name.toLowerCase().startsWith(prefix.toLowerCase()));
187
+ }
188
+ return false;
189
+ }
190
+ exports.optimizeObjectBooleanConditions = (0, createRule_1.createRule)({
191
+ name: 'optimize-object-boolean-conditions',
192
+ meta: {
193
+ type: 'suggestion',
194
+ docs: {
195
+ description: 'Detects and suggests optimizations for boolean conditions formed over objects in React hook dependencies. Suggests extracting boolean conditions into separate variables to reduce unnecessary re-computations when objects change frequently but the boolean condition changes less frequently.',
196
+ recommended: 'error',
197
+ },
198
+ schema: [],
199
+ messages: {
200
+ extractBooleanCondition: 'Dependency array includes boolean condition "{{expression}}" derived from object "{{objectName}}". Hook re-runs every time the object reference changes even when the boolean outcome stays the same, leading to wasted renders and unstable memoization. Extract the condition into a stable boolean like "{{suggestedName}}" and depend on that variable instead.',
201
+ },
202
+ },
203
+ defaultOptions: [],
204
+ create(context) {
205
+ return {
206
+ CallExpression(node) {
207
+ if (!isHookCall(node)) {
208
+ return;
209
+ }
210
+ // Get the dependency array argument (last argument for hooks)
211
+ const depsArg = node.arguments[node.arguments.length - 1];
212
+ if (!depsArg || depsArg.type !== utils_1.AST_NODE_TYPES.ArrayExpression) {
213
+ return;
214
+ }
215
+ // Find boolean conditions in dependencies
216
+ const patterns = findBooleanConditionsInDependencies(depsArg, context);
217
+ for (const pattern of patterns) {
218
+ // Skip if it's already a boolean variable
219
+ if (isAlreadyBooleanVariable(pattern.node)) {
220
+ continue;
221
+ }
222
+ context.report({
223
+ node: pattern.node,
224
+ messageId: 'extractBooleanCondition',
225
+ data: {
226
+ expression: pattern.expression,
227
+ suggestedName: pattern.suggestedName,
228
+ objectName: pattern.objectName,
229
+ },
230
+ });
231
+ }
232
+ },
233
+ };
234
+ },
235
+ });
236
+ //# sourceMappingURL=optimize-object-boolean-conditions.js.map
@@ -0,0 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type Options = [
3
+ {
4
+ sideEffectPatterns?: Array<string | RegExp>;
5
+ }
6
+ ];
7
+ export declare const parallelizeAsyncOperations: TSESLint.RuleModule<"parallelizeAsyncOperations", Options, TSESLint.RuleListener>;
8
+ export {};