feat : add comprehensive test suite for CSS ordering rules

Add tests for all three CSS property ordering rules:

    alphabetical-order,
    concentric-order,
    custom-order,

Tests cover all implemented vanilla-extract APIs, fontFace, globalFontFace, globalKeyframes, globalStyle, keyframes, style, and styleVariants.. Each test verifies both valid and invalid cases, along with proper auto-fixing functionality.
This commit is contained in:
Ante Budimir 2025-03-09 18:12:00 +02:00
parent 3e9bad1b02
commit da4d2d6373
25 changed files with 3635 additions and 24 deletions

50
CHANGELOG.md Normal file
View file

@ -0,0 +1,50 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.1] - 2025-03-09
- Add comprehensive test suite for CSS ordering rules ()
## [1.4.0] - 2025-03-08
- Implement special ordering for fontFace APIs (3e9bad1)
## [1.3.1] - 2025-03-07
- Update milestones (8916be7)
## [1.3.0] - 2025-03-06
- Add script for versioning updates (f2ad87c)
## [1.2.0] - 2025-03-05
- Add support for linting keyframes and globalKeyframes (dea0a32)
## [1.1.2] - 2025-03-05
- add .npmignore to exclude development files from npm package (223a81d)
## [1.1.1] - 2025-03-05
- Improve packaging and TypeScript configuration (c616fb0)
## [1.1.0] - 2025-03-04
- Lower minimum Node.js version to 18.18.0 (44aba94)
## [1.0.2] - 2025-03-04
- Add npm version badge and link to vanilla-extract (87acd61)
## [1.0.1] - 2025-03-04
- Add sample CSS file for linting demo during development (88a9d43)
## [1.0.0] - 2025-03-04
- Initialize project with complete codebase (d569dea)

View file

@ -253,14 +253,14 @@ The roadmap outlines the project's current status and future plans:
- Initial release with support for alphabetical, concentric, and custom group CSS ordering. - Initial release with support for alphabetical, concentric, and custom group CSS ordering.
- Auto-fix capability integrated into ESLint. - Auto-fix capability integrated into ESLint.
- Support for multiple vanilla-extract APIs (e.g., `style`, `styleVariants`, `recipe`, `globalStyle`, `fontFace`, etc.). - Support for multiple vanilla-extract APIs (e.g., `style`, `styleVariants`, `recipe`, `globalStyle`, `fontFace`, etc.).
- Rules tested.
### Current Work ### Current Work
- Test coverage. - `no-empty-blocks` rule to disallow empty blocks.
### Upcoming Features ### Upcoming Features
- `no-empty-blocks` rule to disallow empty blocks.
- `no-unknown-units` rule to disallow unknown units. - `no-unknown-units` rule to disallow unknown units.
- `no-number-trailing-zeros` rule to disallow trailing zeros in numbers. - `no-number-trailing-zeros` rule to disallow trailing zeros in numbers.
- `no-zero-unit` rule to disallow units when the value is zero. - `no-zero-unit` rule to disallow units when the value is zero.

View file

@ -1,6 +1,6 @@
{ {
"name": "@antebudimir/eslint-plugin-vanilla-extract", "name": "@antebudimir/eslint-plugin-vanilla-extract",
"version": "1.4.0", "version": "1.4.1",
"description": "ESLint plugin for enforcing CSS ordering in vanilla-extract styles", "description": "ESLint plugin for enforcing CSS ordering in vanilla-extract styles",
"author": "Ante Budimir", "author": "Ante Budimir",
"license": "MIT", "license": "MIT",
@ -46,6 +46,9 @@
"lint": "eslint src --ext .ts --fix --max-warnings 0", "lint": "eslint src --ext .ts --fix --max-warnings 0",
"prepublishOnly": "pnpm run lint && pnpm run build", "prepublishOnly": "pnpm run lint && pnpm run build",
"publish": "pnpm publish --access public", "publish": "pnpm publish --access public",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"version:update": "node scripts/update-version.mjs" "version:update": "node scripts/update-version.mjs"
}, },
@ -61,16 +64,20 @@
"@types/node": "^20.17.19", "@types/node": "^20.17.19",
"@typescript-eslint/eslint-plugin": "^8.25.0", "@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser": "^8.25.0", "@typescript-eslint/parser": "^8.25.0",
"@typescript-eslint/rule-tester": "^8.26.0",
"@typescript-eslint/utils": "^8.25.0", "@typescript-eslint/utils": "^8.25.0",
"@vanilla-extract/css": "^1.17.1", "@vanilla-extract/css": "^1.17.1",
"@vanilla-extract/recipes": "^0.5.5", "@vanilla-extract/recipes": "^0.5.5",
"@vitest/coverage-v8": "3.0.8",
"eslint": "^9.21.0", "eslint": "^9.21.0",
"eslint-config-prettier": "^10.0.2", "eslint-config-prettier": "^10.0.2",
"eslint-import-resolver-typescript": "^3.8.3", "eslint-import-resolver-typescript": "^3.8.3",
"eslint-plugin-eslint-plugin": "^6.4.0", "eslint-plugin-eslint-plugin": "^6.4.0",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-vitest-rule-tester": "^1.1.0",
"prettier": "^3.5.2", "prettier": "^3.5.2",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.25.0" "typescript-eslint": "^8.25.0",
"vitest": "^3.0.8"
} }
} }

1330
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,111 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import alphabeticalOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/alphabetical-order/animation',
rule: alphabeticalOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// keyframes with alphabetical ordering
`
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
opacity: 0,
transform: 'translateY(10px)'
},
'100%': {
opacity: 1,
transform: 'translateY(0)'
}
});
`,
// globalKeyframes with alphabetical ordering
`
import { globalKeyframes } from '@vanilla-extract/css';
globalKeyframes('fadeIn', {
'0%': {
opacity: 0,
transform: 'translateY(10px)'
},
'100%': {
opacity: 1,
transform: 'translateY(0)'
}
});
`,
],
invalid: [
// keyframes with incorrect ordering
{
code: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
transform: 'translateY(10px)',
opacity: 0
},
'100%': {
transform: 'translateY(0)',
opacity: 1
}
});
`,
errors: [{ messageId: 'alphabeticalOrder' }, { messageId: 'alphabeticalOrder' }],
output: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
opacity: 0,
transform: 'translateY(10px)'
},
'100%': {
opacity: 1,
transform: 'translateY(0)'
}
});
`,
},
// globalKeyframes with incorrect ordering
{
code: `
import { globalKeyframes } from '@vanilla-extract/css';
globalKeyframes('fadeIn', {
'0%': {
transform: 'translateY(10px)',
opacity: 0
},
'100%': {
transform: 'translateY(0)',
opacity: 1
}
});
`,
errors: [{ messageId: 'alphabeticalOrder' }, { messageId: 'alphabeticalOrder' }],
output: `
import { globalKeyframes } from '@vanilla-extract/css';
globalKeyframes('fadeIn', {
'0%': {
opacity: 0,
transform: 'translateY(10px)'
},
'100%': {
opacity: 1,
transform: 'translateY(0)'
}
});
`,
},
],
});

View file

@ -0,0 +1,143 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import alphabeticalOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/alphabetical-order/font-face',
rule: alphabeticalOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// fontFace with src first and other properties alphabetically ordered
`
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
descentOverride: '10%',
fontDisplay: 'swap',
fontFeatureSettings: '"liga" 1',
fontStretch: 'normal',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400'
});
`,
// globalFontFace with src first and other properties alphabetically ordered
`
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
descentOverride: '10%',
fontDisplay: 'swap',
fontFeatureSettings: '"liga" 1',
fontStretch: 'normal',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400'
});
`,
],
invalid: [
// fontFace with src not first
{
code: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
fontWeight: '400',
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal'
});
`,
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
// fontFace with src first but other properties not in alphabetical order
{
code: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
fontWeight: '400',
ascentOverride: '90%',
fontStyle: 'normal'
});
`,
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
// globalFontFace with src not first
{
code: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
fontWeight: '400',
fontStyle: 'normal',
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%'
});
`,
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
// globalFontFace with src first but other properties not in alphabetical order
{
code: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
fontWeight: '400',
fontStyle: 'normal',
ascentOverride: '90%'
});
`,
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
],
});

View file

@ -0,0 +1,52 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import alphabeticalOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/alphabetical-order/global',
rule: alphabeticalOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// globalStyle with alphabetical ordering
`
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
backgroundColor: 'white',
color: 'black',
margin: 0,
padding: 0
});
`,
],
invalid: [
// globalStyle with incorrect ordering
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
color: 'black',
backgroundColor: 'white',
padding: 0,
margin: 0
});
`,
errors: [{ messageId: 'alphabeticalOrder' }],
output: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
backgroundColor: 'white',
color: 'black',
margin: 0,
padding: 0
});
`,
},
],
});

