@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
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferUrlToStringOverToJson = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ const utils_1 = require("@typescript-eslint/utils");
6
+ // Helpers to identify URL constructor expressions
7
+ function isUrlConstructor(newExpr) {
8
+ const callee = newExpr.callee;
9
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
10
+ return callee.name === 'URL';
11
+ }
12
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
13
+ // e.g., new globalThis.URL(...), new window.URL(...)
14
+ return (callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
15
+ callee.property.name === 'URL');
16
+ }
17
+ return false;
18
+ }
19
+ function isJSONStringifyCall(node) {
20
+ const { callee } = node;
21
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
22
+ return false;
23
+ let objectExpr = callee.object;
24
+ while (objectExpr.type === utils_1.AST_NODE_TYPES.MemberExpression) {
25
+ objectExpr = objectExpr.object;
26
+ }
27
+ const isJsonIdentifier = objectExpr.type === utils_1.AST_NODE_TYPES.Identifier && objectExpr.name === 'JSON';
28
+ const isStringifyProperty = callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
29
+ callee.property.name === 'stringify';
30
+ return isJsonIdentifier && isStringifyProperty;
31
+ }
32
+ function findEnclosingJSONStringify(node) {
33
+ let current = node;
34
+ while (current && current.parent) {
35
+ const parent = current.parent;
36
+ if (parent.type === utils_1.AST_NODE_TYPES.CallExpression) {
37
+ if (isJSONStringifyCall(parent) &&
38
+ parent.arguments.includes(current)) {
39
+ return parent;
40
+ }
41
+ }
42
+ current = parent;
43
+ }
44
+ return null;
45
+ }
46
+ function isOptionalMemberExpression(expr) {
47
+ // @typescript-eslint defines optional?: boolean on MemberExpression for optional chaining
48
+ // Cast to any to safely access it across minor version diffs
49
+ return expr.optional === true;
50
+ }
51
+ exports.preferUrlToStringOverToJson = (0, createRule_1.createRule)({
52
+ create(context) {
53
+ const sourceCode = context.sourceCode;
54
+ const parserServices = sourceCode.parserServices;
55
+ const urlIdentifierNames = new Set();
56
+ let checker = null;
57
+ if (parserServices &&
58
+ parserServices.program &&
59
+ typeof parserServices.program.getTypeChecker === 'function') {
60
+ try {
61
+ checker = parserServices.program.getTypeChecker();
62
+ }
63
+ catch {
64
+ checker = null;
65
+ }
66
+ }
67
+ function isUrlType(expr) {
68
+ // Heuristic without types
69
+ if (expr.type === utils_1.AST_NODE_TYPES.NewExpression &&
70
+ isUrlConstructor(expr)) {
71
+ return true;
72
+ }
73
+ if (expr.type === utils_1.AST_NODE_TYPES.Identifier) {
74
+ if (urlIdentifierNames.has(expr.name))
75
+ return true;
76
+ }
77
+ // Typed check via TS when available
78
+ if (checker &&
79
+ parserServices &&
80
+ parserServices.esTreeNodeToTSNodeMap) {
81
+ try {
82
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(expr);
83
+ const type = checker.getTypeAtLocation(tsNode);
84
+ const isUrl = (t) => {
85
+ const sym = t.getSymbol();
86
+ if (sym?.getName() === 'URL')
87
+ return true;
88
+ const unionTypes = t.types;
89
+ return Array.isArray(unionTypes) && unionTypes.some(isUrl);
90
+ };
91
+ if (isUrl(type))
92
+ return true;
93
+ }
94
+ catch {
95
+ // ignore type errors and fall through
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+ return {
101
+ VariableDeclarator(node) {
102
+ if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
103
+ node.init &&
104
+ node.init.type === utils_1.AST_NODE_TYPES.NewExpression &&
105
+ isUrlConstructor(node.init)) {
106
+ urlIdentifierNames.add(node.id.name);
107
+ }
108
+ },
109
+ AssignmentExpression(node) {
110
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
111
+ node.right.type === utils_1.AST_NODE_TYPES.NewExpression &&
112
+ isUrlConstructor(node.right)) {
113
+ urlIdentifierNames.add(node.left.name);
114
+ }
115
+ },
116
+ CallExpression(node) {
117
+ const callee = node.callee;
118
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression)
119
+ return;
120
+ // property should be .toJSON or ['toJSON']
121
+ if ((callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
122
+ callee.property.name !== 'toJSON') ||
123
+ (callee.property.type === utils_1.AST_NODE_TYPES.Literal &&
124
+ callee.property.value !== 'toJSON')) {
125
+ return;
126
+ }
127
+ const objectExpr = callee.object;
128
+ if (!isUrlType(objectExpr))
129
+ return;
130
+ const insideJSONStringify = findEnclosingJSONStringify(node);
131
+ const memberExpr = callee;
132
+ context.report({
133
+ node,
134
+ messageId: 'preferToString',
135
+ data: {
136
+ urlText: sourceCode.getText(objectExpr),
137
+ },
138
+ fix(fixer) {
139
+ // If inside JSON.stringify and not optional chain, replace the entire call with just the object
140
+ if (insideJSONStringify &&
141
+ !isOptionalMemberExpression(memberExpr)) {
142
+ const objText = sourceCode.getText(objectExpr);
143
+ return fixer.replaceText(node, objText);
144
+ }
145
+ // Default: change .toJSON() -> .toString()
146
+ const propertyRange = memberExpr.property.range;
147
+ return fixer.replaceTextRange(propertyRange, 'toString');
148
+ },
149
+ });
150
+ },
151
+ };
152
+ },
153
+ name: 'prefer-url-tostring-over-tojson',
154
+ meta: {
155
+ type: 'suggestion',
156
+ docs: {
157
+ description: 'Enforce the use of toString() over toJSON() on URL objects.',
158
+ recommended: 'error',
159
+ requiresTypeChecking: false,
160
+ },
161
+ fixable: 'code',
162
+ schema: [],
163
+ messages: {
164
+ preferToString: 'URL value {{urlText}} calls toJSON() explicitly. URL.toJSON() delegates directly to toString(), so the call is redundant. Inside JSON.stringify it is unnecessary because JSON.stringify already invokes toJSON automatically; elsewhere it hides intent because readers must remember the delegation to see it produces the same string as toString. Use toString() when you need an explicit string, or pass the URL directly to JSON.stringify to rely on its built-in serialization.',
165
+ },
166
+ },
167
+ defaultOptions: [],
168
+ });
169
+ //# sourceMappingURL=prefer-url-tostring-over-tojson.js.map
@@ -0,0 +1,4 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export type MessageIds = 'preferUseDeepCompareMemo';
3
+ export declare const preferUseDeepCompareMemo: TSESLint.RuleModule<"preferUseDeepCompareMemo", [], TSESLint.RuleListener>;
4
+ export default preferUseDeepCompareMemo;
@@ -0,0 +1,431 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferUseDeepCompareMemo = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ // Consider these as memoizing hooks producing stable references
7
+ const MEMOIZING_HOOKS = new Set([
8
+ 'useMemo',
9
+ 'useCallback',
10
+ 'useDeepCompareMemo',
11
+ 'useLatestCallback',
12
+ ]);
13
+ function isUseMemoCallee(callee) {
14
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
15
+ return callee.name === 'useMemo';
16
+ }
17
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
18
+ !callee.computed &&
19
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
20
+ return callee.property.name === 'useMemo';
21
+ }
22
+ return false;
23
+ }
24
+ function isIdentifierMemoizedAbove(name, memoizedIds) {
25
+ return memoizedIds.has(name);
26
+ }
27
+ function containsJsx(node) {
28
+ if (!node)
29
+ return false;
30
+ const stack = [node];
31
+ while (stack.length) {
32
+ const cur = stack.pop();
33
+ if (cur.type === utils_1.AST_NODE_TYPES.JSXElement ||
34
+ cur.type === utils_1.AST_NODE_TYPES.JSXFragment) {
35
+ return true;
36
+ }
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ for (const key of Object.keys(cur)) {
39
+ if (key === 'parent')
40
+ continue;
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ const child = cur[key];
43
+ if (!child)
44
+ continue;
45
+ if (Array.isArray(child)) {
46
+ for (const c of child) {
47
+ if (c && typeof c === 'object' && 'type' in c) {
48
+ stack.push(c);
49
+ }
50
+ }
51
+ }
52
+ else if (typeof child === 'object' && 'type' in child) {
53
+ stack.push(child);
54
+ }
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ function isNonPrimitiveWithoutTypes(expr) {
60
+ switch (expr.type) {
61
+ case utils_1.AST_NODE_TYPES.ArrayExpression:
62
+ case utils_1.AST_NODE_TYPES.ObjectExpression:
63
+ case utils_1.AST_NODE_TYPES.FunctionExpression:
64
+ case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
65
+ case utils_1.AST_NODE_TYPES.NewExpression:
66
+ case utils_1.AST_NODE_TYPES.ClassExpression:
67
+ return true;
68
+ case utils_1.AST_NODE_TYPES.Identifier:
69
+ case utils_1.AST_NODE_TYPES.Literal:
70
+ case utils_1.AST_NODE_TYPES.TemplateLiteral:
71
+ case utils_1.AST_NODE_TYPES.UnaryExpression:
72
+ case utils_1.AST_NODE_TYPES.BinaryExpression:
73
+ case utils_1.AST_NODE_TYPES.LogicalExpression:
74
+ case utils_1.AST_NODE_TYPES.MemberExpression:
75
+ case utils_1.AST_NODE_TYPES.ChainExpression:
76
+ case utils_1.AST_NODE_TYPES.CallExpression:
77
+ default:
78
+ return false;
79
+ }
80
+ }
81
+ // TypeScript-aware check. Defensive and conservative.
82
+ function isNonPrimitiveWithTypes(context, expr) {
83
+ const services = context.parserServices;
84
+ if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
85
+ return false;
86
+ }
87
+ try {
88
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
89
+ const ts = require('typescript');
90
+ const checker = services.program.getTypeChecker();
91
+ const tsNode = services.esTreeNodeToTSNodeMap.get(expr);
92
+ if (!tsNode)
93
+ return false;
94
+ const type = checker.getTypeAtLocation(tsNode);
95
+ // Only consider explicit functions as non-primitive here
96
+ if (type.getCallSignatures().length > 0)
97
+ return true;
98
+ // Avoid guessing for unknown/any or non-function types
99
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))
100
+ return false;
101
+ return false;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ function collectMemoizedIdentifiers(context) {
108
+ const memoized = new Set();
109
+ const sourceCode = context.sourceCode;
110
+ const program = sourceCode.ast;
111
+ function visit(node) {
112
+ if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
113
+ const id = node.id;
114
+ const init = node.init;
115
+ if (!init)
116
+ return;
117
+ if (init.type === utils_1.AST_NODE_TYPES.CallExpression) {
118
+ let calleeName = null;
119
+ if (init.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
120
+ calleeName = init.callee.name;
121
+ }
122
+ else if (init.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
123
+ !init.callee.computed &&
124
+ init.callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
125
+ calleeName = init.callee.property.name;
126
+ }
127
+ if (calleeName && MEMOIZING_HOOKS.has(calleeName)) {
128
+ if (id.type === utils_1.AST_NODE_TYPES.Identifier) {
129
+ memoized.add(id.name);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ for (const key of Object.keys(node)) {
136
+ if (key === 'parent')
137
+ continue;
138
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
+ const child = node[key];
140
+ if (!child)
141
+ continue;
142
+ if (Array.isArray(child)) {
143
+ for (const c of child) {
144
+ if (c && typeof c === 'object' && 'type' in c) {
145
+ visit(c);
146
+ }
147
+ }
148
+ }
149
+ else if (typeof child === 'object' && 'type' in child) {
150
+ visit(child);
151
+ }
152
+ }
153
+ }
154
+ visit(program);
155
+ return memoized;
156
+ }
157
+ function ensureDeepCompareImportFixes(context, fixer) {
158
+ const fixes = [];
159
+ const sourceCode = context.sourceCode;
160
+ const program = sourceCode.ast;
161
+ // If already imported anywhere, skip adding
162
+ const hasImport = program.body.some((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
163
+ n.source.value === '@blumintinc/use-deep-compare' &&
164
+ n.specifiers.some((s) => s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
165
+ s.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
166
+ s.imported.name === 'useDeepCompareMemo'));
167
+ if (hasImport)
168
+ return fixes;
169
+ // Determine insertion point and indentation
170
+ const importDecls = program.body.filter((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
171
+ const fullText = sourceCode.getText();
172
+ let importText = `import { useDeepCompareMemo } from '@blumintinc/use-deep-compare';\n`;
173
+ if (importDecls.length === 0) {
174
+ fixes.push(fixer.insertTextBeforeRange([0, 0], importText));
175
+ }
176
+ else {
177
+ const firstImport = importDecls[0];
178
+ const before = fullText.slice(0, firstImport.range[0]);
179
+ const lastNewline = before.lastIndexOf('\n');
180
+ const indent = lastNewline >= 0 ? before.slice(lastNewline + 1) : '';
181
+ importText = `${indent}${importText}`;
182
+ fixes.push(fixer.insertTextBefore(firstImport, importText));
183
+ }
184
+ return fixes;
185
+ }
186
+ function findVariableInScope(scope, name) {
187
+ if (!scope)
188
+ return null;
189
+ const found = scope.variables?.find((v) => v.name === name);
190
+ if (found)
191
+ return found;
192
+ if (Array.isArray(scope.childScopes)) {
193
+ for (const child of scope.childScopes) {
194
+ const v = findVariableInScope(child, name);
195
+ if (v)
196
+ return v;
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ function isIdentifierReferenced(sourceCode, name) {
202
+ const scope = sourceCode.scopeManager?.globalScope;
203
+ if (!scope)
204
+ return true;
205
+ const variable = findVariableInScope(scope, name);
206
+ if (!variable)
207
+ return false;
208
+ return variable.references.some((ref) => ref.identifier && ref.identifier.name === name);
209
+ }
210
+ function removeImportSpecifierFixes(sourceCode, fixer, importDecl, specifier) {
211
+ const fixes = [];
212
+ if (importDecl.specifiers.length === 1) {
213
+ fixes.push(fixer.remove(importDecl));
214
+ return fixes;
215
+ }
216
+ const tokenAfter = sourceCode.getTokenAfter(specifier);
217
+ const tokenBefore = sourceCode.getTokenBefore(specifier);
218
+ if (tokenAfter && tokenAfter.value === ',') {
219
+ fixes.push(fixer.removeRange([specifier.range[0], tokenAfter.range[1]]));
220
+ }
221
+ else if (tokenBefore && tokenBefore.value === ',') {
222
+ fixes.push(fixer.removeRange([tokenBefore.range[0], specifier.range[1]]));
223
+ }
224
+ else {
225
+ fixes.push(fixer.remove(specifier));
226
+ }
227
+ return fixes;
228
+ }
229
+ function isImportedIdentifier(context, name) {
230
+ const sourceCode = context.sourceCode;
231
+ const program = sourceCode.ast;
232
+ for (const node of program.body) {
233
+ if (node.type === utils_1.AST_NODE_TYPES.ImportDeclaration) {
234
+ for (const spec of node.specifiers) {
235
+ if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier ||
236
+ spec.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
237
+ spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
238
+ if (spec.local.name === name)
239
+ return true;
240
+ }
241
+ }
242
+ }
243
+ }
244
+ return false;
245
+ }
246
+ function identifierUsedAsObjectOrFunction(callback, name) {
247
+ const stack = [callback.body];
248
+ while (stack.length) {
249
+ const node = stack.pop();
250
+ if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
251
+ let base = node.object;
252
+ while (base.type === utils_1.AST_NODE_TYPES.MemberExpression) {
253
+ base = base.object;
254
+ }
255
+ if (base.type === utils_1.AST_NODE_TYPES.Identifier && base.name === name) {
256
+ return true; // object/array usage via property access
257
+ }
258
+ }
259
+ if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
260
+ const expr = node.expression;
261
+ if (expr.type === utils_1.AST_NODE_TYPES.MemberExpression) {
262
+ let base = expr.object;
263
+ while (base.type === utils_1.AST_NODE_TYPES.MemberExpression) {
264
+ base = base.object;
265
+ }
266
+ if (base.type === utils_1.AST_NODE_TYPES.Identifier && base.name === name) {
267
+ return true;
268
+ }
269
+ }
270
+ }
271
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
272
+ const callee = node.callee;
273
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === name) {
274
+ return true; // function usage
275
+ }
276
+ }
277
+ if (node.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
278
+ if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
279
+ node.argument.name === name) {
280
+ return true;
281
+ }
282
+ }
283
+ if (node.type === utils_1.AST_NODE_TYPES.SpreadElement) {
284
+ if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
285
+ node.argument.name === name) {
286
+ return true;
287
+ }
288
+ }
289
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
290
+ for (const key of Object.keys(node)) {
291
+ if (key === 'parent')
292
+ continue;
293
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
+ const child = node[key];
295
+ if (!child)
296
+ continue;
297
+ if (Array.isArray(child)) {
298
+ for (const c of child) {
299
+ if (c && typeof c === 'object' && 'type' in c) {
300
+ stack.push(c);
301
+ }
302
+ }
303
+ }
304
+ else if (typeof child === 'object' && 'type' in child) {
305
+ stack.push(child);
306
+ }
307
+ }
308
+ }
309
+ return false;
310
+ }
311
+ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
312
+ name: 'prefer-use-deep-compare-memo',
313
+ meta: {
314
+ type: 'suggestion',
315
+ docs: {
316
+ description: 'Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays, functions) that are not already memoized. This prevents unnecessary re-renders due to reference changes.',
317
+ recommended: 'error',
318
+ },
319
+ fixable: 'code',
320
+ schema: [],
321
+ messages: {
322
+ preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays/functions that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
323
+ },
324
+ },
325
+ defaultOptions: [],
326
+ create(context) {
327
+ const memoizedIds = collectMemoizedIdentifiers(context);
328
+ return {
329
+ CallExpression(node) {
330
+ if (!isUseMemoCallee(node.callee))
331
+ return;
332
+ if (node.arguments.length === 0)
333
+ return;
334
+ const callback = node.arguments[0];
335
+ if (callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
336
+ callback.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
337
+ return;
338
+ }
339
+ // Ignore if JSX is present inside the memo callback
340
+ if (containsJsx(callback))
341
+ return;
342
+ // Get dependency array (last argument)
343
+ const depsArg = node.arguments[node.arguments.length - 1];
344
+ if (!depsArg || depsArg.type !== utils_1.AST_NODE_TYPES.ArrayExpression)
345
+ return;
346
+ // Empty dependency arrays should be ignored
347
+ if (depsArg.elements.length === 0)
348
+ return;
349
+ // Determine if any dependency is a non-primitive and not already memoized
350
+ let hasUnmemoizedNonPrimitive = false;
351
+ for (const el of depsArg.elements) {
352
+ if (!el)
353
+ continue; // holes
354
+ if (el.type === utils_1.AST_NODE_TYPES.SpreadElement)
355
+ continue;
356
+ const expr = el;
357
+ // TS-aware check first
358
+ let isNonPrimitive = isNonPrimitiveWithTypes(context, expr);
359
+ // Fallback heuristic without type info for literals/arrays/objects/functions
360
+ if (!isNonPrimitive) {
361
+ isNonPrimitive = isNonPrimitiveWithoutTypes(expr);
362
+ }
363
+ // Identifier-specific heuristic: consider non-primitive only if used as object or function in callback
364
+ if (!isNonPrimitive && expr.type === utils_1.AST_NODE_TYPES.Identifier) {
365
+ // Imported identifiers are treated as stable
366
+ if (isImportedIdentifier(context, expr.name)) {
367
+ isNonPrimitive = false;
368
+ }
369
+ else if (identifierUsedAsObjectOrFunction(callback, expr.name) &&
370
+ !isIdentifierMemoizedAbove(expr.name, memoizedIds)) {
371
+ isNonPrimitive = true;
372
+ }
373
+ }
374
+ if (!isNonPrimitive)
375
+ continue;
376
+ // If identifier and memoized above, skip
377
+ if (expr.type === utils_1.AST_NODE_TYPES.Identifier &&
378
+ isIdentifierMemoizedAbove(expr.name, memoizedIds)) {
379
+ continue;
380
+ }
381
+ hasUnmemoizedNonPrimitive = true;
382
+ break;
383
+ }
384
+ if (!hasUnmemoizedNonPrimitive)
385
+ return;
386
+ context.report({
387
+ node,
388
+ messageId: 'preferUseDeepCompareMemo',
389
+ data: {
390
+ hook: 'useMemo',
391
+ },
392
+ fix(fixer) {
393
+ const fixes = [];
394
+ // Replace callee
395
+ if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier) {
396
+ fixes.push(fixer.replaceText(node.callee, 'useDeepCompareMemo'));
397
+ }
398
+ else if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression) {
399
+ fixes.push(fixer.replaceText(node.callee, 'useDeepCompareMemo'));
400
+ }
401
+ // Ensure import exists
402
+ fixes.push(...ensureDeepCompareImportFixes(context, fixer));
403
+ // Clean up now-unused React/useMemo imports if safe
404
+ const sourceCode = context.sourceCode;
405
+ const program = sourceCode.ast;
406
+ const reactImport = program.body.find((n) => n.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
407
+ n.source.value === 'react');
408
+ if (reactImport) {
409
+ // remove named useMemo if unused
410
+ const useMemoSpec = reactImport.specifiers.find((s) => s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
411
+ s.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
412
+ s.imported.name === 'useMemo');
413
+ if (useMemoSpec &&
414
+ !isIdentifierReferenced(sourceCode, useMemoSpec.local.name)) {
415
+ fixes.push(...removeImportSpecifierFixes(sourceCode, fixer, reactImport, useMemoSpec));
416
+ }
417
+ const defaultSpec = reactImport.specifiers.find((s) => s.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier);
418
+ if (defaultSpec &&
419
+ !isIdentifierReferenced(sourceCode, defaultSpec.local.name)) {
420
+ fixes.push(...removeImportSpecifierFixes(sourceCode, fixer, reactImport, defaultSpec));
421
+ }
422
+ }
423
+ return fixes;
424
+ },
425
+ });
426
+ },
427
+ };
428
+ },
429
+ });
430
+ exports.default = exports.preferUseDeepCompareMemo;
431
+ //# sourceMappingURL=prefer-use-deep-compare-memo.js.map
@@ -29,7 +29,7 @@ exports.preferUseCallbackOverUseMemoForFunctions = (0, createRule_1.createRule)(
29
29
  },
