@trackunit/eslint-plugin-trackunit 0.0.2

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 (147) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +117 -0
  3. package/package.json +31 -0
  4. package/src/index.d.ts +8 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/config/fragments/ignores.d.ts +2 -0
  8. package/src/lib/config/fragments/ignores.js +18 -0
  9. package/src/lib/config/fragments/ignores.js.map +1 -0
  10. package/src/lib/config/fragments/import-rules.d.ts +3 -0
  11. package/src/lib/config/fragments/import-rules.js +58 -0
  12. package/src/lib/config/fragments/import-rules.js.map +1 -0
  13. package/src/lib/config/fragments/jest-overrides.d.ts +2 -0
  14. package/src/lib/config/fragments/jest-overrides.js +30 -0
  15. package/src/lib/config/fragments/jest-overrides.js.map +1 -0
  16. package/src/lib/config/fragments/jsdoc-rules.d.ts +3 -0
  17. package/src/lib/config/fragments/jsdoc-rules.js +71 -0
  18. package/src/lib/config/fragments/jsdoc-rules.js.map +1 -0
  19. package/src/lib/config/fragments/module-boundaries.d.ts +2 -0
  20. package/src/lib/config/fragments/module-boundaries.js +92 -0
  21. package/src/lib/config/fragments/module-boundaries.js.map +1 -0
  22. package/src/lib/config/fragments/react-rules.d.ts +5 -0
  23. package/src/lib/config/fragments/react-rules.js +137 -0
  24. package/src/lib/config/fragments/react-rules.js.map +1 -0
  25. package/src/lib/config/fragments/restricted-imports.d.ts +2 -0
  26. package/src/lib/config/fragments/restricted-imports.js +58 -0
  27. package/src/lib/config/fragments/restricted-imports.js.map +1 -0
  28. package/src/lib/config/fragments/testing-library.d.ts +2 -0
  29. package/src/lib/config/fragments/testing-library.js +7 -0
  30. package/src/lib/config/fragments/testing-library.js.map +1 -0
  31. package/src/lib/config/fragments/typescript-rules.d.ts +2 -0
  32. package/src/lib/config/fragments/typescript-rules.js +97 -0
  33. package/src/lib/config/fragments/typescript-rules.js.map +1 -0
  34. package/src/lib/config/index.d.ts +863 -0
  35. package/src/lib/config/index.js +10 -0
  36. package/src/lib/config/index.js.map +1 -0
  37. package/src/lib/config/plugins.d.ts +90 -0
  38. package/src/lib/config/plugins.js +44 -0
  39. package/src/lib/config/plugins.js.map +1 -0
  40. package/src/lib/config/presets/base.d.ts +265 -0
  41. package/src/lib/config/presets/base.js +145 -0
  42. package/src/lib/config/presets/base.js.map +1 -0
  43. package/src/lib/config/presets/e2e.d.ts +10 -0
  44. package/src/lib/config/presets/e2e.js +19 -0
  45. package/src/lib/config/presets/e2e.js.map +1 -0
  46. package/src/lib/config/presets/public-api.d.ts +147 -0
  47. package/src/lib/config/presets/public-api.js +62 -0
  48. package/src/lib/config/presets/public-api.js.map +1 -0
  49. package/src/lib/config/presets/react.d.ts +598 -0
  50. package/src/lib/config/presets/react.js +97 -0
  51. package/src/lib/config/presets/react.js.map +1 -0
  52. package/src/lib/config/presets/server.d.ts +36 -0
  53. package/src/lib/config/presets/server.js +37 -0
  54. package/src/lib/config/presets/server.js.map +1 -0
  55. package/src/lib/config/utils.d.ts +6 -0
  56. package/src/lib/config/utils.js +28 -0
  57. package/src/lib/config/utils.js.map +1 -0
  58. package/src/lib/config-helpers/create-skip-when.d.ts +35 -0
  59. package/src/lib/config-helpers/create-skip-when.js +54 -0
  60. package/src/lib/config-helpers/create-skip-when.js.map +1 -0
  61. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.d.ts +16 -0
  62. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js +83 -0
  63. package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map +1 -0
  64. package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.d.ts +4 -0
  65. package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js +297 -0
  66. package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js.map +1 -0
  67. package/src/lib/rules/no-internal-barrel-files/examples.d.ts +80 -0
  68. package/src/lib/rules/no-internal-barrel-files/examples.js +84 -0
  69. package/src/lib/rules/no-internal-barrel-files/examples.js.map +1 -0
  70. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.d.ts +29 -0
  71. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js +178 -0
  72. package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js.map +1 -0
  73. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.d.ts +5 -0
  74. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js +67 -0
  75. package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js.map +1 -0
  76. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.d.ts +2 -0
  77. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js +34 -0
  78. package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js.map +1 -0
  79. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.d.ts +16 -0
  80. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js +55 -0
  81. package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js.map +1 -0
  82. package/src/lib/rules/no-typescript-assertion/examples.d.ts +1 -0
  83. package/src/lib/rules/no-typescript-assertion/examples.js +45 -0
  84. package/src/lib/rules/no-typescript-assertion/examples.js.map +1 -0
  85. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.d.ts +20 -0
  86. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js +83 -0
  87. package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js.map +1 -0
  88. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.d.ts +73 -0
  89. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js +333 -0
  90. package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js.map +1 -0
  91. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.d.ts +56 -0
  92. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js +225 -0
  93. package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map +1 -0
  94. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.d.ts +49 -0
  95. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js +75 -0
  96. package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js.map +1 -0
  97. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.d.ts +32 -0
  98. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js +143 -0
  99. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js.map +1 -0
  100. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.d.ts +27 -0
  101. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js +196 -0
  102. package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js.map +1 -0
  103. package/src/lib/rules/prefer-event-specific-callback-naming/utils.d.ts +76 -0
  104. package/src/lib/rules/prefer-event-specific-callback-naming/utils.js +245 -0
  105. package/src/lib/rules/prefer-event-specific-callback-naming/utils.js.map +1 -0
  106. package/src/lib/rules/prefer-field-components/prefer-field-components.d.ts +4 -0
  107. package/src/lib/rules/prefer-field-components/prefer-field-components.js +289 -0
  108. package/src/lib/rules/prefer-field-components/prefer-field-components.js.map +1 -0
  109. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.d.ts +26 -0
  110. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js +402 -0
  111. package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js.map +1 -0
  112. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.d.ts +13 -0
  113. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js +271 -0
  114. package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js.map +1 -0
  115. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.d.ts +15 -0
  116. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js +245 -0
  117. package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js.map +1 -0
  118. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.d.ts +17 -0
  119. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js +133 -0
  120. package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js.map +1 -0
  121. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.d.ts +12 -0
  122. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js +128 -0
  123. package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js.map +1 -0
  124. package/src/lib/rules-map.d.ts +66 -0
  125. package/src/lib/rules-map.js +34 -0
  126. package/src/lib/rules-map.js.map +1 -0
  127. package/src/lib/utils/ast-utils.d.ts +85 -0
  128. package/src/lib/utils/ast-utils.js +530 -0
  129. package/src/lib/utils/ast-utils.js.map +1 -0
  130. package/src/lib/utils/classname-utils.d.ts +150 -0
  131. package/src/lib/utils/classname-utils.js +492 -0
  132. package/src/lib/utils/classname-utils.js.map +1 -0
  133. package/src/lib/utils/file-utils.d.ts +14 -0
  134. package/src/lib/utils/file-utils.js +106 -0
  135. package/src/lib/utils/file-utils.js.map +1 -0
  136. package/src/lib/utils/import-utils.d.ts +85 -0
  137. package/src/lib/utils/import-utils.js +193 -0
  138. package/src/lib/utils/import-utils.js.map +1 -0
  139. package/src/lib/utils/nx-utils.d.ts +59 -0
  140. package/src/lib/utils/nx-utils.js +103 -0
  141. package/src/lib/utils/nx-utils.js.map +1 -0
  142. package/src/lib/utils/package-utils.d.ts +38 -0
  143. package/src/lib/utils/package-utils.js +74 -0
  144. package/src/lib/utils/package-utils.js.map +1 -0
  145. package/src/lib/utils/typescript-utils.d.ts +29 -0
  146. package/src/lib/utils/typescript-utils.js +213 -0
  147. package/src/lib/utils/typescript-utils.js.map +1 -0
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * Type-based detection strategy for prefer-event-specific-callback-naming rule.
4
+ *
5
+ * Uses TypeScript's type checker to detect when a callback with problematic naming
6
+ * is passed to a prop typed as a mouse event handler (MouseEventHandler, MouseEvent, etc.).
7
+ *
8
+ * This catches cases where custom components use non-standard prop names for click handlers:
9
+ * - <MobileButton onTap={onClose} /> where onTap is typed as MouseEventHandler
10
+ * - <PressableArea onPress={onCancel} /> where onPress accepts MouseEvent
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.checkTypeBased = exports.typeBasedMessages = void 0;
14
+ const utils_1 = require("@typescript-eslint/utils");
15
+ const utils_2 = require("../utils");
16
+ exports.typeBasedMessages = {
17
+ preferOnClickNamingTyped: "Callback '{{current}}' passed to {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). " +
18
+ "Reported because {{event}} is typed as a MouseEventHandler. Rename it at its definition.",
19
+ preferOnClickNamingTypedWrapped: "Callback '{{current}}' called in {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). " +
20
+ "Reported because {{event}} is typed as a MouseEventHandler. Rename it at its definition.",
21
+ suggestRenameAllTyped: "Rename '{{current}}' to '{{suggested}}' (definition and all usages in this file)",
22
+ suggestRenameLocalTyped: "Rename to '{{suggested}}' (this reference only - definition is in another file)",
23
+ };
24
+ /**
25
+ * Extracts the expected naming prefix from a suggested name.
26
+ * This is used to show the correct expected pattern in error messages.
27
+ *
28
+ * @example
29
+ * getExpectedPrefix("onClickPrimary") // "onClick"
30
+ * getExpectedPrefix("onMouseDownClose") // "onMouseDown"
31
+ */
32
+ const getExpectedPrefix = (suggestedName) => {
33
+ // Find where the capitalized suffix starts (after the event prefix)
34
+ // e.g., "onClickPrimary" -> find "Primary" -> prefix is "onClick"
35
+ const match = suggestedName.match(/^(on[A-Z][a-z]*)/);
36
+ return match?.[1] ?? suggestedName;
37
+ };
38
+ /**
39
+ * Checks if a type represents a mouse event handler.
40
+ * Looks for:
41
+ * - MouseEventHandler<T>
42
+ * - React.MouseEventHandler<T>
43
+ * - Function types with MouseEvent as first parameter
44
+ */
45
+ const isMouseEventHandlerType = (type, checker) => {
46
+ const typeString = checker.typeToString(type);
47
+ // Quick check for common patterns in the type string
48
+ if (typeString.includes("MouseEventHandler") ||
49
+ typeString.includes("MouseEvent<") ||
50
+ typeString.includes("MouseEvent)")) {
51
+ return true;
52
+ }
53
+ // Check call signatures for MouseEvent parameter
54
+ const signatures = type.getCallSignatures();
55
+ for (const signature of signatures) {
56
+ const params = signature.getParameters();
57
+ if (params.length > 0) {
58
+ const firstParam = params[0];
59
+ if (firstParam) {
60
+ const paramType = checker.getTypeOfSymbol(firstParam);
61
+ const paramTypeString = checker.typeToString(paramType);
62
+ if (paramTypeString.includes("MouseEvent")) {
63
+ return true;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return false;
69
+ };
70
+ /**
71
+ * Checks a JSX attribute for type-based mouse event handler naming issues.
72
+ *
73
+ * Only runs when "onClick" is in the events list (since this check only detects
74
+ * MouseEventHandler types, which are click events).
75
+ *
76
+ * Skips props already in the events list (those are handled by the name-based strategy).
77
+ *
78
+ * @param node - The JSX attribute node to check
79
+ * @param context - The ESLint rule context
80
+ * @param allowedNames - Callback names to exempt from this rule
81
+ * @param events - Configured event names (used to skip already-covered props)
82
+ */
83
+ const checkTypeBased = (node, context, allowedNames, events) => {
84
+ // Only run if "onClick" is in the events list, since this check only detects
85
+ // MouseEventHandler types (which are click events)
86
+ if (!events.includes("onClick")) {
87
+ return;
88
+ }
89
+ // Extract prop name - must be a JSXIdentifier
90
+ if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier) {
91
+ return;
92
+ }
93
+ const propName = node.name.name;
94
+ // Skip props already in the events list - those are handled by the name-based strategy
95
+ if (events.includes(propName)) {
96
+ return;
97
+ }
98
+ // Must have a value that's an expression container
99
+ if (!node.value || node.value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
100
+ return;
101
+ }
102
+ const { expression } = node.value;
103
+ if (expression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
104
+ return;
105
+ }
106
+ // Extract callback identifier (without checking hardcoded onClick pattern)
107
+ const result = (0, utils_2.extractCallbackIdentifier)(expression);
108
+ if (!result) {
109
+ return;
110
+ }
111
+ // Check if this name is in the allowed list
112
+ if (allowedNames.includes(result.callbackName)) {
113
+ return;
114
+ }
115
+ // Skip if callback name already matches or starts with the prop name
116
+ // e.g., onMouseEnter={onMouseEnter} or onMouseEnter={onMouseEnterSomething}
117
+ if (result.callbackName === propName || result.callbackName.startsWith(propName)) {
118
+ return;
119
+ }
120
+ // Skip if callback doesn't start with "on" - we only care about on* callbacks
121
+ // that might be confused with lifecycle/event callbacks
122
+ if (!result.callbackName.startsWith("on")) {
123
+ return;
124
+ }
125
+ // Now check if the prop is typed as a mouse event handler
126
+ try {
127
+ const services = utils_1.ESLintUtils.getParserServices(context);
128
+ const checker = services.program.getTypeChecker();
129
+ // Get the TypeScript node for the expression (already validated above)
130
+ const tsExpressionNode = services.esTreeNodeToTSNodeMap.get(expression);
131
+ // Get the contextual type (what type is expected at this position)
132
+ // The ESTree to TS node map returns a generic node, but getContextualType
133
+ // expects an Expression. This assertion is necessary for TypeScript API interop.
134
+ // eslint-disable-next-line @trackunit/no-typescript-assertion
135
+ const contextualType = checker.getContextualType(tsExpressionNode);
136
+ if (!contextualType) {
137
+ return;
138
+ }
139
+ if (!isMouseEventHandlerType(contextualType, checker)) {
140
+ return;
141
+ }
142
+ // Calculate suggested name using the actual prop name (e.g., onMouseDown -> onMouseDownClose)
143
+ const suggestedName = (0, utils_2.getSuggestedNameForEvent)(result.callbackName, propName);
144
+ // Select the appropriate messageId based on whether the callback is wrapped
145
+ const messageId = result.isWrapped
146
+ ? "preferOnClickNamingTypedWrapped"
147
+ : "preferOnClickNamingTyped";
148
+ // Build suggestions based on whether the definition is in the same file
149
+ const suggestions = [];
150
+ const localInfo = (0, utils_2.findLocalDefinitionInfo)(result.identifierNode, context);
151
+ if (localInfo) {
152
+ // Definition is in this file - offer to rename all occurrences
153
+ suggestions.push({
154
+ messageId: "suggestRenameAllTyped",
155
+ data: {
156
+ current: result.callbackName,
157
+ suggested: suggestedName,
158
+ },
159
+ fix: fixer => {
160
+ return localInfo.allNodesToRename.map(nodeToRename => fixer.replaceText(nodeToRename, suggestedName));
161
+ },
162
+ });
163
+ }
164
+ else {
165
+ // Definition is external - only offer to rename this reference
166
+ suggestions.push({
167
+ messageId: "suggestRenameLocalTyped",
168
+ data: {
169
+ current: result.callbackName,
170
+ suggested: suggestedName,
171
+ },
172
+ fix: fixer => {
173
+ return fixer.replaceText(result.identifierNode, suggestedName);
174
+ },
175
+ });
176
+ }
177
+ context.report({
178
+ node: result.node,
179
+ messageId,
180
+ data: {
181
+ current: result.callbackName,
182
+ suggested: suggestedName,
183
+ event: propName,
184
+ expectedPrefix: getExpectedPrefix(suggestedName),
185
+ },
186
+ suggest: suggestions,
187
+ });
188
+ }
189
+ catch {
190
+ // Type checking failed - silently skip this check
191
+ // This can happen if TypeScript services are not available
192
+ return;
193
+ }
194
+ };
195
+ exports.checkTypeBased = checkTypeBased;
196
+ //# sourceMappingURL=type-based.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-based.js","sourceRoot":"","sources":["../../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Type-based detection strategy for prefer-event-specific-callback-naming rule.\n *\n * Uses TypeScript's type checker to detect when a callback with problematic naming\n * is passed to a prop typed as a mouse event handler (MouseEventHandler, MouseEvent, etc.).\n *\n * This catches cases where custom components use non-standard prop names for click handlers:\n * - <MobileButton onTap={onClose} /> where onTap is typed as MouseEventHandler\n * - <PressableArea onPress={onCancel} /> where onPress accepts MouseEvent\n */\n\nimport { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\nimport * as ts from \"typescript\";\n\nimport { extractCallbackIdentifier, findLocalDefinitionInfo, getSuggestedNameForEvent } from \"../utils\";\n\nexport type TypeBasedMessageIds =\n | \"preferOnClickNamingTyped\"\n | \"preferOnClickNamingTypedWrapped\"\n | \"suggestRenameAllTyped\"\n | \"suggestRenameLocalTyped\";\n\nexport const typeBasedMessages: Record<TypeBasedMessageIds, string> = {\n preferOnClickNamingTyped:\n \"Callback '{{current}}' passed to {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). \" +\n \"Reported because {{event}} is typed as a MouseEventHandler. Rename it at its definition.\",\n preferOnClickNamingTypedWrapped:\n \"Callback '{{current}}' called in {{event}} should use '{{expectedPrefix}}*' naming (e.g., '{{suggested}}'). \" +\n \"Reported because {{event}} is typed as a MouseEventHandler. Rename it at its definition.\",\n suggestRenameAllTyped: \"Rename '{{current}}' to '{{suggested}}' (definition and all usages in this file)\",\n suggestRenameLocalTyped: \"Rename to '{{suggested}}' (this reference only - definition is in another file)\",\n};\n\n/**\n * Extracts the expected naming prefix from a suggested name.\n * This is used to show the correct expected pattern in error messages.\n *\n * @example\n * getExpectedPrefix(\"onClickPrimary\") // \"onClick\"\n * getExpectedPrefix(\"onMouseDownClose\") // \"onMouseDown\"\n */\nconst getExpectedPrefix = (suggestedName: string): string => {\n // Find where the capitalized suffix starts (after the event prefix)\n // e.g., \"onClickPrimary\" -> find \"Primary\" -> prefix is \"onClick\"\n const match = suggestedName.match(/^(on[A-Z][a-z]*)/);\n return match?.[1] ?? suggestedName;\n};\n\ntype SuggestionDescriptor = {\n messageId: TypeBasedMessageIds;\n data: { current: string; suggested: string };\n fix: (fixer: TSESLint.RuleFixer) => TSESLint.RuleFix | ReadonlyArray<TSESLint.RuleFix>;\n};\n\n/**\n * Checks if a type represents a mouse event handler.\n * Looks for:\n * - MouseEventHandler<T>\n * - React.MouseEventHandler<T>\n * - Function types with MouseEvent as first parameter\n */\nconst isMouseEventHandlerType = (type: ts.Type, checker: ts.TypeChecker): boolean => {\n const typeString = checker.typeToString(type);\n\n // Quick check for common patterns in the type string\n if (\n typeString.includes(\"MouseEventHandler\") ||\n typeString.includes(\"MouseEvent<\") ||\n typeString.includes(\"MouseEvent)\")\n ) {\n return true;\n }\n\n // Check call signatures for MouseEvent parameter\n const signatures = type.getCallSignatures();\n for (const signature of signatures) {\n const params = signature.getParameters();\n if (params.length > 0) {\n const firstParam = params[0];\n if (firstParam) {\n const paramType = checker.getTypeOfSymbol(firstParam);\n const paramTypeString = checker.typeToString(paramType);\n\n if (paramTypeString.includes(\"MouseEvent\")) {\n return true;\n }\n }\n }\n }\n\n return false;\n};\n\n/**\n * Checks a JSX attribute for type-based mouse event handler naming issues.\n *\n * Only runs when \"onClick\" is in the events list (since this check only detects\n * MouseEventHandler types, which are click events).\n *\n * Skips props already in the events list (those are handled by the name-based strategy).\n *\n * @param node - The JSX attribute node to check\n * @param context - The ESLint rule context\n * @param allowedNames - Callback names to exempt from this rule\n * @param events - Configured event names (used to skip already-covered props)\n */\nexport const checkTypeBased = (\n node: TSESTree.JSXAttribute,\n context: TSESLint.RuleContext<TypeBasedMessageIds, ReadonlyArray<unknown>>,\n allowedNames: ReadonlyArray<string>,\n events: ReadonlyArray<string>\n): void => {\n // Only run if \"onClick\" is in the events list, since this check only detects\n // MouseEventHandler types (which are click events)\n if (!events.includes(\"onClick\")) {\n return;\n }\n\n // Extract prop name - must be a JSXIdentifier\n if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) {\n return;\n }\n\n const propName = node.name.name;\n\n // Skip props already in the events list - those are handled by the name-based strategy\n if (events.includes(propName)) {\n return;\n }\n\n // Must have a value that's an expression container\n if (!node.value || node.value.type !== AST_NODE_TYPES.JSXExpressionContainer) {\n return;\n }\n\n const { expression } = node.value;\n if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) {\n return;\n }\n\n // Extract callback identifier (without checking hardcoded onClick pattern)\n const result = extractCallbackIdentifier(expression);\n\n if (!result) {\n return;\n }\n\n // Check if this name is in the allowed list\n if (allowedNames.includes(result.callbackName)) {\n return;\n }\n\n // Skip if callback name already matches or starts with the prop name\n // e.g., onMouseEnter={onMouseEnter} or onMouseEnter={onMouseEnterSomething}\n if (result.callbackName === propName || result.callbackName.startsWith(propName)) {\n return;\n }\n\n // Skip if callback doesn't start with \"on\" - we only care about on* callbacks\n // that might be confused with lifecycle/event callbacks\n if (!result.callbackName.startsWith(\"on\")) {\n return;\n }\n\n // Now check if the prop is typed as a mouse event handler\n try {\n const services = ESLintUtils.getParserServices(context);\n const checker = services.program.getTypeChecker();\n\n // Get the TypeScript node for the expression (already validated above)\n const tsExpressionNode = services.esTreeNodeToTSNodeMap.get(expression);\n\n // Get the contextual type (what type is expected at this position)\n // The ESTree to TS node map returns a generic node, but getContextualType\n // expects an Expression. This assertion is necessary for TypeScript API interop.\n // eslint-disable-next-line @trackunit/no-typescript-assertion\n const contextualType = checker.getContextualType(tsExpressionNode as ts.Expression);\n\n if (!contextualType) {\n return;\n }\n\n if (!isMouseEventHandlerType(contextualType, checker)) {\n return;\n }\n\n // Calculate suggested name using the actual prop name (e.g., onMouseDown -> onMouseDownClose)\n const suggestedName = getSuggestedNameForEvent(result.callbackName, propName);\n\n // Select the appropriate messageId based on whether the callback is wrapped\n const messageId: TypeBasedMessageIds = result.isWrapped\n ? \"preferOnClickNamingTypedWrapped\"\n : \"preferOnClickNamingTyped\";\n\n // Build suggestions based on whether the definition is in the same file\n const suggestions: Array<SuggestionDescriptor> = [];\n const localInfo = findLocalDefinitionInfo(result.identifierNode, context);\n\n if (localInfo) {\n // Definition is in this file - offer to rename all occurrences\n suggestions.push({\n messageId: \"suggestRenameAllTyped\",\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n },\n fix: fixer => {\n return localInfo.allNodesToRename.map(nodeToRename => fixer.replaceText(nodeToRename, suggestedName));\n },\n });\n } else {\n // Definition is external - only offer to rename this reference\n suggestions.push({\n messageId: \"suggestRenameLocalTyped\",\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n },\n fix: fixer => {\n return fixer.replaceText(result.identifierNode, suggestedName);\n },\n });\n }\n\n context.report({\n node: result.node,\n messageId,\n data: {\n current: result.callbackName,\n suggested: suggestedName,\n event: propName,\n expectedPrefix: getExpectedPrefix(suggestedName),\n },\n suggest: suggestions,\n });\n } catch {\n // Type checking failed - silently skip this check\n // This can happen if TypeScript services are not available\n return;\n }\n};\n"]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared utilities for the prefer-event-specific-callback-naming rule.
3
+ * These functions handle the naming convention logic and callback extraction
4
+ * used by both string-based and type-based detection strategies.
5
+ */
6
+ import { TSESLint, TSESTree } from "@typescript-eslint/utils";
7
+ /**
8
+ * Result of extracting callback information from an expression.
9
+ * Does not include messageId - each strategy adds its own.
10
+ */
11
+ export type CallbackExtractionResult = {
12
+ callbackName: string;
13
+ suggestedName: string;
14
+ node: TSESTree.Node;
15
+ /** The identifier node for the callback - used for fix ranges */
16
+ identifierNode: TSESTree.Identifier;
17
+ /** Whether the callback is wrapped in an arrow function */
18
+ isWrapped: boolean;
19
+ } | null;
20
+ /**
21
+ * Information about a locally defined variable, including its definition
22
+ * and all references within the same file.
23
+ */
24
+ export type LocalDefinitionInfo = {
25
+ /** The identifier node where the variable is defined */
26
+ definitionNode: TSESTree.Identifier;
27
+ /** All nodes that need to be renamed (variable references + type property definitions) */
28
+ allNodesToRename: ReadonlyArray<TSESTree.Identifier>;
29
+ };
30
+ /**
31
+ * Finds the local definition and all references of a variable in the current file.
32
+ * Also finds TypeScript interface/type property definitions with the same name
33
+ * (for React component props that need to be renamed in both the type and usage).
34
+ * Returns null if the variable is not defined in the current file (e.g., imported).
35
+ *
36
+ * @param identifier - The identifier node to look up
37
+ * @param context - The ESLint rule context
38
+ * @returns LocalDefinitionInfo if defined locally, null if defined externally
39
+ */
40
+ export declare const findLocalDefinitionInfo: (identifier: TSESTree.Identifier, context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>) => LocalDefinitionInfo | null;
41
+ /**
42
+ * Result of extracting a callback identifier from an expression.
43
+ * Unlike CallbackExtractionResult, this doesn't include suggested names
44
+ * since those depend on the specific event handler being used.
45
+ */
46
+ export type CallbackIdentifierResult = {
47
+ callbackName: string;
48
+ node: TSESTree.Node;
49
+ /** The identifier node for the callback - used for fix ranges */
50
+ identifierNode: TSESTree.Identifier;
51
+ /** Whether the callback is wrapped in an arrow function */
52
+ isWrapped: boolean;
53
+ } | null;
54
+ /**
55
+ * Extracts the callback identifier from an expression without checking naming patterns.
56
+ * Used by strict prop events check which has its own naming criteria.
57
+ *
58
+ * @returns Callback identifier info, or null if not a simple identifier/single-call arrow
59
+ */
60
+ export declare const extractCallbackIdentifier: (expression: TSESTree.Expression) => CallbackIdentifierResult;
61
+ /**
62
+ * Generates the suggested name for a callback based on the event handler it's passed to.
63
+ *
64
+ * Uses a strategy-based approach to handle different naming patterns appropriately.
65
+ * See `name-suggestion-strategies.ts` for the individual strategies.
66
+ *
67
+ * @param currentName - The current callback name (e.g., "secondaryAction", "close")
68
+ * @param eventName - The event handler name (e.g., "onClick", "onSubmit", "primaryAction")
69
+ * @returns The suggested name (e.g., "onClickSecondaryAction", "onSubmitClose", "onClickPrimary")
70
+ * @example
71
+ * getSuggestedNameForEvent("close", "onClick") // "onClickClose"
72
+ * getSuggestedNameForEvent("onCancel", "onClick") // "onClickCancel"
73
+ * getSuggestedNameForEvent("onPrimary", "primaryAction") // "onClickPrimary"
74
+ * getSuggestedNameForEvent("onClose", "onClickClose") // "onClickClose"
75
+ */
76
+ export declare const getSuggestedNameForEvent: (currentName: string, eventName: string) => string;
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ /**
3
+ * Shared utilities for the prefer-event-specific-callback-naming rule.
4
+ * These functions handle the naming convention logic and callback extraction
5
+ * used by both string-based and type-based detection strategies.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.getSuggestedNameForEvent = exports.extractCallbackIdentifier = exports.findLocalDefinitionInfo = void 0;
9
+ const utils_1 = require("@typescript-eslint/utils");
10
+ const name_suggestion_strategies_1 = require("./name-suggestion-strategies");
11
+ /**
12
+ * Recursively finds all TSPropertySignature nodes in the AST with the given name.
13
+ * This finds interface/type property definitions that need to be renamed along with the variable.
14
+ */
15
+ const findTypePropertyDefinitions = (ast, propertyName) => {
16
+ const results = [];
17
+ const visit = (node) => {
18
+ // Find interface/type property signatures with matching name
19
+ if (node.type === utils_1.AST_NODE_TYPES.TSPropertySignature &&
20
+ node.key.type === utils_1.AST_NODE_TYPES.Identifier &&
21
+ node.key.name === propertyName) {
22
+ results.push(node.key);
23
+ }
24
+ // Recursively visit all child nodes
25
+ // Use Object.entries to iterate without needing type assertions
26
+ for (const [key, value] of Object.entries(node)) {
27
+ if (key === "parent")
28
+ continue; // Skip parent to avoid cycles
29
+ if (value !== null && typeof value === "object") {
30
+ if (Array.isArray(value)) {
31
+ for (const item of value) {
32
+ if (item !== null && typeof item === "object" && "type" in item) {
33
+ visit(item);
34
+ }
35
+ }
36
+ }
37
+ else if ("type" in value) {
38
+ visit(value);
39
+ }
40
+ }
41
+ }
42
+ };
43
+ visit(ast);
44
+ return results;
45
+ };
46
+ /**
47
+ * Finds the local definition and all references of a variable in the current file.
48
+ * Also finds TypeScript interface/type property definitions with the same name
49
+ * (for React component props that need to be renamed in both the type and usage).
50
+ * Returns null if the variable is not defined in the current file (e.g., imported).
51
+ *
52
+ * @param identifier - The identifier node to look up
53
+ * @param context - The ESLint rule context
54
+ * @returns LocalDefinitionInfo if defined locally, null if defined externally
55
+ */
56
+ const findLocalDefinitionInfo = (identifier, context) => {
57
+ const scope = context.sourceCode.getScope(identifier);
58
+ const variable = scope.references.find(ref => ref.identifier === identifier)?.resolved;
59
+ if (!variable) {
60
+ return null;
61
+ }
62
+ // Check if the variable has definition nodes in this file
63
+ const definitions = variable.defs;
64
+ if (definitions.length === 0) {
65
+ // No definitions found - likely a global or imported variable
66
+ return null;
67
+ }
68
+ // Get the first definition (typically there's only one)
69
+ const definition = definitions[0];
70
+ if (!definition) {
71
+ return null;
72
+ }
73
+ // The definition's name node is where the variable is defined
74
+ // It could be an Identifier, ArrayPattern, or ObjectPattern
75
+ // We only handle simple Identifier definitions
76
+ const definitionName = definition.name;
77
+ if (definitionName.type !== utils_1.AST_NODE_TYPES.Identifier) {
78
+ return null;
79
+ }
80
+ // Collect all variable references (identifiers that refer to this variable)
81
+ const variableReferences = variable.references
82
+ .map(ref => ref.identifier)
83
+ .filter((node) => node.type === utils_1.AST_NODE_TYPES.Identifier);
84
+ // Also find TypeScript type property definitions with the same name
85
+ // This handles the case where a callback is destructured from props:
86
+ // interface Props { onCancel: () => void } <-- need to rename this too
87
+ // const { onCancel } = props; <-- the variable reference
88
+ const ast = context.sourceCode.ast;
89
+ const typePropertyDefinitions = findTypePropertyDefinitions(ast, identifier.name);
90
+ // Combine definition, variable references, and type property definitions
91
+ // Use a Set based on node range to deduplicate (needed because shorthand properties
92
+ // may share node ranges between definition and reference)
93
+ const seenRanges = new Set();
94
+ const allNodesToRename = [];
95
+ for (const node of [definitionName, ...variableReferences, ...typePropertyDefinitions]) {
96
+ const rangeKey = `${node.range[0]}-${node.range[1]}`;
97
+ if (!seenRanges.has(rangeKey)) {
98
+ seenRanges.add(rangeKey);
99
+ allNodesToRename.push(node);
100
+ }
101
+ }
102
+ return {
103
+ definitionNode: definitionName,
104
+ allNodesToRename,
105
+ };
106
+ };
107
+ exports.findLocalDefinitionInfo = findLocalDefinitionInfo;
108
+ /**
109
+ * Checks if a callback name is problematic when used with click handlers.
110
+ * A name is problematic if it starts with "on" but not "onClick".
111
+ *
112
+ * @example
113
+ * isProblematicName("onClose") // true - should be onClickClose
114
+ * isProblematicName("onClickClose") // false - already correct
115
+ * isProblematicName("handleClose") // false - different convention
116
+ * isProblematicName("close") // false - doesn't start with "on"
117
+ */
118
+ const isProblematicName = (name) => {
119
+ return name.startsWith("on") && !name.startsWith("onClick");
120
+ };
121
+ /**
122
+ * Converts a problematic callback name to the suggested onClick* format.
123
+ *
124
+ * @example
125
+ * getSuggestedName("onClose") // "onClickClose"
126
+ * getSuggestedName("onCancel") // "onClickCancel"
127
+ */
128
+ const getSuggestedName = (name) => {
129
+ // Remove "on" prefix and add "onClick" prefix
130
+ return `onClick${name.slice(2)}`;
131
+ };
132
+ /**
133
+ * Extracts callback info from a direct identifier pattern: onClick={onClose}
134
+ */
135
+ const extractFromDirectIdentifier = (expression) => {
136
+ if (expression.type !== utils_1.AST_NODE_TYPES.Identifier) {
137
+ return null;
138
+ }
139
+ const callbackName = expression.name;
140
+ if (!isProblematicName(callbackName)) {
141
+ return null;
142
+ }
143
+ return {
144
+ callbackName,
145
+ suggestedName: getSuggestedName(callbackName),
146
+ node: expression,
147
+ identifierNode: expression,
148
+ isWrapped: false,
149
+ };
150
+ };
151
+ /**
152
+ * Extracts callback info from a single-call arrow function: onClick={() => onClose()}
153
+ */
154
+ const extractFromSingleCallArrow = (expression) => {
155
+ if (expression.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
156
+ return null;
157
+ }
158
+ const { body } = expression;
159
+ // Only check if body is a single call expression (not a block statement)
160
+ if (body.type !== utils_1.AST_NODE_TYPES.CallExpression) {
161
+ return null;
162
+ }
163
+ // Only check if the callee is a simple identifier
164
+ if (body.callee.type !== utils_1.AST_NODE_TYPES.Identifier) {
165
+ return null;
166
+ }
167
+ const callbackName = body.callee.name;
168
+ if (!isProblematicName(callbackName)) {
169
+ return null;
170
+ }
171
+ return {
172
+ callbackName,
173
+ suggestedName: getSuggestedName(callbackName),
174
+ node: body.callee,
175
+ identifierNode: body.callee,
176
+ isWrapped: true,
177
+ };
178
+ };
179
+ /**
180
+ * Extracts callback information from an expression.
181
+ * Handles both direct identifiers (onClick={onClose}) and
182
+ * single-call arrow functions (onClick={() => onClose()}).
183
+ *
184
+ * @returns Callback extraction result without messageId, or null if no problematic callback found
185
+ */
186
+ const extractCallbackInfo = (expression) => {
187
+ return extractFromDirectIdentifier(expression) ?? extractFromSingleCallArrow(expression);
188
+ };
189
+ /**
190
+ * Extracts the callback identifier from an expression without checking naming patterns.
191
+ * Used by strict prop events check which has its own naming criteria.
192
+ *
193
+ * @returns Callback identifier info, or null if not a simple identifier/single-call arrow
194
+ */
195
+ const extractCallbackIdentifier = (expression) => {
196
+ // Direct identifier: onClick={onClose}
197
+ if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
198
+ return {
199
+ callbackName: expression.name,
200
+ node: expression,
201
+ identifierNode: expression,
202
+ isWrapped: false,
203
+ };
204
+ }
205
+ // Single-call arrow function: onClick={() => onClose()}
206
+ if (expression.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
207
+ const { body } = expression;
208
+ // Only check if body is a single call expression (not a block statement)
209
+ if (body.type !== utils_1.AST_NODE_TYPES.CallExpression) {
210
+ return null;
211
+ }
212
+ // Only check if the callee is a simple identifier
213
+ if (body.callee.type !== utils_1.AST_NODE_TYPES.Identifier) {
214
+ return null;
215
+ }
216
+ return {
217
+ callbackName: body.callee.name,
218
+ node: body.callee,
219
+ identifierNode: body.callee,
220
+ isWrapped: true,
221
+ };
222
+ }
223
+ return null;
224
+ };
225
+ exports.extractCallbackIdentifier = extractCallbackIdentifier;
226
+ /**
227
+ * Generates the suggested name for a callback based on the event handler it's passed to.
228
+ *
229
+ * Uses a strategy-based approach to handle different naming patterns appropriately.
230
+ * See `name-suggestion-strategies.ts` for the individual strategies.
231
+ *
232
+ * @param currentName - The current callback name (e.g., "secondaryAction", "close")
233
+ * @param eventName - The event handler name (e.g., "onClick", "onSubmit", "primaryAction")
234
+ * @returns The suggested name (e.g., "onClickSecondaryAction", "onSubmitClose", "onClickPrimary")
235
+ * @example
236
+ * getSuggestedNameForEvent("close", "onClick") // "onClickClose"
237
+ * getSuggestedNameForEvent("onCancel", "onClick") // "onClickCancel"
238
+ * getSuggestedNameForEvent("onPrimary", "primaryAction") // "onClickPrimary"
239
+ * getSuggestedNameForEvent("onClose", "onClickClose") // "onClickClose"
240
+ */
241
+ const getSuggestedNameForEvent = (currentName, eventName) => {
242
+ return (0, name_suggestion_strategies_1.getSuggestedName)({ callbackName: currentName, eventName });
243
+ };
244
+ exports.getSuggestedNameForEvent = getSuggestedNameForEvent;
245
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/prefer-event-specific-callback-naming/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared utilities for the prefer-event-specific-callback-naming rule.\n * These functions handle the naming convention logic and callback extraction\n * used by both string-based and type-based detection strategies.\n */\n\nimport { AST_NODE_TYPES, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\nimport { getSuggestedName as getStrategySuggestedName } from \"./name-suggestion-strategies\";\n\n/**\n * Result of extracting callback information from an expression.\n * Does not include messageId - each strategy adds its own.\n */\nexport type CallbackExtractionResult = {\n callbackName: string;\n suggestedName: string;\n node: TSESTree.Node;\n /** The identifier node for the callback - used for fix ranges */\n identifierNode: TSESTree.Identifier;\n /** Whether the callback is wrapped in an arrow function */\n isWrapped: boolean;\n} | null;\n\n/**\n * Information about a locally defined variable, including its definition\n * and all references within the same file.\n */\nexport type LocalDefinitionInfo = {\n /** The identifier node where the variable is defined */\n definitionNode: TSESTree.Identifier;\n /** All nodes that need to be renamed (variable references + type property definitions) */\n allNodesToRename: ReadonlyArray<TSESTree.Identifier>;\n};\n\n/**\n * Recursively finds all TSPropertySignature nodes in the AST with the given name.\n * This finds interface/type property definitions that need to be renamed along with the variable.\n */\nconst findTypePropertyDefinitions = (ast: TSESTree.Program, propertyName: string): Array<TSESTree.Identifier> => {\n const results: Array<TSESTree.Identifier> = [];\n\n const visit = (node: TSESTree.Node): void => {\n // Find interface/type property signatures with matching name\n if (\n node.type === AST_NODE_TYPES.TSPropertySignature &&\n node.key.type === AST_NODE_TYPES.Identifier &&\n node.key.name === propertyName\n ) {\n results.push(node.key);\n }\n\n // Recursively visit all child nodes\n // Use Object.entries to iterate without needing type assertions\n for (const [key, value] of Object.entries(node)) {\n if (key === \"parent\") continue; // Skip parent to avoid cycles\n\n if (value !== null && typeof value === \"object\") {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== null && typeof item === \"object\" && \"type\" in item) {\n visit(item);\n }\n }\n } else if (\"type\" in value) {\n visit(value);\n }\n }\n }\n };\n\n visit(ast);\n return results;\n};\n\n/**\n * Finds the local definition and all references of a variable in the current file.\n * Also finds TypeScript interface/type property definitions with the same name\n * (for React component props that need to be renamed in both the type and usage).\n * Returns null if the variable is not defined in the current file (e.g., imported).\n *\n * @param identifier - The identifier node to look up\n * @param context - The ESLint rule context\n * @returns LocalDefinitionInfo if defined locally, null if defined externally\n */\nexport const findLocalDefinitionInfo = (\n identifier: TSESTree.Identifier,\n context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>\n): LocalDefinitionInfo | null => {\n const scope = context.sourceCode.getScope(identifier);\n const variable = scope.references.find(ref => ref.identifier === identifier)?.resolved;\n\n if (!variable) {\n return null;\n }\n\n // Check if the variable has definition nodes in this file\n const definitions = variable.defs;\n if (definitions.length === 0) {\n // No definitions found - likely a global or imported variable\n return null;\n }\n\n // Get the first definition (typically there's only one)\n const definition = definitions[0];\n if (!definition) {\n return null;\n }\n\n // The definition's name node is where the variable is defined\n // It could be an Identifier, ArrayPattern, or ObjectPattern\n // We only handle simple Identifier definitions\n const definitionName = definition.name;\n if (definitionName.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n // Collect all variable references (identifiers that refer to this variable)\n const variableReferences = variable.references\n .map(ref => ref.identifier)\n .filter((node): node is TSESTree.Identifier => node.type === AST_NODE_TYPES.Identifier);\n\n // Also find TypeScript type property definitions with the same name\n // This handles the case where a callback is destructured from props:\n // interface Props { onCancel: () => void } <-- need to rename this too\n // const { onCancel } = props; <-- the variable reference\n const ast = context.sourceCode.ast;\n const typePropertyDefinitions = findTypePropertyDefinitions(ast, identifier.name);\n\n // Combine definition, variable references, and type property definitions\n // Use a Set based on node range to deduplicate (needed because shorthand properties\n // may share node ranges between definition and reference)\n const seenRanges = new Set<string>();\n const allNodesToRename: Array<TSESTree.Identifier> = [];\n\n for (const node of [definitionName, ...variableReferences, ...typePropertyDefinitions]) {\n const rangeKey = `${node.range[0]}-${node.range[1]}`;\n if (!seenRanges.has(rangeKey)) {\n seenRanges.add(rangeKey);\n allNodesToRename.push(node);\n }\n }\n\n return {\n definitionNode: definitionName,\n allNodesToRename,\n };\n};\n\n/**\n * Checks if a callback name is problematic when used with click handlers.\n * A name is problematic if it starts with \"on\" but not \"onClick\".\n *\n * @example\n * isProblematicName(\"onClose\") // true - should be onClickClose\n * isProblematicName(\"onClickClose\") // false - already correct\n * isProblematicName(\"handleClose\") // false - different convention\n * isProblematicName(\"close\") // false - doesn't start with \"on\"\n */\nconst isProblematicName = (name: string): boolean => {\n return name.startsWith(\"on\") && !name.startsWith(\"onClick\");\n};\n\n/**\n * Converts a problematic callback name to the suggested onClick* format.\n *\n * @example\n * getSuggestedName(\"onClose\") // \"onClickClose\"\n * getSuggestedName(\"onCancel\") // \"onClickCancel\"\n */\nconst getSuggestedName = (name: string): string => {\n // Remove \"on\" prefix and add \"onClick\" prefix\n return `onClick${name.slice(2)}`;\n};\n\n/**\n * Extracts callback info from a direct identifier pattern: onClick={onClose}\n */\nconst extractFromDirectIdentifier = (expression: TSESTree.Expression): CallbackExtractionResult => {\n if (expression.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n const callbackName = expression.name;\n if (!isProblematicName(callbackName)) {\n return null;\n }\n\n return {\n callbackName,\n suggestedName: getSuggestedName(callbackName),\n node: expression,\n identifierNode: expression,\n isWrapped: false,\n };\n};\n\n/**\n * Extracts callback info from a single-call arrow function: onClick={() => onClose()}\n */\nconst extractFromSingleCallArrow = (expression: TSESTree.Expression): CallbackExtractionResult => {\n if (expression.type !== AST_NODE_TYPES.ArrowFunctionExpression) {\n return null;\n }\n\n const { body } = expression;\n\n // Only check if body is a single call expression (not a block statement)\n if (body.type !== AST_NODE_TYPES.CallExpression) {\n return null;\n }\n\n // Only check if the callee is a simple identifier\n if (body.callee.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n const callbackName = body.callee.name;\n if (!isProblematicName(callbackName)) {\n return null;\n }\n\n return {\n callbackName,\n suggestedName: getSuggestedName(callbackName),\n node: body.callee,\n identifierNode: body.callee,\n isWrapped: true,\n };\n};\n\n/**\n * Extracts callback information from an expression.\n * Handles both direct identifiers (onClick={onClose}) and\n * single-call arrow functions (onClick={() => onClose()}).\n *\n * @returns Callback extraction result without messageId, or null if no problematic callback found\n */\nconst extractCallbackInfo = (expression: TSESTree.Expression): CallbackExtractionResult => {\n return extractFromDirectIdentifier(expression) ?? extractFromSingleCallArrow(expression);\n};\n\n/**\n * Result of extracting a callback identifier from an expression.\n * Unlike CallbackExtractionResult, this doesn't include suggested names\n * since those depend on the specific event handler being used.\n */\nexport type CallbackIdentifierResult = {\n callbackName: string;\n node: TSESTree.Node;\n /** The identifier node for the callback - used for fix ranges */\n identifierNode: TSESTree.Identifier;\n /** Whether the callback is wrapped in an arrow function */\n isWrapped: boolean;\n} | null;\n\n/**\n * Extracts the callback identifier from an expression without checking naming patterns.\n * Used by strict prop events check which has its own naming criteria.\n *\n * @returns Callback identifier info, or null if not a simple identifier/single-call arrow\n */\nexport const extractCallbackIdentifier = (expression: TSESTree.Expression): CallbackIdentifierResult => {\n // Direct identifier: onClick={onClose}\n if (expression.type === AST_NODE_TYPES.Identifier) {\n return {\n callbackName: expression.name,\n node: expression,\n identifierNode: expression,\n isWrapped: false,\n };\n }\n\n // Single-call arrow function: onClick={() => onClose()}\n if (expression.type === AST_NODE_TYPES.ArrowFunctionExpression) {\n const { body } = expression;\n\n // Only check if body is a single call expression (not a block statement)\n if (body.type !== AST_NODE_TYPES.CallExpression) {\n return null;\n }\n\n // Only check if the callee is a simple identifier\n if (body.callee.type !== AST_NODE_TYPES.Identifier) {\n return null;\n }\n\n return {\n callbackName: body.callee.name,\n node: body.callee,\n identifierNode: body.callee,\n isWrapped: true,\n };\n }\n\n return null;\n};\n\n/**\n * Generates the suggested name for a callback based on the event handler it's passed to.\n *\n * Uses a strategy-based approach to handle different naming patterns appropriately.\n * See `name-suggestion-strategies.ts` for the individual strategies.\n *\n * @param currentName - The current callback name (e.g., \"secondaryAction\", \"close\")\n * @param eventName - The event handler name (e.g., \"onClick\", \"onSubmit\", \"primaryAction\")\n * @returns The suggested name (e.g., \"onClickSecondaryAction\", \"onSubmitClose\", \"onClickPrimary\")\n * @example\n * getSuggestedNameForEvent(\"close\", \"onClick\") // \"onClickClose\"\n * getSuggestedNameForEvent(\"onCancel\", \"onClick\") // \"onClickCancel\"\n * getSuggestedNameForEvent(\"onPrimary\", \"primaryAction\") // \"onClickPrimary\"\n * getSuggestedNameForEvent(\"onClose\", \"onClickClose\") // \"onClickClose\"\n */\nexport const getSuggestedNameForEvent = (currentName: string, eventName: string): string => {\n return getStrategySuggestedName({ callbackName: currentName, eventName });\n};\n"]}
@@ -0,0 +1,4 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ export declare const preferFieldComponents: ESLintUtils.RuleModule<"preferFieldComponent", [], unknown, ESLintUtils.RuleListener> & {
3
+ name: string;
4
+ };