uilint-eslint 0.2.163 → 0.2.164
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/dist/index.d.ts +14 -2
- package/dist/index.js +458 -59
- package/dist/index.js.map +1 -1
- package/dist/rules/prefer-tailwind.js +444 -57
- package/dist/rules/prefer-tailwind.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +14 -2
- package/src/rules/prefer-tailwind/index.test.ts +170 -0
- package/src/rules/prefer-tailwind/index.ts +604 -77
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/coverage/coverage-final.json +0 -76
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/Component.test.tsx +0 -8
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/Component.tsx +0 -8
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/NonJsxFile.tsx +0 -5
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/WellTestedComponent.test.tsx +0 -8
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/WellTestedComponent.tsx +0 -4
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/useHook.ts +0 -14
- package/src/rules/__fixtures__/coverage/with-aggregate-coverage/src/utils.ts +0 -8
- package/src/rules/__fixtures__/coverage/with-chunk-coverage/coverage/coverage-final.json +0 -52
- package/src/rules/__fixtures__/coverage/with-chunk-coverage/src/Button.tsx +0 -13
- package/src/rules/__fixtures__/coverage/with-chunk-coverage/src/useCounter.ts +0 -14
- package/src/rules/__fixtures__/coverage/with-chunk-coverage/src/utils.test.ts +0 -11
- package/src/rules/__fixtures__/coverage/with-chunk-coverage/src/utils.ts +0 -19
- package/src/rules/__fixtures__/coverage/with-full-coverage/coverage/coverage-final.json +0 -19
- package/src/rules/__fixtures__/coverage/with-full-coverage/src/utils.test.ts +0 -15
- package/src/rules/__fixtures__/coverage/with-full-coverage/src/utils.ts +0 -15
- package/src/rules/__fixtures__/coverage/with-git-changes/coverage/coverage-final.json +0 -22
- package/src/rules/__fixtures__/coverage/with-git-changes/src/modified.ts +0 -21
- package/src/rules/__fixtures__/coverage/with-jsx-coverage/coverage/coverage-final.json +0 -70
- package/src/rules/__fixtures__/coverage/with-jsx-coverage/src/ComponentWithHandlers.test.tsx +0 -10
- package/src/rules/__fixtures__/coverage/with-jsx-coverage/src/ComponentWithHandlers.tsx +0 -34
- package/src/rules/__fixtures__/coverage/with-jsx-coverage/src/WellTestedComponent.test.tsx +0 -10
- package/src/rules/__fixtures__/coverage/with-jsx-coverage/src/WellTestedComponent.tsx +0 -16
- package/src/rules/__fixtures__/coverage/with-no-coverage-data/src/utils.ts +0 -7
- package/src/rules/__fixtures__/coverage/with-no-tests/coverage/coverage-final.json +0 -15
- package/src/rules/__fixtures__/coverage/with-no-tests/src/hasTest.test.ts +0 -7
- package/src/rules/__fixtures__/coverage/with-no-tests/src/hasTest.ts +0 -7
- package/src/rules/__fixtures__/coverage/with-no-tests/src/noTest.ts +0 -11
- package/src/rules/__fixtures__/coverage/with-partial-coverage/coverage/coverage-final.json +0 -51
- package/src/rules/__fixtures__/coverage/with-partial-coverage/src/covered.test.ts +0 -12
- package/src/rules/__fixtures__/coverage/with-partial-coverage/src/covered.ts +0 -11
- package/src/rules/__fixtures__/coverage/with-partial-coverage/src/uncovered.test.ts +0 -8
- package/src/rules/__fixtures__/coverage/with-partial-coverage/src/uncovered.ts +0 -28
package/dist/index.d.ts
CHANGED
|
@@ -529,7 +529,7 @@ declare const rules: {
|
|
|
529
529
|
}], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
530
530
|
name: string;
|
|
531
531
|
};
|
|
532
|
-
"prefer-tailwind": _typescript_eslint_utils_ts_eslint.RuleModule<"preferTailwind" | "preferSemanticColors" | "preferSemanticColorsWithSuggestion", [({
|
|
532
|
+
"prefer-tailwind": _typescript_eslint_utils_ts_eslint.RuleModule<"preferTailwind" | "preferSemanticColors" | "preferSemanticColorsWithSuggestion" | "preferSemanticClassGroups" | "semanticOpacityModifier", [({
|
|
533
533
|
styleRatioThreshold?: number;
|
|
534
534
|
minElementsForAnalysis?: number;
|
|
535
535
|
allowedStyleProperties?: string[];
|
|
@@ -537,6 +537,12 @@ declare const rules: {
|
|
|
537
537
|
preferSemanticColors?: boolean;
|
|
538
538
|
allowedHardCodedColors?: string[];
|
|
539
539
|
useLlmSuggestions?: boolean;
|
|
540
|
+
preferSemanticClassGroups?: boolean;
|
|
541
|
+
visualUtilityThreshold?: number;
|
|
542
|
+
visualUtilityMinGroups?: number;
|
|
543
|
+
disallowSemanticOpacityModifiers?: boolean;
|
|
544
|
+
allowedOpacityModifierClasses?: string[];
|
|
545
|
+
allowedVisualUtilityClasses?: string[];
|
|
540
546
|
} | undefined)?], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
541
547
|
name: string;
|
|
542
548
|
};
|
|
@@ -649,7 +655,7 @@ declare const plugin: {
|
|
|
649
655
|
}], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
650
656
|
name: string;
|
|
651
657
|
};
|
|
652
|
-
"prefer-tailwind": _typescript_eslint_utils_ts_eslint.RuleModule<"preferTailwind" | "preferSemanticColors" | "preferSemanticColorsWithSuggestion", [({
|
|
658
|
+
"prefer-tailwind": _typescript_eslint_utils_ts_eslint.RuleModule<"preferTailwind" | "preferSemanticColors" | "preferSemanticColorsWithSuggestion" | "preferSemanticClassGroups" | "semanticOpacityModifier", [({
|
|
653
659
|
styleRatioThreshold?: number;
|
|
654
660
|
minElementsForAnalysis?: number;
|
|
655
661
|
allowedStyleProperties?: string[];
|
|
@@ -657,6 +663,12 @@ declare const plugin: {
|
|
|
657
663
|
preferSemanticColors?: boolean;
|
|
658
664
|
allowedHardCodedColors?: string[];
|
|
659
665
|
useLlmSuggestions?: boolean;
|
|
666
|
+
preferSemanticClassGroups?: boolean;
|
|
667
|
+
visualUtilityThreshold?: number;
|
|
668
|
+
visualUtilityMinGroups?: number;
|
|
669
|
+
disallowSemanticOpacityModifiers?: boolean;
|
|
670
|
+
allowedOpacityModifierClasses?: string[];
|
|
671
|
+
allowedVisualUtilityClasses?: string[];
|
|
660
672
|
} | undefined)?], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
661
673
|
name: string;
|
|
662
674
|
};
|
package/dist/index.js
CHANGED
|
@@ -4988,7 +4988,7 @@ function formatSuggestionsForMessage(suggestions, limit = 2) {
|
|
|
4988
4988
|
// src/rules/prefer-tailwind/index.ts
|
|
4989
4989
|
var meta14 = defineRuleMeta({
|
|
4990
4990
|
id: "prefer-tailwind",
|
|
4991
|
-
version: "1.
|
|
4991
|
+
version: "1.2.0",
|
|
4992
4992
|
name: "Prefer Tailwind",
|
|
4993
4993
|
description: "Encourage Tailwind className over inline style attributes",
|
|
4994
4994
|
defaultSeverity: "warn",
|
|
@@ -5005,7 +5005,13 @@ var meta14 = defineRuleMeta({
|
|
|
5005
5005
|
ignoreComponents: [],
|
|
5006
5006
|
preferSemanticColors: true,
|
|
5007
5007
|
allowedHardCodedColors: [],
|
|
5008
|
-
useLlmSuggestions: false
|
|
5008
|
+
useLlmSuggestions: false,
|
|
5009
|
+
preferSemanticClassGroups: true,
|
|
5010
|
+
visualUtilityThreshold: 4,
|
|
5011
|
+
visualUtilityMinGroups: 2,
|
|
5012
|
+
disallowSemanticOpacityModifiers: true,
|
|
5013
|
+
allowedOpacityModifierClasses: [],
|
|
5014
|
+
allowedVisualUtilityClasses: []
|
|
5009
5015
|
}
|
|
5010
5016
|
],
|
|
5011
5017
|
optionSchema: {
|
|
@@ -5058,6 +5064,48 @@ var meta14 = defineRuleMeta({
|
|
|
5058
5064
|
type: "boolean",
|
|
5059
5065
|
defaultValue: false,
|
|
5060
5066
|
description: "When enabled, uses Ollama to suggest semantic color replacements based on your project's theme"
|
|
5067
|
+
},
|
|
5068
|
+
{
|
|
5069
|
+
key: "preferSemanticClassGroups",
|
|
5070
|
+
label: "Prefer semantic class groups",
|
|
5071
|
+
type: "boolean",
|
|
5072
|
+
defaultValue: true,
|
|
5073
|
+
description: "Warn when one element uses many low-level visual utilities that should be captured by a semantic class"
|
|
5074
|
+
},
|
|
5075
|
+
{
|
|
5076
|
+
key: "visualUtilityThreshold",
|
|
5077
|
+
label: "Visual utility threshold",
|
|
5078
|
+
type: "number",
|
|
5079
|
+
defaultValue: 4,
|
|
5080
|
+
description: "Minimum number of visual utilities in one class string before warning"
|
|
5081
|
+
},
|
|
5082
|
+
{
|
|
5083
|
+
key: "visualUtilityMinGroups",
|
|
5084
|
+
label: "Visual utility group threshold",
|
|
5085
|
+
type: "number",
|
|
5086
|
+
defaultValue: 2,
|
|
5087
|
+
description: "Minimum number of distinct visual utility groups before warning"
|
|
5088
|
+
},
|
|
5089
|
+
{
|
|
5090
|
+
key: "disallowSemanticOpacityModifiers",
|
|
5091
|
+
label: "Disallow semantic opacity modifiers",
|
|
5092
|
+
type: "boolean",
|
|
5093
|
+
defaultValue: true,
|
|
5094
|
+
description: "Warn on token opacity like text-foreground/80 or border-border/40"
|
|
5095
|
+
},
|
|
5096
|
+
{
|
|
5097
|
+
key: "allowedOpacityModifierClasses",
|
|
5098
|
+
label: "Allowed opacity modifier classes",
|
|
5099
|
+
type: "text",
|
|
5100
|
+
defaultValue: "",
|
|
5101
|
+
description: "Comma-separated exact classes to allow with semantic opacity modifiers"
|
|
5102
|
+
},
|
|
5103
|
+
{
|
|
5104
|
+
key: "allowedVisualUtilityClasses",
|
|
5105
|
+
label: "Allowed visual utility classes",
|
|
5106
|
+
type: "text",
|
|
5107
|
+
defaultValue: "",
|
|
5108
|
+
description: "Comma-separated exact visual utility classes to ignore in cluster detection"
|
|
5061
5109
|
}
|
|
5062
5110
|
]
|
|
5063
5111
|
},
|
|
@@ -5108,7 +5156,11 @@ but only when the file exceeds a configurable threshold ratio.
|
|
|
5108
5156
|
allowedStyleProperties: ["transform", "animation"], // Skip these properties
|
|
5109
5157
|
ignoreComponents: ["motion.div", "animated.View"], // Skip animation libraries
|
|
5110
5158
|
preferSemanticColors: true, // Warn on hard-coded colors like bg-red-500
|
|
5111
|
-
allowedHardCodedColors: ["gray", "slate"] // Allow specific color palettes
|
|
5159
|
+
allowedHardCodedColors: ["gray", "slate"], // Allow specific color palettes
|
|
5160
|
+
preferSemanticClassGroups: true,
|
|
5161
|
+
visualUtilityThreshold: 4,
|
|
5162
|
+
visualUtilityMinGroups: 2,
|
|
5163
|
+
disallowSemanticOpacityModifiers: true
|
|
5112
5164
|
}]
|
|
5113
5165
|
\`\`\`
|
|
5114
5166
|
|
|
@@ -5136,6 +5188,39 @@ Semantic colors like \`bg-background\`, \`text-foreground\`, \`bg-primary\`, \`b
|
|
|
5136
5188
|
|
|
5137
5189
|
Colors that are always allowed: \`white\`, \`black\`, \`transparent\`, \`inherit\`, \`current\`.
|
|
5138
5190
|
|
|
5191
|
+
## Semantic Class Groups
|
|
5192
|
+
|
|
5193
|
+
When \`preferSemanticClassGroups\` is enabled, the rule warns when a single
|
|
5194
|
+
class string combines many low-level visual utilities such as background,
|
|
5195
|
+
border, radius, shadow, gradient, ring/outline, blur, and decoration classes.
|
|
5196
|
+
This catches generated component styling that should usually become a semantic
|
|
5197
|
+
project class such as \`brand-panel\`, \`ui-cell\`, or \`surface-card\`.
|
|
5198
|
+
|
|
5199
|
+
### \u274C Dense visual utility cluster
|
|
5200
|
+
|
|
5201
|
+
\`\`\`tsx
|
|
5202
|
+
<section className="bg-card rounded-2xl shadow-md border border-border/40" />
|
|
5203
|
+
\`\`\`
|
|
5204
|
+
|
|
5205
|
+
### \u2705 Semantic class
|
|
5206
|
+
|
|
5207
|
+
\`\`\`tsx
|
|
5208
|
+
<section className="brand-panel" />
|
|
5209
|
+
\`\`\`
|
|
5210
|
+
|
|
5211
|
+
## Semantic Opacity Modifiers
|
|
5212
|
+
|
|
5213
|
+
When \`disallowSemanticOpacityModifiers\` is enabled, semantic color tokens with
|
|
5214
|
+
opacity suffixes are reported:
|
|
5215
|
+
|
|
5216
|
+
\`\`\`tsx
|
|
5217
|
+
<p className="text-foreground/80" />
|
|
5218
|
+
<div className="border-border/40 hover:bg-accent/50" />
|
|
5219
|
+
\`\`\`
|
|
5220
|
+
|
|
5221
|
+
Prefer a fully semantic token such as \`text-muted-foreground\`, or define a new
|
|
5222
|
+
theme token/class when the opacity represents a reusable state.
|
|
5223
|
+
|
|
5139
5224
|
## LLM-Powered Suggestions
|
|
5140
5225
|
|
|
5141
5226
|
When \`useLlmSuggestions\` is enabled and Ollama is running locally, the rule will:
|
|
@@ -5235,19 +5320,6 @@ function createHardCodedColorRegex(colorNames) {
|
|
|
5235
5320
|
"g"
|
|
5236
5321
|
);
|
|
5237
5322
|
}
|
|
5238
|
-
function getClassNameValue(attr) {
|
|
5239
|
-
if (!attr.value) return null;
|
|
5240
|
-
if (attr.value.type === "Literal" && typeof attr.value.value === "string") {
|
|
5241
|
-
return attr.value.value;
|
|
5242
|
-
}
|
|
5243
|
-
if (attr.value.type === "JSXExpressionContainer" && attr.value.expression.type === "Literal" && typeof attr.value.expression.value === "string") {
|
|
5244
|
-
return attr.value.expression.value;
|
|
5245
|
-
}
|
|
5246
|
-
if (attr.value.type === "JSXExpressionContainer" && attr.value.expression.type === "TemplateLiteral") {
|
|
5247
|
-
return attr.value.expression.quasis.map((q) => q.value.raw).join(" ");
|
|
5248
|
-
}
|
|
5249
|
-
return null;
|
|
5250
|
-
}
|
|
5251
5323
|
function findHardCodedColors(className, allowedColors) {
|
|
5252
5324
|
const disallowedColorNames = HARD_CODED_COLOR_NAMES.filter(
|
|
5253
5325
|
(c) => !allowedColors.includes(c)
|
|
@@ -5261,6 +5333,205 @@ function findHardCodedColors(className, allowedColors) {
|
|
|
5261
5333
|
}
|
|
5262
5334
|
return matches;
|
|
5263
5335
|
}
|
|
5336
|
+
var CLASS_COMBINER_NAMES = /* @__PURE__ */ new Set([
|
|
5337
|
+
"cn",
|
|
5338
|
+
"clsx",
|
|
5339
|
+
"classnames",
|
|
5340
|
+
"cva",
|
|
5341
|
+
"twMerge"
|
|
5342
|
+
]);
|
|
5343
|
+
var COLOR_UTILITY_PREFIXES = [
|
|
5344
|
+
"bg",
|
|
5345
|
+
"text",
|
|
5346
|
+
"border",
|
|
5347
|
+
"border-t",
|
|
5348
|
+
"border-r",
|
|
5349
|
+
"border-b",
|
|
5350
|
+
"border-l",
|
|
5351
|
+
"border-x",
|
|
5352
|
+
"border-y",
|
|
5353
|
+
"ring",
|
|
5354
|
+
"ring-offset",
|
|
5355
|
+
"outline",
|
|
5356
|
+
"decoration",
|
|
5357
|
+
"accent",
|
|
5358
|
+
"fill",
|
|
5359
|
+
"stroke",
|
|
5360
|
+
"from",
|
|
5361
|
+
"via",
|
|
5362
|
+
"to",
|
|
5363
|
+
"divide",
|
|
5364
|
+
"placeholder",
|
|
5365
|
+
"caret"
|
|
5366
|
+
];
|
|
5367
|
+
var NON_SEMANTIC_COLOR_VALUES = /* @__PURE__ */ new Set([
|
|
5368
|
+
"black",
|
|
5369
|
+
"white",
|
|
5370
|
+
"transparent",
|
|
5371
|
+
"inherit",
|
|
5372
|
+
"current",
|
|
5373
|
+
"currentColor"
|
|
5374
|
+
]);
|
|
5375
|
+
var TEXT_SIZE_VALUES = /* @__PURE__ */ new Set([
|
|
5376
|
+
"xs",
|
|
5377
|
+
"sm",
|
|
5378
|
+
"base",
|
|
5379
|
+
"lg",
|
|
5380
|
+
"xl",
|
|
5381
|
+
"2xl",
|
|
5382
|
+
"3xl",
|
|
5383
|
+
"4xl",
|
|
5384
|
+
"5xl",
|
|
5385
|
+
"6xl",
|
|
5386
|
+
"7xl",
|
|
5387
|
+
"8xl",
|
|
5388
|
+
"9xl"
|
|
5389
|
+
]);
|
|
5390
|
+
var BORDER_WIDTH_VALUES = /* @__PURE__ */ new Set(["0", "2", "4", "8"]);
|
|
5391
|
+
var SHADOW_SIZE_VALUES = /* @__PURE__ */ new Set([
|
|
5392
|
+
"2xs",
|
|
5393
|
+
"xs",
|
|
5394
|
+
"sm",
|
|
5395
|
+
"md",
|
|
5396
|
+
"lg",
|
|
5397
|
+
"xl",
|
|
5398
|
+
"2xl",
|
|
5399
|
+
"inner",
|
|
5400
|
+
"none"
|
|
5401
|
+
]);
|
|
5402
|
+
function stripImportant(value) {
|
|
5403
|
+
return value.replace(/^!/, "").replace(/!$/, "");
|
|
5404
|
+
}
|
|
5405
|
+
function getBaseClass2(token) {
|
|
5406
|
+
let bracketDepth = 0;
|
|
5407
|
+
let lastVariantColon = -1;
|
|
5408
|
+
for (let i = 0; i < token.length; i++) {
|
|
5409
|
+
const char = token[i];
|
|
5410
|
+
if (char === "[") {
|
|
5411
|
+
bracketDepth++;
|
|
5412
|
+
} else if (char === "]" && bracketDepth > 0) {
|
|
5413
|
+
bracketDepth--;
|
|
5414
|
+
} else if (char === ":" && bracketDepth === 0) {
|
|
5415
|
+
lastVariantColon = i;
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
return stripImportant(token.slice(lastVariantColon + 1));
|
|
5419
|
+
}
|
|
5420
|
+
function extractClassTokens(className) {
|
|
5421
|
+
return className.split(/\s+/g).map((token) => token.trim()).filter(Boolean).map((token) => ({
|
|
5422
|
+
original: token,
|
|
5423
|
+
base: getBaseClass2(token)
|
|
5424
|
+
}));
|
|
5425
|
+
}
|
|
5426
|
+
function classIsAllowed(token, allowedClasses) {
|
|
5427
|
+
return allowedClasses.includes(token.original) || allowedClasses.includes(token.base);
|
|
5428
|
+
}
|
|
5429
|
+
function getColorUtilityPrefix(baseClass) {
|
|
5430
|
+
for (const prefix of COLOR_UTILITY_PREFIXES) {
|
|
5431
|
+
if (baseClass === prefix || baseClass.startsWith(`${prefix}-`)) {
|
|
5432
|
+
return prefix;
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
function getUtilityValue(baseClass, prefix) {
|
|
5438
|
+
if (baseClass === prefix) return "";
|
|
5439
|
+
return baseClass.slice(prefix.length + 1);
|
|
5440
|
+
}
|
|
5441
|
+
function isBracketColorValue(value) {
|
|
5442
|
+
return value.startsWith("[") && (value.includes("var(") || value.startsWith("[color:") || value.startsWith("[--") || value.includes("oklch(") || value.includes("rgb(") || value.includes("hsl("));
|
|
5443
|
+
}
|
|
5444
|
+
function isHardCodedTailwindColorValue(value) {
|
|
5445
|
+
const [name, shade] = value.split("-");
|
|
5446
|
+
return HARD_CODED_COLOR_NAMES.includes(name) && /^\d{1,3}$/.test(shade ?? "");
|
|
5447
|
+
}
|
|
5448
|
+
function isSemanticColorValue(value, prefix) {
|
|
5449
|
+
if (!value) return false;
|
|
5450
|
+
if (NON_SEMANTIC_COLOR_VALUES.has(value)) return false;
|
|
5451
|
+
if (prefix === "text" && TEXT_SIZE_VALUES.has(value)) return false;
|
|
5452
|
+
if (prefix.startsWith("border") && BORDER_WIDTH_VALUES.has(value)) return false;
|
|
5453
|
+
if (prefix === "shadow" && SHADOW_SIZE_VALUES.has(value)) return false;
|
|
5454
|
+
if (isHardCodedTailwindColorValue(value)) return false;
|
|
5455
|
+
if (value.startsWith("[")) return isBracketColorValue(value);
|
|
5456
|
+
return true;
|
|
5457
|
+
}
|
|
5458
|
+
function getVisualUtilityGroup(baseClass) {
|
|
5459
|
+
if (baseClass === "border" || baseClass.startsWith("border-") || baseClass.startsWith("divide-")) {
|
|
5460
|
+
return "border";
|
|
5461
|
+
}
|
|
5462
|
+
if (baseClass === "rounded" || baseClass.startsWith("rounded-")) {
|
|
5463
|
+
return "radius";
|
|
5464
|
+
}
|
|
5465
|
+
if (baseClass === "shadow" || baseClass.startsWith("shadow-") || baseClass === "drop-shadow" || baseClass.startsWith("drop-shadow-")) {
|
|
5466
|
+
return "shadow";
|
|
5467
|
+
}
|
|
5468
|
+
if (baseClass.startsWith("bg-gradient-") || baseClass.startsWith("from-") || baseClass.startsWith("via-") || baseClass.startsWith("to-")) {
|
|
5469
|
+
return "gradient";
|
|
5470
|
+
}
|
|
5471
|
+
if (baseClass === "ring" || baseClass.startsWith("ring-") || baseClass === "outline" || baseClass.startsWith("outline-")) {
|
|
5472
|
+
return "ring";
|
|
5473
|
+
}
|
|
5474
|
+
if (baseClass === "blur" || baseClass.startsWith("blur-") || baseClass === "backdrop-blur" || baseClass.startsWith("backdrop-blur-")) {
|
|
5475
|
+
return "blur";
|
|
5476
|
+
}
|
|
5477
|
+
if (baseClass.startsWith("decoration-") || baseClass.startsWith("accent-") || baseClass.startsWith("fill-") || baseClass.startsWith("stroke-")) {
|
|
5478
|
+
return "decoration";
|
|
5479
|
+
}
|
|
5480
|
+
if (baseClass.startsWith("bg-")) {
|
|
5481
|
+
return "surface";
|
|
5482
|
+
}
|
|
5483
|
+
const prefix = getColorUtilityPrefix(baseClass);
|
|
5484
|
+
if (prefix) {
|
|
5485
|
+
const value = getUtilityValue(baseClass, prefix).split("/")[0] ?? "";
|
|
5486
|
+
if (isSemanticColorValue(value, prefix) || isBracketColorValue(value)) {
|
|
5487
|
+
return "surface";
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
return null;
|
|
5491
|
+
}
|
|
5492
|
+
function findVisualUtilityCluster(className, threshold, minGroups, allowedClasses) {
|
|
5493
|
+
const matches = [];
|
|
5494
|
+
for (const token of extractClassTokens(className)) {
|
|
5495
|
+
if (classIsAllowed(token, allowedClasses)) {
|
|
5496
|
+
continue;
|
|
5497
|
+
}
|
|
5498
|
+
const group = getVisualUtilityGroup(token.base);
|
|
5499
|
+
if (group) {
|
|
5500
|
+
matches.push({ token: token.original, group });
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
const groups = new Set(matches.map((match) => match.group));
|
|
5504
|
+
if (matches.length >= threshold && groups.size >= minGroups) {
|
|
5505
|
+
return matches.map((match) => match.token);
|
|
5506
|
+
}
|
|
5507
|
+
return [];
|
|
5508
|
+
}
|
|
5509
|
+
function findSemanticOpacityModifiers(className, allowedClasses) {
|
|
5510
|
+
const matches = [];
|
|
5511
|
+
for (const token of extractClassTokens(className)) {
|
|
5512
|
+
if (classIsAllowed(token, allowedClasses)) {
|
|
5513
|
+
continue;
|
|
5514
|
+
}
|
|
5515
|
+
const opacityMatch = token.base.match(/^(.*)\/(\d{1,3})$/);
|
|
5516
|
+
if (!opacityMatch) {
|
|
5517
|
+
continue;
|
|
5518
|
+
}
|
|
5519
|
+
const baseWithoutOpacity = opacityMatch[1];
|
|
5520
|
+
const opacityValue = Number(opacityMatch[2]);
|
|
5521
|
+
if (opacityValue < 0 || opacityValue > 100) {
|
|
5522
|
+
continue;
|
|
5523
|
+
}
|
|
5524
|
+
const prefix = getColorUtilityPrefix(baseWithoutOpacity);
|
|
5525
|
+
if (!prefix) {
|
|
5526
|
+
continue;
|
|
5527
|
+
}
|
|
5528
|
+
const value = getUtilityValue(baseWithoutOpacity, prefix);
|
|
5529
|
+
if (isSemanticColorValue(value, prefix) || isBracketColorValue(value)) {
|
|
5530
|
+
matches.push(token.original);
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
return matches;
|
|
5534
|
+
}
|
|
5264
5535
|
var prefer_tailwind_default = createRule({
|
|
5265
5536
|
name: "prefer-tailwind",
|
|
5266
5537
|
meta: {
|
|
@@ -5271,7 +5542,9 @@ var prefer_tailwind_default = createRule({
|
|
|
5271
5542
|
messages: {
|
|
5272
5543
|
preferTailwind: "Prefer Tailwind className over inline style. This element uses style attribute without className.",
|
|
5273
5544
|
preferSemanticColors: "Hard-coded colors: {{colors}}. Use semantic classes instead.",
|
|
5274
|
-
preferSemanticColorsWithSuggestion: "Hard-coded colors: {{colors}}. {{suggestion}}"
|
|
5545
|
+
preferSemanticColorsWithSuggestion: "Hard-coded colors: {{colors}}. {{suggestion}}",
|
|
5546
|
+
preferSemanticClassGroups: "Dense visual utility cluster: {{classes}}. Move repeated panel/card styling into a semantic class.",
|
|
5547
|
+
semanticOpacityModifier: "Semantic color opacity modifiers: {{classes}}. Use fully semantic classes or tokens instead."
|
|
5275
5548
|
},
|
|
5276
5549
|
schema: [
|
|
5277
5550
|
{
|
|
@@ -5310,6 +5583,34 @@ var prefer_tailwind_default = createRule({
|
|
|
5310
5583
|
useLlmSuggestions: {
|
|
5311
5584
|
type: "boolean",
|
|
5312
5585
|
description: "Use Ollama LLM to suggest semantic color replacements"
|
|
5586
|
+
},
|
|
5587
|
+
preferSemanticClassGroups: {
|
|
5588
|
+
type: "boolean",
|
|
5589
|
+
description: "Warn when one class string contains many low-level visual utilities"
|
|
5590
|
+
},
|
|
5591
|
+
visualUtilityThreshold: {
|
|
5592
|
+
type: "number",
|
|
5593
|
+
minimum: 1,
|
|
5594
|
+
description: "Minimum visual utility count in one class string before warning"
|
|
5595
|
+
},
|
|
5596
|
+
visualUtilityMinGroups: {
|
|
5597
|
+
type: "number",
|
|
5598
|
+
minimum: 1,
|
|
5599
|
+
description: "Minimum distinct visual utility groups before warning"
|
|
5600
|
+
},
|
|
5601
|
+
disallowSemanticOpacityModifiers: {
|
|
5602
|
+
type: "boolean",
|
|
5603
|
+
description: "Warn on semantic color opacity modifiers like text-foreground/80"
|
|
5604
|
+
},
|
|
5605
|
+
allowedOpacityModifierClasses: {
|
|
5606
|
+
type: "array",
|
|
5607
|
+
items: { type: "string" },
|
|
5608
|
+
description: "Exact classes to allow with semantic opacity modifiers"
|
|
5609
|
+
},
|
|
5610
|
+
allowedVisualUtilityClasses: {
|
|
5611
|
+
type: "array",
|
|
5612
|
+
items: { type: "string" },
|
|
5613
|
+
description: "Exact visual utility classes to ignore in cluster detection"
|
|
5313
5614
|
}
|
|
5314
5615
|
},
|
|
5315
5616
|
additionalProperties: false
|
|
@@ -5324,7 +5625,13 @@ var prefer_tailwind_default = createRule({
|
|
|
5324
5625
|
ignoreComponents: [],
|
|
5325
5626
|
preferSemanticColors: true,
|
|
5326
5627
|
allowedHardCodedColors: [],
|
|
5327
|
-
useLlmSuggestions: false
|
|
5628
|
+
useLlmSuggestions: false,
|
|
5629
|
+
preferSemanticClassGroups: true,
|
|
5630
|
+
visualUtilityThreshold: 4,
|
|
5631
|
+
visualUtilityMinGroups: 2,
|
|
5632
|
+
disallowSemanticOpacityModifiers: true,
|
|
5633
|
+
allowedOpacityModifierClasses: [],
|
|
5634
|
+
allowedVisualUtilityClasses: []
|
|
5328
5635
|
}
|
|
5329
5636
|
],
|
|
5330
5637
|
create(context) {
|
|
@@ -5336,6 +5643,12 @@ var prefer_tailwind_default = createRule({
|
|
|
5336
5643
|
const preferSemanticColors = options.preferSemanticColors ?? true;
|
|
5337
5644
|
const allowedHardCodedColors = options.allowedHardCodedColors ?? [];
|
|
5338
5645
|
const useLlmSuggestions = options.useLlmSuggestions ?? false;
|
|
5646
|
+
const preferSemanticClassGroups = options.preferSemanticClassGroups ?? true;
|
|
5647
|
+
const visualUtilityThreshold = options.visualUtilityThreshold ?? 4;
|
|
5648
|
+
const visualUtilityMinGroups = options.visualUtilityMinGroups ?? 2;
|
|
5649
|
+
const disallowSemanticOpacityModifiers = options.disallowSemanticOpacityModifiers ?? true;
|
|
5650
|
+
const allowedOpacityModifierClasses = options.allowedOpacityModifierClasses ?? [];
|
|
5651
|
+
const allowedVisualUtilityClasses = options.allowedVisualUtilityClasses ?? [];
|
|
5339
5652
|
let fileContent = null;
|
|
5340
5653
|
const filePath = context.filename;
|
|
5341
5654
|
const fileDir = dirname5(filePath);
|
|
@@ -5356,6 +5669,92 @@ var prefer_tailwind_default = createRule({
|
|
|
5356
5669
|
function isClassNameAttribute(attr) {
|
|
5357
5670
|
return attr.name.type === "JSXIdentifier" && (attr.name.name === "className" || attr.name.name === "class");
|
|
5358
5671
|
}
|
|
5672
|
+
function checkClassString(node, className) {
|
|
5673
|
+
if (preferSemanticColors) {
|
|
5674
|
+
const hardCodedColors = findHardCodedColors(
|
|
5675
|
+
className,
|
|
5676
|
+
allowedHardCodedColors
|
|
5677
|
+
);
|
|
5678
|
+
if (hardCodedColors.length > 0) {
|
|
5679
|
+
const colorsStr = hardCodedColors.join(", ");
|
|
5680
|
+
if (useLlmSuggestions) {
|
|
5681
|
+
const { suggestions } = getColorSuggestions(
|
|
5682
|
+
hardCodedColors,
|
|
5683
|
+
fileDir,
|
|
5684
|
+
getFileContent()
|
|
5685
|
+
);
|
|
5686
|
+
const suggestionStr = formatSuggestionsForMessage(suggestions);
|
|
5687
|
+
if (suggestionStr) {
|
|
5688
|
+
context.report({
|
|
5689
|
+
node,
|
|
5690
|
+
messageId: "preferSemanticColorsWithSuggestion",
|
|
5691
|
+
data: { colors: colorsStr, suggestion: suggestionStr }
|
|
5692
|
+
});
|
|
5693
|
+
} else {
|
|
5694
|
+
context.report({
|
|
5695
|
+
node,
|
|
5696
|
+
messageId: "preferSemanticColors",
|
|
5697
|
+
data: { colors: colorsStr }
|
|
5698
|
+
});
|
|
5699
|
+
}
|
|
5700
|
+
} else {
|
|
5701
|
+
context.report({
|
|
5702
|
+
node,
|
|
5703
|
+
messageId: "preferSemanticColors",
|
|
5704
|
+
data: { colors: colorsStr }
|
|
5705
|
+
});
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
if (preferSemanticClassGroups) {
|
|
5710
|
+
const visualUtilities = findVisualUtilityCluster(
|
|
5711
|
+
className,
|
|
5712
|
+
visualUtilityThreshold,
|
|
5713
|
+
visualUtilityMinGroups,
|
|
5714
|
+
allowedVisualUtilityClasses
|
|
5715
|
+
);
|
|
5716
|
+
if (visualUtilities.length > 0) {
|
|
5717
|
+
context.report({
|
|
5718
|
+
node,
|
|
5719
|
+
messageId: "preferSemanticClassGroups",
|
|
5720
|
+
data: { classes: visualUtilities.join(", ") }
|
|
5721
|
+
});
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
if (disallowSemanticOpacityModifiers) {
|
|
5725
|
+
const opacityClasses = findSemanticOpacityModifiers(
|
|
5726
|
+
className,
|
|
5727
|
+
allowedOpacityModifierClasses
|
|
5728
|
+
);
|
|
5729
|
+
if (opacityClasses.length > 0) {
|
|
5730
|
+
context.report({
|
|
5731
|
+
node,
|
|
5732
|
+
messageId: "semanticOpacityModifier",
|
|
5733
|
+
data: { classes: opacityClasses.join(", ") }
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
function processTemplateLiteral(node) {
|
|
5739
|
+
for (const quasi of node.quasis) {
|
|
5740
|
+
checkClassString(quasi, quasi.value.raw);
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
5743
|
+
function processClassAttribute(attr) {
|
|
5744
|
+
const value = attr.value;
|
|
5745
|
+
if (value?.type === "Literal" && typeof value.value === "string") {
|
|
5746
|
+
checkClassString(value, value.value);
|
|
5747
|
+
}
|
|
5748
|
+
if (value?.type === "JSXExpressionContainer") {
|
|
5749
|
+
const expr = value.expression;
|
|
5750
|
+
if (expr.type === "Literal" && typeof expr.value === "string") {
|
|
5751
|
+
checkClassString(expr, expr.value);
|
|
5752
|
+
}
|
|
5753
|
+
if (expr.type === "TemplateLiteral") {
|
|
5754
|
+
processTemplateLiteral(expr);
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5359
5758
|
return {
|
|
5360
5759
|
JSXOpeningElement(node) {
|
|
5361
5760
|
const componentName = getComponentName2(node);
|
|
@@ -5375,45 +5774,7 @@ var prefer_tailwind_default = createRule({
|
|
|
5375
5774
|
}
|
|
5376
5775
|
if (isClassNameAttribute(attr)) {
|
|
5377
5776
|
hasClassName = true;
|
|
5378
|
-
|
|
5379
|
-
const classNameValue = getClassNameValue(attr);
|
|
5380
|
-
if (classNameValue) {
|
|
5381
|
-
const hardCodedColors = findHardCodedColors(
|
|
5382
|
-
classNameValue,
|
|
5383
|
-
allowedHardCodedColors
|
|
5384
|
-
);
|
|
5385
|
-
if (hardCodedColors.length > 0) {
|
|
5386
|
-
const colorsStr = hardCodedColors.join(", ");
|
|
5387
|
-
if (useLlmSuggestions) {
|
|
5388
|
-
const { suggestions } = getColorSuggestions(
|
|
5389
|
-
hardCodedColors,
|
|
5390
|
-
fileDir,
|
|
5391
|
-
getFileContent()
|
|
5392
|
-
);
|
|
5393
|
-
const suggestionStr = formatSuggestionsForMessage(suggestions);
|
|
5394
|
-
if (suggestionStr) {
|
|
5395
|
-
context.report({
|
|
5396
|
-
node,
|
|
5397
|
-
messageId: "preferSemanticColorsWithSuggestion",
|
|
5398
|
-
data: { colors: colorsStr, suggestion: suggestionStr }
|
|
5399
|
-
});
|
|
5400
|
-
} else {
|
|
5401
|
-
context.report({
|
|
5402
|
-
node,
|
|
5403
|
-
messageId: "preferSemanticColors",
|
|
5404
|
-
data: { colors: colorsStr }
|
|
5405
|
-
});
|
|
5406
|
-
}
|
|
5407
|
-
} else {
|
|
5408
|
-
context.report({
|
|
5409
|
-
node,
|
|
5410
|
-
messageId: "preferSemanticColors",
|
|
5411
|
-
data: { colors: colorsStr }
|
|
5412
|
-
});
|
|
5413
|
-
}
|
|
5414
|
-
}
|
|
5415
|
-
}
|
|
5416
|
-
}
|
|
5777
|
+
processClassAttribute(attr);
|
|
5417
5778
|
}
|
|
5418
5779
|
}
|
|
5419
5780
|
}
|
|
@@ -5448,6 +5809,32 @@ var prefer_tailwind_default = createRule({
|
|
|
5448
5809
|
});
|
|
5449
5810
|
}
|
|
5450
5811
|
}
|
|
5812
|
+
},
|
|
5813
|
+
CallExpression(node) {
|
|
5814
|
+
if (node.callee.type !== "Identifier") {
|
|
5815
|
+
return;
|
|
5816
|
+
}
|
|
5817
|
+
if (!CLASS_COMBINER_NAMES.has(node.callee.name)) {
|
|
5818
|
+
return;
|
|
5819
|
+
}
|
|
5820
|
+
for (const arg of node.arguments) {
|
|
5821
|
+
if (arg.type === "Literal" && typeof arg.value === "string") {
|
|
5822
|
+
checkClassString(arg, arg.value);
|
|
5823
|
+
}
|
|
5824
|
+
if (arg.type === "TemplateLiteral") {
|
|
5825
|
+
processTemplateLiteral(arg);
|
|
5826
|
+
}
|
|
5827
|
+
if (arg.type === "ArrayExpression") {
|
|
5828
|
+
for (const element of arg.elements) {
|
|
5829
|
+
if (element?.type === "Literal" && typeof element.value === "string") {
|
|
5830
|
+
checkClassString(element, element.value);
|
|
5831
|
+
}
|
|
5832
|
+
if (element?.type === "TemplateLiteral") {
|
|
5833
|
+
processTemplateLiteral(element);
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5451
5838
|
}
|
|
5452
5839
|
};
|
|
5453
5840
|
}
|
|
@@ -5872,7 +6259,13 @@ var recommendedConfig = {
|
|
|
5872
6259
|
"ignoreComponents": [],
|
|
5873
6260
|
"preferSemanticColors": true,
|
|
5874
6261
|
"allowedHardCodedColors": [],
|
|
5875
|
-
"useLlmSuggestions": false
|
|
6262
|
+
"useLlmSuggestions": false,
|
|
6263
|
+
"preferSemanticClassGroups": true,
|
|
6264
|
+
"visualUtilityThreshold": 4,
|
|
6265
|
+
"visualUtilityMinGroups": 2,
|
|
6266
|
+
"disallowSemanticOpacityModifiers": true,
|
|
6267
|
+
"allowedOpacityModifierClasses": [],
|
|
6268
|
+
"allowedVisualUtilityClasses": []
|
|
5876
6269
|
}
|
|
5877
6270
|
]]
|
|
5878
6271
|
}
|
|
@@ -6006,7 +6399,13 @@ var strictConfig = {
|
|
|
6006
6399
|
"ignoreComponents": [],
|
|
6007
6400
|
"preferSemanticColors": true,
|
|
6008
6401
|
"allowedHardCodedColors": [],
|
|
6009
|
-
"useLlmSuggestions": false
|
|
6402
|
+
"useLlmSuggestions": false,
|
|
6403
|
+
"preferSemanticClassGroups": true,
|
|
6404
|
+
"visualUtilityThreshold": 4,
|
|
6405
|
+
"visualUtilityMinGroups": 2,
|
|
6406
|
+
"disallowSemanticOpacityModifiers": true,
|
|
6407
|
+
"allowedOpacityModifierClasses": [],
|
|
6408
|
+
"allowedVisualUtilityClasses": []
|
|
6010
6409
|
}
|
|
6011
6410
|
]]
|
|
6012
6411
|
}
|