@antebudimir/eslint-plugin-vanilla-extract
Version:
Comprehensive ESLint plugin for vanilla-extract with CSS property ordering, style validation, and best practices enforcement. Supports alphabetical, concentric and custom CSS ordering, auto-fixing, and zero-runtime safety.
445 lines (444 loc) • 13.7 kB
JavaScript
import tsParser from '@typescript-eslint/parser';
import { run } from 'eslint-vitest-rule-tester';
import noUnitlessValuesRule from '../rule-definition.js';
run({
name: 'vanilla-extract/no-unitless-values',
rule: noUnitlessValuesRule,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
},
valid: [
// Zero values are allowed
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: 0,
padding: 0,
width: 0,
});
`,
name: 'should allow zero values without units',
},
// String values with units
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '100px',
margin: '20px',
padding: '1rem',
fontSize: '16px',
});
`,
name: 'should allow string values with units',
},
// String zero values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: '0',
padding: '0',
});
`,
name: 'should allow string zero values without units',
},
// Unitless-valid properties
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
opacity: 0.5,
zIndex: 10,
lineHeight: 1.5,
flexGrow: 1,
flexShrink: 0,
order: 2,
fontWeight: 700,
zoom: 1.2,
});
`,
name: 'should allow unitless values for properties that accept them',
},
// Recipe with valid values
{
code: `
import { recipe } from '@vanilla-extract/css';
const myRecipe = recipe({
base: {
margin: '0',
padding: 0,
opacity: 0.8,
},
variants: {
size: {
small: {
height: '10px',
width: '10px',
zIndex: 1,
},
},
},
});
`,
name: 'should allow valid values in recipe',
},
// Template literals (not checked)
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: \`\${someValue}px\`,
padding: someVariable,
});
`,
name: 'should ignore template literals and variables',
},
// Nested selectors with valid values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '100px',
':hover': {
margin: '20px',
opacity: 0.8,
},
});
`,
name: 'should allow valid values in nested selectors',
},
// Media queries with valid values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: 0,
'@media': {
'(min-width: 768px)': {
padding: '20px',
zIndex: 10,
}
}
});
`,
name: 'should allow valid values in media queries',
},
// globalStyle with valid values
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('html', {
margin: '0',
padding: 0,
lineHeight: 1.5,
});
`,
name: 'should allow valid values in globalStyle',
},
// fontFace with valid values
{
code: `
import { fontFace } from '@vanilla-extract/css';
fontFace({
src: 'url(...)',
fontWeight: 400,
});
`,
name: 'should allow valid values in fontFace',
},
// keyframes (animation values are strings)
{
code: `
import { keyframes } from '@vanilla-extract/css';
const spin = keyframes({
'0%': { transform: 'rotate(0deg)' },
'100%': { transform: 'rotate(360deg)' }
});
`,
name: 'should allow keyframes with string values',
},
// allow option
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: 100,
margin: '20px',
});
`,
options: [{ allow: ['width'] }],
name: 'should allow properties specified in allow option',
},
// Kebab-case unitless-valid properties
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
'z-index': 10,
'line-height': 1.5,
'flex-grow': 1,
});
`,
name: 'should allow unitless values for kebab-case unitless-valid properties',
},
],
invalid: [
// Basic unitless values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: 100,
margin: 20,
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'width', value: '100' } },
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '20' } },
],
name: 'should flag unitless numeric values for length properties',
},
// Decimal values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
padding: 10.5,
fontSize: 16.5,
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'padding', value: '10.5' } },
{ messageId: 'noUnitlessValue', data: { property: 'fontSize', value: '16.5' } },
],
name: 'should flag decimal unitless values',
},
// Recipe with unitless values
{
code: `
import { recipe } from '@vanilla-extract/css';
const myRecipe = recipe({
base: {
margin: 10,
},
variants: {
size: {
small: {
height: 20,
},
},
},
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '10' } },
{ messageId: 'noUnitlessValue', data: { property: 'height', value: '20' } },
],
name: 'should flag unitless values in recipe',
},
// Nested selectors with unitless values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: 100,
':hover': {
margin: 20,
},
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'width', value: '100' } },
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '20' } },
],
name: 'should flag unitless values in nested selectors',
},
// Media queries with unitless values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
'@media': {
'(min-width: 768px)': {
padding: 20,
}
}
});
`,
errors: [{ messageId: 'noUnitlessValue', data: { property: 'padding', value: '20' } }],
name: 'should flag unitless values in media queries',
},
// Multiple levels of nesting
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: 10,
nested: {
object: {
padding: 20,
deeper: {
width: 30
}
}
}
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '10' } },
{ messageId: 'noUnitlessValue', data: { property: 'padding', value: '20' } },
{ messageId: 'noUnitlessValue', data: { property: 'width', value: '30' } },
],
name: 'should flag unitless values in deeply nested objects',
},
// globalStyle with unitless values
{
code: `
import { globalStyle } from '@vanilla-extract/css';
globalStyle('html', {
margin: 10,
padding: 20,
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '10' } },
{ messageId: 'noUnitlessValue', data: { property: 'padding', value: '20' } },
],
name: 'should flag unitless values in globalStyle',
},
// Various length properties
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: 100,
height: 200,
minWidth: 50,
maxWidth: 500,
top: 10,
left: 20,
borderWidth: 2,
borderRadius: 5,
gap: 15,
fontSize: 16,
});
`,
errors: 10,
name: 'should flag all unitless values for various length properties',
},
// Kebab-case properties
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
'margin-top': 10,
'padding-left': 20,
'font-size': 16,
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'margin-top', value: '10' } },
{ messageId: 'noUnitlessValue', data: { property: 'padding-left', value: '20' } },
{ messageId: 'noUnitlessValue', data: { property: 'font-size', value: '16' } },
],
name: 'should flag unitless values for kebab-case properties',
},
// Logical properties
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
marginBlock: 10,
paddingInline: 20,
insetBlockStart: 5,
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'marginBlock', value: '10' } },
{ messageId: 'noUnitlessValue', data: { property: 'paddingInline', value: '20' } },
{ messageId: 'noUnitlessValue', data: { property: 'insetBlockStart', value: '5' } },
],
name: 'should flag unitless values for logical properties',
},
// styleVariants
{
code: `
import { styleVariants } from '@vanilla-extract/css';
const variants = styleVariants({
small: { width: 10 },
large: { width: 100 },
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'width', value: '10' } },
{ messageId: 'noUnitlessValue', data: { property: 'width', value: '100' } },
],
name: 'should flag unitless values in styleVariants',
},
// globalKeyframes
{
code: `
import { globalKeyframes } from '@vanilla-extract/css';
globalKeyframes('slide', {
'0%': { left: 0 },
'100%': { left: 100 }
});
`,
errors: [{ messageId: 'noUnitlessValue', data: { property: 'left', value: '100' } }],
name: 'should flag unitless values in globalKeyframes',
},
// Negative values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: -10,
top: -5,
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '-10' } },
{ messageId: 'noUnitlessValue', data: { property: 'top', value: '-5' } },
],
name: 'should flag negative unitless values',
},
// String unitless values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
width: '100',
margin: '20',
padding: '10.5',
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'width', value: '100' } },
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '20' } },
{ messageId: 'noUnitlessValue', data: { property: 'padding', value: '10.5' } },
],
name: 'should flag string unitless values',
},
// String negative unitless values
{
code: `
import { style } from '@vanilla-extract/css';
const myStyle = style({
margin: '-10',
top: '-5.5',
});
`,
errors: [
{ messageId: 'noUnitlessValue', data: { property: 'margin', value: '-10' } },
{ messageId: 'noUnitlessValue', data: { property: 'top', value: '-5.5' } },
],
name: 'should flag string negative unitless values',
},
],
});