View file

@ -0,0 +1,92 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import alphabeticalOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/alphabetical-order/recipe',
rule: alphabeticalOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Recipe with alphabetical ordering
`
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
alignItems: 'center',
display: 'flex'
},
variants: {
color: {
blue: {
backgroundColor: 'blue',
color: 'white'
},
red: {
backgroundColor: 'red',
color: 'black'
}
}
}
});
`,
],
invalid: [
// Recipe with incorrect ordering
{
code: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
display: 'flex',
alignItems: 'center'
},
variants: {
color: {
blue: {
color: 'white',
backgroundColor: 'blue'
},
red: {
color: 'black',
backgroundColor: 'red'
}
}
}
});
`,
errors: [
{ messageId: 'alphabeticalOrder' },
{ messageId: 'alphabeticalOrder' },
{ messageId: 'alphabeticalOrder' },
],
output: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
alignItems: 'center',
display: 'flex'
},
variants: {
color: {
blue: {
backgroundColor: 'blue',
color: 'white'
},
red: {
backgroundColor: 'red',
color: 'black'
}
}
}
});
`,
},
],
});

View file

@ -0,0 +1,111 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import alphabeticalOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/alphabetical-order/style',
rule: alphabeticalOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Basic style object with alphabetical ordering
`
import { style } from '@vanilla-extract/css';
const myStyle = style({
alignItems: 'center',
backgroundColor: 'red',
color: 'blue',
display: 'flex',
margin: '10px',
padding: '20px',
zIndex: 1
});
`,
// Style with nested selectors
`
import { style } from '@vanilla-extract/css';
const myStyle = style({
alignItems: 'center',
backgroundColor: 'red',
color: 'blue',
selectors: {
'&:hover': {
backgroundColor: 'blue',
color: 'white'
}
}
});
`,
],
invalid: [
// Basic style object with incorrect ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
backgroundColor: 'red',
alignItems: 'center',
padding: '20px',
color: 'blue',
margin: '10px',
display: 'flex',
zIndex: 1
});
`,
errors: [{ messageId: 'alphabeticalOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
alignItems: 'center',
backgroundColor: 'red',
color: 'blue',
display: 'flex',
margin: '10px',
padding: '20px',
zIndex: 1
});
`,
},
// Style with nested selectors having incorrect ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
backgroundColor: 'red',
alignItems: 'center',
color: 'blue',
selectors: {
'&:hover': {
color: 'white',
backgroundColor: 'blue'
}
}
});
`,
errors: [{ messageId: 'alphabeticalOrder' }, { messageId: 'alphabeticalOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
alignItems: 'center',
backgroundColor: 'red',
color: 'blue',
selectors: {
'&:hover': {
backgroundColor: 'blue',
color: 'white'
}
}
});
`,
},
],
});

View file

@ -0,0 +1,64 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import alphabeticalOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/alphabetical-order/variants',
rule: alphabeticalOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// styleVariants with alphabetical ordering
`
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
backgroundColor: 'blue',
color: 'white'
},
secondary: {
backgroundColor: 'gray',
color: 'black'
}
});
`,
],
invalid: [
// styleVariants with incorrect ordering
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
color: 'white',
backgroundColor: 'blue'
},
secondary: {
color: 'black',
backgroundColor: 'gray'
}
});
`,
errors: [{ messageId: 'alphabeticalOrder' }, { messageId: 'alphabeticalOrder' }],
output: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
backgroundColor: 'blue',
color: 'white'
},
secondary: {
backgroundColor: 'gray',
color: 'black'
}
});
`,
},
],
});

View file

@ -0,0 +1,70 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import concentricOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/concentric-order/animation',
rule: concentricOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// keyframes with concentric ordering
`
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
position: 'relative',
transform: 'translateY(1rem)',
opacity: 0
},
'100%': {
position: 'relative',
transform: 'translateY(0)',
opacity: 1
}
});
`,
],
invalid: [
// keyframes with incorrect ordering
{
code: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
opacity: 0,
transform: 'translateY(1rem)',
position: 'relative'
},
'100%': {
opacity: 1,
transform: 'translateY(0)',
position: 'relative'
}
});
`,
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
position: 'relative',
transform: 'translateY(1rem)',
opacity: 0
},
'100%': {
position: 'relative',
transform: 'translateY(0)',
opacity: 1
}
});
`,
},
],
});

View file

@ -0,0 +1,97 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import concentricOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/concentric-order/font-face',
rule: concentricOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// fontFace with src first and other properties alphabetically ordered
`
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
descentOverride: '10%',
fontDisplay: 'swap',
fontFeatureSettings: '"liga" 1',
fontStretch: 'normal',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400'
});
`,
// globalFontFace with src first and other properties alphabetically ordered
`
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
descentOverride: '10%',
fontDisplay: 'swap',
fontFeatureSettings: '"liga" 1',
fontStretch: 'normal',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400'
});
`,
],
invalid: [
// fontFace with src not first
{
code: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
fontWeight: '400',
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal'
});
`,
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
// globalFontFace with src not first
{
code: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
fontWeight: '400',
fontStyle: 'normal',
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%'
});
`,
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
],
});

View file

@ -0,0 +1,58 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import concentricOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/concentric-order/global',
rule: concentricOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// globalStyle with concentric ordering
`
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
position: 'relative',
display: 'block',
margin: 0,
backgroundColor: 'white',
padding: 0,
color: 'black'
});
`,
],
invalid: [
// globalStyle with incorrect ordering
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
color: 'black',
margin: 0,
backgroundColor: 'white',
padding: 0,
display: 'block',
position: 'relative'
});
`,
errors: [{ messageId: 'incorrectOrder' }],
output: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
position: 'relative',
display: 'block',
margin: 0,
backgroundColor: 'white',
padding: 0,
color: 'black'
});
`,
},
],
});

View file

@ -0,0 +1,101 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import concentricOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/concentric-order/recipe',
rule: concentricOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Recipe with concentric ordering
`
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
position: 'relative',
display: 'flex',
alignItems: 'center',
backgroundColor: 'white',
width: '100%'
},
variants: {
color: {
blue: {
position: 'relative',
backgroundColor: 'blue',
color: 'white'
},
red: {
position: 'relative',
backgroundColor: 'red',
color: 'black'
}
}
}
});
`,
],
invalid: [
// Recipe with incorrect ordering
{
code: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
backgroundColor: 'white',
width: '100%',
display: 'flex',
alignItems: 'center'
},
variants: {
color: {
blue: {
color: 'white',
backgroundColor: 'blue',
position: 'relative'
},
red: {
color: 'black',
backgroundColor: 'red',
position: 'relative'
}
}
}
});
`,
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
display: 'flex',
alignItems: 'center',
backgroundColor: 'white',
width: '100%'
},
variants: {
color: {
blue: {
position: 'relative',
backgroundColor: 'blue',
color: 'white'
},
red: {
position: 'relative',
backgroundColor: 'red',
color: 'black'
}
}
}
});
`,
},
],
});

View file

@ -0,0 +1,129 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import concentricOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/concentric-order/style',
rule: concentricOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Basic style object with concentric ordering
`
import { style } from '@vanilla-extract/css';
const myStyle = style({
boxSizing: 'border-box',
position: 'relative',
zIndex: 1,
display: 'flex',
alignItems: 'center',
transform: 'none',
opacity: 1,
margin: '1rem',
border: '1px solid black',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
backgroundColor: 'red',
padding: '2rem',
width: '10rem',
height: '10rem',
color: 'blue',
fontSize: '16rem'
});
`,
// Style with nested selectors
`
import { style } from '@vanilla-extract/css';
const myStyle = style({
position: 'relative',
display: 'flex',
alignItems: 'center',
backgroundColor: 'red',
color: 'blue',
selectors: {
'&:hover': {
position: 'relative',
opacity: 0.8,
backgroundColor: 'blue',
color: 'white'
}
}
});
`,
],
invalid: [
// Basic style object with incorrect ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
color: 'blue',
width: '10rem',
display: 'flex',
backgroundColor: 'red',
margin: '1rem',
position: 'relative'
});
`,
errors: [{ messageId: 'incorrectOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
position: 'relative',
display: 'flex',
margin: '1rem',
backgroundColor: 'red',
width: '10rem',
color: 'blue'
});
`,
},
// Style with nested selectors having incorrect ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
color: 'blue',
display: 'flex',
backgroundColor: 'red',
position: 'relative',
selectors: {
'&:hover': {
color: 'white',
position: 'relative',
backgroundColor: 'blue'
}
}
});
`,
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
position: 'relative',
display: 'flex',
backgroundColor: 'red',
color: 'blue',
selectors: {
'&:hover': {
position: 'relative',
backgroundColor: 'blue',
color: 'white'
}
}
});
`,
},
],
});

View file

@ -0,0 +1,135 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import concentricOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/concentric-order/variants',
rule: concentricOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// styleVariants with concentric ordering
`
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
position: 'relative',
display: 'flex',
margin: '1rem',
backgroundColor: 'blue',
padding: '0.5rem',
color: 'white'
},
secondary: {
position: 'relative',
display: 'flex',
margin: '0.8rem',
backgroundColor: 'gray',
padding: '0.4rem',
color: 'black'
}
});
`,
],
invalid: [
// styleVariants with incorrect ordering
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
color: 'white',
backgroundColor: 'blue',
padding: '0.5rem',
margin: '1rem',
display: 'flex',
position: 'relative'
},
secondary: {
color: 'black',
backgroundColor: 'gray',
padding: '0.4rem',
margin: '0.8rem',
display: 'flex',
position: 'relative'
}
});
`,
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
position: 'relative',
display: 'flex',
margin: '1rem',
backgroundColor: 'blue',
padding: '0.5rem',
color: 'white'
},
secondary: {
position: 'relative',
display: 'flex',
margin: '0.8rem',
backgroundColor: 'gray',
padding: '0.4rem',
color: 'black'
}
});
`,
},
// styleVariants with some variants having incorrect ordering
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
position: 'relative',
display: 'flex',
margin: '1rem',
padding: '0.5rem',
backgroundColor: 'blue',
color: 'white'
},
secondary: {
color: 'black',
backgroundColor: 'gray',
padding: '0.4rem',
margin: '0.8rem',
display: 'flex',
position: 'relative'
}
});
`,
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
position: 'relative',
display: 'flex',
margin: '1rem',
backgroundColor: 'blue',
padding: '0.5rem',
color: 'white'
},
secondary: {
position: 'relative',
display: 'flex',
margin: '0.8rem',
backgroundColor: 'gray',
padding: '0.4rem',
color: 'black'
}
});
`,
},
],
});

View file

@ -0,0 +1,151 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import customGroupOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/custom-order/animation',
rule: customGroupOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// keyframes with custom group ordering (concentric for remaining)
{
code: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
position: 'relative',
transform: 'translateY(1rem)',
opacity: 0
},
'100%': {
position: 'relative',
transform: 'translateY(0)',
opacity: 1
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
},
// keyframes with custom group ordering (alphabetical for remaining)
{
code: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
opacity: 0,
position: 'relative',
transform: 'translateY(1rem)'
},
'100%': {
opacity: 1,
position: 'relative',
transform: 'translateY(0)'
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
},
],
invalid: [
// keyframes with incorrect ordering (concentric for remaining)
{
code: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
opacity: 0,
transform: 'translateY(1rem)',
position: 'relative'
},
'100%': {
opacity: 1,
transform: 'translateY(0)',
position: 'relative'
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
position: 'relative',
transform: 'translateY(1rem)',
opacity: 0
},
'100%': {
position: 'relative',
transform: 'translateY(0)',
opacity: 1
}
});
`,
},
// keyframes with incorrect ordering (alphabetical for remaining)
{
code: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
transform: 'translateY(1rem)',
position: 'relative',
opacity: 0
},
'100%': {
transform: 'translateY(0)',
position: 'relative',
opacity: 1
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { keyframes } from '@vanilla-extract/css';
const fadeIn = keyframes({
'0%': {
opacity: 0,
position: 'relative',
transform: 'translateY(1rem)'
},
'100%': {
opacity: 1,
position: 'relative',
transform: 'translateY(0)'
}
});
`,
},
],
});

View file

@ -0,0 +1,127 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import customGroupOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/custom-order/font-face',
rule: customGroupOrderRule,
configs: {
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
},
valid: [
// fontFace
{
code: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
descentOverride: '10%',
fontDisplay: 'swap',
fontFeatureSettings: '"liga" 1',
fontStretch: 'normal',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font'],
sortRemainingProperties: 'concentric',
},
],
},
// globalFontFace
{
code: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
descentOverride: '10%',
fontDisplay: 'swap',
fontFeatureSettings: '"liga" 1',
fontStretch: 'normal',
fontStyle: 'normal',
fontVariant: 'normal',
fontWeight: '400'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font'],
sortRemainingProperties: 'concentric',
},
],
},
],
invalid: [
// fontFace with src not first
{
code: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
fontWeight: '400',
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { fontFace } from '@vanilla-extract/css';
const myFont = fontFace({
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
// globalFontFace with src not first
{
code: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
fontWeight: '400',
fontStyle: 'normal',
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'fontFaceOrder' }],
output: `
import { globalFontFace } from '@vanilla-extract/css';
globalFontFace('GlobalFont', {
src: ['url("/fonts/MyFont.woff2") format("woff2")'],
ascentOverride: '90%',
fontStyle: 'normal',
fontWeight: '400'
});
`,
},
],
});

View file

@ -0,0 +1,127 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import customGroupOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/custom-order/global',
rule: customGroupOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// globalStyle with custom group ordering (concentric for remaining)
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
margin: 0,
position: 'relative',
display: 'block',
backgroundColor: 'white',
padding: 0,
color: 'black'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
},
// globalStyle with custom group ordering (alphabetical for remaining)
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
margin: 0,
backgroundColor: 'white',
color: 'black',
display: 'block',
padding: 0,
position: 'relative'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
},
],
invalid: [
// globalStyle with incorrect ordering (concentric for remaining)
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
color: 'black',
margin: 0,
backgroundColor: 'white',
padding: 0,
display: 'block',
position: 'relative'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'incorrectOrder' }],
output: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
margin: 0,
position: 'relative',
display: 'block',
backgroundColor: 'white',
padding: 0,
color: 'black'
});
`,
},
// globalStyle with incorrect ordering (alphabetical for remaining)
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
position: 'relative',
display: 'block',
margin: 0,
backgroundColor: 'white',
padding: 0,
color: 'black'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
errors: [{ messageId: 'incorrectOrder' }],
output: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('body', {
margin: 0,
backgroundColor: 'white',
color: 'black',
display: 'block',
padding: 0,
position: 'relative'
});
`,
},
],
});

View file

@ -0,0 +1,211 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import customGroupOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/custom-order/recipe',
rule: customGroupOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Recipe with custom group ordering (concentric for remaining)
{
code: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
width: '100%',
margin: 0,
position: 'relative',
display: 'flex',
alignItems: 'center',
backgroundColor: 'white'
},
variants: {
color: {
blue: {
position: 'relative',
backgroundColor: 'blue',
color: 'white'
},
red: {
position: 'relative',
backgroundColor: 'red',
color: 'black'
}
}
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
},
// Recipe with custom group ordering (alphabetical for remaining)
{
code: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
width: '100%',
margin: 0,
alignItems: 'center',
backgroundColor: 'white',
display: 'flex',
position: 'relative'
},
variants: {
color: {
blue: {
backgroundColor: 'blue',
color: 'white',
position: 'relative'
},
red: {
backgroundColor: 'red',
color: 'black',
position: 'relative'
}
}
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
},
],
invalid: [
// Recipe with incorrect ordering (concentric for remaining)
{
code: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
backgroundColor: 'white',
width: '100%',
display: 'flex',
alignItems: 'center',
margin: 0
},
variants: {
color: {
blue: {
color: 'white',
backgroundColor: 'blue',
position: 'relative'
},
red: {
color: 'black',
backgroundColor: 'red',
position: 'relative'
}
}
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
width: '100%',
margin: 0,
display: 'flex',
alignItems: 'center',
backgroundColor: 'white'
},
variants: {
color: {
blue: {
position: 'relative',
backgroundColor: 'blue',
color: 'white'
},
red: {
position: 'relative',
backgroundColor: 'red',
color: 'black'
}
}
}
});
`,
},
// Recipe with incorrect ordering (alphabetical for remaining)
{
code: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
position: 'relative',
display: 'flex',
alignItems: 'center',
backgroundColor: 'white',
width: '100%',
margin: 0
},
variants: {
color: {
blue: {
position: 'relative',
backgroundColor: 'blue',
color: 'white'
}
}
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { recipe } from '@vanilla-extract/recipes';
const myRecipe = recipe({
base: {
width: '100%',
margin: 0,
alignItems: 'center',
backgroundColor: 'white',
display: 'flex',
position: 'relative'
},
variants: {
color: {
blue: {
backgroundColor: 'blue',
color: 'white',
position: 'relative'
}
}
}
});
`,
},
],
});

View file

