@tenphi/eslint-plugin-tasty 1.18.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -45
- package/docs/rules/consistent-token-usage.md +74 -0
- package/docs/rules/known-property.md +55 -0
- package/docs/rules/no-classname-prop.md +62 -0
- package/docs/rules/no-ignored-pseudo-element-state.md +97 -0
- package/docs/rules/no-important.md +25 -0
- package/docs/rules/no-nested-selector.md +39 -0
- package/docs/rules/no-nested-state-map.md +33 -0
- package/docs/rules/no-own-at-root.md +57 -0
- package/docs/rules/no-protected-restyle.md +194 -0
- package/docs/rules/no-raw-color-values.md +51 -0
- package/docs/rules/no-raw-motion-duration.md +50 -0
- package/docs/rules/no-raw-transition-duration.md +56 -0
- package/docs/rules/no-runtime-styles-mutation.md +58 -0
- package/docs/rules/no-state-in-selector.md +142 -0
- package/docs/rules/no-style-prop.md +129 -0
- package/docs/rules/no-style-spread.md +55 -0
- package/docs/rules/no-styles-prop.md +59 -0
- package/docs/rules/no-unknown-state-alias.md +45 -0
- package/docs/rules/prefer-auto-calc.md +30 -0
- package/docs/rules/prefer-custom-property-syntax.md +56 -0
- package/docs/rules/prefer-directional-shorthand.md +46 -0
- package/docs/rules/prefer-element-selector.md +77 -0
- package/docs/rules/prefer-fill-mix.md +119 -0
- package/docs/rules/prefer-hide.md +31 -0
- package/docs/rules/prefer-longhand-property.md +25 -0
- package/docs/rules/prefer-shorthand-property.md +194 -0
- package/docs/rules/prefer-state-negation.md +44 -0
- package/docs/rules/require-default-state.md +50 -0
- package/docs/rules/static-no-dynamic-values.md +32 -0
- package/docs/rules/static-valid-selector.md +36 -0
- package/docs/rules/valid-boolean-property.md +39 -0
- package/docs/rules/valid-color-token.md +64 -0
- package/docs/rules/valid-custom-property.md +36 -0
- package/docs/rules/valid-custom-unit.md +34 -0
- package/docs/rules/valid-default-state-order.md +44 -0
- package/docs/rules/valid-directional-modifier.md +46 -0
- package/docs/rules/valid-preset.md +35 -0
- package/docs/rules/valid-radius-shape.md +31 -0
- package/docs/rules/valid-recipe.md +35 -0
- package/docs/rules/valid-state-definition.md +46 -0
- package/docs/rules/valid-state-key.md +62 -0
- package/docs/rules/valid-styles-structure.md +48 -0
- package/docs/rules/valid-sub-element.md +100 -0
- package/docs/rules/valid-transition.md +46 -0
- package/docs/rules/valid-value.md +65 -0
- package/harness/dev.harnesspm/hooks.json +12 -0
- package/harness/dev.harnesspm/rules/usage.md +11 -0
- package/harness/dev.harnesspm/scripts/check-edit.mjs +163 -0
- package/harness/dev.harnesspm/scripts/check.mjs +254 -0
- package/harness/dev.harnesspm/scripts/lint.mjs +318 -0
- package/harness/dev.harnesspm/scripts/project-config.mjs +316 -0
- package/harness/plugin.json +23 -0
- package/package.json +11 -7
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# tasty/prefer-shorthand-property
|
|
2
|
+
|
|
3
|
+
Prefer Tasty's shorthand properties when defining a complete style, such as
|
|
4
|
+
`fill` instead of `backgroundColor` or `height` instead of separate size constraints.
|
|
5
|
+
|
|
6
|
+
**Presets:** warning in recommended and strict. **Fix:** autofix for pure key renames.
|
|
7
|
+
|
|
8
|
+
## Why
|
|
9
|
+
|
|
10
|
+
Tasty's shorthands carry its units, tokens, modifiers and defaults: `fill` takes
|
|
11
|
+
color tokens and mixes, `padding: '2x top'` names its side, `blockPadding`
|
|
12
|
+
emits native logical CSS. A native longhand bypasses that handler.
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// ❌ When defining the complete style, prefer tasty shorthand 'fill' instead of 'backgroundColor'.
|
|
18
|
+
tasty({ styles: { backgroundColor: '#purple' } });
|
|
19
|
+
|
|
20
|
+
// ✅ After the autofix
|
|
21
|
+
tasty({ styles: { fill: '#purple' } });
|
|
22
|
+
|
|
23
|
+
// ❌ Prefer 'padding: '... top'' instead of 'paddingTop'. Reported without a fix.
|
|
24
|
+
tasty({ styles: { paddingTop: '2x' } });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Mapping
|
|
28
|
+
|
|
29
|
+
| Native CSS | Tasty |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `backgroundColor` | `fill` |
|
|
32
|
+
| `backgroundImage` | `image` |
|
|
33
|
+
| `borderColor`, `borderWidth`, `borderStyle` | `border` |
|
|
34
|
+
| `borderTop`, `borderRight`, `borderBottom`, `borderLeft` | `border: '... top'` and the other sides |
|
|
35
|
+
| `borderRadius` | `radius` |
|
|
36
|
+
| `minWidth`, `maxWidth` | `width: 'min ...'`, `width: 'max ...'` |
|
|
37
|
+
| `minHeight`, `maxHeight` | `height: 'min ...'`, `height: 'max ...'` |
|
|
38
|
+
| `flexDirection`, `flexWrap`, `flexFlow`, `gridAutoFlow` | `flow` |
|
|
39
|
+
| `flexGap` | `gap` |
|
|
40
|
+
| `gridTemplateColumns`, `gridTemplateRows`, `gridTemplateAreas` | `gridColumns`, `gridRows`, `gridAreas` |
|
|
41
|
+
| `top`, `right`, `bottom`, `left` | `inset: '... top'` and the other sides |
|
|
42
|
+
| `outlineColor`, `outlineWidth`, `outlineStyle` | `outline` |
|
|
43
|
+
| `outlineOffset` | `outline: '... / offset'` |
|
|
44
|
+
| `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft` | `padding: '... top'` and the other sides |
|
|
45
|
+
| `marginTop`, `marginRight`, `marginBottom`, `marginLeft` | `margin: '... top'` and the other sides |
|
|
46
|
+
| `fontSize`, `letterSpacing`, `textTransform` | `preset` |
|
|
47
|
+
| `fontWeight`, `lineHeight`, `fontStyle` | `preset: 'name / strong'`, `'name / tight'`, `'name / italic'` |
|
|
48
|
+
| `fontFamily` | `font` (`preset` for a CSS-wide keyword such as `inherit`) |
|
|
49
|
+
| `boxShadow` | `shadow` |
|
|
50
|
+
| `scrollbarWidth`, `scrollbarColor`, `scrollbarGutter` | `scrollbar` |
|
|
51
|
+
| `lineClamp` | `textOverflow: 'ellipsis / {lines}'` |
|
|
52
|
+
|
|
53
|
+
**Logical properties** (Tasty 3.8 and later). Tasty moved the enhanced behavior
|
|
54
|
+
onto the `<axis><Category>` names and left the native spellings as plain CSS:
|
|
55
|
+
|
|
56
|
+
| Native CSS | Tasty |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `paddingBlock`, `paddingInline` | `blockPadding`, `inlinePadding` |
|
|
59
|
+
| `paddingBlockStart`, `paddingInlineEnd`, … | `blockPadding: '... start'`, `inlinePadding: '... end'`, … |
|
|
60
|
+
| `marginBlock`, `marginInline` and their edges | `blockMargin`, `inlineMargin` |
|
|
61
|
+
| `insetBlock`, `insetInline` and their edges | `blockInset`, `inlineInset` |
|
|
62
|
+
| `scrollMarginBlock`, `scrollPaddingInline` and the like | `blockScrollMargin`, `inlineScrollPadding` and the like |
|
|
63
|
+
| `borderBlock`, `borderBlockColor`, `borderBlockWidth`, `borderBlockStyle` | `blockBorder` |
|
|
64
|
+
| `borderInlineStart`, … | `inlineBorder: '... start'`, … |
|
|
65
|
+
| `minBlockSize`, `maxBlockSize` | `blockSize: 'min ...'`, `blockSize: 'max ...'` |
|
|
66
|
+
| `minInlineSize`, `maxInlineSize` | `inlineSize: 'min ...'`, `inlineSize: 'max ...'` |
|
|
67
|
+
|
|
68
|
+
Logical corners (`borderStartStartRadius` and the like) are left out on purpose:
|
|
69
|
+
`radius` sets physical corners, so pointing a logical corner at it would be wrong.
|
|
70
|
+
|
|
71
|
+
## Autofix
|
|
72
|
+
|
|
73
|
+
Only renames that change nothing but the key are fixed, outside extension
|
|
74
|
+
layers: `backgroundColor` to `fill`, `borderRadius` to `radius`, and the logical
|
|
75
|
+
axis renames such as `paddingBlock: '1x 2x'` to `blockPadding: '1x 2x'`, which
|
|
76
|
+
emit the same `padding-block: 1x 2x`.
|
|
77
|
+
|
|
78
|
+
The others stay report-only. An edge longhand needs a `start`, `end` or side
|
|
79
|
+
modifier in the value, and the axis handler resets the edge you do not name. A
|
|
80
|
+
`border-*` rename adds the style and color defaults that `border-block` never
|
|
81
|
+
had (`border-block: 1bw` renders nothing).
|
|
82
|
+
|
|
83
|
+
## Extension layers
|
|
84
|
+
|
|
85
|
+
In an extension layer (`tasty(Base, {...})`, `tastyStatic(Base, {...})` or a
|
|
86
|
+
`styles` prop), the report says the shorthand would replace the base's whole
|
|
87
|
+
property, and has no fix. Styles merge per key there, so renaming the key
|
|
88
|
+
replaces the base component's whole property instead of patching the part the
|
|
89
|
+
layer writes: `paddingTop: '2x'` to `padding: '2x top'` sets the base's other
|
|
90
|
+
three edges to `0`, and `backgroundColor` to `fill` replaces a `fill` state map
|
|
91
|
+
the layer never mentioned.
|
|
92
|
+
|
|
93
|
+
The recommended shape is a token in the base component, set from the layer:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// base component: the seam
|
|
97
|
+
const Card = tasty({ styles: { padding: '$v-padding $h-padding' } });
|
|
98
|
+
|
|
99
|
+
// extension layer: no raw CSS longhand needed
|
|
100
|
+
const TallCard = tasty(Card, { styles: { '$v-padding': '4x' } });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Ownership.** That advice helps only if the base component is yours to edit,
|
|
104
|
+
so the report is skipped when the base is imported from a package. Over a UI kit
|
|
105
|
+
component, the only options left are the longhand already written or a rewrite
|
|
106
|
+
that would clobber the base. Components declared in the same file count as
|
|
107
|
+
owned, as do relative, absolute, `~`, `#` and `@/` imports. List a design system
|
|
108
|
+
published from the same monorepo in `ownedSources` in `tasty.config.ts`. A base
|
|
109
|
+
the plugin cannot resolve to an import is treated as owned.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
import { Button } from '@uikit/button';
|
|
113
|
+
import { Card } from './card';
|
|
114
|
+
|
|
115
|
+
// ✅ Not reported: '@uikit/button' is not this project's to change
|
|
116
|
+
tasty(Button, { styles: { paddingTop: '2x' } });
|
|
117
|
+
|
|
118
|
+
// ❌ Reported without a fix: the seam belongs in ./card
|
|
119
|
+
tasty(Card, { styles: { paddingTop: '2x' } });
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Changing one part with a token
|
|
123
|
+
|
|
124
|
+
A shorthand can also reset values you did not intend to change. For example,
|
|
125
|
+
replacing `minHeight: '20x'` with `height: 'min 20x'` resets `height` to `auto` and
|
|
126
|
+
`max-height` to `initial`. In a component extension, it replaces the base's whole
|
|
127
|
+
`height` definition, including its state map. The rule reports these changes
|
|
128
|
+
without an autofix.
|
|
129
|
+
|
|
130
|
+
For an independent override, expose the relevant part as a token in the original
|
|
131
|
+
component definition. Keep the other values and any states already defined there:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
const Card = tasty({
|
|
135
|
+
styles: {
|
|
136
|
+
'$min-height': '100vh',
|
|
137
|
+
height: '$min-height auto 100vw',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Changes only min-height; height and max-height keep their original values.
|
|
142
|
+
<Card tokens={{ '$min-height': '20x' }} />;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The default token preserves the original minimum height until it is overridden.
|
|
146
|
+
Use your existing height and maximum height in place of `auto` and `100vw`.
|
|
147
|
+
The diagnostic's `initial` placeholders describe an otherwise unconstrained
|
|
148
|
+
dimension; keep any constraints from the original definition when adding a token.
|
|
149
|
+
|
|
150
|
+
Two dimension values mean **minimum and maximum**, with the main size set to
|
|
151
|
+
`auto`: `height: '$min-height 100vw'` sets `min-height` and `max-height`. Three
|
|
152
|
+
values mean **minimum, size, maximum**: `height: '$min-height 100vh 100vw'` also
|
|
153
|
+
sets an explicit height. The same pattern works for `width`, `blockSize`, and
|
|
154
|
+
`inlineSize`, and for independent maximum-size overrides.
|
|
155
|
+
|
|
156
|
+
The diagnostics also recommend tokens for other partial shorthand overrides,
|
|
157
|
+
such as one padding edge or one border part. Add the token in the original
|
|
158
|
+
definition while preserving its other values, then override it through `tokens`.
|
|
159
|
+
Extension diagnostics point to the base component, where the token must be
|
|
160
|
+
exposed. Reports over imported components you cannot edit remain suppressed;
|
|
161
|
+
`ownedSources` can opt a design system from your own project back in.
|
|
162
|
+
|
|
163
|
+
For sub-elements, declare the token default on the component root and reference
|
|
164
|
+
it from the sub-element's shorthand. A default declared on the sub-element itself
|
|
165
|
+
would take precedence over a token inherited from the root.
|
|
166
|
+
|
|
167
|
+
`useStyles`, `useGlobalStyles`, and `tastyStatic` produce classes or CSS rather than
|
|
168
|
+
a component accepting `tokens`. Their diagnostics instead recommend setting the
|
|
169
|
+
corresponding CSS custom property on the target element, for example
|
|
170
|
+
`element.style.setProperty('--min-height', '160px')`. These values must use CSS
|
|
171
|
+
units; the component `tokens` prop processes Tasty units such as `20x` for you.
|
|
172
|
+
|
|
173
|
+
Preset names and modifiers are static. For independent typography overrides,
|
|
174
|
+
use the CSS value tokens referenced by the original named preset, such as
|
|
175
|
+
`$body-font-size` for `preset: 'body'`. A token cannot replace the preset name
|
|
176
|
+
or its modifiers. Scrollbar width and gutter modifiers are also static;
|
|
177
|
+
independent token overrides belong in the `scrollbarWidth` or `scrollbarGutter`
|
|
178
|
+
longhand instead of the `scrollbar` DSL.
|
|
179
|
+
|
|
180
|
+
## Motion longhands
|
|
181
|
+
|
|
182
|
+
Motion longhands are left alone on purpose. Tasty has an enhanced `transition`
|
|
183
|
+
shorthand and accepts the native CSS `animation` shorthand. Replacing
|
|
184
|
+
`transitionDuration`, `animationDuration` or another motion longhand with a
|
|
185
|
+
shorthand could reset other values in a state or extension, and `animation` can
|
|
186
|
+
also reset `animationTimeline` and `animationRange`. Use a shorthand to define a
|
|
187
|
+
complete effect and longhands to change one part.
|
|
188
|
+
[`tasty/no-raw-transition-duration`](no-raw-transition-duration.md) and
|
|
189
|
+
[`tasty/no-raw-motion-duration`](no-raw-motion-duration.md) check raw timing in
|
|
190
|
+
either form.
|
|
191
|
+
|
|
192
|
+
## Options
|
|
193
|
+
|
|
194
|
+
None.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# tasty/prefer-state-negation
|
|
2
|
+
|
|
3
|
+
Reports a top-level `:not(...)` in a state key. Tasty's `!` prefix says the same thing.
|
|
4
|
+
|
|
5
|
+
**Presets:** warning in recommended and strict. **Fix:** autofix.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
`!` is Tasty's negation operator. It works on every kind of state: modifiers, attributes, pseudo-classes and aliases. One spelling for negation keeps state maps easy to read.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ❌ Use the '!' prefix instead of top-level :not() in state keys.
|
|
15
|
+
fill: { '': '#white', ':not(:hover)': '#blue' }
|
|
16
|
+
|
|
17
|
+
// ✅ After the autofix
|
|
18
|
+
fill: { '': '#white', '!:hover': '#blue' }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Autofix
|
|
22
|
+
|
|
23
|
+
| Before | After |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `':not(:hover)'` | `'!:hover'` |
|
|
26
|
+
| `':not(:nth-child(2n + 1))'` | `'!:nth-child(2n + 1)'` |
|
|
27
|
+
| `':not(.active)'`, `':not([data-kind="x"])'` | `'!:is(.active)'`, `'!:is([data-kind="x"])'` |
|
|
28
|
+
| `':not(:hover, :focus)'` | `'!:is(:hover, :focus)'` |
|
|
29
|
+
| `'hovered & (:not(:hover) \| :not(:focus))'` | `'hovered & (!:hover \| !:focus)'` |
|
|
30
|
+
|
|
31
|
+
Anything but a single known pseudo-class is wrapped in `:is(...)`, so it keeps its meaning.
|
|
32
|
+
|
|
33
|
+
The rule reports without a fix when the key has JavaScript escapes, when the new key already exists in the map, or when the map has a spread or a computed key that could hold it.
|
|
34
|
+
|
|
35
|
+
## Not reported
|
|
36
|
+
|
|
37
|
+
- `:not()` nested inside another selector: `':has(~ :not([data-drop-indicator]))'`, `':is(:not(:hover), :focus)'`.
|
|
38
|
+
- `:not()` inside `@parent(...)`, `@root(...)` or `@own(...)`.
|
|
39
|
+
- Malformed keys, which [`tasty/valid-state-key`](valid-state-key.md) reports.
|
|
40
|
+
- Selector arguments, such as `tastyStatic(':not(:hover)', …)`.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
None.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# tasty/require-default-state
|
|
2
|
+
|
|
3
|
+
Reports a state map without a `''` default and without a `_` fallback floor.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Without `''` or `_`, the property has no value in the normal state: it applies only while one of the listed states holds. That is rarely intended in a component's own styles.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ❌ State mapping for 'fill' has no default ('') or fallback floor ('_') value.
|
|
15
|
+
tasty({
|
|
16
|
+
styles: {
|
|
17
|
+
fill: { hovered: '#blue' },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// ✅ A '' default
|
|
22
|
+
tasty({
|
|
23
|
+
styles: {
|
|
24
|
+
fill: { '': '#white', hovered: '#blue' },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// ✅ A '_' fallback floor gives a value in the normal state too
|
|
29
|
+
tasty({
|
|
30
|
+
styles: {
|
|
31
|
+
inset: { _: 'auto', '@(scroll-state(stuck: top))': '0 auto auto auto' },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// ✅ Extending a component: no default keeps the base's
|
|
36
|
+
tasty(Button, {
|
|
37
|
+
styles: {
|
|
38
|
+
fill: { loading: '#yellow' },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Not reported
|
|
44
|
+
|
|
45
|
+
- Extension layers: `tasty(Base, { styles })`, a `styles` prop, a later `mergeStyles` argument and other partial overrides. There, a map without `''` extends the base's map.
|
|
46
|
+
- A state map that spreads another (`{ ...BASE_STYLES.fill, hovered: '#blue' }`): the spread may bring the default.
|
|
47
|
+
|
|
48
|
+
## Options
|
|
49
|
+
|
|
50
|
+
None.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# tasty/static-no-dynamic-values
|
|
2
|
+
|
|
3
|
+
Reports a value in a `tastyStatic()` call that is not a literal: variables, templates with expressions, calls, spreads and other expressions.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
The Babel plugin turns `tastyStatic()` into CSS at build time. It cannot evaluate JavaScript, so a dynamic value fails the build. This is a stricter, build-time version of [`tasty/no-runtime-styles-mutation`](no-runtime-styles-mutation.md).
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ❌ tastyStatic() values must be static.
|
|
15
|
+
const size = '2x';
|
|
16
|
+
tastyStatic({ padding: size });
|
|
17
|
+
|
|
18
|
+
// ❌ tastyStatic() values must be static.
|
|
19
|
+
tastyStatic({ padding: `${base}x` });
|
|
20
|
+
|
|
21
|
+
// ✅ Valid
|
|
22
|
+
tastyStatic({ padding: '2x' });
|
|
23
|
+
tastyStatic({ padding: { '': '2x', '@mobile': '1x' } });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Checks
|
|
27
|
+
|
|
28
|
+
Every value must be a string, number, boolean or `null`, or an object or array of those (state maps, sub-elements). Variables, templates with expressions, calls, spreads and other expressions are reported.
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
|
|
32
|
+
None.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# tasty/static-valid-selector
|
|
2
|
+
|
|
3
|
+
Reports an invalid selector in `tastyStatic(selector, styles)`: an empty string, unbalanced brackets, or a selector that is not a string literal.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
The Babel plugin reads the selector at build time. It cannot evaluate a variable, and a broken selector produces CSS the browser drops.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ✅ Valid
|
|
15
|
+
tastyStatic('body', { fill: '#dark' });
|
|
16
|
+
tastyStatic('.heading', { preset: 'h1' });
|
|
17
|
+
tastyStatic(':root', { $gap: '8px' });
|
|
18
|
+
|
|
19
|
+
// ❌ Invalid CSS selector: Selector cannot be empty
|
|
20
|
+
tastyStatic('', { fill: '#dark' });
|
|
21
|
+
|
|
22
|
+
// ❌ Invalid CSS selector: Unbalanced brackets in selector
|
|
23
|
+
tastyStatic('.card[', { fill: '#dark' });
|
|
24
|
+
|
|
25
|
+
// ❌ tastyStatic() selector must be a string literal.
|
|
26
|
+
tastyStatic(getSelector(), { fill: '#dark' });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Checks
|
|
30
|
+
|
|
31
|
+
1. The selector is a string literal.
|
|
32
|
+
2. Basic syntax: not empty, balanced brackets, valid characters. Tag names, `.class`, `#id`, `:pseudo`, `[attr]` and combinators are always valid.
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
None.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# tasty/valid-boolean-property
|
|
2
|
+
|
|
3
|
+
Reports `true` on a property that has no default to apply, such as `textAlign: true` or `fill: true`. `false` is valid everywhere.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
On a property with a design-system default, `true` means "use the default": `padding: true` is `1x`, `border: true` is `1bw`. On any other property, Tasty passes `true` through, and the browser drops the declaration.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ✅ Valid
|
|
15
|
+
border: true
|
|
16
|
+
radius: true
|
|
17
|
+
padding: true
|
|
18
|
+
padding: false // false removes the property, on any property
|
|
19
|
+
|
|
20
|
+
// ❌ Property 'textAlign' does not accept boolean true.
|
|
21
|
+
textAlign: true
|
|
22
|
+
|
|
23
|
+
// ❌ The same check applies inside a state map
|
|
24
|
+
fill: { '': '#surface', hovered: true }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Properties that take `true`
|
|
28
|
+
|
|
29
|
+
`border`, `radius`, `padding`, `margin`, `inset`, `gap`, `color`, `fade`, `outline`, `shadow`, `width`, `height`, `hide`, `preset`, `font`, `scrollbar`, `scrollMargin`, `scrollPadding`, and the enhanced logical styles (`blockSize`, `inlinePadding`, `blockBorder`, …). Each one carries its category's default: `1x` for spacing and scroll edges, `0` for inset, `1bw` for border, a reset for the sizes.
|
|
30
|
+
|
|
31
|
+
Left out on purpose:
|
|
32
|
+
|
|
33
|
+
- `fill`, though the Tasty docs list it. As of Tasty 3.8 the handler passes the boolean through, so `fill: true` emits `background-color: true`: the browser drops it and the element has no background. A canary in `constants.round-trip.test.ts` fails when Tasty changes this.
|
|
34
|
+
- `minWidth`, `maxWidth`, `minHeight`, `maxHeight` and the logical `minBlockSize` and the like. Tasty accepts `true` there but resets the whole axis rather than the one constraint named. Write `width: true` or `blockSize: true` to ask for that.
|
|
35
|
+
- Native logical CSS such as `paddingInlineStart`, which has no category default.
|
|
36
|
+
|
|
37
|
+
## Options
|
|
38
|
+
|
|
39
|
+
None.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# tasty/valid-color-token
|
|
2
|
+
|
|
3
|
+
Reports a color token with invalid syntax, such as `#purple.-5`, and a color token that your config does not list.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Tasty turns `#name` into `var(--name-color)`. A token that no one defines resolves to nothing, and the browser drops the declaration without an error.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ❌ Invalid color token '#purple.-5': Opacity cannot be negative.
|
|
15
|
+
fill: '#purple.-5'
|
|
16
|
+
|
|
17
|
+
// ❌ Unknown color token '#pruple' (with tokens listed in tasty.config)
|
|
18
|
+
fill: '#pruple'
|
|
19
|
+
|
|
20
|
+
// ✅ Valid
|
|
21
|
+
fill: '#purple'
|
|
22
|
+
fill: '#purple.5'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Syntax
|
|
26
|
+
|
|
27
|
+
Always checked.
|
|
28
|
+
|
|
29
|
+
| Valid | Meaning |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `#purple` | Full opacity |
|
|
32
|
+
| `#purple.5` | 50% opacity |
|
|
33
|
+
| `#purple.05` | 5% opacity |
|
|
34
|
+
| `#purple.100` | 10% opacity, the same as `#purple.1` |
|
|
35
|
+
| `#purple.999` | 99.9% opacity |
|
|
36
|
+
| `#current`, `#current.5` | `currentcolor`, at full or 50% opacity |
|
|
37
|
+
| `##purple` | The raw `--purple-color` reference, for transitions and `@property` |
|
|
38
|
+
| `(#primary, #secondary)` | A fallback |
|
|
39
|
+
| `#name.$prop` | Opacity from a custom property |
|
|
40
|
+
|
|
41
|
+
The digits after the dot are a decimal fraction, so any digit string is in range.
|
|
42
|
+
|
|
43
|
+
| Invalid | Why |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `#` | Empty token name |
|
|
46
|
+
| `#purple.` | Trailing dot with no opacity value |
|
|
47
|
+
| `#purple.5.5` | The opacity is not a digit string |
|
|
48
|
+
| `#purple.-5` | Negative opacity |
|
|
49
|
+
|
|
50
|
+
Raw hex colors such as `#ff0000` are not tokens. [`tasty/no-raw-color-values`](no-raw-color-values.md) reports them.
|
|
51
|
+
|
|
52
|
+
## Existence
|
|
53
|
+
|
|
54
|
+
Checked only when `tokens` in `tasty.config.*` lists names. A token is known when it is:
|
|
55
|
+
|
|
56
|
+
- one of Tasty's built-in color tokens: `#current`, `#clear`, `#white`, `#black`, `#border`, `#outline`, with or without an opacity;
|
|
57
|
+
- listed in `tokens`;
|
|
58
|
+
- defined as a `#name` key in any style object of the same file, such as a sibling `'#accent': '#purple'` or a key in `tastyStatic(':root', { … })`.
|
|
59
|
+
|
|
60
|
+
`##name` is checked the same way as `#name`. `tokens: false` turns the existence check off, also when an extended config lists tokens.
|
|
61
|
+
|
|
62
|
+
## Options
|
|
63
|
+
|
|
64
|
+
None.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# tasty/valid-custom-property
|
|
2
|
+
|
|
3
|
+
Reports a `$name` custom property reference with invalid syntax, and one that nothing defines.
|
|
4
|
+
|
|
5
|
+
**Presets:** warning in strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Tasty turns `$name` into `var(--name)`. A misspelled name resolves to nothing, and the declaration is dropped without an error.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ✅ Valid
|
|
15
|
+
padding: '$spacing'
|
|
16
|
+
transition: '$$gradient-angle 0.3s'
|
|
17
|
+
|
|
18
|
+
// ❌ Unknown custom property '$spacign'
|
|
19
|
+
padding: '$spacign'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Checks
|
|
23
|
+
|
|
24
|
+
1. **Syntax.** A name is `$` followed by letters, digits or hyphens, such as `$accent-color`. The fallback form `($name, fallback)` is checked too.
|
|
25
|
+
2. **Existence.** Checked only when `tokens` in `tasty.config.*` lists names. A property is known when it is:
|
|
26
|
+
- one of Tasty's built-in custom properties: `$gap`, `$radius`, `$border-width`, `$outline-width`, `$transition`, `$sharp-radius`, `$bold-font-weight`, `$font-sans-fallback`, `$font-mono-fallback`;
|
|
27
|
+
- listed in `tokens`;
|
|
28
|
+
- a `$name` key in any style object of the same file, including `tastyStatic(':root', { … })`;
|
|
29
|
+
- a `$name` key of a literal `tokens={{ … }}` JSX prop, or of the `tokens` option of `tasty()` or a configured options factory, in the same file;
|
|
30
|
+
- a `'--name'` key of a literal inline `style={{ … }}` object in the same file, which declares `$name`.
|
|
31
|
+
|
|
32
|
+
`$$name` (the raw `--name`, for transitions) is checked the same way as `$name`. `tokens: false` turns the existence check off.
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
None.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# tasty/valid-custom-unit
|
|
2
|
+
|
|
3
|
+
Reports a number with a unit that is neither a Tasty unit, a CSS unit, nor a unit from your config, such as `2q`.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Tasty expands its own units (`2x` becomes `calc(2 * var(--gap))`) and passes CSS units through. A unit it does not know reaches the browser as is, and the browser drops the declaration.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ✅ Valid
|
|
15
|
+
padding: '2x'
|
|
16
|
+
radius: '1r'
|
|
17
|
+
border: '1bw'
|
|
18
|
+
|
|
19
|
+
// ❌ Unknown unit 'q' in '2q'
|
|
20
|
+
padding: '2q'
|
|
21
|
+
|
|
22
|
+
// ✅ Valid when tasty.config lists 'cols' in units
|
|
23
|
+
width: '3cols'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Known units
|
|
27
|
+
|
|
28
|
+
- **Tasty's units:** `x`, `r`, `cr`, `bw`, `ow`, `lh`, `sf`.
|
|
29
|
+
- **CSS units:** `px`, `em`, `rem`, `%`, `vw`, `vh`, `vmin`, `vmax`, `ch`, `ex`, `cm`, `mm`, `in`, `pt`, `pc`, `fr`, `deg`, `rad`, `turn`, `grad`, `s`, `ms`, `dpi`, `dpcm`, `dppx`, the small, large and dynamic viewport units (`svw`, `lvh`, `dvh`, …), the container units (`cqw`, `cqh`, `cqi`, `cqb`, `cqmin`, `cqmax`), `cap`, `ic`, `rlh`, `vi` and `vb`.
|
|
30
|
+
- **Your units:** the `units` list in `tasty.config.*`. `units: false` turns the check off.
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
None.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# tasty/valid-default-state-order
|
|
2
|
+
|
|
3
|
+
Reports a `''` default that is not the first key of a state map, a `_` fallback floor that is not the first key, and a `''` that is redundant next to `_`.
|
|
4
|
+
|
|
5
|
+
**Presets:** warning in recommended and strict. **Fix:** autofix.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
A state map resolves in the order it is written: the last key whose condition holds wins. The `''` default always holds, so after other states it overrides them. The `_` floor is the lowest layer of the cascade, so it goes first, above `''`.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ❌ Default state ('') in 'fill' must be the first key in the state map.
|
|
15
|
+
fill: { hovered: '#blue', '': '#white' }
|
|
16
|
+
|
|
17
|
+
// ✅ After the autofix
|
|
18
|
+
fill: { '': '#white', hovered: '#blue' }
|
|
19
|
+
|
|
20
|
+
// ❌ Fallback floor ('_') in 'fill' must be the first key in the state map, above the '' default.
|
|
21
|
+
fill: { '': '#white', _: '#gray', hovered: '#blue' }
|
|
22
|
+
|
|
23
|
+
// ✅ After the autofix
|
|
24
|
+
fill: { _: '#gray', '': '#white', hovered: '#blue' }
|
|
25
|
+
|
|
26
|
+
// ❌ Default state ('') in 'fill' is redundant when '_' is the only other state.
|
|
27
|
+
fill: { _: '#gray', '': '#white' }
|
|
28
|
+
|
|
29
|
+
// ✅ After the autofix
|
|
30
|
+
fill: { _: '#gray' }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Checks
|
|
34
|
+
|
|
35
|
+
1. With `''` and `_` as the only keys, `''` is removed: `_` already gives the always-on value.
|
|
36
|
+
2. A `_` that is not first moves to the top.
|
|
37
|
+
3. With `_` first, a `''` moves right after it.
|
|
38
|
+
4. Without `_`, a `''` that is not first moves to the top.
|
|
39
|
+
|
|
40
|
+
The fix applies one step at a time, so a map with both keys misplaced takes two `--fix` passes, which ESLint runs on its own.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
None.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# tasty/valid-directional-modifier
|
|
2
|
+
|
|
3
|
+
Reports a direction a property does not take, such as `start` on `padding` or `top` on `blockPadding`, and more than one value in a group that names directions.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Physical properties speak `top`, `right`, `bottom` and `left`. Logical properties speak `start` and `end`. Each handler drops a direction from the other vocabulary without a word. In a group that names directions, Tasty keeps values and modifiers apart, so `'2x 4x top right'` does not pair `2x` with `top`.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ✅ Valid
|
|
15
|
+
border: '1bw #red top'
|
|
16
|
+
padding: '2x left right'
|
|
17
|
+
inlinePadding: '1x start, 2x end'
|
|
18
|
+
blockBorder: '1bw solid #accent start'
|
|
19
|
+
|
|
20
|
+
// ❌ Modifier 'top' is not valid for 'blockPadding'. Accepted: start, end.
|
|
21
|
+
blockPadding: '1x top'
|
|
22
|
+
|
|
23
|
+
// ❌ Modifier 'start' is not valid for 'padding'. Accepted: top, right, bottom, left.
|
|
24
|
+
padding: '1x start'
|
|
25
|
+
|
|
26
|
+
// ❌ A group that names directions takes a single value, but '2x 4x top right' has 2.
|
|
27
|
+
padding: '2x 4x top right'
|
|
28
|
+
|
|
29
|
+
// ✅ One group per side
|
|
30
|
+
padding: '2x top, 4x right'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Directions per property
|
|
34
|
+
|
|
35
|
+
| Property | Directions |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `border`, `padding`, `margin`, `fade`, `scrollMargin`, `scrollPadding` | `top`, `right`, `bottom`, `left` |
|
|
38
|
+
| `radius` | `top`, `right`, `bottom`, `left`, `top-left`, `top-right`, `bottom-left`, `bottom-right` |
|
|
39
|
+
| `inset` | `top`, `right`, `bottom`, `left`, `dock` |
|
|
40
|
+
| Every enhanced logical style (`blockPadding`, `inlineBorder`, …) | `start`, `end` |
|
|
41
|
+
|
|
42
|
+
A group that names directions takes one value, applied to every direction it names. With `dock`, it takes up to two: the second insets the sides the dock spans. Colors do not count, since a `fade` group carries a width and up to two mask colors.
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
None.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# tasty/valid-preset
|
|
2
|
+
|
|
3
|
+
Reports a `preset` name your config does not list, and a preset modifier Tasty does not know.
|
|
4
|
+
|
|
5
|
+
**Presets:** error in recommended and strict. **Fix:** none.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
An unknown preset name renders no typography at all. The component falls back to inherited font styles, and nothing reports it at runtime.
|
|
10
|
+
|
|
11
|
+
## Examples
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
// ✅ Valid, when tasty.config lists 'h1' and 't2' in presets
|
|
15
|
+
preset: 'h1'
|
|
16
|
+
preset: 'h1 strong'
|
|
17
|
+
preset: 't2 italic'
|
|
18
|
+
|
|
19
|
+
// ❌ Unknown preset 'heading1'
|
|
20
|
+
preset: 'heading1'
|
|
21
|
+
|
|
22
|
+
// ❌ Unknown preset modifier 'heavy'
|
|
23
|
+
preset: 'h1 / heavy'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Checks
|
|
27
|
+
|
|
28
|
+
1. **Name.** When `presets` in `tasty.config.*` lists names, the first word of the value must be one of them.
|
|
29
|
+
2. **Modifiers.** Words after a `/` must be `strong`, `bold`, `italic`, `icon`, `tight` or `normal` (`line-height: normal`).
|
|
30
|
+
|
|
31
|
+
A value made only of modifiers, such as `preset: 'normal'` or `preset: 'bold italic'`, is the modifier-only shorthand: its words are not checked against `presets`. CSS-wide keywords (`inherit`, `initial`, …) and `preset: true` are always valid. Without `presets` in the config, only modifiers are checked.
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
None.
|