@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,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireClassnameAlternatives = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const classname_utils_1 = require("../../utils/classname-utils");
6
+ const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`);
7
+ /**
8
+ * Replace a specific pattern in a string with its replacement.
9
+ * Handles both prefix-based patterns (e.g., bg-red-500 -> bg-danger-500)
10
+ * and exact matches.
11
+ */
12
+ function replacePattern(value, oldPattern, newPattern, hasPrefixes) {
13
+ if (hasPrefixes) {
14
+ // With prefixes, replace -oldPattern- with -newPattern-
15
+ const pattern = new RegExp(`-${oldPattern}(-|$|/)`, "g");
16
+ return value.replace(pattern, `-${newPattern}$1`);
17
+ }
18
+ else {
19
+ // Without prefixes, replace exact word boundaries
20
+ const pattern = new RegExp(`(^|\\s)${oldPattern}($|\\s)`, "g");
21
+ return value.replace(pattern, `$1${newPattern}$2`);
22
+ }
23
+ }
24
+ /**
25
+ * Build a regex pattern that matches class names based on configuration.
26
+ *
27
+ * With prefixes: Matches patterns like bg-red-500, text-blue-200, hover:border-green-400
28
+ * Without prefixes: Matches exact class names as standalone words
29
+ */
30
+ function buildBannedPattern(bannedPatterns, prefixes) {
31
+ const patternList = bannedPatterns.join("|");
32
+ const variantPrefixes = "(?:hover:|focus:|focus-visible:|focus-within:|active:|disabled:|group-hover:|peer-hover:|dark:|light:|sm:|md:|lg:|xl:|2xl:)*";
33
+ if (prefixes && prefixes.length > 0) {
34
+ const prefixPattern = prefixes.join("|");
35
+ // Match: [variant:]prefix-banned[-suffix][/opacity]
36
+ return new RegExp(`(?:^|\\s)(${variantPrefixes}(?:${prefixPattern})-(${patternList})(?:-[a-zA-Z0-9]+)?(?:\\/\\d+)?)(?=\\s|$|")`, "g");
37
+ }
38
+ else {
39
+ // Exact match: standalone class name
40
+ return new RegExp(`(?:^|\\s)(${variantPrefixes}(${patternList}))(?=\\s|$|")`, "g");
41
+ }
42
+ }
43
+ exports.requireClassnameAlternatives = createRule({
44
+ name: "require-classname-alternatives",
45
+ meta: {
46
+ type: "problem",
47
+ fixable: "code",
48
+ hasSuggestions: true,
49
+ docs: {
50
+ description: "Require specific class names to be replaced with alternatives. Configurable via rule options.",
51
+ },
52
+ messages: {
53
+ bannedClassAutoFix: '"{{match}}" is not allowed. Use "{{alternative}}" instead.',
54
+ bannedClassSuggest: '"{{match}}" is not allowed. Consider using: {{alternatives}}.',
55
+ suggestReplacement: 'Replace with "{{replacement}}"',
56
+ },
57
+ schema: [
58
+ {
59
+ type: "object",
60
+ properties: {
61
+ alternatives: {
62
+ type: "object",
63
+ description: "Map of banned patterns to their allowed alternatives (array of strings)",
64
+ additionalProperties: {
65
+ type: "array",
66
+ items: { type: "string" },
67
+ },
68
+ },
69
+ prefixes: {
70
+ type: "array",
71
+ description: "Prefixes to match (e.g., 'bg', 'text'). If not provided, matches exact class names.",
72
+ items: { type: "string" },
73
+ },
74
+ },
75
+ additionalProperties: false,
76
+ },
77
+ ],
78
+ },
79
+ defaultOptions: [],
80
+ create(context) {
81
+ const options = context.options[0];
82
+ // Return early if no options or empty alternatives to save compute
83
+ if (!options || Object.keys(options.alternatives).length === 0) {
84
+ return {};
85
+ }
86
+ const { alternatives, prefixes } = options;
87
+ const bannedPatterns = Object.keys(alternatives);
88
+ const hasPrefixes = prefixes !== undefined && prefixes.length > 0;
89
+ const isBannedPattern = (pattern) => pattern in alternatives;
90
+ const getAlternatives = (pattern) => {
91
+ return alternatives[pattern] ?? [];
92
+ };
93
+ const isAutoFixable = (pattern) => getAlternatives(pattern).length === 1;
94
+ const BANNED_PATTERN = buildBannedPattern(bannedPatterns, prefixes);
95
+ /**
96
+ * Find all banned pattern usages in a string value
97
+ */
98
+ function findBannedUsages(value) {
99
+ const results = [];
100
+ BANNED_PATTERN.lastIndex = 0;
101
+ let regexMatch;
102
+ while ((regexMatch = BANNED_PATTERN.exec(value)) !== null) {
103
+ const matchedClass = regexMatch[1];
104
+ const matchedPattern = regexMatch[2];
105
+ if (matchedClass !== undefined && matchedPattern !== undefined && isBannedPattern(matchedPattern)) {
106
+ results.push({
107
+ match: matchedClass.trim(),
108
+ pattern: matchedPattern,
109
+ });
110
+ }
111
+ }
112
+ return results;
113
+ }
114
+ /**
115
+ * Replace ALL auto-fixable patterns in a string at once
116
+ */
117
+ function replaceAllAutoFixablePatterns(value) {
118
+ let result = value;
119
+ for (const [pattern, alts] of Object.entries(alternatives)) {
120
+ const singleAlternative = alts.length === 1 ? alts[0] : undefined;
121
+ if (singleAlternative) {
122
+ result = replacePattern(result, pattern, singleAlternative, hasPrefixes);
123
+ }
124
+ }
125
+ return result;
126
+ }
127
+ // Track which string literals have already had a fix generated
128
+ // to avoid duplicate/conflicting fixes for the same literal
129
+ const fixedLiterals = new WeakSet();
130
+ /**
131
+ * Create a fixer function that replaces all auto-fixable patterns in a string literal
132
+ */
133
+ function createFixer(stringLiteralNode) {
134
+ return (fixer) => {
135
+ switch (stringLiteralNode.type) {
136
+ case utils_1.AST_NODE_TYPES.Literal: {
137
+ const originalValue = stringLiteralNode.value;
138
+ if (typeof originalValue !== "string") {
139
+ return null;
140
+ }
141
+ const fixedValue = replaceAllAutoFixablePatterns(originalValue);
142
+ const raw = stringLiteralNode.raw;
143
+ const quote = raw.charAt(0);
144
+ return fixer.replaceText(stringLiteralNode, `${quote}${fixedValue}${quote}`);
145
+ }
146
+ case utils_1.AST_NODE_TYPES.TemplateLiteral: {
147
+ const fixes = [];
148
+ const autoFixablePatterns = Object.entries(alternatives)
149
+ .filter(([, alts]) => alts.length === 1)
150
+ .map(([pattern]) => pattern);
151
+ for (const quasi of stringLiteralNode.quasis) {
152
+ const hasFixablePattern = autoFixablePatterns.some(pattern => hasPrefixes ? quasi.value.raw.includes(`-${pattern}`) : quasi.value.raw.includes(pattern));
153
+ if (hasFixablePattern) {
154
+ const fixedRaw = replaceAllAutoFixablePatterns(quasi.value.raw);
155
+ const rangeStart = quasi.range[0] + (quasi.tail ? 1 : 0);
156
+ const rangeEnd = quasi.range[1] - (quasi.tail ? 1 : 0);
157
+ fixes.push(fixer.replaceTextRange([rangeStart, rangeEnd], quasi.tail ? fixedRaw : fixedRaw + (quasi.value.raw.endsWith(" ") ? "" : "")));
158
+ }
159
+ }
160
+ return fixes.length > 0 ? fixes : null;
161
+ }
162
+ default:
163
+ return null;
164
+ }
165
+ };
166
+ }
167
+ /**
168
+ * Create a suggestion fixer for a specific pattern replacement
169
+ */
170
+ function createSuggestionFixer(stringLiteralNode, oldPattern, newPattern) {
171
+ return (fixer) => {
172
+ switch (stringLiteralNode.type) {
173
+ case utils_1.AST_NODE_TYPES.Literal: {
174
+ const originalValue = stringLiteralNode.value;
175
+ if (typeof originalValue !== "string") {
176
+ return null;
177
+ }
178
+ const fixedValue = replacePattern(originalValue, oldPattern, newPattern, hasPrefixes);
179
+ const raw = stringLiteralNode.raw;
180
+ const quote = raw.charAt(0);
181
+ return fixer.replaceText(stringLiteralNode, `${quote}${fixedValue}${quote}`);
182
+ }
183
+ case utils_1.AST_NODE_TYPES.TemplateLiteral: {
184
+ const fixes = [];
185
+ for (const quasi of stringLiteralNode.quasis) {
186
+ const containsPattern = hasPrefixes
187
+ ? quasi.value.raw.includes(`-${oldPattern}`)
188
+ : quasi.value.raw.includes(oldPattern);
189
+ if (containsPattern) {
190
+ const fixedRaw = replacePattern(quasi.value.raw, oldPattern, newPattern, hasPrefixes);
191
+ const rangeStart = quasi.range[0] + (quasi.tail ? 1 : 0);
192
+ const rangeEnd = quasi.range[1] - (quasi.tail ? 1 : 0);
193
+ fixes.push(fixer.replaceTextRange([rangeStart, rangeEnd], quasi.tail ? fixedRaw : fixedRaw + (quasi.value.raw.endsWith(" ") ? "" : "")));
194
+ }
195
+ }
196
+ return fixes.length > 0 ? fixes : null;
197
+ }
198
+ default:
199
+ return null;
200
+ }
201
+ };
202
+ }
203
+ /**
204
+ * Report banned pattern usages for a given node and value.
205
+ * - Auto-fixable patterns (1 alternative): applies fix automatically
206
+ * - Multi-alternative patterns: shows suggestions in editor quick-fix menu
207
+ */
208
+ function checkAndReport(node, value, stringLiteralNode) {
209
+ const usages = findBannedUsages(value);
210
+ let fixProvided = false;
211
+ for (const usage of usages) {
212
+ const patternIsAutoFixable = isAutoFixable(usage.pattern);
213
+ const patternAlternatives = getAlternatives(usage.pattern);
214
+ let fix;
215
+ let suggest;
216
+ if (stringLiteralNode) {
217
+ if (patternIsAutoFixable && !fixProvided && !fixedLiterals.has(stringLiteralNode)) {
218
+ fixedLiterals.add(stringLiteralNode);
219
+ fixProvided = true;
220
+ fix = createFixer(stringLiteralNode);
221
+ }
222
+ if (!patternIsAutoFixable) {
223
+ suggest = patternAlternatives.map(alternative => ({
224
+ messageId: "suggestReplacement",
225
+ data: { replacement: replacePattern(usage.match, usage.pattern, alternative, hasPrefixes) },
226
+ fix: createSuggestionFixer(stringLiteralNode, usage.pattern, alternative),
227
+ }));
228
+ }
229
+ }
230
+ if (patternIsAutoFixable) {
231
+ const singleAlternative = patternAlternatives[0];
232
+ context.report({
233
+ node,
234
+ messageId: "bannedClassAutoFix",
235
+ data: {
236
+ match: usage.match,
237
+ alternative: singleAlternative ?? "",
238
+ },
239
+ fix,
240
+ });
241
+ }
242
+ else {
243
+ context.report({
244
+ node,
245
+ messageId: "bannedClassSuggest",
246
+ data: {
247
+ match: usage.match,
248
+ alternatives: patternAlternatives.join(", "),
249
+ },
250
+ suggest,
251
+ });
252
+ }
253
+ }
254
+ }
255
+ return {
256
+ JSXAttribute(node) {
257
+ const locations = (0, classname_utils_1.findClassnameStringsInAttribute)(node);
258
+ for (const location of locations) {
259
+ checkAndReport(location.reportNode, location.value, location.fixNode);
260
+ }
261
+ },
262
+ CallExpression(node) {
263
+ const locations = (0, classname_utils_1.findClassnameStringsInCall)(node);
264
+ for (const location of locations) {
265
+ checkAndReport(location.reportNode, location.value, location.fixNode);
266
+ }
267
+ },
268
+ };
269
+ },
270
+ });
271
+ //# sourceMappingURL=require-classname-alternatives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-classname-alternatives.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/require-classname-alternatives/require-classname-alternatives.ts"],"names":[],"mappings":"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","sourcesContent":["import { AST_NODE_TYPES, ESLintUtils, TSESTree } from \"@typescript-eslint/utils\";\nimport { findClassnameStringsInAttribute, findClassnameStringsInCall } from \"../../utils/classname-utils\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`\n);\n\ntype MessageIds = \"bannedClassAutoFix\" | \"bannedClassSuggest\" | \"suggestReplacement\";\n\ntype RuleConfig = {\n /** Map of banned class name patterns to their allowed alternatives */\n alternatives: Record<string, Array<string>>;\n /** Optional prefixes to match (e.g., \"bg\", \"text\", \"border\"). If not provided, matches exact class names. */\n prefixes?: Array<string>;\n};\n\ntype Options = [RuleConfig?];\n\n/**\n * Replace a specific pattern in a string with its replacement.\n * Handles both prefix-based patterns (e.g., bg-red-500 -> bg-danger-500)\n * and exact matches.\n */\nfunction replacePattern(value: string, oldPattern: string, newPattern: string, hasPrefixes: boolean): string {\n if (hasPrefixes) {\n // With prefixes, replace -oldPattern- with -newPattern-\n const pattern = new RegExp(`-${oldPattern}(-|$|/)`, \"g\");\n return value.replace(pattern, `-${newPattern}$1`);\n } else {\n // Without prefixes, replace exact word boundaries\n const pattern = new RegExp(`(^|\\\\s)${oldPattern}($|\\\\s)`, \"g\");\n return value.replace(pattern, `$1${newPattern}$2`);\n }\n}\n\n/**\n * Build a regex pattern that matches class names based on configuration.\n *\n * With prefixes: Matches patterns like bg-red-500, text-blue-200, hover:border-green-400\n * Without prefixes: Matches exact class names as standalone words\n */\nfunction buildBannedPattern(bannedPatterns: Array<string>, prefixes?: Array<string>): RegExp {\n const patternList = bannedPatterns.join(\"|\");\n const variantPrefixes =\n \"(?:hover:|focus:|focus-visible:|focus-within:|active:|disabled:|group-hover:|peer-hover:|dark:|light:|sm:|md:|lg:|xl:|2xl:)*\";\n\n if (prefixes && prefixes.length > 0) {\n const prefixPattern = prefixes.join(\"|\");\n // Match: [variant:]prefix-banned[-suffix][/opacity]\n return new RegExp(\n `(?:^|\\\\s)(${variantPrefixes}(?:${prefixPattern})-(${patternList})(?:-[a-zA-Z0-9]+)?(?:\\\\/\\\\d+)?)(?=\\\\s|$|\")`,\n \"g\"\n );\n } else {\n // Exact match: standalone class name\n return new RegExp(`(?:^|\\\\s)(${variantPrefixes}(${patternList}))(?=\\\\s|$|\")`, \"g\");\n }\n}\n\nexport const requireClassnameAlternatives = createRule<Options, MessageIds>({\n name: \"require-classname-alternatives\",\n meta: {\n type: \"problem\",\n fixable: \"code\",\n hasSuggestions: true,\n docs: {\n description: \"Require specific class names to be replaced with alternatives. Configurable via rule options.\",\n },\n messages: {\n bannedClassAutoFix: '\"{{match}}\" is not allowed. Use \"{{alternative}}\" instead.',\n bannedClassSuggest: '\"{{match}}\" is not allowed. Consider using: {{alternatives}}.',\n suggestReplacement: 'Replace with \"{{replacement}}\"',\n },\n schema: [\n {\n type: \"object\",\n properties: {\n alternatives: {\n type: \"object\",\n description: \"Map of banned patterns to their allowed alternatives (array of strings)\",\n additionalProperties: {\n type: \"array\",\n items: { type: \"string\" },\n },\n },\n prefixes: {\n type: \"array\",\n description: \"Prefixes to match (e.g., 'bg', 'text'). If not provided, matches exact class names.\",\n items: { type: \"string\" },\n },\n },\n additionalProperties: false,\n },\n ],\n },\n defaultOptions: [],\n create(context) {\n const options = context.options[0];\n\n // Return early if no options or empty alternatives to save compute\n if (!options || Object.keys(options.alternatives).length === 0) {\n return {};\n }\n\n const { alternatives, prefixes } = options;\n const bannedPatterns = Object.keys(alternatives);\n const hasPrefixes = prefixes !== undefined && prefixes.length > 0;\n\n const isBannedPattern = (pattern: string): boolean => pattern in alternatives;\n\n const getAlternatives = (pattern: string): Array<string> => {\n return alternatives[pattern] ?? [];\n };\n\n const isAutoFixable = (pattern: string): boolean => getAlternatives(pattern).length === 1;\n\n const BANNED_PATTERN = buildBannedPattern(bannedPatterns, prefixes);\n\n /**\n * Find all banned pattern usages in a string value\n */\n function findBannedUsages(value: string): Array<{ match: string; pattern: string }> {\n const results: Array<{ match: string; pattern: string }> = [];\n BANNED_PATTERN.lastIndex = 0;\n\n let regexMatch;\n while ((regexMatch = BANNED_PATTERN.exec(value)) !== null) {\n const matchedClass = regexMatch[1];\n const matchedPattern = regexMatch[2];\n if (matchedClass !== undefined && matchedPattern !== undefined && isBannedPattern(matchedPattern)) {\n results.push({\n match: matchedClass.trim(),\n pattern: matchedPattern,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Replace ALL auto-fixable patterns in a string at once\n */\n function replaceAllAutoFixablePatterns(value: string): string {\n let result = value;\n for (const [pattern, alts] of Object.entries(alternatives)) {\n const singleAlternative = alts.length === 1 ? alts[0] : undefined;\n if (singleAlternative) {\n result = replacePattern(result, pattern, singleAlternative, hasPrefixes);\n }\n }\n return result;\n }\n\n // Track which string literals have already had a fix generated\n // to avoid duplicate/conflicting fixes for the same literal\n const fixedLiterals = new WeakSet<TSESTree.Literal | TSESTree.TemplateLiteral>();\n\n /**\n * Create a fixer function that replaces all auto-fixable patterns in a string literal\n */\n function createFixer(stringLiteralNode: TSESTree.Literal | TSESTree.TemplateLiteral) {\n return (fixer: Parameters<NonNullable<Parameters<typeof context.report>[0][\"fix\"]>>[0]) => {\n switch (stringLiteralNode.type) {\n case AST_NODE_TYPES.Literal: {\n const originalValue = stringLiteralNode.value;\n if (typeof originalValue !== \"string\") {\n return null;\n }\n const fixedValue = replaceAllAutoFixablePatterns(originalValue);\n const raw = stringLiteralNode.raw;\n const quote = raw.charAt(0);\n return fixer.replaceText(stringLiteralNode, `${quote}${fixedValue}${quote}`);\n }\n case AST_NODE_TYPES.TemplateLiteral: {\n const fixes: Array<ReturnType<typeof fixer.replaceTextRange>> = [];\n const autoFixablePatterns = Object.entries(alternatives)\n .filter(([, alts]) => alts.length === 1)\n .map(([pattern]) => pattern);\n\n for (const quasi of stringLiteralNode.quasis) {\n const hasFixablePattern = autoFixablePatterns.some(pattern =>\n hasPrefixes ? quasi.value.raw.includes(`-${pattern}`) : quasi.value.raw.includes(pattern)\n );\n if (hasFixablePattern) {\n const fixedRaw = replaceAllAutoFixablePatterns(quasi.value.raw);\n const rangeStart = quasi.range[0] + (quasi.tail ? 1 : 0);\n const rangeEnd = quasi.range[1] - (quasi.tail ? 1 : 0);\n fixes.push(\n fixer.replaceTextRange(\n [rangeStart, rangeEnd],\n quasi.tail ? fixedRaw : fixedRaw + (quasi.value.raw.endsWith(\" \") ? \"\" : \"\")\n )\n );\n }\n }\n return fixes.length > 0 ? fixes : null;\n }\n default:\n return null;\n }\n };\n }\n\n /**\n * Create a suggestion fixer for a specific pattern replacement\n */\n function createSuggestionFixer(\n stringLiteralNode: TSESTree.Literal | TSESTree.TemplateLiteral,\n oldPattern: string,\n newPattern: string\n ) {\n return (fixer: Parameters<NonNullable<Parameters<typeof context.report>[0][\"fix\"]>>[0]) => {\n switch (stringLiteralNode.type) {\n case AST_NODE_TYPES.Literal: {\n const originalValue = stringLiteralNode.value;\n if (typeof originalValue !== \"string\") {\n return null;\n }\n const fixedValue = replacePattern(originalValue, oldPattern, newPattern, hasPrefixes);\n const raw = stringLiteralNode.raw;\n const quote = raw.charAt(0);\n return fixer.replaceText(stringLiteralNode, `${quote}${fixedValue}${quote}`);\n }\n case AST_NODE_TYPES.TemplateLiteral: {\n const fixes: Array<ReturnType<typeof fixer.replaceTextRange>> = [];\n for (const quasi of stringLiteralNode.quasis) {\n const containsPattern = hasPrefixes\n ? quasi.value.raw.includes(`-${oldPattern}`)\n : quasi.value.raw.includes(oldPattern);\n if (containsPattern) {\n const fixedRaw = replacePattern(quasi.value.raw, oldPattern, newPattern, hasPrefixes);\n const rangeStart = quasi.range[0] + (quasi.tail ? 1 : 0);\n const rangeEnd = quasi.range[1] - (quasi.tail ? 1 : 0);\n fixes.push(\n fixer.replaceTextRange(\n [rangeStart, rangeEnd],\n quasi.tail ? fixedRaw : fixedRaw + (quasi.value.raw.endsWith(\" \") ? \"\" : \"\")\n )\n );\n }\n }\n return fixes.length > 0 ? fixes : null;\n }\n default:\n return null;\n }\n };\n }\n\n /**\n * Report banned pattern usages for a given node and value.\n * - Auto-fixable patterns (1 alternative): applies fix automatically\n * - Multi-alternative patterns: shows suggestions in editor quick-fix menu\n */\n function checkAndReport(\n node: TSESTree.Node,\n value: string,\n stringLiteralNode?: TSESTree.Literal | TSESTree.TemplateLiteral\n ): void {\n const usages = findBannedUsages(value);\n let fixProvided = false;\n\n for (const usage of usages) {\n const patternIsAutoFixable = isAutoFixable(usage.pattern);\n const patternAlternatives = getAlternatives(usage.pattern);\n\n let fix: Parameters<typeof context.report>[0][\"fix\"];\n let suggest: Parameters<typeof context.report>[0][\"suggest\"];\n\n if (stringLiteralNode) {\n if (patternIsAutoFixable && !fixProvided && !fixedLiterals.has(stringLiteralNode)) {\n fixedLiterals.add(stringLiteralNode);\n fixProvided = true;\n fix = createFixer(stringLiteralNode);\n }\n\n if (!patternIsAutoFixable) {\n suggest = patternAlternatives.map(alternative => ({\n messageId: \"suggestReplacement\" as const,\n data: { replacement: replacePattern(usage.match, usage.pattern, alternative, hasPrefixes) },\n fix: createSuggestionFixer(stringLiteralNode, usage.pattern, alternative),\n }));\n }\n }\n\n if (patternIsAutoFixable) {\n const singleAlternative = patternAlternatives[0];\n context.report({\n node,\n messageId: \"bannedClassAutoFix\",\n data: {\n match: usage.match,\n alternative: singleAlternative ?? \"\",\n },\n fix,\n });\n } else {\n context.report({\n node,\n messageId: \"bannedClassSuggest\",\n data: {\n match: usage.match,\n alternatives: patternAlternatives.join(\", \"),\n },\n suggest,\n });\n }\n }\n }\n\n return {\n JSXAttribute(node: TSESTree.JSXAttribute) {\n const locations = findClassnameStringsInAttribute(node);\n for (const location of locations) {\n checkAndReport(location.reportNode, location.value, location.fixNode);\n }\n },\n\n CallExpression(node: TSESTree.CallExpression) {\n const locations = findClassnameStringsInCall(node);\n for (const location of locations) {\n checkAndReport(location.reportNode, location.value, location.fixNode);\n }\n },\n };\n },\n});\n"]}
@@ -0,0 +1,15 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ type Options = [
3
+ {
4
+ /**
5
+ * Whether to allow custom components that extend VirtualizationListItemProps
6
+ * instead of requiring explicit spreading of listItemProps
7
+ */
8
+ allowCustomComponents?: boolean;
9
+ }
10
+ ];
11
+ type MessageIds = "requireVirtualizationProps" | "spreadListItemProps" | "useSemanticListItem";
12
+ export declare const requireListItemVirtualizationProps: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
13
+ name: string;
14
+ };
15
+ export {};
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireListItemVirtualizationProps = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`);
6
+ /**
7
+ * Check if a spread attribute argument contains listItemProps
8
+ */
9
+ const isListItemPropsSpread = (argument) => {
10
+ // Direct identifier: {...listItemProps}
11
+ if (argument.type === utils_1.AST_NODE_TYPES.Identifier) {
12
+ return argument.name === "listItemProps" || argument.name.includes("ItemProps");
13
+ }
14
+ // Member expression: {...params.listItemProps}
15
+ if (argument.type === utils_1.AST_NODE_TYPES.MemberExpression) {
16
+ // Check if property is an identifier (not computed like params["listItemProps"])
17
+ if (!argument.computed && argument.property.type === utils_1.AST_NODE_TYPES.Identifier) {
18
+ const propertyName = argument.property.name;
19
+ if (propertyName === "listItemProps" || propertyName.includes("ItemProps")) {
20
+ return true;
21
+ }
22
+ }
23
+ }
24
+ return false;
25
+ };
26
+ /**
27
+ * Check if a JSX element has the required virtualization props
28
+ */
29
+ const hasRequiredVirtualizationProps = (element, context) => {
30
+ // ref is functionally required for virtualization even though TypeScript marks it optional
31
+ const requiredProps = ["className", "data-index", "tabIndex", "ref"];
32
+ const attributes = element.openingElement.attributes;
33
+ const propNames = new Set();
34
+ let hasSpreadListItemProps = false;
35
+ for (const attr of attributes) {
36
+ if (attr.type === utils_1.AST_NODE_TYPES.JSXAttribute && attr.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
37
+ propNames.add(attr.name.name);
38
+ }
39
+ else if (attr.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
40
+ // Check if spreading listItemProps or similar
41
+ if (isListItemPropsSpread(attr.argument)) {
42
+ hasSpreadListItemProps = true;
43
+ }
44
+ else {
45
+ // Fallback: check source code text for listItemProps
46
+ // This handles edge cases where AST structure might be different
47
+ const sourceCode = context.sourceCode.getText(attr.argument);
48
+ const hasListItemProps = sourceCode.includes("listItemProps");
49
+ const hasItemProps = sourceCode.includes("ItemProps");
50
+ if (hasListItemProps || hasItemProps) {
51
+ hasSpreadListItemProps = true;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ // If spreading listItemProps, assume it has all required props
57
+ if (hasSpreadListItemProps) {
58
+ return true;
59
+ }
60
+ // Check if all required props are present
61
+ return requiredProps.every(prop => propNames.has(prop));
62
+ };
63
+ /**
64
+ * Check if we're inside a List component's render function
65
+ */
66
+ const isInsideListRenderFunction = (node) => {
67
+ let current = node.parent;
68
+ while (current) {
69
+ // Look for JSX element named "List"
70
+ if (current.type === utils_1.AST_NODE_TYPES.JSXElement &&
71
+ current.openingElement.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier &&
72
+ current.openingElement.name.name === "List") {
73
+ return true;
74
+ }
75
+ current = current.parent;
76
+ }
77
+ return false;
78
+ };
79
+ /**
80
+ * Check if a JSX element is a direct list item (directly returned from List render function)
81
+ */
82
+ const isListItemElement = (element) => {
83
+ // Check if this element is the direct return value from a List render function
84
+ const current = element.parent;
85
+ // We need to find if this element is DIRECTLY returned from the render function
86
+ // This means it should be the immediate child of a return statement or arrow function body
87
+ // Case 1: Direct return statement - return (<li>...</li>)
88
+ if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
89
+ return isInsideListRenderFunction(current);
90
+ }
91
+ // Case 2: Arrow function body - ({ key, listItemProps, item }) => (<li>...</li>)
92
+ if (current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
93
+ return isInsideListRenderFunction(current);
94
+ }
95
+ // Case 3: Conditional expression - item ? <li>...</li> : null
96
+ if (current.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
97
+ // Check if the conditional is directly returned or is the arrow function body
98
+ const conditionalParent = current.parent;
99
+ if (conditionalParent.type === utils_1.AST_NODE_TYPES.ReturnStatement ||
100
+ conditionalParent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
101
+ return isInsideListRenderFunction(conditionalParent);
102
+ }
103
+ }
104
+ return false;
105
+ };
106
+ /**
107
+ * Get the name of a JSX element
108
+ */
109
+ const getJSXElementName = (element) => {
110
+ if (element.openingElement.name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
111
+ return element.openingElement.name.name;
112
+ }
113
+ return null;
114
+ };
115
+ /**
116
+ * Check if element is a semantic list item element
117
+ */
118
+ const isSemanticListItem = (elementName) => {
119
+ return elementName.toLowerCase() === "li";
120
+ };
121
+ /**
122
+ * Check if element name is a built-in HTML element (vs custom React component)
123
+ */
124
+ const isHTMLElement = (elementName) => {
125
+ // HTML elements are lowercase, React components start with uppercase
126
+ if (elementName.length === 0) {
127
+ return false;
128
+ }
129
+ const firstChar = elementName[0];
130
+ if (!firstChar) {
131
+ return false;
132
+ }
133
+ return firstChar === firstChar.toLowerCase();
134
+ };
135
+ exports.requireListItemVirtualizationProps = createRule({
136
+ name: "require-list-item-virtualization-props",
137
+ meta: {
138
+ type: "problem",
139
+ docs: {
140
+ description: "Require list items within <List> to have VirtualizationListItemProps",
141
+ },
142
+ hasSuggestions: true,
143
+ schema: [
144
+ {
145
+ type: "object",
146
+ properties: {
147
+ allowCustomComponents: { type: "boolean" },
148
+ },
149
+ additionalProperties: false,
150
+ },
151
+ ],
152
+ messages: {
153
+ requireVirtualizationProps: "List item '{{elementName}}' must include VirtualizationListItemProps. " +
154
+ "Spread {{...listItemProps}} or manually specify className, data-index, tabIndex, ref props.",
155
+ spreadListItemProps: "Spread {...listItemProps} on this element",
156
+ useSemanticListItem: "Use semantic <li> elements for list items instead of '{{elementName}}'. " +
157
+ "List items should be <li> elements with proper VirtualizationListItemProps.",
158
+ },
159
+ },
160
+ defaultOptions: [
161
+ {
162
+ allowCustomComponents: true,
163
+ },
164
+ ],
165
+ create(context, options) {
166
+ const config = options[0];
167
+ return {
168
+ JSXElement(node) {
169
+ // Skip if not inside a List render function
170
+ if (!isListItemElement(node)) {
171
+ return;
172
+ }
173
+ const elementName = getJSXElementName(node);
174
+ if (!elementName) {
175
+ return;
176
+ }
177
+ // Check element type and apply appropriate rules
178
+ if (isHTMLElement(elementName)) {
179
+ // For HTML elements, enforce semantic list items
180
+ if (!isSemanticListItem(elementName)) {
181
+ context.report({
182
+ node: node.openingElement,
183
+ messageId: "useSemanticListItem",
184
+ data: { elementName },
185
+ });
186
+ return;
187
+ }
188
+ // For <li> elements, require virtualization props
189
+ if (!hasRequiredVirtualizationProps(node, context)) {
190
+ context.report({
191
+ node: node.openingElement,
192
+ messageId: "requireVirtualizationProps",
193
+ data: { elementName },
194
+ suggest: [
195
+ {
196
+ messageId: "spreadListItemProps",
197
+ fix(fixer) {
198
+ // Add {...listItemProps} as the first attribute
199
+ const openingTag = node.openingElement;
200
+ const firstAttr = openingTag.attributes[0];
201
+ if (firstAttr) {
202
+ return fixer.insertTextBefore(firstAttr, "{...listItemProps} ");
203
+ }
204
+ else {
205
+ // Insert after the element name
206
+ return fixer.insertTextAfter(openingTag.name, " {...listItemProps}");
207
+ }
208
+ },
209
+ },
210
+ ],
211
+ });
212
+ }
213
+ }
214
+ else {
215
+ // For custom components, check allowCustomComponents setting
216
+ if (config.allowCustomComponents === false && !hasRequiredVirtualizationProps(node, context)) {
217
+ context.report({
218
+ node: node.openingElement,
219
+ messageId: "requireVirtualizationProps",
220
+ data: { elementName },
221
+ suggest: [
222
+ {
223
+ messageId: "spreadListItemProps",
224
+ fix(fixer) {
225
+ // Add {...listItemProps} as the first attribute
226
+ const openingTag = node.openingElement;
227
+ const firstAttr = openingTag.attributes[0];
228
+ if (firstAttr) {
229
+ return fixer.insertTextBefore(firstAttr, "{...listItemProps} ");
230
+ }
231
+ else {
232
+ // Insert after the element name
233
+ return fixer.insertTextAfter(openingTag.name, " {...listItemProps}");
234
+ }
235
+ },
236
+ },
237
+ ],
238
+ });
239
+ }
240
+ }
241
+ },
242
+ };
243
+ },
244
+ });
245
+ //# sourceMappingURL=require-list-item-virtualization-props.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-list-item-virtualization-props.js","sourceRoot":"","sources":["../../../../../../../../libs/eslint/plugin-trackunit/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.ts"],"names":[],"mappings":"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","sourcesContent":["import { AST_NODE_TYPES, ESLintUtils, TSESLint, TSESTree } from \"@typescript-eslint/utils\";\n\nconst createRule = ESLintUtils.RuleCreator(\n name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`\n);\n\ntype Options = [\n {\n /**\n * Whether to allow custom components that extend VirtualizationListItemProps\n * instead of requiring explicit spreading of listItemProps\n */\n allowCustomComponents?: boolean;\n },\n];\n\ntype MessageIds = \"requireVirtualizationProps\" | \"spreadListItemProps\" | \"useSemanticListItem\";\n\n/**\n * Check if a spread attribute argument contains listItemProps\n */\nconst isListItemPropsSpread = (argument: TSESTree.Expression): boolean => {\n // Direct identifier: {...listItemProps}\n if (argument.type === AST_NODE_TYPES.Identifier) {\n return argument.name === \"listItemProps\" || argument.name.includes(\"ItemProps\");\n }\n\n // Member expression: {...params.listItemProps}\n if (argument.type === AST_NODE_TYPES.MemberExpression) {\n // Check if property is an identifier (not computed like params[\"listItemProps\"])\n if (!argument.computed && argument.property.type === AST_NODE_TYPES.Identifier) {\n const propertyName = argument.property.name;\n if (propertyName === \"listItemProps\" || propertyName.includes(\"ItemProps\")) {\n return true;\n }\n }\n }\n\n return false;\n};\n\n/**\n * Check if a JSX element has the required virtualization props\n */\nconst hasRequiredVirtualizationProps = (\n element: TSESTree.JSXElement,\n context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>\n): boolean => {\n // ref is functionally required for virtualization even though TypeScript marks it optional\n const requiredProps = [\"className\", \"data-index\", \"tabIndex\", \"ref\"];\n\n const attributes = element.openingElement.attributes;\n const propNames = new Set<string>();\n let hasSpreadListItemProps = false;\n\n for (const attr of attributes) {\n if (attr.type === AST_NODE_TYPES.JSXAttribute && attr.name.type === AST_NODE_TYPES.JSXIdentifier) {\n propNames.add(attr.name.name);\n } else if (attr.type === AST_NODE_TYPES.JSXSpreadAttribute) {\n // Check if spreading listItemProps or similar\n if (isListItemPropsSpread(attr.argument)) {\n hasSpreadListItemProps = true;\n } else {\n // Fallback: check source code text for listItemProps\n // This handles edge cases where AST structure might be different\n const sourceCode = context.sourceCode.getText(attr.argument);\n const hasListItemProps = sourceCode.includes(\"listItemProps\");\n const hasItemProps = sourceCode.includes(\"ItemProps\");\n if (hasListItemProps || hasItemProps) {\n hasSpreadListItemProps = true;\n }\n }\n }\n }\n\n // If spreading listItemProps, assume it has all required props\n if (hasSpreadListItemProps) {\n return true;\n }\n\n // Check if all required props are present\n return requiredProps.every(prop => propNames.has(prop));\n};\n\n/**\n * Check if we're inside a List component's render function\n */\nconst isInsideListRenderFunction = (node: TSESTree.Node): boolean => {\n let current: TSESTree.Node | undefined = node.parent;\n\n while (current) {\n // Look for JSX element named \"List\"\n if (\n current.type === AST_NODE_TYPES.JSXElement &&\n current.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier &&\n current.openingElement.name.name === \"List\"\n ) {\n return true;\n }\n current = current.parent;\n }\n\n return false;\n};\n\n/**\n * Check if a JSX element is a direct list item (directly returned from List render function)\n */\nconst isListItemElement = (element: TSESTree.JSXElement): boolean => {\n // Check if this element is the direct return value from a List render function\n const current: TSESTree.Node | undefined = element.parent;\n\n // We need to find if this element is DIRECTLY returned from the render function\n // This means it should be the immediate child of a return statement or arrow function body\n\n // Case 1: Direct return statement - return (<li>...</li>)\n if (current.type === AST_NODE_TYPES.ReturnStatement) {\n return isInsideListRenderFunction(current);\n }\n\n // Case 2: Arrow function body - ({ key, listItemProps, item }) => (<li>...</li>)\n if (current.type === AST_NODE_TYPES.ArrowFunctionExpression) {\n return isInsideListRenderFunction(current);\n }\n\n // Case 3: Conditional expression - item ? <li>...</li> : null\n if (current.type === AST_NODE_TYPES.ConditionalExpression) {\n // Check if the conditional is directly returned or is the arrow function body\n const conditionalParent = current.parent;\n if (\n conditionalParent.type === AST_NODE_TYPES.ReturnStatement ||\n conditionalParent.type === AST_NODE_TYPES.ArrowFunctionExpression\n ) {\n return isInsideListRenderFunction(conditionalParent);\n }\n }\n\n return false;\n};\n\n/**\n * Get the name of a JSX element\n */\nconst getJSXElementName = (element: TSESTree.JSXElement): string | null => {\n if (element.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier) {\n return element.openingElement.name.name;\n }\n return null;\n};\n\n/**\n * Check if element is a semantic list item element\n */\nconst isSemanticListItem = (elementName: string): boolean => {\n return elementName.toLowerCase() === \"li\";\n};\n\n/**\n * Check if element name is a built-in HTML element (vs custom React component)\n */\nconst isHTMLElement = (elementName: string): boolean => {\n // HTML elements are lowercase, React components start with uppercase\n if (elementName.length === 0) {\n return false;\n }\n const firstChar = elementName[0];\n if (!firstChar) {\n return false;\n }\n return firstChar === firstChar.toLowerCase();\n};\n\nexport const requireListItemVirtualizationProps = createRule<Options, MessageIds>({\n name: \"require-list-item-virtualization-props\",\n meta: {\n type: \"problem\",\n docs: {\n description: \"Require list items within <List> to have VirtualizationListItemProps\",\n },\n hasSuggestions: true,\n schema: [\n {\n type: \"object\",\n properties: {\n allowCustomComponents: { type: \"boolean\" },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n requireVirtualizationProps:\n \"List item '{{elementName}}' must include VirtualizationListItemProps. \" +\n \"Spread {{...listItemProps}} or manually specify className, data-index, tabIndex, ref props.\",\n spreadListItemProps: \"Spread {...listItemProps} on this element\",\n useSemanticListItem:\n \"Use semantic <li> elements for list items instead of '{{elementName}}'. \" +\n \"List items should be <li> elements with proper VirtualizationListItemProps.\",\n },\n },\n defaultOptions: [\n {\n allowCustomComponents: true,\n },\n ],\n create(context, options) {\n const config = options[0];\n\n return {\n JSXElement(node: TSESTree.JSXElement) {\n // Skip if not inside a List render function\n if (!isListItemElement(node)) {\n return;\n }\n\n const elementName = getJSXElementName(node);\n if (!elementName) {\n return;\n }\n\n // Check element type and apply appropriate rules\n if (isHTMLElement(elementName)) {\n // For HTML elements, enforce semantic list items\n if (!isSemanticListItem(elementName)) {\n context.report({\n node: node.openingElement,\n messageId: \"useSemanticListItem\",\n data: { elementName },\n });\n return;\n }\n\n // For <li> elements, require virtualization props\n if (!hasRequiredVirtualizationProps(node, context)) {\n context.report({\n node: node.openingElement,\n messageId: \"requireVirtualizationProps\",\n data: { elementName },\n suggest: [\n {\n messageId: \"spreadListItemProps\",\n fix(fixer) {\n // Add {...listItemProps} as the first attribute\n const openingTag = node.openingElement;\n const firstAttr = openingTag.attributes[0];\n\n if (firstAttr) {\n return fixer.insertTextBefore(firstAttr, \"{...listItemProps} \");\n } else {\n // Insert after the element name\n return fixer.insertTextAfter(openingTag.name, \" {...listItemProps}\");\n }\n },\n },\n ],\n });\n }\n } else {\n // For custom components, check allowCustomComponents setting\n if (config.allowCustomComponents === false && !hasRequiredVirtualizationProps(node, context)) {\n context.report({\n node: node.openingElement,\n messageId: \"requireVirtualizationProps\",\n data: { elementName },\n suggest: [\n {\n messageId: \"spreadListItemProps\",\n fix(fixer) {\n // Add {...listItemProps} as the first attribute\n const openingTag = node.openingElement;\n const firstAttr = openingTag.attributes[0];\n\n if (firstAttr) {\n return fixer.insertTextBefore(firstAttr, \"{...listItemProps} \");\n } else {\n // Insert after the element name\n return fixer.insertTextAfter(openingTag.name, \" {...listItemProps}\");\n }\n },\n },\n ],\n });\n }\n }\n },\n };\n },\n});\n"]}
@@ -0,0 +1,17 @@
1
+ import { ESLintUtils } from "@typescript-eslint/utils";
2
+ import { type MessageIds } from "./suggestion-utils";
3
+ type Options = [
4
+ {
5
+ allowNullableObject?: boolean;
6
+ allowNullableBoolean?: boolean;
7
+ allowNullableString?: boolean;
8
+ allowNullableNumber?: boolean;
9
+ allowNullableEnum?: boolean;
10
+ allowNullableStringLiteralUnion?: boolean;
11
+ onlyRequireUsedProps?: boolean;
12
+ }
13
+ ];
14
+ export declare const requireOptionalPropInitialization: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener> & {
15
+ name: string;
16
+ };
17
+ export {};