@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,365 @@
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.enforceUniqueCursorHeaders = exports.RULE_NAME = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const minimatch_1 = require("minimatch");
9
+ const createRule_1 = require("../utils/createRule");
10
+ const DEFAULT_OPTIONS = {
11
+ requiredPatterns: ['**/*.{ts,tsx,js,jsx}'],
12
+ excludedPatterns: [
13
+ '**/*.d.ts',
14
+ '**/dist/**',
15
+ '**/build/**',
16
+ '**/coverage/**',
17
+ ],
18
+ requiredTags: ['@fileoverview'],
19
+ allowSplitHeaders: true,
20
+ headerTemplate: null,
21
+ ignoreGeneratedFiles: true,
22
+ generatedMarkers: [
23
+ '@generated',
24
+ 'auto-generated',
25
+ 'autogenerated',
26
+ 'do not edit',
27
+ 'do not edit manually',
28
+ ],
29
+ excludedAtDirectives: [
30
+ '@ts-nocheck',
31
+ '@ts-check',
32
+ '@ts-ignore',
33
+ '@ts-expect-error',
34
+ '@jsx',
35
+ '@jsxRuntime',
36
+ '@jsxImportSource',
37
+ '@format',
38
+ '@flow',
39
+ '@jest-environment',
40
+ '@vitest-environment',
41
+ ],
42
+ };
43
+ /**
44
+ * Maximum number of characters to scan at the beginning of a file to detect generated markers.
45
+ */
46
+ const GENERATED_MARKER_SCAN_LENGTH = 500;
47
+ const normalizeArrayOption = (values, fallback) => values && values.length > 0 ? values : fallback;
48
+ const normalizeArrayOptionAllowEmpty = (values, fallback) => (values === undefined ? fallback : values);
49
+ const normalizeBooleanOption = (value, fallback) => value ?? fallback;
50
+ const normalizeNullableOption = (value, fallback) => (value === undefined ? fallback : value);
51
+ const normalizeOptions = (options) => ({
52
+ requiredPatterns: normalizeArrayOption(options?.requiredPatterns, DEFAULT_OPTIONS.requiredPatterns),
53
+ excludedPatterns: normalizeArrayOptionAllowEmpty(options?.excludedPatterns, DEFAULT_OPTIONS.excludedPatterns),
54
+ requiredTags: normalizeArrayOption(options?.requiredTags, DEFAULT_OPTIONS.requiredTags),
55
+ allowSplitHeaders: normalizeBooleanOption(options?.allowSplitHeaders, DEFAULT_OPTIONS.allowSplitHeaders),
56
+ headerTemplate: normalizeNullableOption(options?.headerTemplate, DEFAULT_OPTIONS.headerTemplate),
57
+ ignoreGeneratedFiles: normalizeBooleanOption(options?.ignoreGeneratedFiles, DEFAULT_OPTIONS.ignoreGeneratedFiles),
58
+ generatedMarkers: normalizeArrayOptionAllowEmpty(options?.generatedMarkers, DEFAULT_OPTIONS.generatedMarkers),
59
+ excludedAtDirectives: normalizeArrayOptionAllowEmpty(options?.excludedAtDirectives, DEFAULT_OPTIONS.excludedAtDirectives).map((directive) => directive.toLowerCase()),
60
+ });
61
+ const normalizeCommentValue = (comment) => comment.value
62
+ .split('\n')
63
+ .map((line) => line.replace(/^\s*\*\s?/, '').trim())
64
+ .join('\n')
65
+ .trim();
66
+ const hasRequiredTags = (text, requiredTagsLower) => {
67
+ const lowerText = text.toLowerCase();
68
+ return requiredTagsLower.some((tag) => lowerText.includes(tag));
69
+ };
70
+ const hasAllRequiredTags = (text, requiredTagsLower) => {
71
+ const lowerText = text.toLowerCase();
72
+ return requiredTagsLower.every((tag) => lowerText.includes(tag));
73
+ };
74
+ const hasHeaderMarker = (text, excludedAtDirectives) => text.split('\n').some((line) => {
75
+ const trimmed = line.trimStart();
76
+ if (!trimmed.startsWith('@')) {
77
+ return false;
78
+ }
79
+ const marker = trimmed.split(/\s+/u)[0].toLowerCase();
80
+ return !excludedAtDirectives.has(marker);
81
+ });
82
+ const isHeaderCandidate = (text, requiredTagsLower, excludedAtDirectives) => hasRequiredTags(text, requiredTagsLower) ||
83
+ hasHeaderMarker(text, excludedAtDirectives);
84
+ const isCommentAdjacent = (nextComment, lastComment) => nextComment.loc.start.line <= lastComment.loc.end.line + 1;
85
+ const shouldStopMerging = (groupedHasAllTags, nextHasAllTags) => groupedHasAllTags && nextHasAllTags;
86
+ const updateMergedState = (mergedComments, mergedText, nextComment, nextNormalized, requiredTagsLower) => {
87
+ const updatedText = `${mergedText}\n${nextNormalized}`.trim();
88
+ const updatedComments = [...mergedComments, nextComment];
89
+ const updatedHasAllTags = hasAllRequiredTags(updatedText, requiredTagsLower);
90
+ return {
91
+ mergedComments: updatedComments,
92
+ mergedText: updatedText,
93
+ groupedHasAllTags: updatedHasAllTags,
94
+ };
95
+ };
96
+ /**
97
+ * Merges adjacent header candidate comments into a single group if they are not already complete headers.
98
+ * This handles "split headers" where metadata is spread across multiple comment blocks.
99
+ */
100
+ const mergeAdjacentHeaderCandidates = (comments, startIndex, initialComment, initialText, requiredTagsLower, excludedAtDirectives) => {
101
+ let mergedComments = [initialComment];
102
+ let mergedText = initialText;
103
+ let groupedHasAllTags = hasAllRequiredTags(mergedText, requiredTagsLower);
104
+ let nextIndex = startIndex + 1;
105
+ while (nextIndex < comments.length) {
106
+ const nextComment = comments[nextIndex];
107
+ const lastComment = mergedComments[mergedComments.length - 1];
108
+ if (!isCommentAdjacent(nextComment, lastComment)) {
109
+ break;
110
+ }
111
+ const nextNormalized = normalizeCommentValue(nextComment);
112
+ if (!isHeaderCandidate(nextNormalized, requiredTagsLower, excludedAtDirectives)) {
113
+ break;
114
+ }
115
+ const nextHasAllTags = hasAllRequiredTags(nextNormalized, requiredTagsLower);
116
+ if (shouldStopMerging(groupedHasAllTags, nextHasAllTags)) {
117
+ break;
118
+ }
119
+ const updatedState = updateMergedState(mergedComments, mergedText, nextComment, nextNormalized, requiredTagsLower);
120
+ mergedComments = updatedState.mergedComments;
121
+ mergedText = updatedState.mergedText;
122
+ groupedHasAllTags = updatedState.groupedHasAllTags;
123
+ nextIndex += 1;
124
+ }
125
+ return {
126
+ mergedComments,
127
+ mergedText,
128
+ lastIndex: nextIndex - 1,
129
+ };
130
+ };
131
+ const collectHeaderGroups = (comments, options, excludedAtDirectives) => {
132
+ const requiredTagsLower = options.requiredTags.map((tag) => tag.toLowerCase());
133
+ const groups = [];
134
+ for (let index = 0; index < comments.length; index += 1) {
135
+ const comment = comments[index];
136
+ const normalized = normalizeCommentValue(comment);
137
+ if (!isHeaderCandidate(normalized, requiredTagsLower, excludedAtDirectives)) {
138
+ continue;
139
+ }
140
+ let groupedComments = [comment];
141
+ let groupedText = normalized;
142
+ if (options.allowSplitHeaders) {
143
+ const merged = mergeAdjacentHeaderCandidates(comments, index, comment, normalized, requiredTagsLower, excludedAtDirectives);
144
+ groupedComments = merged.mergedComments;
145
+ groupedText = merged.mergedText;
146
+ index = merged.lastIndex;
147
+ }
148
+ groups.push({ comments: groupedComments, text: groupedText });
149
+ }
150
+ return groups;
151
+ };
152
+ const expandRangeToFullLines = (range, sourceText) => {
153
+ let [start, end] = range;
154
+ while (start > 0 && sourceText[start - 1] !== '\n') {
155
+ start -= 1;
156
+ }
157
+ while (end < sourceText.length && sourceText[end] !== '\n') {
158
+ end += 1;
159
+ }
160
+ if (end < sourceText.length) {
161
+ end += 1;
162
+ }
163
+ return [start, end];
164
+ };
165
+ // Preserve author-provided trailing newlines while guaranteeing a blank line after the header.
166
+ const buildHeaderInsertionText = (template) => {
167
+ const normalizedWhitespace = template
168
+ .replace(/[ \t]+(\r?\n)/gu, '$1')
169
+ .replace(/[ \t]+$/u, '');
170
+ const trailingNewlines = normalizedWhitespace.match(/\n+$/u)?.[0].length ?? 0;
171
+ const requiredNewlines = trailingNewlines >= 2 ? 0 : 2 - trailingNewlines;
172
+ return `${normalizedWhitespace}${'\n'.repeat(requiredNewlines)}`;
173
+ };
174
+ const shouldSkipGeneratedFile = (sourceText, options) => {
175
+ if (!options.ignoreGeneratedFiles) {
176
+ return false;
177
+ }
178
+ const prefix = sourceText
179
+ .slice(0, GENERATED_MARKER_SCAN_LENGTH)
180
+ .toLowerCase();
181
+ return options.generatedMarkers.some((marker) => prefix.includes(marker.toLowerCase()));
182
+ };
183
+ const getTopComments = (sourceCode, node) => {
184
+ const allComments = sourceCode.getAllComments();
185
+ const firstToken = sourceCode.getFirstToken(node);
186
+ const firstTokenLine = firstToken?.loc.start.line ?? Infinity;
187
+ return allComments.filter((comment) => comment.loc.start.line < firstTokenLine);
188
+ };
189
+ const analyzeHeaderGroups = (candidateGroups, options) => {
190
+ const requiredTagsLower = options.requiredTags.map((tag) => tag.toLowerCase());
191
+ const headerGroups = candidateGroups.filter((group) => hasAllRequiredTags(group.text, requiredTagsLower));
192
+ const primaryHeader = headerGroups[0];
193
+ const duplicateGroups = headerGroups.slice(1);
194
+ const splitHeaderGroups = options.allowSplitHeaders
195
+ ? []
196
+ : candidateGroups.filter((group) => {
197
+ if (group === primaryHeader || !primaryHeader) {
198
+ return false;
199
+ }
200
+ const groupStart = group.comments[0].loc.start.line;
201
+ const groupEnd = group.comments[group.comments.length - 1].loc.end.line;
202
+ const primaryStart = primaryHeader.comments[0].loc.start.line;
203
+ const primaryEnd = primaryHeader.comments[primaryHeader.comments.length - 1].loc.end
204
+ .line;
205
+ /**
206
+ * Check if group is adjacent to primary (before or after).
207
+ * The +1 allows for touching lines (no blank line in between).
208
+ */
209
+ const isAdjacentToPrimary = groupStart <= primaryEnd + 1 && groupEnd + 1 >= primaryStart;
210
+ const hasAllTags = hasAllRequiredTags(group.text, requiredTagsLower);
211
+ return isAdjacentToPrimary && !hasAllTags;
212
+ });
213
+ return { primaryHeader, duplicateGroups, splitHeaderGroups };
214
+ };
215
+ const computeHeaderInsertion = (sourceText, headerTemplate) => {
216
+ const headerText = buildHeaderInsertionText(headerTemplate);
217
+ if (!sourceText.startsWith('#!')) {
218
+ return { index: 0, text: headerText };
219
+ }
220
+ const shebangNewlineIndex = sourceText.indexOf('\n');
221
+ if (shebangNewlineIndex === -1) {
222
+ /** Shebang exists but file has no newline - append after shebang */
223
+ return { index: sourceText.length, text: `\n${headerText}` };
224
+ }
225
+ /** Insert after shebang line */
226
+ return { index: shebangNewlineIndex + 1, text: headerText };
227
+ };
228
+ const reportMissingHeader = (context, fileName, sourceText, options) => {
229
+ const template = options.headerTemplate;
230
+ context.report({
231
+ loc: { line: 1, column: 0 },
232
+ messageId: 'missingHeader',
233
+ data: {
234
+ fileName: path_1.default.basename(fileName),
235
+ tags: options.requiredTags.join(', '),
236
+ },
237
+ fix: template !== null
238
+ ? (fixer) => {
239
+ const { index, text } = computeHeaderInsertion(sourceText, template);
240
+ return fixer.insertTextBeforeRange([index, index], text);
241
+ }
242
+ : null,
243
+ });
244
+ };
245
+ const reportDuplicates = (context, duplicateGroups, sourceText) => {
246
+ duplicateGroups.forEach((group) => {
247
+ const firstComment = group.comments[0];
248
+ const removalRange = expandRangeToFullLines([
249
+ firstComment.range[0],
250
+ group.comments[group.comments.length - 1].range[1],
251
+ ], sourceText);
252
+ context.report({
253
+ loc: firstComment.loc,
254
+ messageId: 'duplicateHeader',
255
+ data: {
256
+ line: firstComment.loc.start.line.toString(),
257
+ },
258
+ fix: (fixer) => fixer.removeRange(removalRange),
259
+ });
260
+ });
261
+ };
262
+ const reportSplitFragments = (context, splitHeaderGroups, options) => {
263
+ splitHeaderGroups.forEach((group) => {
264
+ const firstComment = group.comments[0];
265
+ context.report({
266
+ loc: firstComment.loc,
267
+ messageId: 'splitHeaderFragment',
268
+ data: {
269
+ tags: options.requiredTags.join(', '),
270
+ },
271
+ fix: null,
272
+ });
273
+ });
274
+ };
275
+ exports.RULE_NAME = 'enforce-unique-cursor-headers';
276
+ exports.enforceUniqueCursorHeaders = (0, createRule_1.createRule)({
277
+ name: exports.RULE_NAME,
278
+ meta: {
279
+ type: 'suggestion',
280
+ docs: {
281
+ description: 'Ensure files have exactly one cursor header containing required tags (e.g., @fileoverview) before any code',
282
+ recommended: 'error',
283
+ },
284
+ fixable: 'code',
285
+ schema: [
286
+ {
287
+ type: 'object',
288
+ properties: {
289
+ requiredPatterns: {
290
+ type: 'array',
291
+ items: { type: 'string' },
292
+ },
293
+ excludedPatterns: {
294
+ type: 'array',
295
+ items: { type: 'string' },
296
+ },
297
+ requiredTags: {
298
+ type: 'array',
299
+ items: { type: 'string' },
300
+ },
301
+ allowSplitHeaders: {
302
+ type: 'boolean',
303
+ },
304
+ headerTemplate: {
305
+ type: ['string', 'null'],
306
+ },
307
+ ignoreGeneratedFiles: {
308
+ type: 'boolean',
309
+ },
310
+ generatedMarkers: {
311
+ type: 'array',
312
+ items: { type: 'string' },
313
+ },
314
+ excludedAtDirectives: {
315
+ type: 'array',
316
+ items: { type: 'string' },
317
+ },
318
+ },
319
+ additionalProperties: false,
320
+ },
321
+ ],
322
+ messages: {
323
+ missingHeader: 'File "{{fileName}}" is missing a cursor header before any imports or code → Cursor headers keep ownership and metadata discoverable for automation → Add one top-of-file cursor header containing: {{tags}}.',
324
+ duplicateHeader: 'Multiple cursor headers detected before the first statement (extra header starts on line {{line}}) → Duplicate metadata blocks conflict and make ownership ambiguous → Keep exactly one cursor header at the top and remove the extra block.',
325
+ splitHeaderFragment: 'Cursor header metadata is split across adjacent comment blocks → Fragmented headers are easy to miss and let required tags drift out of sync → Merge the fragments into a single top-of-file header containing: {{tags}}.',
326
+ },
327
+ },
328
+ defaultOptions: [DEFAULT_OPTIONS],
329
+ create(context, [userOptions]) {
330
+ const options = normalizeOptions(userOptions);
331
+ const fileName = context.getFilename();
332
+ const matchPath = fileName.split(path_1.default.sep).join('/');
333
+ const excludedAtDirectives = new Set(options.excludedAtDirectives);
334
+ if (fileName === '<input>') {
335
+ return {};
336
+ }
337
+ const matchesRequired = options.requiredPatterns.some((pattern) => (0, minimatch_1.minimatch)(matchPath, pattern, { dot: true }));
338
+ if (!matchesRequired) {
339
+ return {};
340
+ }
341
+ const matchesExcluded = options.excludedPatterns.some((pattern) => (0, minimatch_1.minimatch)(matchPath, pattern, { dot: true }));
342
+ if (matchesExcluded) {
343
+ return {};
344
+ }
345
+ return {
346
+ Program(node) {
347
+ const sourceCode = context.getSourceCode();
348
+ const sourceText = sourceCode.getText();
349
+ if (shouldSkipGeneratedFile(sourceText, options)) {
350
+ return;
351
+ }
352
+ const topComments = getTopComments(sourceCode, node);
353
+ const candidateGroups = collectHeaderGroups(topComments, options, excludedAtDirectives);
354
+ const { primaryHeader, duplicateGroups, splitHeaderGroups } = analyzeHeaderGroups(candidateGroups, options);
355
+ if (!primaryHeader) {
356
+ reportMissingHeader(context, fileName, sourceText, options);
357
+ return;
358
+ }
359
+ reportDuplicates(context, duplicateGroups, sourceText);
360
+ reportSplitFragments(context, splitHeaderGroups, options);
361
+ },
362
+ };
363
+ },
364
+ });
365
+ //# sourceMappingURL=enforce-unique-cursor-headers.js.map
@@ -4537,7 +4537,7 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4537
4537
  },
