mirror of
https://github.com/antebudimir/eslint-plugin-vanilla-extract.git
synced 2025-12-31 08:53:33 +00:00
feat 🥁: add no-zero-unit rule
This rule enforces unitless zero values in vanilla-extract style objects: - Automatically removes unnecessary units from zero values - Handles both positive and negative zero values - Preserves units where required (time properties, CSS functions) - Works with all vanilla-extract APIs
This commit is contained in:
parent
52d38d4477
commit
7dc7204749
20 changed files with 650 additions and 737 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -30,3 +30,6 @@ pnpm-debug.log*
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# dev helper
|
||||||
|
src/css-sample/
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,15 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.8.0] - 2025-04-12
|
||||||
|
|
||||||
|
- add new rule `no-zero-unit` that enforces unitless zero values in vanilla-extract style objects
|
||||||
|
- Automatically removes unnecessary units from zero values (e.g., '0px' → '0')
|
||||||
|
- Handles both positive and negative zero values
|
||||||
|
- Preserves units where required (time properties, CSS functions)
|
||||||
|
- Works with all vanilla-extract APIs including style, recipe, fontFace, and keyframes
|
||||||
|
- Supports nested objects, media queries, and pseudo-selectors
|
||||||
|
|
||||||
## [1.7.0] - 2025-04-07
|
## [1.7.0] - 2025-04-07
|
||||||
|
|
||||||
- add a recommended configuration preset that enables concentric-order and no-empty-style-blocks rules with error severity.
|
- add a recommended configuration preset that enables concentric-order and no-empty-style-blocks rules with error severity.
|
||||||
|
|
|
||||||
37
README.md
37
README.md
|
|
@ -66,6 +66,7 @@ export default [
|
||||||
// Optionally override specific rules
|
// Optionally override specific rules
|
||||||
// 'vanilla-extract/concentric-order': 'warn', // Change severity from error to warn
|
// 'vanilla-extract/concentric-order': 'warn', // Change severity from error to warn
|
||||||
// 'vanilla-extract/no-empty-style-blocks': 'off', // Disable a recommended rule
|
// 'vanilla-extract/no-empty-style-blocks': 'off', // Disable a recommended rule
|
||||||
|
// 'vanilla-extract/no-zero-unit': 'warn', // Change severity from error to warn
|
||||||
|
|
||||||
// Add additional rules not in recommended config
|
// Add additional rules not in recommended config
|
||||||
// 'vanilla-extract/alphabetical-order': 'error', // Override concentric-order rule
|
// 'vanilla-extract/alphabetical-order': 'error', // Override concentric-order rule
|
||||||
|
|
@ -80,6 +81,7 @@ The recommended configuration enables the following rules with error severity:
|
||||||
|
|
||||||
- `vanilla-extract/concentric-order`: Enforces concentric CSS property ordering
|
- `vanilla-extract/concentric-order`: Enforces concentric CSS property ordering
|
||||||
- `vanilla-extract/no-empty-style-blocks`: Prevents empty style blocks
|
- `vanilla-extract/no-empty-style-blocks`: Prevents empty style blocks
|
||||||
|
- `vanilla-extract/no-zero-unit`: removes unnecessary units for zero values
|
||||||
|
|
||||||
You can use the recommended configuration as a starting point and override rules as needed for your project.
|
You can use the recommended configuration as a starting point and override rules as needed for your project.
|
||||||
|
|
||||||
|
|
@ -108,6 +110,7 @@ export default [
|
||||||
sortRemainingProperties: 'concentric', // 'alphabetical' is default
|
sortRemainingProperties: 'concentric', // 'alphabetical' is default
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'vanilla-extract/no-zero-unit': 'warn',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -266,6 +269,34 @@ export const recipeWithEmptyVariants = recipe({
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## vanilla-extract/no-zero-unit
|
||||||
|
|
||||||
|
This rule enforces the removal of unnecessary units for zero values in vanilla-extract style objects. It helps maintain cleaner and more consistent CSS by eliminating redundant units when the value is zero.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Incorrect
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const myStyle = style({
|
||||||
|
margin: '0px',
|
||||||
|
padding: '0rem',
|
||||||
|
width: '0%',
|
||||||
|
height: '0vh',
|
||||||
|
top: '-0em',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Correct
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const myStyle = style({
|
||||||
|
margin: '0',
|
||||||
|
padding: '0',
|
||||||
|
width: '0',
|
||||||
|
height: '0',
|
||||||
|
top: '0',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Font Face Declarations
|
## Font Face Declarations
|
||||||
|
|
||||||
For `fontFace` and `globalFontFace` API calls, all three ordering rules (alphabetical, concentric, and custom) enforce the same special ordering:
|
For `fontFace` and `globalFontFace` API calls, all three ordering rules (alphabetical, concentric, and custom) enforce the same special ordering:
|
||||||
|
|
@ -341,16 +372,16 @@ The roadmap outlines the project's current status and future plans:
|
||||||
- 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.).
|
||||||
- `no-empty-style-blocks` rule to disallow empty blocks.
|
- `no-empty-style-blocks` rule to disallow empty blocks.
|
||||||
- Recommended ESLint configuration for the plugin.
|
- Recommended ESLint configuration for the plugin.
|
||||||
|
- `no-zero-unit` rule to disallow units when the value is zero.
|
||||||
- Comprehensive rule testing.
|
- Comprehensive rule testing.
|
||||||
|
|
||||||
### Current Work
|
### Current Work
|
||||||
|
|
||||||
- `no-zero-unit` rule to disallow units when the value is zero.
|
- `no-unknown-unit` rule to disallow unknown units.
|
||||||
|
|
||||||
### Upcoming Features
|
### Upcoming Features
|
||||||
|
|
||||||
- `no-unknown-units` rule to disallow unknown units.
|
- `no-number-trailing-zero` rule to disallow trailing zeros in numbers.
|
||||||
- `no-number-trailing-zeros` rule to disallow trailing zeros in numbers.
|
|
||||||
- `no-px-unit` rule to disallow use of `px` units with configurable whitelist.
|
- `no-px-unit` rule to disallow use of `px` units with configurable whitelist.
|
||||||
- `prefer-logical-properties` rule to enforce use of logical properties.
|
- `prefer-logical-properties` rule to enforce use of logical properties.
|
||||||
- `prefer-theme-tokens` rule to enforce use of theme tokens instead of hard-coded values when available.
|
- `prefer-theme-tokens` rule to enforce use of theme tokens instead of hard-coded values when available.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@antebudimir/eslint-plugin-vanilla-extract",
|
"name": "@antebudimir/eslint-plugin-vanilla-extract",
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"description": "ESLint plugin for enforcing best practices in vanilla-extract CSS styles, including CSS property ordering and additional linting rules.",
|
"description": "ESLint plugin for enforcing best practices in vanilla-extract CSS styles, including CSS property ordering and additional linting rules.",
|
||||||
"author": "Ante Budimir",
|
"author": "Ante Budimir",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ import { reportEmptyDeclaration } from './fix-utils.js';
|
||||||
/**
|
/**
|
||||||
* Handles conditional expressions with empty objects.
|
* Handles conditional expressions with empty objects.
|
||||||
*/
|
*/
|
||||||
export function processConditionalExpression(
|
export const processConditionalExpression = (
|
||||||
context: Rule.RuleContext,
|
context: Rule.RuleContext,
|
||||||
node: TSESTree.ConditionalExpression,
|
node: TSESTree.ConditionalExpression,
|
||||||
reportedNodes: Set<TSESTree.Node>,
|
reportedNodes: Set<TSESTree.Node>,
|
||||||
callNode: TSESTree.CallExpression,
|
callNode: TSESTree.CallExpression,
|
||||||
): void {
|
): void => {
|
||||||
const isConsequentEmpty = node.consequent.type === 'ObjectExpression' && isEmptyObject(node.consequent);
|
const isConsequentEmpty = node.consequent.type === 'ObjectExpression' && isEmptyObject(node.consequent);
|
||||||
const isAlternateEmpty = node.alternate.type === 'ObjectExpression' && isEmptyObject(node.alternate);
|
const isAlternateEmpty = node.alternate.type === 'ObjectExpression' && isEmptyObject(node.alternate);
|
||||||
|
|
||||||
|
|
@ -33,4 +33,4 @@ export function processConditionalExpression(
|
||||||
messageId: 'emptyConditionalStyle',
|
messageId: 'emptyConditionalStyle',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import { areAllChildrenEmpty, getStyleKeyName } from './property-utils.js';
|
||||||
/**
|
/**
|
||||||
* Processes nested style objects like selectors and media queries.
|
* Processes nested style objects like selectors and media queries.
|
||||||
*/
|
*/
|
||||||
export function processEmptyNestedStyles(
|
export const processEmptyNestedStyles = (
|
||||||
ruleContext: Rule.RuleContext,
|
ruleContext: Rule.RuleContext,
|
||||||
node: TSESTree.ObjectExpression,
|
node: TSESTree.ObjectExpression,
|
||||||
reportedNodes: Set<TSESTree.Node>,
|
reportedNodes: Set<TSESTree.Node>,
|
||||||
): void {
|
): void => {
|
||||||
node.properties.forEach((property) => {
|
node.properties.forEach((property) => {
|
||||||
if (property.type !== 'Property') {
|
if (property.type !== 'Property') {
|
||||||
return;
|
return;
|
||||||
|
|
@ -72,4 +72,4 @@ export function processEmptyNestedStyles(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,128 +9,10 @@ import { getStyleKeyName } from './property-utils.js';
|
||||||
import { processRecipeProperties } from './recipe-processor.js';
|
import { processRecipeProperties } from './recipe-processor.js';
|
||||||
import { processStyleVariants } from './style-variants-processor.js';
|
import { processStyleVariants } from './style-variants-processor.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates ESLint rule visitors for detecting empty style blocks in vanilla-extract.
|
|
||||||
* @param ruleContext The ESLint rule rule context.
|
|
||||||
* @returns An object with visitor functions for the ESLint rule.
|
|
||||||
*/
|
|
||||||
export const createEmptyStyleVisitors = (ruleContext: Rule.RuleContext): Rule.RuleListener => {
|
|
||||||
// Track reported nodes to prevent duplicate reports
|
|
||||||
const reportedNodes = new Set<TSESTree.Node>();
|
|
||||||
|
|
||||||
return {
|
|
||||||
CallExpression(node) {
|
|
||||||
if (node.callee.type !== 'Identifier') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target vanilla-extract style functions
|
|
||||||
const styleApiFunctions = [
|
|
||||||
'style',
|
|
||||||
'styleVariants',
|
|
||||||
'recipe',
|
|
||||||
'globalStyle',
|
|
||||||
'fontFace',
|
|
||||||
'globalFontFace',
|
|
||||||
'keyframes',
|
|
||||||
'globalKeyframes',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!styleApiFunctions.includes(node.callee.name) || node.arguments.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle styleVariants specifically
|
|
||||||
if (node.callee.name === 'styleVariants' && node.arguments[0]?.type === 'ObjectExpression') {
|
|
||||||
processStyleVariants(ruleContext, node.arguments[0] as TSESTree.ObjectExpression, reportedNodes);
|
|
||||||
|
|
||||||
// If the entire styleVariants object is empty after processing, remove the declaration
|
|
||||||
if (isEmptyObject(node.arguments[0] as TSESTree.ObjectExpression)) {
|
|
||||||
reportEmptyDeclaration(ruleContext, node.arguments[0] as TSESTree.Node, node as TSESTree.CallExpression);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultStyleArgumentIndex = 0;
|
|
||||||
const globalFunctionNames = ['globalStyle', 'globalFontFace', 'globalKeyframes'];
|
|
||||||
// Determine the style argument index based on the function name
|
|
||||||
const styleArgumentIndex = globalFunctionNames.includes(node.callee.name) ? 1 : defaultStyleArgumentIndex;
|
|
||||||
|
|
||||||
// For global functions, check if we have enough arguments
|
|
||||||
if (styleArgumentIndex === 1 && node.arguments.length <= styleArgumentIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const styleArgument = node.arguments[styleArgumentIndex];
|
|
||||||
|
|
||||||
// This defensive check prevents duplicate processing of nodes.
|
|
||||||
// This code path's difficult to test because the ESLint visitor pattern
|
|
||||||
// typically ensures each node is only visited once per rule execution.
|
|
||||||
if (reportedNodes.has(styleArgument as TSESTree.Node)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle conditional expressions
|
|
||||||
if (styleArgument?.type === 'ConditionalExpression') {
|
|
||||||
processConditionalExpression(
|
|
||||||
ruleContext,
|
|
||||||
styleArgument as TSESTree.ConditionalExpression,
|
|
||||||
reportedNodes,
|
|
||||||
node as TSESTree.CallExpression,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Direct empty object case - remove the entire declaration
|
|
||||||
if (styleArgument?.type === 'ObjectExpression' && isEmptyObject(styleArgument as TSESTree.ObjectExpression)) {
|
|
||||||
reportedNodes.add(styleArgument as TSESTree.ObjectExpression);
|
|
||||||
reportEmptyDeclaration(ruleContext, styleArgument as TSESTree.Node, node as TSESTree.CallExpression);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For recipe - check if entire recipe is effectively empty
|
|
||||||
if (node.callee.name === 'recipe' && styleArgument?.type === 'ObjectExpression') {
|
|
||||||
if (isEffectivelyEmptyStylesObject(styleArgument as TSESTree.ObjectExpression)) {
|
|
||||||
reportedNodes.add(styleArgument as TSESTree.ObjectExpression);
|
|
||||||
reportEmptyDeclaration(ruleContext, styleArgument as TSESTree.Node, node as TSESTree.CallExpression);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process individual properties in recipe
|
|
||||||
processRecipeProperties(ruleContext, styleArgument as TSESTree.ObjectExpression, reportedNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For style objects with nested empty objects
|
|
||||||
if (styleArgument?.type === 'ObjectExpression') {
|
|
||||||
// Check for spread elements
|
|
||||||
styleArgument.properties.forEach((property) => {
|
|
||||||
if (
|
|
||||||
property.type === 'SpreadElement' &&
|
|
||||||
property.argument.type === 'ObjectExpression' &&
|
|
||||||
isEmptyObject(property.argument as TSESTree.ObjectExpression)
|
|
||||||
) {
|
|
||||||
reportedNodes.add(property.argument as TSESTree.Node);
|
|
||||||
ruleContext.report({
|
|
||||||
node: property.argument as Rule.Node,
|
|
||||||
messageId: 'emptySpreadObject',
|
|
||||||
fix(fixer) {
|
|
||||||
return removeNodeWithComma(ruleContext, property as TSESTree.Node, fixer);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process nested selectors and media queries
|
|
||||||
processEmptyNestedStyles(ruleContext, styleArgument as TSESTree.ObjectExpression, reportedNodes);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a style object is effectively empty (contains only empty objects).
|
* Checks if a style object is effectively empty (contains only empty objects).
|
||||||
*/
|
*/
|
||||||
export function isEffectivelyEmptyStylesObject(stylesObject: TSESTree.ObjectExpression): boolean {
|
export const isEffectivelyEmptyStylesObject = (stylesObject: TSESTree.ObjectExpression): boolean => {
|
||||||
// Empty object itself
|
// Empty object itself
|
||||||
if (stylesObject.properties.length === 0) {
|
if (stylesObject.properties.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -225,4 +107,122 @@ export function isEffectivelyEmptyStylesObject(stylesObject: TSESTree.ObjectExpr
|
||||||
|
|
||||||
// If we have special properties and they're all empty, the style is effectively empty
|
// If we have special properties and they're all empty, the style is effectively empty
|
||||||
return specialProperties.length > 0 && allSpecialPropertiesEmpty;
|
return specialProperties.length > 0 && allSpecialPropertiesEmpty;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates ESLint rule visitors for detecting empty style blocks in vanilla-extract.
|
||||||
|
* @param ruleContext The ESLint rule rule context.
|
||||||
|
* @returns An object with visitor functions for the ESLint rule.
|
||||||
|
*/
|
||||||
|
export const createEmptyStyleVisitors = (ruleContext: Rule.RuleContext): Rule.RuleListener => {
|
||||||
|
// Track reported nodes to prevent duplicate reports
|
||||||
|
const reportedNodes = new Set<TSESTree.ObjectExpression>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
CallExpression(node) {
|
||||||
|
if (node.callee.type !== 'Identifier') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target vanilla-extract style functions
|
||||||
|
const styleApiFunctions = [
|
||||||
|
'style',
|
||||||
|
'styleVariants',
|
||||||
|
'recipe',
|
||||||
|
'globalStyle',
|
||||||
|
'fontFace',
|
||||||
|
'globalFontFace',
|
||||||
|
'keyframes',
|
||||||
|
'globalKeyframes',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!styleApiFunctions.includes(node.callee.name) || node.arguments.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle styleVariants specifically
|
||||||
|
if (node.callee.name === 'styleVariants' && node.arguments[0]?.type === 'ObjectExpression') {
|
||||||
|
processStyleVariants(ruleContext, node.arguments[0] as TSESTree.ObjectExpression, reportedNodes);
|
||||||
|
|
||||||
|
// If the entire styleVariants object is empty after processing, remove the declaration
|
||||||
|
if (isEmptyObject(node.arguments[0] as TSESTree.ObjectExpression)) {
|
||||||
|
reportEmptyDeclaration(ruleContext, node.arguments[0] as TSESTree.Node, node as TSESTree.CallExpression);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultStyleArgumentIndex = 0;
|
||||||
|
const globalFunctionNames = ['globalStyle', 'globalFontFace', 'globalKeyframes'];
|
||||||
|
// Determine the style argument index based on the function name
|
||||||
|
const styleArgumentIndex = globalFunctionNames.includes(node.callee.name) ? 1 : defaultStyleArgumentIndex;
|
||||||
|
|
||||||
|
// For global functions, check if we have enough arguments
|
||||||
|
if (styleArgumentIndex === 1 && node.arguments.length <= styleArgumentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styleArgument = node.arguments[styleArgumentIndex];
|
||||||
|
|
||||||
|
// This defensive check prevents duplicate processing of nodes.
|
||||||
|
// This code path's difficult to test because the ESLint visitor pattern
|
||||||
|
// typically ensures each node is only visited once per rule execution.
|
||||||
|
if (reportedNodes.has(styleArgument as TSESTree.ObjectExpression)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle conditional expressions
|
||||||
|
if (styleArgument?.type === 'ConditionalExpression') {
|
||||||
|
processConditionalExpression(
|
||||||
|
ruleContext,
|
||||||
|
styleArgument as TSESTree.ConditionalExpression,
|
||||||
|
reportedNodes,
|
||||||
|
node as TSESTree.CallExpression,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct empty object case - remove the entire declaration
|
||||||
|
if (styleArgument?.type === 'ObjectExpression' && isEmptyObject(styleArgument as TSESTree.ObjectExpression)) {
|
||||||
|
reportedNodes.add(styleArgument as TSESTree.ObjectExpression);
|
||||||
|
reportEmptyDeclaration(ruleContext, styleArgument as TSESTree.Node, node as TSESTree.CallExpression);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For recipe - check if entire recipe is effectively empty
|
||||||
|
if (node.callee.name === 'recipe' && styleArgument?.type === 'ObjectExpression') {
|
||||||
|
if (isEffectivelyEmptyStylesObject(styleArgument as TSESTree.ObjectExpression)) {
|
||||||
|
reportedNodes.add(styleArgument as TSESTree.ObjectExpression);
|
||||||
|
reportEmptyDeclaration(ruleContext, styleArgument as TSESTree.Node, node as TSESTree.CallExpression);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process individual properties in recipe
|
||||||
|
processRecipeProperties(ruleContext, styleArgument as TSESTree.ObjectExpression, reportedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For style objects with nested empty objects
|
||||||
|
if (styleArgument?.type === 'ObjectExpression') {
|
||||||
|
// Check for spread elements
|
||||||
|
styleArgument.properties.forEach((property) => {
|
||||||
|
if (
|
||||||
|
property.type === 'SpreadElement' &&
|
||||||
|
property.argument.type === 'ObjectExpression' &&
|
||||||
|
isEmptyObject(property.argument as TSESTree.ObjectExpression)
|
||||||
|
) {
|
||||||
|
reportedNodes.add(property.argument as TSESTree.ObjectExpression);
|
||||||
|
ruleContext.report({
|
||||||
|
node: property.argument as Rule.Node,
|
||||||
|
messageId: 'emptySpreadObject',
|
||||||
|
fix(fixer) {
|
||||||
|
return removeNodeWithComma(ruleContext, property as TSESTree.Node, fixer);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process nested selectors and media queries
|
||||||
|
processEmptyNestedStyles(ruleContext, styleArgument as TSESTree.ObjectExpression, reportedNodes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import type { TSESTree } from '@typescript-eslint/utils';
|
||||||
* @param fixer The ESLint fixer.
|
* @param fixer The ESLint fixer.
|
||||||
* @returns The fix object.
|
* @returns The fix object.
|
||||||
*/
|
*/
|
||||||
export function removeNodeWithComma(ruleContext: Rule.RuleContext, node: TSESTree.Node, fixer: Rule.RuleFixer) {
|
export const removeNodeWithComma = (ruleContext: Rule.RuleContext, node: TSESTree.Node, fixer: Rule.RuleFixer) => {
|
||||||
const sourceCode = ruleContext.sourceCode;
|
const sourceCode = ruleContext.sourceCode;
|
||||||
const tokenAfter = sourceCode.getTokenAfter(node as Rule.Node);
|
const tokenAfter = sourceCode.getTokenAfter(node as Rule.Node);
|
||||||
if (tokenAfter && tokenAfter.value === ',' && node.range && tokenAfter.range) {
|
if (tokenAfter && tokenAfter.value === ',' && node.range && tokenAfter.range) {
|
||||||
return fixer.removeRange([node.range[0], tokenAfter.range[1]]);
|
return fixer.removeRange([node.range[0], tokenAfter.range[1]]);
|
||||||
}
|
}
|
||||||
return fixer.remove(node as Rule.Node);
|
return fixer.remove(node as Rule.Node);
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { isEmptyObject } from '../shared-utils/empty-object-processor.js';
|
||||||
/**
|
/**
|
||||||
* Gets the property name regardless of whether it's an identifier or a literal.
|
* Gets the property name regardless of whether it's an identifier or a literal.
|
||||||
*/
|
*/
|
||||||
export function getStyleKeyName(key: TSESTree.Expression | TSESTree.PrivateIdentifier): string | null {
|
export const getStyleKeyName = (key: TSESTree.Expression | TSESTree.PrivateIdentifier): string | null => {
|
||||||
if (key.type === 'Identifier') {
|
if (key.type === 'Identifier') {
|
||||||
return key.name;
|
return key.name;
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ export function getStyleKeyName(key: TSESTree.Expression | TSESTree.PrivateIdent
|
||||||
return key.value;
|
return key.value;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if all properties in a style object are empty objects.
|
* Checks if all properties in a style object are empty objects.
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import { areAllChildrenEmpty, getStyleKeyName } from './property-utils.js';
|
||||||
* @param recipeNode The recipe object node to process.
|
* @param recipeNode The recipe object node to process.
|
||||||
* @param reportedNodes A set of nodes that have already been reported by other processors.
|
* @param reportedNodes A set of nodes that have already been reported by other processors.
|
||||||
*/
|
*/
|
||||||
export function processRecipeProperties(
|
export const processRecipeProperties = (
|
||||||
ruleContext: Rule.RuleContext,
|
ruleContext: Rule.RuleContext,
|
||||||
recipeNode: TSESTree.ObjectExpression,
|
recipeNode: TSESTree.ObjectExpression,
|
||||||
reportedNodes: Set<TSESTree.Node>,
|
reportedNodes: Set<TSESTree.Node>,
|
||||||
): void {
|
): void => {
|
||||||
recipeNode.properties.forEach((property) => {
|
recipeNode.properties.forEach((property) => {
|
||||||
if (property.type !== 'Property') {
|
if (property.type !== 'Property') {
|
||||||
return;
|
return;
|
||||||
|
|
@ -139,4 +139,4 @@ export function processRecipeProperties(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ import { removeNodeWithComma } from './node-remover.js';
|
||||||
* @param node The styleVariants call argument (object expression).
|
* @param node The styleVariants call argument (object expression).
|
||||||
* @param reportedNodes A set of nodes that have already been reported.
|
* @param reportedNodes A set of nodes that have already been reported.
|
||||||
*/
|
*/
|
||||||
export function processStyleVariants(
|
export const processStyleVariants = (
|
||||||
ruleContext: Rule.RuleContext,
|
ruleContext: Rule.RuleContext,
|
||||||
node: TSESTree.ObjectExpression,
|
node: TSESTree.ObjectExpression,
|
||||||
reportedNodes: Set<TSESTree.Node>,
|
reportedNodes: Set<TSESTree.Node>,
|
||||||
): void {
|
): void => {
|
||||||
node.properties.forEach((property) => {
|
node.properties.forEach((property) => {
|
||||||
if (property.type !== 'Property') {
|
if (property.type !== 'Property') {
|
||||||
return;
|
return;
|
||||||
|
|
@ -50,4 +50,4 @@ export function processStyleVariants(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
||||||
341
src/css-rules/no-zero-unit/_tests_/no-zero-unit.test.ts
Normal file
341
src/css-rules/no-zero-unit/_tests_/no-zero-unit.test.ts
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
import { run } from 'eslint-vitest-rule-tester';
|
||||||
|
import noZeroUnitRule from '../rule-definition.js';
|
||||||
|
|
||||||
|
run({
|
||||||
|
name: 'vanilla-extract/no-zero-unit',
|
||||||
|
rule: noZeroUnitRule,
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0',
|
||||||
|
padding: 0,
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { recipe } from '@vanilla-extract/css';
|
||||||
|
const myRecipe = recipe({
|
||||||
|
base: {
|
||||||
|
margin: '0',
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
height: '0',
|
||||||
|
width: '10px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
...spreadProps,
|
||||||
|
margin: 0,
|
||||||
|
'@media': {
|
||||||
|
'0rem': '0' // Key shouldn't be checked
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should ignore spread elements and object keys',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: \`0\${someUnit}\`, // Template literal
|
||||||
|
padding: someVariable
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should ignore non-literal values',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { globalStyle } from '@vanilla-extract/css';
|
||||||
|
const callExpression = someObject.fontFace({ src: '...' }); // Non-Identifier callee
|
||||||
|
`,
|
||||||
|
name: 'should ignore member expression callees',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { fontFace } from '@vanilla-extract/css';
|
||||||
|
fontFace(); // Missing arguments
|
||||||
|
`,
|
||||||
|
name: 'should handle missing fontFace arguments',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { globalFontFace } from '@vanilla-extract/css';
|
||||||
|
globalFontFace('my-font'); // Missing style argument
|
||||||
|
`,
|
||||||
|
name: 'should handle missing globalFontFace style argument',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0px',
|
||||||
|
padding: '0rem',
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: [{ messageId: 'noZeroUnit' }, { messageId: 'noZeroUnit' }],
|
||||||
|
output: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0',
|
||||||
|
padding: '0',
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { recipe } from '@vanilla-extract/css';
|
||||||
|
const myRecipe = recipe({
|
||||||
|
base: {
|
||||||
|
margin: '0px',
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
height: '0vh',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: [{ messageId: 'noZeroUnit' }, { messageId: 'noZeroUnit' }],
|
||||||
|
output: `
|
||||||
|
import { recipe } from '@vanilla-extract/css';
|
||||||
|
const myRecipe = recipe({
|
||||||
|
base: {
|
||||||
|
margin: '0',
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: {
|
||||||
|
height: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0px',
|
||||||
|
'@media': {
|
||||||
|
'(min-width: 768px)': {
|
||||||
|
padding: '0rem'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: 2,
|
||||||
|
output: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0',
|
||||||
|
'@media': {
|
||||||
|
'(min-width: 768px)': {
|
||||||
|
padding: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should handle nested media queries',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
'::before': {
|
||||||
|
content: '""',
|
||||||
|
margin: '0px'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
output: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
'::before': {
|
||||||
|
content: '""',
|
||||||
|
margin: '0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should handle pseudo-elements',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0px',
|
||||||
|
nested: {
|
||||||
|
object: {
|
||||||
|
padding: '0rem',
|
||||||
|
deeper: {
|
||||||
|
width: '0%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: 3,
|
||||||
|
output: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0',
|
||||||
|
nested: {
|
||||||
|
object: {
|
||||||
|
padding: '0',
|
||||||
|
deeper: {
|
||||||
|
width: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should handle multiple levels of nesting',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { fontFace, globalFontFace } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
fontFace({
|
||||||
|
src: '...',
|
||||||
|
lineGap: '0rem'
|
||||||
|
});
|
||||||
|
|
||||||
|
globalFontFace('my-font', {
|
||||||
|
src: '...',
|
||||||
|
sizeAdjust: '0%'
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: 2,
|
||||||
|
output: `
|
||||||
|
import { fontFace, globalFontFace } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
fontFace({
|
||||||
|
src: '...',
|
||||||
|
lineGap: '0'
|
||||||
|
});
|
||||||
|
|
||||||
|
globalFontFace('my-font', {
|
||||||
|
src: '...',
|
||||||
|
sizeAdjust: '0'
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should handle fontFace and globalFontFace arguments',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 0deg is valid (deg isn't in our unit check)
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { globalKeyframes, globalStyle } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
globalKeyframes('spin', {
|
||||||
|
'0%': { transform: 'rotate(0deg)' },
|
||||||
|
'100%': { transform: 'rotate(0deg)' }
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle('html', {
|
||||||
|
margin: '0px',
|
||||||
|
padding: '0rem'
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: 2,
|
||||||
|
output: `
|
||||||
|
import { globalKeyframes, globalStyle } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
globalKeyframes('spin', {
|
||||||
|
'0%': { transform: 'rotate(0deg)' },
|
||||||
|
'100%': { transform: 'rotate(0deg)' }
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle('html', {
|
||||||
|
margin: '0',
|
||||||
|
padding: '0'
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should handle globalKeyframes and globalStyle arguments',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { globalStyle } from '@vanilla-extract/css';
|
||||||
|
globalStyle('html', {
|
||||||
|
'@media': {
|
||||||
|
'(min-width: 768px)': {
|
||||||
|
margin: '0px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
output: `
|
||||||
|
import { globalStyle } from '@vanilla-extract/css';
|
||||||
|
globalStyle('html', {
|
||||||
|
'@media': {
|
||||||
|
'(min-width: 768px)': {
|
||||||
|
margin: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should handle nested globalStyle arguments',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '-0px',
|
||||||
|
padding: '-0rem',
|
||||||
|
top: '-0vh',
|
||||||
|
left: '-0%',
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
errors: [
|
||||||
|
{ messageId: 'noZeroUnit' },
|
||||||
|
{ messageId: 'noZeroUnit' },
|
||||||
|
{ messageId: 'noZeroUnit' },
|
||||||
|
{ messageId: 'noZeroUnit' },
|
||||||
|
],
|
||||||
|
output: `
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
const myStyle = style({
|
||||||
|
margin: '0',
|
||||||
|
padding: '0',
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
name: 'should convert negative zero with units to simple zero',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
3
src/css-rules/no-zero-unit/index.ts
Normal file
3
src/css-rules/no-zero-unit/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import noZeroUnitRule from './rule-definition.js';
|
||||||
|
|
||||||
|
export default noZeroUnitRule;
|
||||||
23
src/css-rules/no-zero-unit/rule-definition.ts
Normal file
23
src/css-rules/no-zero-unit/rule-definition.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { Rule } from 'eslint';
|
||||||
|
import { createZeroUnitVisitors } from './zero-unit-visitor-creator.js';
|
||||||
|
|
||||||
|
const noZeroUnitRule: Rule.RuleModule = {
|
||||||
|
meta: {
|
||||||
|
type: 'suggestion',
|
||||||
|
docs: {
|
||||||
|
description: 'enforce unitless zero in numeric values',
|
||||||
|
category: 'Stylistic Issues',
|
||||||
|
recommended: true,
|
||||||
|
},
|
||||||
|
fixable: 'code',
|
||||||
|
schema: [],
|
||||||
|
messages: {
|
||||||
|
noZeroUnit: 'Unit with zero value is unnecessary. Use 0 instead.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
return createZeroUnitVisitors(context);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default noZeroUnitRule;
|
||||||
36
src/css-rules/no-zero-unit/zero-unit-processor.ts
Normal file
36
src/css-rules/no-zero-unit/zero-unit-processor.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import type { Rule } from 'eslint';
|
||||||
|
import { TSESTree } from '@typescript-eslint/utils';
|
||||||
|
|
||||||
|
const ZERO_VALUE_WITH_UNIT_REGEX = /^-?0(px|em|rem|%|vh|vw|vmin|vmax|ex|ch|cm|mm|in|pt|pc|Q|fr)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively processes a style object, reporting and fixing instances of zero values with units.
|
||||||
|
*
|
||||||
|
* @param ruleContext The ESLint rule context.
|
||||||
|
* @param node The ObjectExpression node representing the style object to be processed.
|
||||||
|
*/
|
||||||
|
export const processZeroUnitInStyleObject = (ruleContext: Rule.RuleContext, node: TSESTree.ObjectExpression): void => {
|
||||||
|
node.properties.forEach((property) => {
|
||||||
|
if (property.type !== 'Property') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process direct string literal values
|
||||||
|
if (
|
||||||
|
property.value.type === 'Literal' &&
|
||||||
|
typeof property.value.value === 'string' &&
|
||||||
|
ZERO_VALUE_WITH_UNIT_REGEX.test(property.value.value)
|
||||||
|
) {
|
||||||
|
ruleContext.report({
|
||||||
|
node: property.value,
|
||||||
|
messageId: 'noZeroUnit',
|
||||||
|
fix: (fixer) => fixer.replaceText(property.value, "'0'"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process nested objects (selectors, media queries, etc.)
|
||||||
|
if (property.value.type === 'ObjectExpression') {
|
||||||
|
processZeroUnitInStyleObject(ruleContext, property.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
56
src/css-rules/no-zero-unit/zero-unit-visitor-creator.ts
Normal file
56
src/css-rules/no-zero-unit/zero-unit-visitor-creator.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import type { Rule } from 'eslint';
|
||||||
|
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';
|
||||||
|
import { processRecipeProperties } from '../shared-utils/recipe-property-processor.js';
|
||||||
|
import { processStyleNode } from '../shared-utils/style-node-processor.js';
|
||||||
|
import { processZeroUnitInStyleObject } from './zero-unit-processor.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates ESLint rule visitors for detecting and processing zero values with units in style-related function calls.
|
||||||
|
*
|
||||||
|
* @param context The ESLint rule context.
|
||||||
|
* @returns An object with visitor functions for the ESLint rule.
|
||||||
|
*
|
||||||
|
* This function sets up visitors for the following cases:
|
||||||
|
* 1. The `fontFace` and `globalFontFace` functions, processing their object arguments.
|
||||||
|
* 2. Style-related functions: `keyframes`, `style`, `styleVariants`, processing their style objects.
|
||||||
|
* 3. The `globalKeyframes` and `globalStyle` functions, processing the second argument as style objects.
|
||||||
|
* 4. The `recipe` function, processing the first argument as the recipe object.
|
||||||
|
*/
|
||||||
|
export const createZeroUnitVisitors = (context: Rule.RuleContext): Rule.RuleListener => {
|
||||||
|
return {
|
||||||
|
CallExpression(node) {
|
||||||
|
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['fontFace', 'globalFontFace'].includes(node.callee.name)) {
|
||||||
|
const argumentIndex = node.callee.name === 'fontFace' ? 0 : 1;
|
||||||
|
if (
|
||||||
|
node.arguments.length > argumentIndex &&
|
||||||
|
node.arguments[argumentIndex]?.type === AST_NODE_TYPES.ObjectExpression
|
||||||
|
) {
|
||||||
|
processZeroUnitInStyleObject(context, node.arguments[argumentIndex] as TSESTree.ObjectExpression);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['keyframes', 'style', 'styleVariants'].includes(node.callee.name)) {
|
||||||
|
if (node.arguments.length > 0) {
|
||||||
|
processStyleNode(context, node.arguments[0] as TSESTree.ObjectExpression, processZeroUnitInStyleObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['globalKeyframes', 'globalStyle'].includes(node.callee.name) && node.arguments.length >= 2) {
|
||||||
|
processStyleNode(context, node.arguments[1] as TSESTree.ObjectExpression, processZeroUnitInStyleObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
node.callee.name === 'recipe' &&
|
||||||
|
node.arguments.length > 0 &&
|
||||||
|
node.arguments[0]?.type === AST_NODE_TYPES.ObjectExpression
|
||||||
|
) {
|
||||||
|
processRecipeProperties(context, node.arguments[0] as TSESTree.ObjectExpression, processZeroUnitInStyleObject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -44,13 +44,8 @@ export const createNodeVisitors = (
|
||||||
if (!userDefinedGroupOrder || userDefinedGroupOrder.length === 0) {
|
if (!userDefinedGroupOrder || userDefinedGroupOrder.length === 0) {
|
||||||
return enforceAlphabeticalCSSOrderInStyleObject;
|
return enforceAlphabeticalCSSOrderInStyleObject;
|
||||||
}
|
}
|
||||||
return (ruleContext: Rule.RuleContext, node: TSESTree.Node) =>
|
return (ruleContext: Rule.RuleContext, node: TSESTree.ObjectExpression) =>
|
||||||
enforceUserDefinedGroupOrderInStyleObject(
|
enforceUserDefinedGroupOrderInStyleObject(ruleContext, node, userDefinedGroupOrder, sortRemainingProperties);
|
||||||
ruleContext,
|
|
||||||
node as TSESTree.ObjectExpression,
|
|
||||||
userDefinedGroupOrder,
|
|
||||||
sortRemainingProperties,
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return enforceAlphabeticalCSSOrderInStyleObject;
|
return enforceAlphabeticalCSSOrderInStyleObject;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,589 +0,0 @@
|
||||||
import {
|
|
||||||
fontFace,
|
|
||||||
globalFontFace,
|
|
||||||
globalKeyframes,
|
|
||||||
globalStyle,
|
|
||||||
keyframes,
|
|
||||||
style,
|
|
||||||
styleVariants,
|
|
||||||
} from '@vanilla-extract/css';
|
|
||||||
import { recipe } from '@vanilla-extract/recipes';
|
|
||||||
|
|
||||||
// fontFaces
|
|
||||||
export const theFont = fontFace({
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
src: ['url("/fonts/MyFont.woff2") format("woff2")', 'url("/fonts/MyFont.woff") format("woff")'],
|
|
||||||
ascentOverride: '90%',
|
|
||||||
descentOverride: '10%',
|
|
||||||
fontDisplay: 'swap',
|
|
||||||
fontFeatureSettings: '"liga" 1',
|
|
||||||
fontStretch: 'normal',
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontVariant: 'normal',
|
|
||||||
fontVariationSettings: '"wght" 400',
|
|
||||||
fontWeight: '400 700',
|
|
||||||
lineGapOverride: '10%',
|
|
||||||
sizeAdjust: '90%',
|
|
||||||
unicodeRange:
|
|
||||||
'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
|
|
||||||
});
|
|
||||||
|
|
||||||
globalFontFace('GlobalFont', {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
src: ['url("/fonts/MyFont.woff2") format("woff2")', 'url("/fonts/MyFont.woff") format("woff")'],
|
|
||||||
ascentOverride: '90%',
|
|
||||||
descentOverride: '10%',
|
|
||||||
fontDisplay: 'swap',
|
|
||||||
fontFeatureSettings: '"liga" 1',
|
|
||||||
fontStretch: 'normal',
|
|
||||||
fontStyle: 'normal',
|
|
||||||
fontVariant: 'normal',
|
|
||||||
fontVariationSettings: '"wght" 400',
|
|
||||||
fontWeight: '400 700',
|
|
||||||
lineGapOverride: '10%',
|
|
||||||
sizeAdjust: '90%',
|
|
||||||
unicodeRange:
|
|
||||||
'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
|
|
||||||
});
|
|
||||||
|
|
||||||
// keyframes
|
|
||||||
export const spinster = globalKeyframes('spin', {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
from: {
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
to: {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const starter = keyframes({
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
'0%': {
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
'100%': {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
globalStyle('*, ::before, ::after', {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
});
|
|
||||||
|
|
||||||
// style with an array
|
|
||||||
const accordionContentBase = style([
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
{
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
|
|
||||||
// special selector to test that the linter doesn't remove it
|
|
||||||
'@supports': {
|
|
||||||
'(hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none)': {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const accordionContent = recipe({
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
base: accordionContentBase,
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
variants: {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
isOpen: {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
false: {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
|
|
||||||
true: {
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
|
|
||||||
// pseudo selector inside a variant
|
|
||||||
':hover': {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const item = style({
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
|
|
||||||
// pseudo selector inside a style
|
|
||||||
':focus-visible': {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
|
|
||||||
selectors: {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
'&[data-pressed]': {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const selectButtonVariants = styleVariants({
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
bordered: {
|
|
||||||
// Comment to test that the linter doesn't remove it
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
|
|
||||||
borderless: {
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
fontSize: 'large',
|
|
||||||
border: 'Background',
|
|
||||||
borderRight: 'ActiveBorder',
|
|
||||||
borderLeft: 'ActiveBorder',
|
|
||||||
borderRadius: 'initial',
|
|
||||||
borderBottomLeftRadius: 'initial',
|
|
||||||
borderBottomRightRadius: 'initial',
|
|
||||||
boxShadow: 'none',
|
|
||||||
boxSizing: 'inherit',
|
|
||||||
position: 'relative',
|
|
||||||
right: 'inherit',
|
|
||||||
display: 'flex',
|
|
||||||
gap: 'revert',
|
|
||||||
transform: 'none',
|
|
||||||
outline: 'none',
|
|
||||||
backgroundColor: 'initial',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test cases for noEmptyStyleBlocksRule
|
|
||||||
|
|
||||||
// export const myRecipe = recipe({
|
|
||||||
// base: {
|
|
||||||
// color: 'blue',
|
|
||||||
// selectors: {},
|
|
||||||
// '@media': {},
|
|
||||||
// '@supports': {},
|
|
||||||
// },
|
|
||||||
// variants: {
|
|
||||||
// size: {
|
|
||||||
// small: {
|
|
||||||
// selectors: {
|
|
||||||
// '&:hover': {},
|
|
||||||
// },
|
|
||||||
// '@media': {
|
|
||||||
// '(min-width: 768px)': {},
|
|
||||||
// },
|
|
||||||
// '@supports': {
|
|
||||||
// '(display: grid)': {},
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const base = style({ padding: 12 });
|
|
||||||
// export const variant = styleVariants({
|
|
||||||
// primary: [],
|
|
||||||
// secondary: [],
|
|
||||||
// bordered: {},
|
|
||||||
// borderless: {},
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const baseStyles = {
|
|
||||||
// color: 'blue',
|
|
||||||
// margin: '10px',
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const isDarkMode = false;
|
|
||||||
|
|
||||||
// export const spreadStyle = style({
|
|
||||||
// ...baseStyles,
|
|
||||||
// ...{},
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const recipeWithNonObjectValue = recipe({
|
|
||||||
// base: { color: 'black' },
|
|
||||||
// variants: {
|
|
||||||
// color: {
|
|
||||||
// red: { color: 'red' },
|
|
||||||
// // string instead of object
|
|
||||||
// string: 'string',
|
|
||||||
// // variable instead of object
|
|
||||||
// variable: baseStyles,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const conditionalStyle = style(isDarkMode ? {} : {});
|
|
||||||
|
|
||||||
// export const recipeWithEmptyVariantValues = recipe({
|
|
||||||
// base: { color: 'black' },
|
|
||||||
// variants: {
|
|
||||||
// color: {
|
|
||||||
// blue: {},
|
|
||||||
// red: {},
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const nestedEmptyStyle = style({
|
|
||||||
// selectors: {
|
|
||||||
// '&:hover': {},
|
|
||||||
// '&:focus': {},
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const myEmptyStyle = style({});
|
|
||||||
// export { myEmptyStyle };
|
|
||||||
|
|
||||||
// export const emptyStyle1 = style({});
|
|
||||||
// export const emptyStyle2 = style({});
|
|
||||||
// export const emptyVariants = styleVariants({});
|
|
||||||
// export const emptyRecipe = recipe({});
|
|
||||||
|
|
||||||
// export const styleWithComments = style({
|
|
||||||
// /* This is an empty style */
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const styleWithEmptyMedia = style({
|
|
||||||
// color: 'blue',
|
|
||||||
// '@media': {
|
|
||||||
// '(min-width: 768px)': {},
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const styleWithEmptySelector = style({
|
|
||||||
// color: 'blue',
|
|
||||||
|
|
||||||
// selectors: {
|
|
||||||
// '&:hover': {},
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const recipeWithBothEmpty = recipe({
|
|
||||||
// base: {},
|
|
||||||
// variants: {},
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const recipeWithEmptyVariants = recipe({
|
|
||||||
// base: { color: 'black' },
|
|
||||||
// variants: {},
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const recipeWithEmptyBase = recipe({
|
|
||||||
// base: {},
|
|
||||||
// variants: {
|
|
||||||
// color: {
|
|
||||||
// blue: { color: 'blue' },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const recipe = recipe({
|
|
||||||
// base: {},
|
|
||||||
// variants: {
|
|
||||||
// color: {
|
|
||||||
// red: {},
|
|
||||||
// blue: {},
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// export const recipeWithNonObjectVariants = recipe({
|
|
||||||
// base: { color: 'blue' },
|
|
||||||
// variants: {
|
|
||||||
// color: {
|
|
||||||
// size: 'string instead of object', // This is a string, not an object
|
|
||||||
// red: {},
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Using the same empty object reference in both branches
|
|
||||||
// export const myStyle = style(true ? {} : {});
|
|
||||||
|
|
||||||
// export const emptyFontFace = fontFace({});
|
|
||||||
// globalFontFace('GlobalFont', {});
|
|
||||||
// globalKeyframes('a', {});
|
|
||||||
// export const emptyKeyframes = keyframes({});
|
|
||||||
// globalStyle('ul', {});
|
|
||||||
// export const emptyStyleVariants = styleVariants({});
|
|
||||||
// export const emptyStyle = style({});
|
|
||||||
|
|
@ -2,17 +2,19 @@ import alphabeticalOrderRule from './css-rules/alphabetical-order/index.js';
|
||||||
import concentricOrderRule from './css-rules/concentric-order/index.js';
|
import concentricOrderRule from './css-rules/concentric-order/index.js';
|
||||||
import customOrderRule from './css-rules/custom-order/rule-definition.js';
|
import customOrderRule from './css-rules/custom-order/rule-definition.js';
|
||||||
import noEmptyStyleBlocksRule from './css-rules/no-empty-blocks/rule-definition.js';
|
import noEmptyStyleBlocksRule from './css-rules/no-empty-blocks/rule-definition.js';
|
||||||
|
import noZeroUnitRule from './css-rules/no-zero-unit/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.7.0',
|
version: '1.8.0',
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'alphabetical-order': alphabeticalOrderRule,
|
'alphabetical-order': alphabeticalOrderRule,
|
||||||
'concentric-order': concentricOrderRule,
|
'concentric-order': concentricOrderRule,
|
||||||
'custom-order': customOrderRule,
|
'custom-order': customOrderRule,
|
||||||
'no-empty-style-blocks': noEmptyStyleBlocksRule,
|
'no-empty-style-blocks': noEmptyStyleBlocksRule,
|
||||||
|
'no-zero-unit': noZeroUnitRule,
|
||||||
},
|
},
|
||||||
configs: {
|
configs: {
|
||||||
recommended: {
|
recommended: {
|
||||||
|
|
@ -20,6 +22,7 @@ export const vanillaExtract = {
|
||||||
rules: {
|
rules: {
|
||||||
'vanilla-extract/concentric-order': 'error',
|
'vanilla-extract/concentric-order': 'error',
|
||||||
'vanilla-extract/no-empty-style-blocks': 'error',
|
'vanilla-extract/no-empty-style-blocks': 'error',
|
||||||
|
'vanilla-extract/no-zero-unit': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
"useUnknownInCatchVariables": true,
|
"useUnknownInCatchVariables": true,
|
||||||
|
|
||||||
// Interop Options
|
// Interop Options
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue