@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.
- package/CHANGELOG.md +9 -0
- package/README.md +117 -0
- package/package.json +31 -0
- package/src/index.d.ts +8 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/config/fragments/ignores.d.ts +2 -0
- package/src/lib/config/fragments/ignores.js +18 -0
- package/src/lib/config/fragments/ignores.js.map +1 -0
- package/src/lib/config/fragments/import-rules.d.ts +3 -0
- package/src/lib/config/fragments/import-rules.js +58 -0
- package/src/lib/config/fragments/import-rules.js.map +1 -0
- package/src/lib/config/fragments/jest-overrides.d.ts +2 -0
- package/src/lib/config/fragments/jest-overrides.js +30 -0
- package/src/lib/config/fragments/jest-overrides.js.map +1 -0
- package/src/lib/config/fragments/jsdoc-rules.d.ts +3 -0
- package/src/lib/config/fragments/jsdoc-rules.js +71 -0
- package/src/lib/config/fragments/jsdoc-rules.js.map +1 -0
- package/src/lib/config/fragments/module-boundaries.d.ts +2 -0
- package/src/lib/config/fragments/module-boundaries.js +92 -0
- package/src/lib/config/fragments/module-boundaries.js.map +1 -0
- package/src/lib/config/fragments/react-rules.d.ts +5 -0
- package/src/lib/config/fragments/react-rules.js +137 -0
- package/src/lib/config/fragments/react-rules.js.map +1 -0
- package/src/lib/config/fragments/restricted-imports.d.ts +2 -0
- package/src/lib/config/fragments/restricted-imports.js +58 -0
- package/src/lib/config/fragments/restricted-imports.js.map +1 -0
- package/src/lib/config/fragments/testing-library.d.ts +2 -0
- package/src/lib/config/fragments/testing-library.js +7 -0
- package/src/lib/config/fragments/testing-library.js.map +1 -0
- package/src/lib/config/fragments/typescript-rules.d.ts +2 -0
- package/src/lib/config/fragments/typescript-rules.js +97 -0
- package/src/lib/config/fragments/typescript-rules.js.map +1 -0
- package/src/lib/config/index.d.ts +863 -0
- package/src/lib/config/index.js +10 -0
- package/src/lib/config/index.js.map +1 -0
- package/src/lib/config/plugins.d.ts +90 -0
- package/src/lib/config/plugins.js +44 -0
- package/src/lib/config/plugins.js.map +1 -0
- package/src/lib/config/presets/base.d.ts +265 -0
- package/src/lib/config/presets/base.js +145 -0
- package/src/lib/config/presets/base.js.map +1 -0
- package/src/lib/config/presets/e2e.d.ts +10 -0
- package/src/lib/config/presets/e2e.js +19 -0
- package/src/lib/config/presets/e2e.js.map +1 -0
- package/src/lib/config/presets/public-api.d.ts +147 -0
- package/src/lib/config/presets/public-api.js +62 -0
- package/src/lib/config/presets/public-api.js.map +1 -0
- package/src/lib/config/presets/react.d.ts +598 -0
- package/src/lib/config/presets/react.js +97 -0
- package/src/lib/config/presets/react.js.map +1 -0
- package/src/lib/config/presets/server.d.ts +36 -0
- package/src/lib/config/presets/server.js +37 -0
- package/src/lib/config/presets/server.js.map +1 -0
- package/src/lib/config/utils.d.ts +6 -0
- package/src/lib/config/utils.js +28 -0
- package/src/lib/config/utils.js.map +1 -0
- package/src/lib/config-helpers/create-skip-when.d.ts +35 -0
- package/src/lib/config-helpers/create-skip-when.js +54 -0
- package/src/lib/config-helpers/create-skip-when.js.map +1 -0
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.d.ts +16 -0
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js +83 -0
- package/src/lib/rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array.js.map +1 -0
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.d.ts +4 -0
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js +297 -0
- package/src/lib/rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match.js.map +1 -0
- package/src/lib/rules/no-internal-barrel-files/examples.d.ts +80 -0
- package/src/lib/rules/no-internal-barrel-files/examples.js +84 -0
- package/src/lib/rules/no-internal-barrel-files/examples.js.map +1 -0
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.d.ts +29 -0
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js +178 -0
- package/src/lib/rules/no-internal-barrel-files/no-internal-barrel-files.js.map +1 -0
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.d.ts +5 -0
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js +67 -0
- package/src/lib/rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public.js.map +1 -0
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.d.ts +2 -0
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js +34 -0
- package/src/lib/rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks.js.map +1 -0
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.d.ts +16 -0
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js +55 -0
- package/src/lib/rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop.js.map +1 -0
- package/src/lib/rules/no-typescript-assertion/examples.d.ts +1 -0
- package/src/lib/rules/no-typescript-assertion/examples.js +45 -0
- package/src/lib/rules/no-typescript-assertion/examples.js.map +1 -0
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.d.ts +20 -0
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js +83 -0
- package/src/lib/rules/no-typescript-assertion/no-typescript-assertion.js.map +1 -0
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.d.ts +73 -0
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js +333 -0
- package/src/lib/rules/prefer-destructured-imports/prefer-destructured-imports.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.d.ts +56 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js +225 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/name-suggestion-strategies.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.d.ts +49 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js +75 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.d.ts +32 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js +143 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/string-based.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.d.ts +27 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js +196 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/strategies/type-based.js.map +1 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.d.ts +76 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.js +245 -0
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.js.map +1 -0
- package/src/lib/rules/prefer-field-components/prefer-field-components.d.ts +4 -0
- package/src/lib/rules/prefer-field-components/prefer-field-components.js +289 -0
- package/src/lib/rules/prefer-field-components/prefer-field-components.js.map +1 -0
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.d.ts +26 -0
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js +402 -0
- package/src/lib/rules/prefer-mouse-event-handler-in-react-props/prefer-mouse-event-handler-in-react-props.js.map +1 -0
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.d.ts +13 -0
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js +271 -0
- package/src/lib/rules/require-classname-alternatives/require-classname-alternatives.js.map +1 -0
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.d.ts +15 -0
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js +245 -0
- package/src/lib/rules/require-list-item-virtualization-props/require-list-item-virtualization-props.js.map +1 -0
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.d.ts +17 -0
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js +133 -0
- package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.js.map +1 -0
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.d.ts +12 -0
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js +128 -0
- package/src/lib/rules/require-optional-prop-initialization/suggestion-utils.js.map +1 -0
- package/src/lib/rules-map.d.ts +66 -0
- package/src/lib/rules-map.js +34 -0
- package/src/lib/rules-map.js.map +1 -0
- package/src/lib/utils/ast-utils.d.ts +85 -0
- package/src/lib/utils/ast-utils.js +530 -0
- package/src/lib/utils/ast-utils.js.map +1 -0
- package/src/lib/utils/classname-utils.d.ts +150 -0
- package/src/lib/utils/classname-utils.js +492 -0
- package/src/lib/utils/classname-utils.js.map +1 -0
- package/src/lib/utils/file-utils.d.ts +14 -0
- package/src/lib/utils/file-utils.js +106 -0
- package/src/lib/utils/file-utils.js.map +1 -0
- package/src/lib/utils/import-utils.d.ts +85 -0
- package/src/lib/utils/import-utils.js +193 -0
- package/src/lib/utils/import-utils.js.map +1 -0
- package/src/lib/utils/nx-utils.d.ts +59 -0
- package/src/lib/utils/nx-utils.js +103 -0
- package/src/lib/utils/nx-utils.js.map +1 -0
- package/src/lib/utils/package-utils.d.ts +38 -0
- package/src/lib/utils/package-utils.js +74 -0
- package/src/lib/utils/package-utils.js.map +1 -0
- package/src/lib/utils/typescript-utils.d.ts +29 -0
- package/src/lib/utils/typescript-utils.js +213 -0
- 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"]}
|
package/src/lib/rules/require-optional-prop-initialization/require-optional-prop-initialization.d.ts
ADDED
|
@@ -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 {};
|