diff --git a/src/rules/enforce-consistent-class-order.test.ts b/src/rules/enforce-consistent-class-order.test.ts index fd99153..0ab3518 100644 --- a/src/rules/enforce-consistent-class-order.test.ts +++ b/src/rules/enforce-consistent-class-order.test.ts @@ -92,6 +92,46 @@ describe(enforceConsistentClassOrder.name, () => { ); }); + it("should sort alphabetically in a locale-independent way for `asc` and `desc`", () => { + lint( + enforceConsistentClassOrder, + { + invalid: [ + { + angular: ``, + angularOutput: ``, + html: ``, + htmlOutput: ``, + jsx: `() => `, + jsxOutput: `() => `, + svelte: ``, + svelteOutput: ``, + vue: ``, + vueOutput: ``, + + errors: 1, + options: [{ order: "asc" }] + }, + { + angular: ``, + angularOutput: ``, + html: ``, + htmlOutput: ``, + jsx: `() => `, + jsxOutput: `() => `, + svelte: ``, + svelteOutput: ``, + vue: ``, + vueOutput: ``, + + errors: 1, + options: [{ order: "desc" }] + } + ] + } + ); + }); + it("should group all classes with the same variant together", () => { lint(enforceConsistentClassOrder, { invalid: [ @@ -591,6 +631,46 @@ describe(enforceConsistentClassOrder.name, () => { ); }); + it("should sort unknown classes alphabetically in a locale-independent way", () => { + lint( + enforceConsistentClassOrder, + { + invalid: [ + { + angular: ``, + angularOutput: ``, + html: ``, + htmlOutput: ``, + jsx: `() => `, + jsxOutput: `() => `, + svelte: ``, + svelteOutput: ``, + vue: ``, + vueOutput: ``, + + errors: 1, + options: [{ order: "official", unknownClassOrder: "asc", unknownClassPosition: "start" }] + }, + { + angular: ``, + angularOutput: ``, + html: ``, + htmlOutput: ``, + jsx: `() => `, + jsxOutput: `() => `, + svelte: ``, + svelteOutput: ``, + vue: ``, + vueOutput: ``, + + errors: 1, + options: [{ order: "official", unknownClassOrder: "desc", unknownClassPosition: "end" }] + } + ] + } + ); + }); + it.runIf(getTailwindCSSVersion().major >= 4)("should sort component classes to the start by default in tailwind >= 4", () => { lint( enforceConsistentClassOrder, @@ -631,6 +711,80 @@ describe(enforceConsistentClassOrder.name, () => { ); }); + it.runIf(getTailwindCSSVersion().major >= 4)("should sort component classes alphabetically in a locale-independent way in tailwind >= 4", () => { + lint( + enforceConsistentClassOrder, + { + invalid: [ + { + angular: ``, + angularOutput: ``, + html: ``, + htmlOutput: ``, + jsx: `() => `, + jsxOutput: `() => `, + svelte: ``, + svelteOutput: ``, + vue: ``, + vueOutput: ``, + + errors: 1, + + files: { + "tailwind.css": css` + @import "tailwindcss"; + + @layer components { + .cx-12, .cy-24 { + @apply font-bold; + } + } + ` + }, + options: [{ + componentClassOrder: "asc", + componentClassPosition: "start", + detectComponentClasses: true, + entryPoint: "./tailwind.css" + }] + }, + { + angular: ``, + angularOutput: ``, + html: ``, + htmlOutput: ``, + jsx: `() => `, + jsxOutput: `() => `, + svelte: ``, + svelteOutput: ``, + vue: ``, + vueOutput: ``, + + errors: 1, + + files: { + "tailwind.css": css` + @import "tailwindcss"; + + @layer components { + .cx-12, .cy-24 { + @apply font-bold; + } + } + ` + }, + options: [{ + componentClassOrder: "desc", + componentClassPosition: "end", + detectComponentClasses: true, + entryPoint: "./tailwind.css" + }] + } + ] + } + ); + }); + it.runIf(getTailwindCSSVersion().major >= 4)("should differentiate between custom component classes and unknown classes in tailwind >= 4", () => { lint( enforceConsistentClassOrder, diff --git a/src/rules/enforce-consistent-class-order.ts b/src/rules/enforce-consistent-class-order.ts index 79b3246..a78c00c 100644 --- a/src/rules/enforce-consistent-class-order.ts +++ b/src/rules/enforce-consistent-class-order.ts @@ -162,11 +162,11 @@ function sortClassNames(ctx: Context, classe const { componentClassOrder, componentClassPosition, order, unknownClassOrder, unknownClassPosition } = ctx.options; if(order === "asc"){ - return [classes.toSorted((a, b) => a.localeCompare(b))]; + return [classes.toSorted((a, b) => compareClasses(a, b))]; } if(order === "desc"){ - return [classes.toSorted((a, b) => b.localeCompare(a))]; + return [classes.toSorted((a, b) => compareClasses(b, a))]; } const { classOrder, warnings } = getClassOrder(async(ctx), classes); @@ -319,10 +319,10 @@ function getCustomOrder(position: "end" | "start", order: "asc" | "desc" | "pres if(aIsCustomClass && bIsCustomClass){ if(order === "asc"){ - return classA.localeCompare(classB); + return compareClasses(classA, classB); } if(order === "desc"){ - return classB.localeCompare(classA); + return compareClasses(classB, classA); } return 0; } @@ -333,10 +333,10 @@ function getCustomOrder(position: "end" | "start", order: "asc" | "desc" | "pres if(aIsCustomClass && bIsCustomClass){ if(order === "asc"){ - return classA.localeCompare(classB); + return compareClasses(classA, classB); } if(order === "desc"){ - return classB.localeCompare(classA); + return compareClasses(classB, classA); } return 0; } @@ -344,6 +344,11 @@ function getCustomOrder(position: "end" | "start", order: "asc" | "desc" | "pres } +function compareClasses(classA: string, classB: string): number { + if(classA === classB){ return 0; } + return classA < classB ? -1 : +1; +} + function isArbitrary(variant?: string): boolean { if(!variant){ return false;