eslint-plugin-logical-properties
Version:
367 lines (305 loc) • 11 kB
Markdown
# eslint-plugin-logical-properties
Transform your normal TS/JS-based CSS
into RTL-ready CSS (think: vanilla extract or JSX style attribute).
This works by targeting certain JSX attributes
and TS/JS function calls to identify what
objects need transforming. Needless to say, this works on pure TS/JS Objects.
## Usage
> [!WARNING]
> This plugin will only work with a flat config ESLint (please upgrade, it's great!)
As with any eslint plugin, it comes with presets
and ways of adapting it to your own needs.
### Basic Setup (Preset Only)
The simplest way to use this plugin is with one of the presets:
```ts
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const finalEslintConfig = [
logicalPropertiesPlugin.configs.recommended,
// logicalPropertiesPlugin.configs.warning,
// logicalPropertiesPlugin.configs.disable,
];
```
Available presets:
- `recommended` (all rules turned on, `error` severity)
- `warning` (all rules set to `warn` severity)
- `disable` (all rules turned off)
These presets include the plugin and default settings automatically.
### Advanced Setup (Preset + Custom Settings)
```ts
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const customConfig = [
{
name: 'logical-properties',
plugins: {
'logical-properties': logicalPropertiesPlugin,
},
settings: {
'logical-properties': {
functions: ['style', 'globalStyle', 'myStyleFactory'],
keyframes: ['keyframes', 'myKeyframes'],
jsxAttributes: ['style', 'sx'],
resolvers: ['selectors.*', '@media.*', '@supports.*', 'variants.*.*', 'custom.tokens.*'],
},
},
rules: {
...logicalPropertiesPlugin.configs.recommended.rules,
},
},
];
```
### Per-Rule Override
If one rule needs custom behavior, rule options override shared settings:
```ts
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const finalEslintConfig = [
logicalPropertiesPlugin.configs.recommended,
{
name: 'logical-properties-overrides',
rules: {
'logical-properties/padding': ['error', {
functions: ['myStyleFactory'],
resolvers: ['custom.tokens.*'],
}],
},
},
];
```
### From Scratch (No Preset)
For complete control, you can build the config manually without presets:
```ts
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const finalEslintConfig = [
{
name: 'logical-properties',
plugins: {
'logical-properties': logicalPropertiesPlugin,
},
settings: {
'logical-properties': {
functions: ['style', 'globalStyle'],
keyframes: ['keyframes'],
jsxAttributes: ['style'],
resolvers: ['selectors.*', '@media.*'],
},
},
rules: {
'logical-properties/padding': 'error',
'logical-properties/margin': 'error',
'logical-properties/inset': 'error',
// ... add other rules as needed
},
},
];
```
### Custom Configuration
If you want explicit all-rules overrides in one place, use `createLogicalPropertiesConfig`.
This helper keeps its previous behavior and generates a complete ESLint config.
- helper-based all-rules config:
```ts
import { createLogicalPropertiesConfig } from 'eslint-plugin-logical-properties';
const config = createLogicalPropertiesConfig('error', {
functions: ['style', 'globalStyle'],
keyframes: ['keyframes'],
jsxAttributes: ['style'],
resolvers: ['selectors.*', '@media.*'],
});
```
> [!NOTE]
> The helper generates an entire config,
with the plugin attached, not just rules,
so you can use it directly in your ESLint config.
### Configuration Priority
Option resolution happens in this order:
1. Internal defaults
2. Shared plugin settings (`settings['logical-properties']`)
3. Per-rule options (`rules['logical-properties/<rule>'][1]`)
## Configuration
All rules share the same options:
- functions
- keyframes
- jsxAttributes
- resolvers
The difference lies in how they are processed:
- functions: expected to be called with CSS objects
or arrays of CSS objects (ignores all else, like strings)
- keyframes: expected to be called with an object
whose properties are CSS objects
- jsxAttributes: attributes on your JSX components
that should be treated as CSS objects
- resolvers: nested object paths to recursively inspect for CSS objects
> [!NOTE]
> When referring to CSS Objects, it's understood
as an object whose keys are CSS properties
### Default Config
```ts
const options = {
functions: ['style', 'globalStyle', 'recipe', 'sprinkles', 'defineProperties'],
keyframes: ['keyframes'],
jsxAttributes: ['style'],
resolvers: [
'selectors.*',
'@media.*',
'@supports.*',
'base',
'variants.*.*',
'compoundVariants.*.style',
],
}
// Helper
const generatedConfig = createLogicalPropertiesConfig('error', options);
// Manual Config
const manualConfig = {
rules: {
'logical-properties/padding': ['error', options]
}
};
```
## Shorthand Behavior
For shorthand rules (`padding`, `margin`, `borderRadius`), fixes are conservative:
- 1 value: unchanged
- 2 values: rewritten to logical pair properties
- 3 values: expanded to block-start, inline (single), block-end
- 4 values: expanded to primitive logical properties
Examples:
```ts
// 1 value -> unchanged
style({ padding: '8px' })
// 2 values -> logical pair
style({ padding: '8px 16px' })
// becomes
style({ paddingBlock: '8px', paddingInline: '16px' })
// 3 values -> block-start, single inline, block-end
style({ padding: '8px 16px 4px' })
// becomes
style({
paddingBlockStart: '8px',
paddingInline: '16px',
paddingBlockEnd: '4px',
})
// 4 values -> primitive logical properties
style({ padding: '8px 16px 4px 12px' })
// becomes
style({
paddingBlockStart: '8px',
paddingInlineEnd: '16px',
paddingBlockEnd: '4px',
paddingInlineStart: '12px',
})
```
The same logic also works for quoted strings and template strings.
## Migration Notes
If you are upgrading from a version that always expanded shorthand values to four properties,
the biggest changes are two-value and three-value shorthand behavior.
### Before/After Autofix
```ts
// Before (old behavior - 2 values)
style({ padding: '8px 16px' })
// autofix
style({
paddingBlockStart: '8px',
paddingBlockEnd: '8px',
paddingInlineStart: '16px',
paddingInlineEnd: '16px',
})
// After (current behavior - 2 values)
style({ padding: '8px 16px' })
// autofix
style({
paddingBlock: '8px',
paddingInline: '16px',
})
```
```ts
// Before (old behavior - 3 values)
style({ padding: '8px 16px 4px' })
// autofix
style({
paddingBlockStart: '8px',
paddingInlineStart: '16px',
paddingInlineEnd: '16px',
paddingBlockEnd: '4px',
})
// After (current behavior - 3 values)
style({ padding: '8px 16px 4px' })
// autofix
style({
paddingBlockStart: '8px',
paddingInline: '16px',
paddingBlockEnd: '4px',
})
```
```ts
// Before and after for one-value shorthand
style({ padding: '8px' })
// no autofix
style({ padding: '8px' })
```
```ts
// Before and after for 4 values
style({ padding: '8px 16px 4px 12px' })
// autofix (unchanged)
style({
paddingBlockStart: '8px',
paddingInlineEnd: '16px',
paddingBlockEnd: '4px',
paddingInlineStart: '12px',
})
```
## Rules
| Rule | Source | Replacement | Type |
| --- | --- | --- | --- |
| border | borderLeft | borderInlineStart | property |
| border | borderLeftColor | borderInlineStartColor | property |
| border | borderLeftStyle | borderInlineStartStyle | property |
| border | borderLeftWidth | borderInlineStartWidth | property |
| border | borderRight | borderInlineEnd | property |
| border | borderRightColor | borderInlineEndColor | property |
| border | borderRightStyle | borderInlineEndStyle | property |
| border | borderRightWidth | borderInlineEndWidth | property |
| border | borderTop | borderBlockStart | property |
| border | borderTopColor | borderBlockStartColor | property |
| border | borderTopStyle | borderBlockStartStyle | property |
| border | borderTopWidth | borderBlockStartWidth | property |
| border | borderBottom | borderBlockEnd | property |
| border | borderBottomColor | borderBlockEndColor | property |
| border | borderBottomStyle | borderBlockEndStyle | property |
| border | borderBottomWidth | borderBlockEndWidth | property |
| border | border | borderInlineStart, borderInlineEnd, borderBlockStart, borderBlockEnd | shorthand mapping |
| border-radius | borderTopLeftRadius | borderStartStartRadius | property |
| border-radius | borderTopRightRadius | borderStartEndRadius | property |
| border-radius | borderBottomLeftRadius | borderEndStartRadius | property |
| border-radius | borderBottomRightRadius | borderEndEndRadius | property |
| border-radius | borderRadius (2 values) | borderStartStartRadius + borderEndEndRadius, borderStartEndRadius + borderEndStartRadius | shorthand pair mapping |
| border-radius | borderRadius (3-4 values) | primitive logical radius properties | shorthand expansion |
| inset | top | insetBlockStart | property |
| inset | bottom | insetBlockEnd | property |
| inset | left | insetInlineStart | property |
| inset | right | insetInlineEnd | property |
| margin | marginLeft | marginInlineStart | property |
| margin | marginRight | marginInlineEnd | property |
| margin | marginTop | marginBlockStart | property |
| margin | marginBottom | marginBlockEnd | property |
| margin | margin (2 values) | marginBlock, marginInline | shorthand pair mapping |
| margin | margin (3 values) | marginBlockStart, marginInline, marginBlockEnd | shorthand 3-value |
| margin | margin (4 values) | primitive logical margin properties | shorthand expansion |
| padding | paddingLeft | paddingInlineStart | property |
| padding | paddingRight | paddingInlineEnd | property |
| padding | paddingTop | paddingBlockStart | property |
| padding | paddingBottom | paddingBlockEnd | property |
| padding | padding (2 values) | paddingBlock, paddingInline | shorthand pair mapping |
| padding | padding (3 values) | paddingBlockStart, paddingInline, paddingBlockEnd | shorthand 3-value |
| padding | padding (4 values) | primitive logical padding properties | shorthand expansion |
| overflow | overflowX | overflowInline | property |
| overflow | overflowY | overflowBlock | property |
| overscroll-behavior | overscrollBehaviorX | overscrollBehaviorInline | property |
| overscroll-behavior | overscrollBehaviorY | overscrollBehaviorBlock | property |
| size | width | inlineSize | property |
| size | height | blockSize | property |
| size | minWidth | minInlineSize | property |
| size | minHeight | minBlockSize | property |
| size | maxWidth | maxInlineSize | property |
| size | maxHeight | maxBlockSize | property |
| float | float: left | float: inline-start | value |
| float | float: right | float: inline-end | value |
| clear | clear: left | clear: inline-start | value |
| clear | clear: right | clear: inline-end | value |