@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.
Files changed (54) hide show
  1. package/README.md +72 -45
  2. package/docs/rules/consistent-token-usage.md +74 -0
  3. package/docs/rules/known-property.md +55 -0
  4. package/docs/rules/no-classname-prop.md +62 -0
  5. package/docs/rules/no-ignored-pseudo-element-state.md +97 -0
  6. package/docs/rules/no-important.md +25 -0
  7. package/docs/rules/no-nested-selector.md +39 -0
  8. package/docs/rules/no-nested-state-map.md +33 -0
  9. package/docs/rules/no-own-at-root.md +57 -0
  10. package/docs/rules/no-protected-restyle.md +194 -0
  11. package/docs/rules/no-raw-color-values.md +51 -0
  12. package/docs/rules/no-raw-motion-duration.md +50 -0
  13. package/docs/rules/no-raw-transition-duration.md +56 -0
  14. package/docs/rules/no-runtime-styles-mutation.md +58 -0
  15. package/docs/rules/no-state-in-selector.md +142 -0
  16. package/docs/rules/no-style-prop.md +129 -0
  17. package/docs/rules/no-style-spread.md +55 -0
  18. package/docs/rules/no-styles-prop.md +59 -0
  19. package/docs/rules/no-unknown-state-alias.md +45 -0
  20. package/docs/rules/prefer-auto-calc.md +30 -0
  21. package/docs/rules/prefer-custom-property-syntax.md +56 -0
  22. package/docs/rules/prefer-directional-shorthand.md +46 -0
  23. package/docs/rules/prefer-element-selector.md +77 -0
  24. package/docs/rules/prefer-fill-mix.md +119 -0
  25. package/docs/rules/prefer-hide.md +31 -0
  26. package/docs/rules/prefer-longhand-property.md +25 -0
  27. package/docs/rules/prefer-shorthand-property.md +194 -0
  28. package/docs/rules/prefer-state-negation.md +44 -0
  29. package/docs/rules/require-default-state.md +50 -0
  30. package/docs/rules/static-no-dynamic-values.md +32 -0
  31. package/docs/rules/static-valid-selector.md +36 -0
  32. package/docs/rules/valid-boolean-property.md +39 -0
  33. package/docs/rules/valid-color-token.md +64 -0
  34. package/docs/rules/valid-custom-property.md +36 -0
  35. package/docs/rules/valid-custom-unit.md +34 -0
  36. package/docs/rules/valid-default-state-order.md +44 -0
  37. package/docs/rules/valid-directional-modifier.md +46 -0
  38. package/docs/rules/valid-preset.md +35 -0
  39. package/docs/rules/valid-radius-shape.md +31 -0
  40. package/docs/rules/valid-recipe.md +35 -0
  41. package/docs/rules/valid-state-definition.md +46 -0
  42. package/docs/rules/valid-state-key.md +62 -0
  43. package/docs/rules/valid-styles-structure.md +48 -0
  44. package/docs/rules/valid-sub-element.md +100 -0
  45. package/docs/rules/valid-transition.md +46 -0
  46. package/docs/rules/valid-value.md +65 -0
  47. package/harness/dev.harnesspm/hooks.json +12 -0
  48. package/harness/dev.harnesspm/rules/usage.md +11 -0
  49. package/harness/dev.harnesspm/scripts/check-edit.mjs +163 -0
  50. package/harness/dev.harnesspm/scripts/check.mjs +254 -0
  51. package/harness/dev.harnesspm/scripts/lint.mjs +318 -0
  52. package/harness/dev.harnesspm/scripts/project-config.mjs +316 -0
  53. package/harness/plugin.json +23 -0
  54. 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.