@ -0,0 +1,252 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import customGroupOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/custom-order/style',
rule: customGroupOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Style with custom group ordering (dimensions, margin, font, border, boxShadow)
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
// dimensions group first
width: '10rem',
minWidth: '5rem',
height: '10rem',
maxHeight: '20rem',
// margin group second
margin: '1rem',
marginTop: '0.5rem',
// font group third
fontFamily: 'sans-serif',
fontSize: '1rem',
fontWeight: 'bold',
// border group fourth
border: '1px solid black',
borderRadius: '4px',
// boxShadow group fifth
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
// remaining properties in concentric order
position: 'relative',
display: 'flex',
backgroundColor: 'red',
padding: '2rem',
color: 'blue'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
},
// Style with nested selectors following custom group ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '10rem',
margin: '1rem',
fontFamily: 'sans-serif',
border: '1px solid black',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
position: 'relative',
backgroundColor: 'red',
selectors: {
'&:hover': {
width: '12rem',
margin: '12px',
fontSize: '1rem',
borderColor: 'blue',
boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
position: 'relative',
backgroundColor: 'blue',
color: 'white'
}
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
},
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
// dimensions group first
width: '10rem',
height: '10rem',
// margin group second
margin: '1rem',
// font group third
fontFamily: 'sans-serif',
// border group fourth
border: '1px solid black',
// boxShadow group fifth
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
// remaining properties in alphabetical order
backgroundColor: 'red',
color: 'blue',
display: 'flex',
padding: '2rem',
position: 'relative'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
},
],
invalid: [
// Style with incorrect custom group ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
position: 'relative',
border: '1px solid black',
width: '10rem',
color: 'blue',
margin: '1rem',
fontFamily: 'sans-serif',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
backgroundColor: 'red'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'incorrectOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '10rem',
margin: '1rem',
fontFamily: 'sans-serif',
border: '1px solid black',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
position: 'relative',
backgroundColor: 'red',
color: 'blue'
});
`,
},
// Style with nested selectors having incorrect ordering
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
border: '1px solid black',
width: '10rem',
position: 'relative',
margin: '1rem',
selectors: {
'&:hover': {
color: 'white',
width: '12rem',
border: '2px solid blue',
margin: '1.2rem',
backgroundColor: 'blue'
}
}
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '10rem',
margin: '1rem',
border: '1px solid black',
position: 'relative',
selectors: {
'&:hover': {
width: '12rem',
margin: '1.2rem',
border: '2px solid blue',
backgroundColor: 'blue',
color: 'white'
}
}
});
`,
},
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
position: 'relative',
border: '1px solid black',
width: '10rem',
padding: '2rem',
color: 'blue',
margin: '1rem',
display: 'flex',
fontFamily: 'sans-serif',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
backgroundColor: 'red'
});
`,
options: [
{
groupOrder: ['dimensions', 'margin', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
errors: [{ messageId: 'incorrectOrder' }],
output: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '10rem',
margin: '1rem',
fontFamily: 'sans-serif',
border: '1px solid black',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
backgroundColor: 'red',
color: 'blue',
display: 'flex',
padding: '2rem',
position: 'relative'
});
`,
},
],
});

View file

@ -0,0 +1,187 @@
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import customGroupOrderRule from '../rule-definition.js';
run({
name: 'vanilla-extract/custom-order/variants',
rule: customGroupOrderRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// styleVariants with custom group ordering (concentric for remaining)
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
margin: '1rem',
padding: '0.5rem',
position: 'relative',
display: 'flex',
backgroundColor: 'blue',
color: 'white'
},
secondary: {
margin: '0.8rem',
padding: '0.4rem',
position: 'relative',
display: 'flex',
backgroundColor: 'gray',
color: 'black'
}
});
`,
options: [
{
groupOrder: ['margin', 'padding', 'dimensions', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
},
// styleVariants with custom group ordering (alphabetical for remaining)
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
margin: '1rem',
padding: '0.5rem',
backgroundColor: 'blue',
color: 'white',
display: 'flex',
position: 'relative'
},
secondary: {
margin: '0.8rem',
padding: '0.4rem',
backgroundColor: 'gray',
color: 'black',
display: 'flex',
position: 'relative'
}
});
`,
options: [
{
groupOrder: ['margin', 'padding', 'dimensions', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
},
],
invalid: [
// styleVariants with incorrect ordering (concentric for remaining)
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
color: 'white',
backgroundColor: 'blue',
padding: '0.5rem',
margin: '1rem',
display: 'flex',
position: 'relative'
},
secondary: {
color: 'black',
backgroundColor: 'gray',
padding: '0.4rem',
margin: '0.8rem',
display: 'flex',
position: 'relative'
}
});
`,
options: [
{
groupOrder: ['margin', 'padding', 'dimensions', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'concentric',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
margin: '1rem',
padding: '0.5rem',
position: 'relative',
display: 'flex',
backgroundColor: 'blue',
color: 'white'
},
secondary: {
margin: '0.8rem',
padding: '0.4rem',
position: 'relative',
display: 'flex',
backgroundColor: 'gray',
color: 'black'
}
});
`,
},
// styleVariants with incorrect ordering (alphabetical for remaining)
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
position: 'relative',
display: 'flex',
margin: '1rem',
padding: '0.5rem',
backgroundColor: 'blue',
color: 'white'
},
secondary: {
color: 'black',
backgroundColor: 'gray',
padding: '0.4rem',
margin: '0.8rem',
display: 'flex',
position: 'relative'
}
});
`,
options: [
{
groupOrder: ['margin', 'padding', 'dimensions', 'font', 'border', 'boxShadow'],
sortRemainingProperties: 'alphabetical',
},
],
errors: [{ messageId: 'incorrectOrder' }, { messageId: 'incorrectOrder' }],
output: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
primary: {
margin: '1rem',
padding: '0.5rem',
backgroundColor: 'blue',
color: 'white',
display: 'flex',
position: 'relative'
},
secondary: {
margin: '0.8rem',
padding: '0.4rem',
backgroundColor: 'gray',
color: 'black',
display: 'flex',
position: 'relative'
}
});
`,
},
],
});

