@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
@@ -24,17 +24,43 @@ const SUGGESTED_ALTERNATIVES = {
24
24
  process: ['transform', 'sanitize', 'compute'],
25
25
  do: ['execute', 'perform', 'apply'],
26
26
  };
27
+ function extractFirstWord(name) {
28
+ let firstWord = name;
29
+ for (let i = 1; i < name.length; i++) {
30
+ const currentChar = name[i];
31
+ const prevChar = name[i - 1];
32
+ // lowercase -> Uppercase (camel/Pascal boundary)
33
+ if (prevChar >= 'a' &&
34
+ prevChar <= 'z' &&
35
+ currentChar >= 'A' &&
36
+ currentChar <= 'Z') {
37
+ firstWord = name.substring(0, i);
38
+ break;
39
+ }
40
+ // UPPERCASE acronym -> lowercase word start (e.g., XML|Http)
41
+ if (prevChar >= 'A' &&
42
+ prevChar <= 'Z' &&
43
+ currentChar >= 'a' &&
44
+ currentChar <= 'z') {
45
+ if (i > 1) {
46
+ firstWord = name.substring(0, i - 1);
47
+ break;
48
+ }
49
+ }
50
+ }
51
+ return firstWord;
52
+ }
27
53
  exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
28
54
  name: 'semantic-function-prefixes',
29
55
  meta: {
30
56
  type: 'suggestion',
31
57
  docs: {
32
- description: 'Enforce semantic function prefixes over generic ones like "get" and "update"',
58
+ description: 'Require semantic function prefixes instead of generic verbs so callers know whether a function fetches data, transforms input, or mutates state',
33
59
  recommended: 'error',
34
60
  },
35
61
  schema: [],
36
62
  messages: {
37
- avoidGenericPrefix: 'Avoid using generic prefix "{{prefix}}". Consider using one of these alternatives: {{alternatives}}',
63
+ avoidGenericPrefix: 'Function "{{functionName}}" starts with the generic prefix "{{prefix}}", which hides whether it fetches remote data, transforms input, or mutates state. Use a semantic verb such as {{alternatives}} to describe the operation and set caller expectations.',
38
64
  },
39
65
  },
40
66
  defaultOptions: [],
@@ -54,20 +80,17 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
54
80
  if (NEXTJS_DATA_FUNCTIONS.has(methodName))
55
81
  return;
56
82
  // Extract first word from PascalCase/camelCase
57
- let firstWord = methodName;
58
- for (let i = 1; i < methodName.length; i++) {
59
- if (methodName[i] >= 'A' && methodName[i] <= 'Z') {
60
- firstWord = methodName.substring(0, i);
61
- break;
62
- }
63
- }
83
+ const firstWord = extractFirstWord(methodName);
64
84
  // Check for disallowed prefixes