4538
4538
  schema: [],
4539
4539
  messages: {
4540
- functionVerbPhrase: 'Function names should start with a verb phrase (e.g., fetchData, processRequest)',
4540
+ functionVerbPhrase: 'Function "{{name}}" should start with an action verb followed by the thing it acts on. Verb-first names tell readers this symbol performs work instead of representing data, which keeps APIs predictable and prevents accidental misuse. Rename "{{name}}" to a verb-noun phrase such as "fetchUsers" or "processRequest".',
4541
4541
  },
4542
4542
  },
4543
4543
  defaultOptions: [],
@@ -4600,18 +4600,86 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4600
4600
  }
4601
4601
  return false;
4602
4602
  }
4603
+ function bodyContainsJsx(node) {
4604
+ const stack = [node];
4605
+ while (stack.length) {
4606
+ const current = stack.pop();
4607
+ if (current.type === utils_1.AST_NODE_TYPES.JSXElement ||
4608
+ current.type === utils_1.AST_NODE_TYPES.JSXFragment) {
4609
+ return true;
4610
+ }
4611
+ for (const key of Object.keys(current)) {
4612
+ if (key === 'parent')
4613
+ continue;
4614
+ const value = current[key];
4615
+ if (Array.isArray(value)) {
4616
+ for (const child of value) {
4617
+ if (child && typeof child.type === 'string') {
4618
+ stack.push(child);
4619
+ }
4620
+ }
4621
+ }
4622
+ else if (value && typeof value.type === 'string') {
4623
+ stack.push(value);
4624
+ }
4625
+ }
4626
+ }
4627
+ return false;
4628
+ }
4603
4629
  function hasJsxReturn(node) {
4604
- const sourceCode = context.getSourceCode();
4605
- const text = sourceCode.getText(node);
4606
- // Check for direct JSX returns
4607
- if (text.includes('return <') || text.includes('=> <')) {
4608
- return true;
4630
+ /**
4631
+ * Recursively checks if an expression looks like JSX
4632
+ */
4633
+ function isJsxExpression(expr) {
4634
+ if (!expr)
4635
+ return false;
4636
+ // Handle parenthesized expressions
4637
+ if (expr.type === 'ParenthesizedExpression') {
4638
+ return isJsxExpression(expr.expression);
4639
+ }
4640
+ switch (expr.type) {
4641
+ case utils_1.AST_NODE_TYPES.JSXElement:
4642
+ case utils_1.AST_NODE_TYPES.JSXFragment:
4643
+ return true;
4644
+ case utils_1.AST_NODE_TYPES.ConditionalExpression:
4645
+ return (isJsxExpression(expr.consequent) ||
4646
+ isJsxExpression(expr.alternate));
4647
+ case utils_1.AST_NODE_TYPES.LogicalExpression:
4648
+ // e.g. condition && <div />
4649
+ return isJsxExpression(expr.right);
4650
+ default:
4651
+ return false;
4652
+ }
4609
4653
  }
4610
- // Check for JSX assigned to variables then returned
4611
- if (text.includes('const ') &&
4612
- text.includes('<') &&
4613
- text.includes('return')) {
4614
- return true;
4654
+ /**
4655
+ * Recursively checks if a statement/body returns JSX
4656
+ */
4657
+ function checkBodyForJsx(body) {
4658
+ if (body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
4659
+ for (const stmt of body.body) {
4660
+ if (stmt.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
4661
+ if (isJsxExpression(stmt.argument))
4662
+ return true;
4663
+ }
4664
+ else if (stmt.type === utils_1.AST_NODE_TYPES.IfStatement) {
4665
+ if (checkBodyForJsx(stmt.consequent))
4666
+ return true;
4667
+ if (stmt.alternate && checkBodyForJsx(stmt.alternate))
4668
+ return true;
4669
+ }
4670
+ // We could traverse more (try/catch, switch), but this covers most idiomatic React
4671
+ }
4672
+ }
4673
+ else {
4674
+ // Implicit return (arrow function expression body)
4675
+ return isJsxExpression(body);
4676
+ }
4677
+ return false;
4678
+ }
4679
+ if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
4680
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
4681
+ node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
4682
+ return checkBodyForJsx(node.body);
4615
4683
  }
4616
4684
  return false;
4617
4685
  }
@@ -4650,9 +4718,7 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4650
4718
  const id = parent.id;
4651
4719
  if (id.type === utils_1.AST_NODE_TYPES.Identifier &&
4652
4720
  id.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
4653
- const typeText = context
4654
- .getSourceCode()
4655
- .getText(id.typeAnnotation.typeAnnotation);
4721
+ const typeText = context.sourceCode.getText(id.typeAnnotation.typeAnnotation);
4656
4722
  return (typeText.includes('React.') ||
4657
4723
  typeText.includes('FC') ||
4658
4724
  typeText.includes('FunctionComponent'));
@@ -4664,6 +4730,13 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4664
4730
  // 5. Component name ends with "Unmemoized" (common React pattern)
4665
4731
  functionName && functionName.endsWith('Unmemoized'),
4666
4732
  ];
4733
+ // If the function has a PascalCase name AND returns JSX, it's definitely a React component
4734
+ if (functionName && isPascalCase(functionName) && hasJsxReturn(node)) {
4735
+ return true;
4736
+ }
4737
+ if (functionName && isPascalCase(functionName) && bodyContainsJsx(node)) {
4738
+ return true;
4739
+ }
4667
4740
  // Consider it a React component if it matches at least 2 indicators
4668
4741
  // OR if it has the Unmemoized suffix (strong indicator of a React component)
4669
4742
  return (indicators.filter(Boolean).length >= 2 ||
@@ -4680,6 +4753,7 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4680
4753
  context.report({
4681
4754
  node: node.id,
4682
4755
  messageId: 'functionVerbPhrase',
4756
+ data: { name: node.id.name },
4683
4757
  });
4684
4758
  }
4685
4759
  },