View file

@ -18,9 +18,10 @@ import { processStyleNode } from './style-node-processor.js';
* @returns An object with visitor functions for the ESLint rule. * @returns An object with visitor functions for the ESLint rule.
* *
* This function sets up visitors for the following cases: * This function sets up visitors for the following cases:
* 1. Style-related functions: 'style', 'styleVariants', 'createVar', 'createTheme', 'createThemeContract' * 1. The fontFace and globalFontFace functions.
* 2. The 'globalStyle' function * 2. Style-related functions: 'keyframes', 'style', 'styleVariants'.
* 3. The 'recipe' function * 3. The 'globalStyle' and 'globalKeyframes' function
* 4. The 'recipe' function
* *
* Each visitor applies the appropriate ordering strategy to the style objects in these function calls. * Each visitor applies the appropriate ordering strategy to the style objects in these function calls.
*/ */
@ -80,11 +81,7 @@ export const createNodeVisitors = (
} }
// Handle style-related functions // Handle style-related functions
if ( if (['keyframes', 'style', 'styleVariants'].includes(node.callee.name)) {
['createThemeContract', 'createVar', 'createTheme', 'keyframes', 'style', 'styleVariants'].includes(
node.callee.name,
)
) {
if (node.arguments.length > 0) { if (node.arguments.length > 0) {
const styleArguments = node.arguments[0]; const styleArguments = node.arguments[0];
processStyleNode(ruleContext, styleArguments as TSESTree.Node, processProperty); processStyleNode(ruleContext, styleArguments as TSESTree.Node, processProperty);

View file

@ -5,7 +5,7 @@ import customOrderRule from './css-rules/custom-order/rule-definition.js';
export const vanillaExtract = { export const vanillaExtract = {
meta: { meta: {
name: '@antebudimir/eslint-plugin-vanilla-extract', name: '@antebudimir/eslint-plugin-vanilla-extract',
version: '1.4.0', version: '1.4.1',
}, },
rules: { rules: {
'alphabetical-order': alphabeticalOrderRule, 'alphabetical-order': alphabeticalOrderRule,

31
vitest.config.mjs Normal file
View file

@ -0,0 +1,31 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['html', 'json', 'text'],
reportsDirectory: './coverage/vitest-reports',
// include: ['src/**/rule-definition.ts'],
include: ['src/**/*.ts', '!src/**/*.test.ts', '!src/**/__tests__/**'],
// exclude: [
// 'src/**/*.css.ts',
// 'src/**/*.test.ts',
// 'src/**/*constants.ts',
// 'src/**/*index.ts',
// // Exclude all icon directories and their contents
// 'src/components/common/icons/**',
// // But include the CheckboxIcon component
// '!src/components/common/icons/checkbox-icon/CheckboxIcon.tsx',
// ],
},
reporters: [
'default',
['json', { outputFile: './coverage/vitest-results/vitest-results.json' }],
['junit', { outputFile: './coverage/vitest-results/vitest-results.xml' }],
],
environment: 'node',
include: ['src/**/*.test.ts'],
globals: true,
},
});