85
+ // Only flag if the disallowed word is used as a prefix (not the entire name)
65
86
  for (const prefix of DISALLOWED_PREFIXES) {
66
- if (firstWord.toLowerCase() === prefix.toLowerCase()) {
87
+ if (firstWord.toLowerCase() === prefix.toLowerCase() &&
88
+ firstWord.length < methodName.length) {
67
89
  context.report({
68
90
  node: node.key,
69
91
  messageId: 'avoidGenericPrefix',
70
92
  data: {
93
+ functionName: methodName,
71
94
  prefix,
72
95
  alternatives: SUGGESTED_ALTERNATIVES[prefix].join(', '),
73
96
  },
@@ -99,20 +122,17 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
99
122
  if (NEXTJS_DATA_FUNCTIONS.has(functionName))
100
123
  return;
101
124
  // Extract first word from PascalCase/camelCase
102
- let firstWord = functionName;
103
- for (let i = 1; i < functionName.length; i++) {
104
- if (functionName[i] >= 'A' && functionName[i] <= 'Z') {
105
- firstWord = functionName.substring(0, i);
106
- break;
107
- }
108
- }
125
+ const firstWord = extractFirstWord(functionName);
109
126
  // Check for disallowed prefixes
127
+ // Only flag if the disallowed word is used as a prefix (not the entire name)
110
128
  for (const prefix of DISALLOWED_PREFIXES) {
111
- if (firstWord.toLowerCase() === prefix.toLowerCase()) {
129
+ if (firstWord.toLowerCase() === prefix.toLowerCase() &&
130
+ firstWord.length < functionName.length) {
112
131
  context.report({
113
132
  node: node.id || node,
114
133
  messageId: 'avoidGenericPrefix',
115
134
  data: {
135
+ functionName,
116
136
  prefix,
117
137
  alternatives: SUGGESTED_ALTERNATIVES[prefix].join(', '),
118
138
  },
@@ -14,7 +14,7 @@ exports.syncOnwriteNameFunc = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- mismatchedName: 'The name field should match the func field in onWrite handlers',
17
+ mismatchedName: 'OnWrite handler name "{{nameValue}}" does not match func reference "{{funcName}}". The name field is what gets registered for deploys, logs, and alerts, so a mismatch hides which function is actually running. Rename the "name" value to "{{funcName}}" or point "func" to a function named "{{nameValue}}" so the trigger label and implementation stay in sync.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -68,6 +68,10 @@ exports.syncOnwriteNameFunc = (0, createRule_1.createRule)({
68
68
  context.report({
69
69
  node: nameProperty,
70
70
  messageId: 'mismatchedName',
71
+ data: {
72
+ nameValue,
73
+ funcName,
74
+ },
71
75
  fix(fixer) {
72
76
  return fixer.replaceText(value, `'${funcName}'`);
73
77
  },
@@ -0,0 +1 @@
1
+ export declare const testFileLocationEnforcement: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"misplacedTestFile", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.testFileLocationEnforcement = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const createRule_1 = require("../utils/createRule");
10
+ const TEST_FILE_PATTERN = /\.test\.tsx?$/i;
11
+ const SUPPORTED_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
12
+ exports.testFileLocationEnforcement = (0, createRule_1.createRule)({
13
+ name: 'test-file-location-enforcement',
14
+ meta: {
15
+ type: 'suggestion',
16
+ docs: {
17
+ description: 'Enforce colocating *.test.ts or *.test.tsx files with the code they cover.',
18
+ recommended: 'error',
19
+ },
20
+ schema: [],
21
+ messages: {
22
+ misplacedTestFile: 'Test file "{{testFile}}" is not colocated with its subject. Keep tests in the same directory as {{expectedNames}} so refactors move code and coverage together and engineers can find the implementation without searching separate test folders.',
23
+ },
24
+ },
25
+ defaultOptions: [],
26
+ create(context) {
27
+ return {
28
+ Program(node) {
29
+ const filename = context.getFilename();
30
+ if (filename === '<input>' ||
31
+ filename === '<text>' ||
32
+ filename.includes('node_modules') ||
33
+ !TEST_FILE_PATTERN.test(filename)) {
34
+ return;
35
+ }
36
+ const directory = path_1.default.dirname(filename);
37
+ const testFileName = path_1.default.basename(filename);
38
+ const baseName = testFileName.replace(TEST_FILE_PATTERN, '');
39
+ const candidates = SUPPORTED_EXTENSIONS.map((extension) => path_1.default.join(directory, `${baseName}${extension}`));
40
+ const hasSibling = candidates.some((candidate) => fs_1.default.existsSync(candidate));
41
+ if (hasSibling) {
42
+ return;
43
+ }
44
+ const relativePath = path_1.default.isAbsolute(filename)
45
+ ? path_1.default.relative(process.cwd(), filename) || filename
46
+ : filename;
47
+ const expectedNames = SUPPORTED_EXTENSIONS.map((extension) => `"${baseName}${extension}"`).join(' or ');
48
+ context.report({
49
+ node,
50
+ messageId: 'misplacedTestFile',
51
+ data: { testFile: relativePath, expectedNames },
52
+ });
53
+ },
54
+ };
55
+ },
56
+ });
57
+ //# sourceMappingURL=test-file-location-enforcement.js.map
@@ -13,7 +13,7 @@ exports.useCustomLink = (0, createRule_1.createRule)({
13
13
  fixable: 'code',
14
14
  schema: [],
15
15
  messages: {
16
- useCustomLink: 'Import Link from src/components/Link instead of next/link',
16
+ useCustomLink: 'Import "{{localName}}" from src/components/Link instead of next/link. The custom Link wraps Next.js navigation with design system defaults and analytics hooks; importing next/link bypasses those wrappers and leads to inconsistent styling and missing instrumentation. Replace the import source with src/components/Link so routing uses the shared wrapper.',
17
17
  },
18
18
  },
19
19
  defaultOptions: [],
@@ -30,6 +30,11 @@ exports.useCustomLink = (0, createRule_1.createRule)({
30
30
  context.report({
31
31
  node,
32
32
  messageId: 'useCustomLink',
33
+ data: {
34
+ localName: defaultSpecifier?.local?.name ||
35
+ defaultAsSpecifier?.local?.name ||
36
+ 'Link',
37
+ },
33
38
  fix(fixer) {
34
39
  // Get the local name of the imported Link component
35
40
  const localName = defaultSpecifier?.local?.name ||
@@ -14,7 +14,7 @@ exports.useCustomRouter = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- useCustomRouter: 'Import useRouter from src/hooks/routing/useRouter instead of next/router',
17
+ useCustomRouter: 'useRouter import "{{imports}}" comes from next/router, which bypasses the app-specific hook that applies auth checks, analytics, and redirect helpers. Import it from src/hooks/routing/useRouter so routing code stays consistent with the rest of the app.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -29,6 +29,11 @@ exports.useCustomRouter = (0, createRule_1.createRule)({
29
29
  context.report({
30
30
  node,
31
31
  messageId: 'useCustomRouter',
32
+ data: {
33
+ imports: specifiers
34
+ .map((specifier) => specifier.local.name)
35
+ .join(', '),
36
+ },
32
37
  fix(fixer) {
33
38
  // If there are other imports from next/router, keep them
34
39
  const otherSpecifiers = node.specifiers.filter((specifier) => specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier ||
@@ -0,0 +1,3 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const useLatestCallback: TSESLint.RuleModule<"useLatestCallback", [], TSESLint.RuleListener>;
3
+ export default useLatestCallback;
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useLatestCallback = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.useLatestCallback = (0, createRule_1.createRule)({
7
+ name: 'use-latest-callback',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce using useLatestCallback from use-latest-callback instead of React useCallback',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ useLatestCallback: 'Replace {{currentHook}} with {{recommendedHook}} from "use-latest-callback" so the callback keeps a stable reference while still reading the latest props/state. useCallback recreates functions whenever dependencies change, which can trigger needless renders and stale closures. Drop the dependency array when switching to {{recommendedHook}}.',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const filename = context.getFilename();
23
+ if (filename.includes('/node_modules/')) {
24
+ return {};
25
+ }
26
+ // Track if the file already has a useLatestCallback import
27
+ let hasUseLatestCallbackImport = false;
28
+ let useLatestCallbackImportName = 'useLatestCallback';
29
+ // Track if any useCallback calls should be replaced
30
+ let hasNonJsxUseCallbacks = false;
31
+ const useCallbackLocalNames = new Set();
32
+ const reactNamespaceNames = new Set();
33
+ const reactDefaultLikeNames = new Set();
34
+ let hasReactMemberUseCallback = false;
35
+ const isJSXLikeReturn = (node) => {
36
+ if (!node)
37
+ return false;
38
+ if (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
39
+ node.type === utils_1.AST_NODE_TYPES.JSXFragment ||
40
+ node.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
41
+ return true;
42
+ }
43
+ if (node.type === 'ParenthesizedExpression') {
44
+ return isJSXLikeReturn(node.expression);
45
+ }
46
+ if (node.type === utils_1.AST_NODE_TYPES.SequenceExpression) {
47
+ return isJSXLikeReturn(node.expressions[node.expressions.length - 1]);
48
+ }
49
+ if (node.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
50
+ return node.elements.some((el) => isJSXLikeReturn(el && el.type === utils_1.AST_NODE_TYPES.SpreadElement
51
+ ? el.argument
52
+ : el));
53
+ }
54
+ if (node.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
55
+ return (isJSXLikeReturn(node.consequent) || isJSXLikeReturn(node.alternate));
56
+ }
57
+ if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
58
+ return isJSXLikeReturn(node.left) || isJSXLikeReturn(node.right);
59
+ }
60
+ if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
61
+ if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
62
+ node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
63
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
64
+ reactNamespaceNames.has(node.callee.object.name) &&
65
+ node.callee.property.name === 'createElement') {
66
+ return true;
67
+ }
68
+ }
69
+ return false;
70
+ };
71
+ return {
72
+ // First pass: check imports
73
+ ImportDeclaration(node) {
74
+ if (node.importKind && node.importKind !== 'value')
75
+ return;
76
+ if (node.source.value === 'use-latest-callback') {
77
+ // Check if useLatestCallback is imported
78
+ const specifiers = node.specifiers.filter((specifier) => {
79
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
80
+ return (specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
81
+ specifier.imported.name === 'useLatestCallback');
82
+ }
83
+ return (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
84
+ specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
85
+ });
86
+ if (specifiers.length > 0) {
87
+ hasUseLatestCallbackImport = true;
88
+ // Get the local name of the import (in case it's renamed)
89
+ if (specifiers[0].type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
90
+ useLatestCallbackImportName = specifiers[0].local.name;
91
+ }
92
+ else if (specifiers[0].type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier) {
93
+ useLatestCallbackImportName = specifiers[0].local.name;
94
+ }
95
+ else if (specifiers[0].type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
96
+ useLatestCallbackImportName = `${specifiers[0].local.name}.useLatestCallback`;
97
+ }
98
+ }
99
+ }
100
+ else if (node.source.value === 'react') {
101
+ // Check if useCallback is imported from React
102
+ const useCallbackSpecifiers = node.specifiers.filter((specifier) => {
103
+ return (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
104
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
105
+ specifier.imported.name === 'useCallback');
106
+ });
107
+ node.specifiers.forEach((specifier) => {
108
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
109
+ specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
110
+ reactNamespaceNames.add(specifier.local.name);
111
+ reactDefaultLikeNames.add(specifier.local.name);
112
+ }
113
+ });
114
+ if (useCallbackSpecifiers.length > 0) {
115
+ useCallbackSpecifiers.forEach((spec) => {
116
+ useCallbackLocalNames.add(spec.local.name);
117
+ });
118
+ }
119
+ }
120
+ },
121
+ // Check for useCallback usage and identify JSX-returning callbacks
122
+ CallExpression(node) {
123
+ const isUseCallbackIdentifierCall = node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
124
+ useCallbackLocalNames.has(node.callee.name);
125
+ const isUseCallbackMemberCall = node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
126
+ node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
127
+ reactDefaultLikeNames.has(node.callee.object.name) &&
128
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
129
+ node.callee.property.name === 'useCallback';
130
+ if ((isUseCallbackIdentifierCall || isUseCallbackMemberCall) &&
131
+ node.arguments.length >= 1) {
132
+ if (isUseCallbackMemberCall) {
133
+ hasReactMemberUseCallback = true;
134
+ }
135
+ // Check if this is a JSX-returning callback (edge case where we shouldn't replace)
136
+ const callback = node.arguments[0];
137
+ let isJsxReturning = false;
138
+ if ((callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
139
+ callback.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
140
+ callback.body) {
141
+ // For arrow functions with implicit return
142
+ if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
143
+ if (isJSXLikeReturn(callback.body)) {
144
+ isJsxReturning = true;
145
+ }
146
+ }
147
+ // For functions with block body - check all return statements
148
+ else if (callback.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
149
+ const returnStatements = callback.body.body.filter((statement) => statement.type === utils_1.AST_NODE_TYPES.ReturnStatement);
150
+ // If there are return statements, check if any return JSX
151
+ if (returnStatements.length > 0) {
152
+ isJsxReturning = returnStatements.some((returnStatement) => returnStatement.argument &&
153
+ isJSXLikeReturn(returnStatement.argument));
154
+ }
155
+ }
156
+ }
157
+ if (!isJsxReturning) {
158
+ hasNonJsxUseCallbacks = true;
159
+ const currentCallbackName = node.callee.type === utils_1.AST_NODE_TYPES.Identifier
160
+ ? node.callee.name
161
+ : node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
162
+ node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier
163
+ ? node.callee.property.name
164
+ : 'useCallback';
165
+ const replacementName = hasUseLatestCallbackImport
166
+ ? useLatestCallbackImportName
167
+ : currentCallbackName === 'useCallback'
168
+ ? 'useLatestCallback'
169
+ : currentCallbackName;
170
+ // Report the useCallback call for replacement
171
+ context.report({
172
+ node,
173
+ messageId: 'useLatestCallback',
174
+ data: {
175
+ currentHook: currentCallbackName,
176
+ recommendedHook: replacementName,
177
+ },
178
+ fix(fixer) {
179
+ const sourceCode = context.sourceCode;
180
+ const callbackText = sourceCode.getText(node.arguments[0]);
181
+ const typeParams = node.typeParameters
182
+ ? sourceCode.getText(node.typeParameters)
183
+ : '';
184
+ // Replace useCallback with useLatestCallback and remove the dependency array
185
+ return fixer.replaceText(node, `${replacementName}${typeParams}(${callbackText})`);
186
+ },
187
+ });
188
+ }
189
+ }
190
+ },
191
+ // Final pass: check for useCallback import from react (only if there are non-JSX callbacks)
192
+ 'Program:exit'() {
193
+ if (!hasNonJsxUseCallbacks) {
194
+ return; // Don't modify imports if all useCallback calls return JSX
195
+ }
196
+ const sourceCode = context.sourceCode;
197
+ const program = sourceCode.ast;
198
+ for (const statement of program.body) {
199
+ if (statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
200
+ statement.source.value === 'react') {
201
+ if (statement.importKind && statement.importKind !== 'value') {
202
+ continue;
203
+ }
204
+ const specifiers = statement.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
205
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
206
+ specifier.imported.name === 'useCallback');
207
+ if (specifiers.length > 0 || hasReactMemberUseCallback) {
208
+ const useCallbackLocalName = specifiers[0]?.local.name ?? 'useCallback';
209
+ const recommendedHook = hasUseLatestCallbackImport
210
+ ? useLatestCallbackImportName
211
+ : useCallbackLocalName === 'useCallback'
212
+ ? 'useLatestCallback'
213
+ : useCallbackLocalName;
214
+ context.report({
215
+ node: statement,
216
+ messageId: 'useLatestCallback',
217
+ data: {
218
+ currentHook: useCallbackLocalName,
219
+ recommendedHook,
220
+ },
221
+ fix(fixer) {
222
+ const importText = `import ${useCallbackLocalName === 'useCallback'
223
+ ? 'useLatestCallback'
224
+ : useCallbackLocalName} from 'use-latest-callback';`;
225
+ if (specifiers.length === 0) {
226
+ if (hasUseLatestCallbackImport)
227
+ return null;
228
+ return fixer.insertTextBefore(statement, `${importText}\n`);
229
+ }
230
+ const defaultOrNamespace = statement.specifiers.find((s) => s.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
231
+ s.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier);
232
+ const remainingNamed = statement.specifiers.filter((s) => s.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
233
+ s.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
234
+ s.imported.name !== 'useCallback');
235
+ const prefix = statement.importKind && statement.importKind !== 'value'
236
+ ? 'import type'
237
+ : 'import';
238
+ if (remainingNamed.length === 0 && !defaultOrNamespace) {
239
+ if (!hasUseLatestCallbackImport) {
240
+ return fixer.replaceText(statement, importText);
241
+ }
242
+ return fixer.remove(statement);
243
+ }
244
+ const parts = [];
245
+ if (defaultOrNamespace) {
246
+ parts.push(sourceCode.getText(defaultOrNamespace));
247
+ }
248
+ if (remainingNamed.length > 0) {
249
+ parts.push(`{ ${remainingNamed
250
+ .map((s) => sourceCode.getText(s))
251
+ .join(', ')} }`);
252
+ }
253
+ const replacement = `${prefix} ${parts.join(', ')} from 'react';`;
254
+ const fixes = [];
255
+ if (!hasUseLatestCallbackImport) {
256
+ fixes.push(fixer.insertTextBefore(statement, `${importText}\n`));
257
+ }
258
+ fixes.push(fixer.replaceText(statement, replacement));
259
+ return fixes;
260
+ },
261
+ });
262
+ }
263
+ break;
264
+ }
265
+ }
266
+ },
267
+ };
268
+ },
269
+ });
270
+ exports.default = exports.useLatestCallback;
271
+ //# sourceMappingURL=use-latest-callback.js.map
@@ -0,0 +1,16 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type GroupName = 'event-handlers' | 'utilities' | 'other';
3
+ type DependencyDirection = 'callers-first' | 'callees-first';
4
+ type ExportPlacement = 'top' | 'bottom' | 'ignore';
5
+ type Options = [
6
+ Partial<{
7
+ exportPlacement: ExportPlacement;
8
+ dependencyDirection: DependencyDirection;
9
+ groupOrder: GroupName[];
10
+ eventHandlerPattern: string;
11
+ utilityPattern: string;
12
+ }>
13
+ ];
14
+ type MessageIds = 'misorderedFunction';
15
+ export declare const verticallyGroupRelatedFunctions: TSESLint.RuleModule<MessageIds, Options>;
16
+ export {};