@@ -4696,6 +4770,7 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4696
4770
  context.report({
4697
4771
  node: node.id,
4698
4772
  messageId: 'functionVerbPhrase',
4773
+ data: { name: node.id.name },
4699
4774
  });
4700
4775
  }
4701
4776
  }
@@ -4713,6 +4788,7 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4713
4788
  context.report({
4714
4789
  node: node.key,
4715
4790
  messageId: 'functionVerbPhrase',
4791
+ data: { name: node.key.name },
4716
4792
  });
4717
4793
  }
4718
4794
  },
@@ -23,6 +23,24 @@ function isAbsoluteOrFixedPosition(propertyName, propertyValue) {
23
23
  function isPointerEventsProperty(propertyName) {
24
24
  return propertyName === 'pointerEvents' || propertyName === 'pointer-events';
25
25
  }
26
+ function formatSelector(selector) {
27
+ if (!selector)
28
+ return 'pseudo-element';
29
+ const trimmedSelector = selector.trim();
30
+ const candidates = trimmedSelector
31
+ .split(',')
32
+ .map((part) => part.trim())
33
+ .filter(Boolean);
34
+ for (const candidate of candidates) {
35
+ const match = candidate.match(/::?(before|after)\b/i);
36
+ if (match)
37
+ return `::${match[1].toLowerCase()}`;
38
+ }
39
+ if (trimmedSelector.length === 0)
40
+ return 'pseudo-element';
41
+ const snippet = trimmedSelector.slice(0, 40);
42
+ return trimmedSelector.length > 40 ? `${snippet}...` : trimmedSelector;
43
+ }
26
44
  exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
27
45
  name: 'ensure-pointer-events-none',
28
46
  meta: {
@@ -34,7 +52,9 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
34
52
  fixable: 'code',
35
53
  schema: [],
36
54
  messages: {
37
- missingPointerEventsNone: 'Pseudo-elements (::before, ::after) with position: absolute or fixed should have pointer-events: none to prevent blocking interactions with underlying elements',
55
+ missingPointerEventsNone: 'What\'s wrong: pseudo-element "{{selector}}" uses absolute or fixed positioning without pointer-events: none. ' +
56
+ 'Why it matters: positioned overlays capture clicks, hover, and focus, blocking the underlying control and harming accessibility. ' +
57
+ 'How to fix: add pointer-events: none so the pseudo-element stays decorative and does not intercept interactions.',
38
58
  },
39
59
  },
40
60
  defaultOptions: [],
@@ -104,9 +124,12 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
104
124
  context.report({
105
125
  node,
106
126
  messageId: 'missingPointerEventsNone',
127
+ data: {
128
+ selector: formatSelector(selector),
129
+ },
107
130
  fix(fixer) {
108
131
  // Find the last property in the object
109
- const sourceCode = context.getSourceCode();
132
+ const sourceCode = context.sourceCode;
110
133
  const properties = node.properties;
111
134
  if (properties.length === 0)
112
135
  return null;
@@ -155,6 +178,9 @@ exports.ensurePointerEventsNone = (0, createRule_1.createRule)({
155
178
  context.report({
156
179
  node,
157
180
  messageId: 'missingPointerEventsNone',
181
+ data: {
182
+ selector: formatSelector(template),
183
+ },
158
184
  });
159
185
  }
160
186
  }
@@ -8,20 +8,31 @@ exports.exportIfInDoubt = (0, createRule_1.createRule)({
8
8
  meta: {
9
9
  type: 'suggestion',
10
10
  docs: {
11
- description: 'All top-level const definitions, type definitions, and functions should be exported',
11
+ description: 'All top-level variable declarations, type definitions, and functions should be exported',
12
12
  recommended: 'error',
13
13
  },
14
14
  schema: [],
15
15
  messages: {
16
- exportIfInDoubt: 'Top-level const definitions, type definitions, and functions should be exported.',
16
+ exportIfInDoubt: 'What\'s wrong: Top-level {{kind}} "{{name}}" is not exported. Why it matters: Top-level declarations define your module\'s public API; leaving this unexported makes it unusable from other files and hides reusable utilities (often resulting in dead code or duplicated implementations). How to fix: Export it (for example: {{exportExample}}) or move it into a narrower scope if it is intentionally private.',
17
17
  },
18
18
  },
19
19
  defaultOptions: [],
20
20
  create(context) {
21
- // List of top-level declarations
22
- // List of exported identifiers
23
21
  const topLevelDeclarations = [];
24
22
  const exportedIdentifiers = [];
23
+ const getDeclarationKind = (node) => {
24
+ if (node.type === 'VariableDeclarator') {
25
+ const variableParent = node.parent;
26
+ if (variableParent?.type === 'VariableDeclaration') {
27
+ return variableParent.kind;
28
+ }
29
+ return 'const';
30
+ }
31
+ if (node.type === 'FunctionDeclaration') {
32
+ return 'function';
33
+ }
34
+ return 'type';
35
+ };
25
36
  return {
26
37
  'Program > VariableDeclaration > VariableDeclarator, Program > FunctionDeclaration, Program > TSTypeAliasDeclaration'(node) {
27
38
  topLevelDeclarations.push(node);
@@ -55,9 +66,21 @@ exports.exportIfInDoubt = (0, createRule_1.createRule)({
55
66
  node.type === 'TSTypeAliasDeclaration') &&
56
67
  node.id.type === 'Identifier' &&
57
68
  !exportedIdentifiers.includes(node.id.name)) {
69
+ const name = node.id.name;
70
+ const kind = getDeclarationKind(node);
71
+ const exportExample = node.type === 'VariableDeclarator'
72
+ ? `export ${kind} ${name} = undefined;`
73
+ : node.type === 'FunctionDeclaration'
74
+ ? `export function ${name}() {}`
75
+ : `export type ${name} = unknown;`;
58
76
  context.report({
59
77
  node,
60
78
  messageId: 'exportIfInDoubt',
79
+ data: {
80
+ name,
81
+ kind,
82
+ exportExample,
83
+ },
61
84
  });
62
85
  }
63
86
  });