30
30
  ],
31
31
  messages: {
32
- preferUseCallback: 'Use useCallback instead of useMemo for memoizing functions',
32
+ preferUseCallback: 'useMemo returns {{callbackDescription}}, which hides that you are memoizing a callback. useCallback is built to keep function identities stable and signals intent to other hook consumers. Replace useMemo with useCallback and keep the same dependency array so props relying on stable references do not re-render unnecessarily.',
33
33
  },
34
34
  },
35
35
  defaultOptions: [{ allowComplexBodies: false, allowFunctionFactories: true }],
@@ -144,8 +144,29 @@ exports.preferUseCallbackOverUseMemoForFunctions = (0, createRule_1.createRule)(
144
144
  };
145
145
  },
146
146
  });
147
+ function getMemoizedFunctionDescription(node) {
148
+ const parent = node.parent;
149
+ if (parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
150
+ parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
151
+ return `the callback "${parent.id.name}"`;
152
+ }
153
+ if (parent?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
154
+ parent.left.type === utils_1.AST_NODE_TYPES.Identifier) {
155
+ return `the callback "${parent.left.name}"`;
156
+ }
157
+ if (parent?.type === utils_1.AST_NODE_TYPES.Property &&
158
+ parent.key.type === utils_1.AST_NODE_TYPES.Identifier) {
159
+ return `the "${parent.key.name}" property callback`;
160
+ }
161
+ if (parent?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
162
+ parent.parent?.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
163
+ parent.parent.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
164
+ return `the "${parent.parent.name.name}" prop callback`;
165
+ }
166
+ return 'this callback value';
167
+ }
147
168
  function reportAndFix(node, context) {
148
- const sourceCode = context.getSourceCode();
169
+ const sourceCode = context.sourceCode;
149
170
  const useMemoCallback = node.arguments[0];
150
171
  const dependencyArray = node.arguments[1]
151
172
  ? sourceCode.getText(node.arguments[1])
@@ -168,9 +189,11 @@ function reportAndFix(node, context) {
168
189
  const typeParametersText = hasTypeParameters
169
190
  ? sourceCode.getText(node.typeParameters)
170
191
  : '';
192
+ const callbackDescription = getMemoizedFunctionDescription(node);
171
193
  context.report({
172
194
  node,
173
195
  messageId: 'preferUseCallback',
196
+ data: { callbackDescription },
174
197
  fix: (fixer) => {
175
198
  return fixer.replaceText(node, `useCallback${typeParametersText}(${returnedFunctionText}, ${dependencyArray})`);
176
199
  },