@tenphi/eslint-plugin-tasty 1.0.5 → 1.2.0

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 (38) hide show
  1. package/README.md +177 -4
  2. package/dist/config.js +8 -2
  3. package/dist/config.js.map +1 -1
  4. package/dist/configs.js +1 -0
  5. package/dist/configs.js.map +1 -1
  6. package/dist/constants.js +294 -38
  7. package/dist/constants.js.map +1 -1
  8. package/dist/context.js +75 -25
  9. package/dist/context.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +4 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/property-expectations.js +71 -2
  14. package/dist/property-expectations.js.map +1 -1
  15. package/dist/rules/consistent-token-usage.js +12 -1
  16. package/dist/rules/consistent-token-usage.js.map +1 -1
  17. package/dist/rules/motion-duration-utils.js +30 -0
  18. package/dist/rules/motion-duration-utils.js.map +1 -0
  19. package/dist/rules/no-own-at-root.js +1 -9
  20. package/dist/rules/no-own-at-root.js.map +1 -1
  21. package/dist/rules/no-raw-motion-duration.js +105 -0
  22. package/dist/rules/no-raw-motion-duration.js.map +1 -0
  23. package/dist/rules/no-raw-transition-duration.js +156 -0
  24. package/dist/rules/no-raw-transition-duration.js.map +1 -0
  25. package/dist/rules/no-unknown-state-alias.js +2 -2
  26. package/dist/rules/no-unknown-state-alias.js.map +1 -1
  27. package/dist/rules/valid-boolean-property.js +19 -8
  28. package/dist/rules/valid-boolean-property.js.map +1 -1
  29. package/dist/rules/valid-directional-modifier.js +16 -1
  30. package/dist/rules/valid-directional-modifier.js.map +1 -1
  31. package/dist/rules/valid-state-key.js +4 -13
  32. package/dist/rules/valid-state-key.js.map +1 -1
  33. package/dist/rules/valid-sub-element.js +5 -4
  34. package/dist/rules/valid-sub-element.js.map +1 -1
  35. package/dist/types.d.ts +14 -1
  36. package/dist/utils.js +8 -21
  37. package/dist/utils.js.map +1 -1
  38. package/package.json +5 -3
package/README.md CHANGED
@@ -4,7 +4,7 @@ ESLint plugin for validating `tasty()`, `tastyStatic()`, `useStyles()`, `useGlob
4
4
 
5
5
  Catch typos, invalid syntax, and enforce best practices in your tasty style objects at lint time.
6
6
 
7
- **Targets `@tenphi/tasty` v3.** v1 of this plugin validates the v3 style DSL: kebab-case at-rule keys (`@property`, `@font-face`, `@counter-style`, `@function`), `$$name(...)` CSS-function calls, and one value per directional group. The v2 at-rule spellings are reported with an auto-fix, so `eslint --fix` handles most of the upgrade. For tasty v2, pin `@tenphi/eslint-plugin-tasty@^0.11`.
7
+ **Targets `@tenphi/tasty` v3.8+.** v1 of this plugin validates the v3 style DSL: kebab-case at-rule keys (`@property`, `@font-face`, `@counter-style`, `@function`), `$$name(...)` CSS-function calls, and one value per directional group. The v2 at-rule spellings are reported with an auto-fix, so `eslint --fix` handles most of the upgrade. For tasty v2, pin `@tenphi/eslint-plugin-tasty@^0.11`.
8
8
 
9
9
  ## Installation
10
10
 
@@ -54,6 +54,26 @@ export default [
54
54
  ];
55
55
  ```
56
56
 
57
+ ### oxlint
58
+
59
+ Use oxlint's [JavaScript plugins](https://oxc.rs/docs/guide/usage/linter/js-plugins)
60
+ to load the same plugin and rule maps. JavaScript plugin support is experimental;
61
+ the integration tests run against oxlint 1.83.0.
62
+
63
+ ```ts
64
+ // oxlint.config.ts
65
+ import { defineConfig } from 'oxlint';
66
+ import { recommended } from '@tenphi/eslint-plugin-tasty';
67
+
68
+ export default defineConfig({
69
+ jsPlugins: [{ name: 'tasty', specifier: '@tenphi/eslint-plugin-tasty' }],
70
+ rules: recommended,
71
+ });
72
+ ```
73
+
74
+ Run `oxlint --config oxlint.config.ts src`. Both linters read `tasty.config.*`
75
+ relative to the file being linted, including custom function signatures below.
76
+
57
77
  ## Project Configuration
58
78
 
59
79
  Create a `tasty.config.ts` (or `.js`, `.json`) at your project root to configure validation:
@@ -89,6 +109,60 @@ list a scope here only for a design system you publish from your own monorepo
89
109
  > `functions` was called `funcs` before v1. The old spelling is still read as a
90
110
  > deprecated alias.
91
111
 
112
+ ### Custom style functions
113
+
114
+ Design systems can wrap Tasty with their own component factories and style
115
+ helpers. Register their named exports in `styleFunctions` and list their modules
116
+ in `importSources`:
117
+
118
+ ```ts
119
+ // tasty.config.ts
120
+ import type { TastyValidationConfig } from '@tenphi/eslint-plugin-tasty';
121
+
122
+ export default {
123
+ importSources: ['@my-org/styling'],
124
+ styleFunctions: {
125
+ defineComponent: { argument: 1, kind: 'options' },
126
+ resolveComponentStyles: { argument: 1, kind: 'styles' },
127
+ mergeStyles: { argument: 'all', kind: 'styles', partial: true },
128
+ },
129
+ } satisfies TastyValidationConfig;
130
+ ```
131
+
132
+ ```ts
133
+ import { defineComponent as component, resolveComponentStyles, mergeStyles } from '@my-org/styling';
134
+
135
+ component('Card', {
136
+ styles: { padding: '1x', Label: { color: '#text' } },
137
+ variants: { compact: { padding: '0.5x' } },
138
+ });
139
+ resolveComponentStyles('Card', { padding: '1x' });
140
+ mergeStyles(base, { fill: { hovered: '#active' } });
141
+ ```
142
+
143
+ | Option | Meaning |
144
+ | --- | --- |
145
+ | `argument` | Zero-based argument index, or `'all'` to inspect every argument. |
146
+ | `kind: 'styles'` | The argument itself is a Tasty style object. |
147
+ | `kind: 'options'` | The argument contains `styles` and/or `variants`; each variant is a style object. Other options are ignored. |
148
+ | `partial` | Defaults to `false`. Set to `true` for helpers that merge partial overrides into existing styles. Allows state maps without a default and disables shorthand fixes that could overwrite a base style. Variants remain independent definitions, like `tasty(Base, options)`. |
149
+
150
+ Signatures are keyed by the **exported name**, so named import aliases work.
151
+ Default imports, namespace calls, local functions, and imports from unlisted
152
+ modules are not matched through this configuration. A local binding that shadows
153
+ an imported helper is ignored. Only inline object literals are inspected, including
154
+ TypeScript `as` and `satisfies` wrappers; arguments are not evaluated or followed
155
+ through variable references. Existing `Styles` variable detection still applies.
156
+ For a fixed argument index, calls with a preceding spread argument are skipped
157
+ because its length could move the object to a different parameter.
158
+
159
+ Sub-elements inherit their containing style context. Built-in Tasty signatures
160
+ take precedence and cannot be redefined. Config inheritance merges `styleFunctions`
161
+ by name, with the nearest config replacing the entire signature for that name.
162
+ The `functions` setting above continues to describe functions inside style values;
163
+ `styleFunctions` describes JavaScript calls. `StyleFunctionConfig` is also exported
164
+ for typing shared signatures.
165
+
92
166
  ## Rules
93
167
 
94
168
  ### Recommended
@@ -99,13 +173,13 @@ list a scope here only for a design system you publish from your own monorepo
99
173
  | `tasty/valid-value` | error | Malformed style values (unbalanced parens, !important) |
100
174
  | `tasty/valid-color-token` | error | Invalid color token syntax or unknown tokens |
101
175
  | `tasty/valid-custom-unit` | error | Unknown custom units |
102
- | `tasty/valid-boolean-property` | error | `true` on properties that don't support it |
176
+ | `tasty/valid-boolean-property` | error | `true` on properties that don't support it, in a state map as well as on a direct value. Includes `fill`, which tasty documents as taking `true` but currently renders as `background-color: true` |
103
177
  | `tasty/valid-state-key` | error | Invalid state key syntax in style mappings (including misuse of the `_` fallback floor) |
104
178
  | `tasty/valid-styles-structure` | error | Invalid styles object structure, and the v2 at-rule key spellings (auto-fixable) |
105
179
  | `tasty/no-nested-state-map` | error | Nested state maps (not supported) |
106
180
  | `tasty/no-important` | error | `!important` usage (breaks tasty specificity) |
107
181
  | `tasty/valid-sub-element` | error | Sub-element values must be style objects |
108
- | `tasty/valid-directional-modifier` | error | Directional modifiers on wrong properties, and more than one value in a group that names directions |
182
+ | `tasty/valid-directional-modifier` | error | Directional modifiers on wrong properties, and more than one value in a group that names directions. Physical properties take `top`/`right`/`bottom`/`left`, logical ones `start`/`end`, and mixing the two is reported in either direction |
109
183
  | `tasty/valid-radius-shape` | error | Unknown radius shape keywords |
110
184
  | `tasty/valid-preset` | error | Unknown preset names |
111
185
  | `tasty/valid-recipe` | error | Unknown recipe names |
@@ -116,8 +190,9 @@ list a scope here only for a design system you publish from your own monorepo
116
190
  | `tasty/require-default-state` | error | Missing default (`''`) or fallback floor (`_`) key in state mappings (skipped for extending calls) |
117
191
  | `tasty/no-own-at-root` | warn | `@own()` used at root level where it is redundant |
118
192
  | `tasty/valid-default-state-order` | warn | Misplaced default (`''`) or redundant `''` when only `_` is present |
119
- | `tasty/prefer-shorthand-property` | warn | Use Tasty shorthand instead of native CSS properties (`backgroundColor` → `fill`, etc.). In an extension layer the rewrite is report-only and points at a token in the base component, and over a base you cannot edit it is skipped (see `ownedSources`) |
193
+ | `tasty/prefer-shorthand-property` | warn | Use Tasty shorthand instead of native CSS properties (`backgroundColor` → `fill`, `paddingBlock` → `blockPadding`, etc.). In an extension layer the rewrite is report-only and points at a token in the base component, and over a base you cannot edit it is skipped (see `ownedSources`) |
120
194
  | `tasty/no-raw-color-values` | warn | Raw hex/rgb/`okhsl`/`okhst`/`oklch`/named colors instead of `#color` tokens |
195
+ | `tasty/no-raw-transition-duration` | warn | Hardcoded `transition` duration (`fill 0.2s`) in Tasty styles or a local `tasty()` component's `transition` prop. Suggests a configured duration token or the implicit per-name timing; zero and delays are left alone |
121
196
  | `tasty/consistent-token-usage` | warn | Raw px values when custom units or tokens exist |
122
197
  | `tasty/prefer-auto-calc` | warn | `calc(...)` instead of Tasty auto-calc `(...)` |
123
198
  | `tasty/prefer-custom-property-syntax` | warn | `var(--prop)` / `$x-color` / `transparent` / `currentColor` instead of `$prop` / `#color` / `#clear` / `#current` |
@@ -135,6 +210,104 @@ list a scope here only for a design system you publish from your own monorepo
135
210
  | `tasty/no-styles-prop` | warn | Direct `styles` prop usage |
136
211
  | `tasty/no-runtime-styles-mutation` | warn | Dynamic values in style objects |
137
212
 
213
+ ### Optional motion duration rule
214
+
215
+ Enable `tasty/no-raw-motion-duration` as a warning if your design system uses
216
+ tokens for animation timing and explicit CSS durations:
217
+
218
+ ```js
219
+ import tasty from '@tenphi/eslint-plugin-tasty';
220
+
221
+ export default [
222
+ tasty.configs.recommended,
223
+ { rules: { 'tasty/no-raw-motion-duration': 'warn' } },
224
+ ];
225
+ ```
226
+
227
+ It checks `animation`, `animationDuration`, and `transitionDuration` in Tasty
228
+ style objects. It suggests configured `$…duration` tokens when
229
+ available. It never suggests removing a duration, since these properties do not
230
+ inherit Tasty's implicit transition timing. Zero, delays, and values based on
231
+ tokens are left alone. Lengths and widths remain covered by the separate
232
+ `consistent-token-usage` rule; valid custom units such as `x` and `bw` are fine.
233
+
234
+ ### Animation and transition shorthands
235
+
236
+ Use Tasty's semantic `transition` value when defining a complete transition,
237
+ and the native CSS `animation` shorthand when defining a complete animation.
238
+ Tasty already supports both: `transition` uses semantic names and duration
239
+ tokens, while `animation` accepts tokens and tracks local keyframes. No
240
+ additional animation style handler is needed. Keep longhands such as
241
+ `transitionDuration`, `animationDuration`, and `animationTimeline` when changing
242
+ one part of an existing effect in a state or component extension.
243
+
244
+ `prefer-shorthand-property` deliberately leaves these motion longhands alone.
245
+ Replacing one with a shorthand can reset the other parts of the effect, including
246
+ an animation's timeline and range. The duration rules above still encourage
247
+ tokens wherever a duration is written.
248
+
249
+ ## Logical styles
250
+
251
+ Tasty 3.8 added one enhanced handler per logical axis/category pair, which the plugin
252
+ validates alongside the physical properties:
253
+
254
+ | Category | Block axis | Inline axis |
255
+ |---|---|---|
256
+ | Size | `blockSize` | `inlineSize` |
257
+ | Padding | `blockPadding` | `inlinePadding` |
258
+ | Margin | `blockMargin` | `inlineMargin` |
259
+ | Inset | `blockInset` | `inlineInset` |
260
+ | Scroll margin | `blockScrollMargin` | `inlineScrollMargin` |
261
+ | Scroll padding | `blockScrollPadding` | `inlineScrollPadding` |
262
+ | Border | `blockBorder` | `inlineBorder` |
263
+
264
+ ```js
265
+ tasty({
266
+ styles: {
267
+ direction: 'rtl',
268
+ inlinePadding: '1x start, 2x end',
269
+ inlineBorder: '1bw solid #accent start',
270
+ blockInset: '0 end',
271
+ },
272
+ });
273
+ ```
274
+
275
+ These take `start`/`end` modifiers (never physical sides), accept `true` for their
276
+ category default, and follow the same one-value-per-directional-group rule as their
277
+ physical counterparts.
278
+
279
+ ### Migrating off the native CSS spellings
280
+
281
+ Tasty 3.8 stopped reading `paddingBlock`, `paddingInline`, `insetBlock`, `insetInline`,
282
+ `marginBlock`, `marginInline` and the scroll variants in its physical handlers. They stay
283
+ valid keys as ordinary CSS, so nothing breaks — but `prefer-shorthand-property` reports
284
+ each one with an **auto-fix** onto the enhanced style, so `eslint --fix` migrates a
285
+ codebase in one pass:
286
+
287
+ ```diff
288
+ - paddingBlock: '1x 2x'
289
+ + blockPadding: '1x 2x'
290
+ - insetInline: '0'
291
+ + inlineInset: '0'
292
+ ```
293
+
294
+ These are pure key renames: both keys emit the same declaration, which the test suite
295
+ asserts against the installed tasty rather than by hand.
296
+
297
+ Three groups are reported **without** a fix, because the rewrite is not equivalent — apply
298
+ them by hand:
299
+
300
+ | Native CSS | Suggested | Why no auto-fix |
301
+ |---|---|---|
302
+ | `paddingBlockStart`, `insetInlineEnd`, … | `blockPadding: '... start'` | The axis handler resets the edge you do not name, so the rewrite zeroes the opposite edge |
303
+ | `borderBlock`, `borderInlineColor`, … | `blockBorder` | Fills in the style and colour defaults `border-block` never had (`border-block: 1bw` renders nothing) |
304
+ | `minBlockSize`, `maxInlineSize`, … | `blockSize: 'min ...'` | Also emits `block-size` and `max-block-size` |
305
+
306
+ **Requires `@tenphi/tasty` >= 3.8.** The plugin never imports tasty, so it cannot adapt to
307
+ your installed version — and on 3.7 and below `blockPadding` is not a style tasty knows
308
+ (an unhandled camelCase key renders through to `block-padding`, which the browser drops).
309
+ The peer range floor is 3.8.0 for that reason.
310
+
138
311
  ## License
139
312
 
140
313
  MIT
package/dist/config.js CHANGED
@@ -177,6 +177,10 @@ function mergeConfigs(parent, child) {
177
177
  if (Array.isArray(parentVal) && Array.isArray(childVal)) result[key] = [...new Set([...parentVal, ...childVal])];
178
178
  else result[key] = childVal;
179
179
  }
180
+ if (child.styleFunctions !== void 0) result.styleFunctions = {
181
+ ...parent.styleFunctions,
182
+ ...child.styleFunctions
183
+ };
180
184
  return result;
181
185
  }
182
186
  function resolveConfigChain(configPath, visited = /* @__PURE__ */ new Set()) {
@@ -224,7 +228,8 @@ function toResolved(config) {
224
228
  recipes: config.recipes ?? [],
225
229
  styles: config.styles ?? [],
226
230
  importSources: config.importSources ?? DEFAULT_IMPORT_SOURCES,
227
- ownedSources: config.ownedSources ?? []
231
+ ownedSources: config.ownedSources ?? [],
232
+ styleFunctions: config.styleFunctions ?? {}
228
233
  };
229
234
  }
230
235
  const DEFAULT_CONFIG = {
@@ -236,7 +241,8 @@ const DEFAULT_CONFIG = {
236
241
  recipes: [],
237
242
  styles: [],
238
243
  importSources: DEFAULT_IMPORT_SOURCES,
239
- ownedSources: []
244
+ ownedSources: [],
245
+ styleFunctions: {}
240
246
  };
241
247
  function getMtimes(paths) {
242
248
  const mtimes = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport { createJiti } from 'jiti';\n\nimport type { ResolvedConfig, TastyValidationConfig } from './types.js';\nimport { DEFAULT_IMPORT_SOURCES } from './constants.js';\n\nconst CONFIG_FILENAMES = [\n 'tasty.config.ts',\n 'tasty.config.js',\n 'tasty.config.mjs',\n 'tasty.config.json',\n];\n\ninterface CachedConfig {\n config: ResolvedConfig;\n fileMtimes: Map<string, number>;\n}\n\nconst configCache = new Map<string, CachedConfig>();\n\nfunction findProjectRoot(startDir: string): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, 'package.json'))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(\n packageName: string,\n startDir: string,\n): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n const candidate = join(dir, 'node_modules', ...packageName.split('/'));\n if (existsSync(candidate) && existsSync(join(candidate, 'package.json'))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction findConfigInDir(dir: string): string | null {\n for (const name of CONFIG_FILENAMES) {\n const path = join(dir, name);\n if (existsSync(path)) {\n return path;\n }\n }\n return null;\n}\n\nfunction collectConfigFiles(startDir: string, projectRoot: string): string[] {\n const configs: string[] = [];\n let dir = startDir;\n const root = resolve(projectRoot);\n\n while (true) {\n const found = findConfigInDir(dir);\n if (found) configs.push(found);\n if (resolve(dir) === root) break;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return configs;\n}\n\nconst TASTY_PACKAGE_NAME = '@tenphi/tasty';\n\nfunction stripComments(source: string): string {\n let result = '';\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n result += ch;\n i++;\n while (i < source.length && source[i] !== ch) {\n if (source[i] === '\\\\') {\n result += source[i++];\n }\n if (i < source.length) {\n result += source[i++];\n }\n }\n if (i < source.length) {\n result += source[i++];\n }\n continue;\n }\n if (ch === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i += 2;\n continue;\n }\n if (ch === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') {\n i++;\n }\n continue;\n }\n result += source[i++];\n }\n return result;\n}\n\nfunction stripImports(source: string): string {\n return source.replace(/^\\s*import\\s+.*?;\\s*$/gm, '');\n}\n\nfunction extractBalancedBraces(content: string, start: number): string | null {\n if (content[start] !== '{') return null;\n let depth = 0;\n for (let i = start; i < content.length; i++) {\n const ch = content[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n i++;\n while (i < content.length && content[i] !== ch) {\n if (content[i] === '\\\\') i++;\n i++;\n }\n continue;\n }\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n if (depth === 0) return content.slice(start, i + 1);\n }\n return null;\n}\n\nfunction extractImportPaths(configPath: string): string[] {\n const source = readFileSync(configPath, 'utf-8');\n const configDir = dirname(configPath);\n const paths: string[] = [];\n const re = /^\\s*import\\s+.*?\\s+from\\s+['\"]([^'\"]+)['\"]/gm;\n let m;\n\n while ((m = re.exec(source)) !== null) {\n const specifier = m[1];\n\n if (!specifier.startsWith('.')) continue;\n\n const abs = resolve(configDir, specifier);\n\n for (const ext of ['', '.ts', '.js', '.mjs', '.tsx', '.jsx']) {\n const candidate = abs + ext;\n\n if (existsSync(candidate)) {\n paths.push(candidate);\n break;\n }\n }\n }\n\n return paths;\n}\n\nfunction stripTypeScriptSyntax(source: string): string {\n return source\n .replace(/\\bas\\s+const\\b/g, '')\n .replace(/\\bsatisfies\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '')\n .replace(/\\bas\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '');\n}\n\nfunction loadRawConfig(configPath: string): TastyValidationConfig {\n if (configPath.endsWith('.json')) {\n return JSON.parse(\n readFileSync(configPath, 'utf-8'),\n ) as TastyValidationConfig;\n }\n\n // Try jiti first — it resolves imports and TypeScript natively.\n try {\n const jiti = createJiti(dirname(configPath), { moduleCache: false });\n const mod = jiti(configPath) as Record<string, unknown>;\n const config = (mod.default ?? mod) as TastyValidationConfig;\n\n if (config && typeof config === 'object') {\n return config;\n }\n } catch {\n // Fall through to text-based parsing.\n }\n\n // Fallback: text-based extraction (no import support).\n const content = readFileSync(configPath, 'utf-8');\n const stripped = stripImports(stripComments(content));\n const match = stripped.match(/export\\s+default\\s+/);\n\n if (match && match.index != null) {\n const braceStart = match.index + match[0].length;\n const objectStr = extractBalancedBraces(stripped, braceStart);\n\n if (objectStr) {\n const cleaned = stripTypeScriptSyntax(objectStr);\n\n try {\n const fn = new Function(`return (${cleaned})`);\n return fn() as TastyValidationConfig;\n } catch (err) {\n console.warn(\n `[eslint-plugin-tasty] Failed to parse config file ${configPath}: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n }\n\n return {};\n}\n\nfunction mergeConfigs(\n parent: TastyValidationConfig,\n child: TastyValidationConfig,\n): TastyValidationConfig {\n const result: TastyValidationConfig = { ...parent };\n\n const arrayKeys = [\n 'tokens',\n 'units',\n 'functions',\n 'funcs',\n 'states',\n 'presets',\n 'recipes',\n 'styles',\n 'importSources',\n 'ownedSources',\n ] as const;\n\n for (const key of arrayKeys) {\n const childVal = child[key];\n if (childVal === undefined) continue;\n\n if (childVal === false) {\n (result as Record<string, unknown>)[key] = false;\n continue;\n }\n\n const parentVal = parent[key];\n if (Array.isArray(parentVal) && Array.isArray(childVal)) {\n (result as Record<string, unknown>)[key] = [\n ...new Set([...parentVal, ...childVal]),\n ];\n } else {\n (result as Record<string, unknown>)[key] = childVal;\n }\n }\n\n return result;\n}\n\ninterface ConfigChainResult {\n config: TastyValidationConfig;\n chainPaths: string[];\n}\n\nfunction resolveConfigChain(\n configPath: string,\n visited = new Set<string>(),\n): ConfigChainResult {\n const absPath = resolve(configPath);\n if (visited.has(absPath)) return { config: {}, chainPaths: [] };\n visited.add(absPath);\n\n const config = loadRawConfig(absPath);\n const importPaths = extractImportPaths(absPath);\n const chainPaths = [absPath, ...importPaths];\n\n if (!config.extends) return { config, chainPaths };\n\n let parentPath: string;\n if (config.extends.startsWith('.') || config.extends.startsWith('/')) {\n parentPath = resolve(dirname(absPath), config.extends);\n } else {\n const pkgDir = resolvePackageDir(config.extends, dirname(absPath));\n if (pkgDir) {\n const pkgConfig = findConfigInDir(pkgDir);\n if (pkgConfig) {\n parentPath = pkgConfig;\n } else {\n return { config, chainPaths };\n }\n } else {\n return { config, chainPaths };\n }\n }\n\n const parentResult = resolveConfigChain(parentPath, visited);\n return {\n config: mergeConfigs(parentResult.config, config),\n chainPaths: [...parentResult.chainPaths, ...chainPaths],\n };\n}\n\nfunction toResolved(config: TastyValidationConfig): ResolvedConfig {\n return {\n tokens: config.tokens ?? [],\n units: config.units ?? [],\n // `funcs` is the v2 spelling; `functions` wins when both are present.\n functions: config.functions ?? config.funcs ?? [],\n states: config.states ?? [],\n presets: config.presets ?? [],\n recipes: config.recipes ?? [],\n styles: config.styles ?? [],\n importSources: config.importSources ?? DEFAULT_IMPORT_SOURCES,\n ownedSources: config.ownedSources ?? [],\n };\n}\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n tokens: [],\n units: [],\n functions: [],\n states: [],\n presets: [],\n recipes: [],\n styles: [],\n importSources: DEFAULT_IMPORT_SOURCES,\n ownedSources: [],\n};\n\nfunction getMtimes(paths: string[]): Map<string, number> {\n const mtimes = new Map<string, number>();\n for (const p of paths) {\n try {\n mtimes.set(p, statSync(p).mtimeMs);\n } catch {\n mtimes.set(p, -1);\n }\n }\n return mtimes;\n}\n\nfunction mtimesMatch(\n cached: Map<string, number>,\n current: Map<string, number>,\n): boolean {\n if (cached.size !== current.size) return false;\n for (const [path, mtime] of cached) {\n if (current.get(path) !== mtime) return false;\n }\n return true;\n}\n\nexport function loadConfig(filePath: string): ResolvedConfig {\n const fileDir = dirname(resolve(filePath));\n const projectRoot = findProjectRoot(fileDir);\n if (!projectRoot) return DEFAULT_CONFIG;\n\n const configFiles = collectConfigFiles(fileDir, projectRoot);\n\n const pkgDir = resolvePackageDir(TASTY_PACKAGE_NAME, projectRoot);\n const pkgConfigFile = pkgDir ? findConfigInDir(pkgDir) : null;\n\n if (configFiles.length === 0 && !pkgConfigFile) return DEFAULT_CONFIG;\n\n const cacheKey = [pkgConfigFile ?? '', ...configFiles].join('\\0');\n\n const cached = configCache.get(cacheKey);\n if (cached) {\n const currentMtimes = getMtimes([...cached.fileMtimes.keys()]);\n if (mtimesMatch(cached.fileMtimes, currentMtimes)) {\n return cached.config;\n }\n }\n\n let merged: TastyValidationConfig = {};\n const allChainPaths: string[] = [];\n\n if (pkgConfigFile) {\n const result = resolveConfigChain(pkgConfigFile);\n merged = result.config;\n allChainPaths.push(...result.chainPaths);\n }\n\n for (let i = configFiles.length - 1; i >= 0; i--) {\n const { config, chainPaths } = resolveConfigChain(configFiles[i]);\n merged = mergeConfigs(merged, config);\n allChainPaths.push(...chainPaths);\n }\n\n const resolved = toResolved(merged);\n const fileMtimes = getMtimes(allChainPaths);\n\n configCache.set(cacheKey, { config: resolved, fileMtimes });\n\n return resolved;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport { createJiti } from 'jiti';\n\nimport type { ResolvedConfig, TastyValidationConfig } from './types.js';\nimport { DEFAULT_IMPORT_SOURCES } from './constants.js';\n\nconst CONFIG_FILENAMES = [\n 'tasty.config.ts',\n 'tasty.config.js',\n 'tasty.config.mjs',\n 'tasty.config.json',\n];\n\ninterface CachedConfig {\n config: ResolvedConfig;\n fileMtimes: Map<string, number>;\n}\n\nconst configCache = new Map<string, CachedConfig>();\n\nfunction findProjectRoot(startDir: string): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, 'package.json'))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(\n packageName: string,\n startDir: string,\n): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n const candidate = join(dir, 'node_modules', ...packageName.split('/'));\n if (existsSync(candidate) && existsSync(join(candidate, 'package.json'))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction findConfigInDir(dir: string): string | null {\n for (const name of CONFIG_FILENAMES) {\n const path = join(dir, name);\n if (existsSync(path)) {\n return path;\n }\n }\n return null;\n}\n\nfunction collectConfigFiles(startDir: string, projectRoot: string): string[] {\n const configs: string[] = [];\n let dir = startDir;\n const root = resolve(projectRoot);\n\n while (true) {\n const found = findConfigInDir(dir);\n if (found) configs.push(found);\n if (resolve(dir) === root) break;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return configs;\n}\n\nconst TASTY_PACKAGE_NAME = '@tenphi/tasty';\n\nfunction stripComments(source: string): string {\n let result = '';\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n result += ch;\n i++;\n while (i < source.length && source[i] !== ch) {\n if (source[i] === '\\\\') {\n result += source[i++];\n }\n if (i < source.length) {\n result += source[i++];\n }\n }\n if (i < source.length) {\n result += source[i++];\n }\n continue;\n }\n if (ch === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i += 2;\n continue;\n }\n if (ch === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') {\n i++;\n }\n continue;\n }\n result += source[i++];\n }\n return result;\n}\n\nfunction stripImports(source: string): string {\n return source.replace(/^\\s*import\\s+.*?;\\s*$/gm, '');\n}\n\nfunction extractBalancedBraces(content: string, start: number): string | null {\n if (content[start] !== '{') return null;\n let depth = 0;\n for (let i = start; i < content.length; i++) {\n const ch = content[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n i++;\n while (i < content.length && content[i] !== ch) {\n if (content[i] === '\\\\') i++;\n i++;\n }\n continue;\n }\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n if (depth === 0) return content.slice(start, i + 1);\n }\n return null;\n}\n\nfunction extractImportPaths(configPath: string): string[] {\n const source = readFileSync(configPath, 'utf-8');\n const configDir = dirname(configPath);\n const paths: string[] = [];\n const re = /^\\s*import\\s+.*?\\s+from\\s+['\"]([^'\"]+)['\"]/gm;\n let m;\n\n while ((m = re.exec(source)) !== null) {\n const specifier = m[1];\n\n if (!specifier.startsWith('.')) continue;\n\n const abs = resolve(configDir, specifier);\n\n for (const ext of ['', '.ts', '.js', '.mjs', '.tsx', '.jsx']) {\n const candidate = abs + ext;\n\n if (existsSync(candidate)) {\n paths.push(candidate);\n break;\n }\n }\n }\n\n return paths;\n}\n\nfunction stripTypeScriptSyntax(source: string): string {\n return source\n .replace(/\\bas\\s+const\\b/g, '')\n .replace(/\\bsatisfies\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '')\n .replace(/\\bas\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '');\n}\n\nfunction loadRawConfig(configPath: string): TastyValidationConfig {\n if (configPath.endsWith('.json')) {\n return JSON.parse(\n readFileSync(configPath, 'utf-8'),\n ) as TastyValidationConfig;\n }\n\n // Try jiti first — it resolves imports and TypeScript natively.\n try {\n const jiti = createJiti(dirname(configPath), { moduleCache: false });\n const mod = jiti(configPath) as Record<string, unknown>;\n const config = (mod.default ?? mod) as TastyValidationConfig;\n\n if (config && typeof config === 'object') {\n return config;\n }\n } catch {\n // Fall through to text-based parsing.\n }\n\n // Fallback: text-based extraction (no import support).\n const content = readFileSync(configPath, 'utf-8');\n const stripped = stripImports(stripComments(content));\n const match = stripped.match(/export\\s+default\\s+/);\n\n if (match && match.index != null) {\n const braceStart = match.index + match[0].length;\n const objectStr = extractBalancedBraces(stripped, braceStart);\n\n if (objectStr) {\n const cleaned = stripTypeScriptSyntax(objectStr);\n\n try {\n const fn = new Function(`return (${cleaned})`);\n return fn() as TastyValidationConfig;\n } catch (err) {\n console.warn(\n `[eslint-plugin-tasty] Failed to parse config file ${configPath}: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n }\n\n return {};\n}\n\nfunction mergeConfigs(\n parent: TastyValidationConfig,\n child: TastyValidationConfig,\n): TastyValidationConfig {\n const result: TastyValidationConfig = { ...parent };\n\n const arrayKeys = [\n 'tokens',\n 'units',\n 'functions',\n 'funcs',\n 'states',\n 'presets',\n 'recipes',\n 'styles',\n 'importSources',\n 'ownedSources',\n ] as const;\n\n for (const key of arrayKeys) {\n const childVal = child[key];\n if (childVal === undefined) continue;\n\n if (childVal === false) {\n (result as Record<string, unknown>)[key] = false;\n continue;\n }\n\n const parentVal = parent[key];\n if (Array.isArray(parentVal) && Array.isArray(childVal)) {\n (result as Record<string, unknown>)[key] = [\n ...new Set([...parentVal, ...childVal]),\n ];\n } else {\n (result as Record<string, unknown>)[key] = childVal;\n }\n }\n\n if (child.styleFunctions !== undefined) {\n result.styleFunctions = {\n ...parent.styleFunctions,\n ...child.styleFunctions,\n };\n }\n\n return result;\n}\n\ninterface ConfigChainResult {\n config: TastyValidationConfig;\n chainPaths: string[];\n}\n\nfunction resolveConfigChain(\n configPath: string,\n visited = new Set<string>(),\n): ConfigChainResult {\n const absPath = resolve(configPath);\n if (visited.has(absPath)) return { config: {}, chainPaths: [] };\n visited.add(absPath);\n\n const config = loadRawConfig(absPath);\n const importPaths = extractImportPaths(absPath);\n const chainPaths = [absPath, ...importPaths];\n\n if (!config.extends) return { config, chainPaths };\n\n let parentPath: string;\n if (config.extends.startsWith('.') || config.extends.startsWith('/')) {\n parentPath = resolve(dirname(absPath), config.extends);\n } else {\n const pkgDir = resolvePackageDir(config.extends, dirname(absPath));\n if (pkgDir) {\n const pkgConfig = findConfigInDir(pkgDir);\n if (pkgConfig) {\n parentPath = pkgConfig;\n } else {\n return { config, chainPaths };\n }\n } else {\n return { config, chainPaths };\n }\n }\n\n const parentResult = resolveConfigChain(parentPath, visited);\n return {\n config: mergeConfigs(parentResult.config, config),\n chainPaths: [...parentResult.chainPaths, ...chainPaths],\n };\n}\n\nfunction toResolved(config: TastyValidationConfig): ResolvedConfig {\n return {\n tokens: config.tokens ?? [],\n units: config.units ?? [],\n // `funcs` is the v2 spelling; `functions` wins when both are present.\n functions: config.functions ?? config.funcs ?? [],\n states: config.states ?? [],\n presets: config.presets ?? [],\n recipes: config.recipes ?? [],\n styles: config.styles ?? [],\n importSources: config.importSources ?? DEFAULT_IMPORT_SOURCES,\n ownedSources: config.ownedSources ?? [],\n styleFunctions: config.styleFunctions ?? {},\n };\n}\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n tokens: [],\n units: [],\n functions: [],\n states: [],\n presets: [],\n recipes: [],\n styles: [],\n importSources: DEFAULT_IMPORT_SOURCES,\n ownedSources: [],\n styleFunctions: {},\n};\n\nfunction getMtimes(paths: string[]): Map<string, number> {\n const mtimes = new Map<string, number>();\n for (const p of paths) {\n try {\n mtimes.set(p, statSync(p).mtimeMs);\n } catch {\n mtimes.set(p, -1);\n }\n }\n return mtimes;\n}\n\nfunction mtimesMatch(\n cached: Map<string, number>,\n current: Map<string, number>,\n): boolean {\n if (cached.size !== current.size) return false;\n for (const [path, mtime] of cached) {\n if (current.get(path) !== mtime) return false;\n }\n return true;\n}\n\nexport function loadConfig(filePath: string): ResolvedConfig {\n const fileDir = dirname(resolve(filePath));\n const projectRoot = findProjectRoot(fileDir);\n if (!projectRoot) return DEFAULT_CONFIG;\n\n const configFiles = collectConfigFiles(fileDir, projectRoot);\n\n const pkgDir = resolvePackageDir(TASTY_PACKAGE_NAME, projectRoot);\n const pkgConfigFile = pkgDir ? findConfigInDir(pkgDir) : null;\n\n if (configFiles.length === 0 && !pkgConfigFile) return DEFAULT_CONFIG;\n\n const cacheKey = [pkgConfigFile ?? '', ...configFiles].join('\\0');\n\n const cached = configCache.get(cacheKey);\n if (cached) {\n const currentMtimes = getMtimes([...cached.fileMtimes.keys()]);\n if (mtimesMatch(cached.fileMtimes, currentMtimes)) {\n return cached.config;\n }\n }\n\n let merged: TastyValidationConfig = {};\n const allChainPaths: string[] = [];\n\n if (pkgConfigFile) {\n const result = resolveConfigChain(pkgConfigFile);\n merged = result.config;\n allChainPaths.push(...result.chainPaths);\n }\n\n for (let i = configFiles.length - 1; i >= 0; i--) {\n const { config, chainPaths } = resolveConfigChain(configFiles[i]);\n merged = mergeConfigs(merged, config);\n allChainPaths.push(...chainPaths);\n }\n\n const resolved = toResolved(merged);\n const fileMtimes = getMtimes(allChainPaths);\n\n configCache.set(cacheKey, { config: resolved, fileMtimes });\n\n return resolved;\n}\n"],"mappings":"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"}
package/dist/configs.js CHANGED
@@ -23,6 +23,7 @@ const recommended = {
23
23
  "tasty/valid-default-state-order": "warn",
24
24
  "tasty/prefer-shorthand-property": "warn",
25
25
  "tasty/no-raw-color-values": "warn",
26
+ "tasty/no-raw-transition-duration": "warn",
26
27
  "tasty/consistent-token-usage": "warn",
27
28
  "tasty/prefer-auto-calc": "warn",
28
29
  "tasty/prefer-custom-property-syntax": "warn",
@@ -1 +1 @@
1
- {"version":3,"file":"configs.js","names":[],"sources":["../src/configs.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\n\nexport const recommended: TSESLint.SharedConfig.RulesRecord = {\n 'tasty/known-property': 'warn',\n 'tasty/valid-value': 'error',\n 'tasty/valid-color-token': 'error',\n 'tasty/valid-custom-unit': 'error',\n 'tasty/valid-boolean-property': 'error',\n 'tasty/valid-state-key': 'error',\n 'tasty/valid-styles-structure': 'error',\n 'tasty/no-nested-state-map': 'error',\n 'tasty/no-important': 'error',\n 'tasty/valid-sub-element': 'error',\n 'tasty/valid-directional-modifier': 'error',\n 'tasty/valid-radius-shape': 'error',\n 'tasty/no-nested-selector': 'error',\n 'tasty/static-no-dynamic-values': 'error',\n 'tasty/static-valid-selector': 'error',\n 'tasty/valid-preset': 'error',\n 'tasty/valid-recipe': 'error',\n 'tasty/valid-transition': 'warn',\n 'tasty/require-default-state': 'error',\n 'tasty/no-own-at-root': 'warn',\n 'tasty/valid-default-state-order': 'warn',\n 'tasty/prefer-shorthand-property': 'warn',\n 'tasty/no-raw-color-values': 'warn',\n 'tasty/consistent-token-usage': 'warn',\n 'tasty/prefer-auto-calc': 'warn',\n 'tasty/prefer-custom-property-syntax': 'warn',\n 'tasty/prefer-hide': 'warn',\n 'tasty/prefer-directional-shorthand': 'warn',\n 'tasty/prefer-longhand-property': 'error',\n};\n\nexport const strict: TSESLint.SharedConfig.RulesRecord = {\n ...recommended,\n 'tasty/valid-custom-property': 'warn',\n 'tasty/no-unknown-state-alias': 'warn',\n 'tasty/no-styles-prop': 'warn',\n 'tasty/no-runtime-styles-mutation': 'warn',\n 'tasty/valid-state-definition': 'warn',\n};\n"],"mappings":";AAEA,MAAa,cAAiD;CAC5D,wBAAwB;CACxB,qBAAqB;CACrB,2BAA2B;CAC3B,2BAA2B;CAC3B,gCAAgC;CAChC,yBAAyB;CACzB,gCAAgC;CAChC,6BAA6B;CAC7B,sBAAsB;CACtB,2BAA2B;CAC3B,oCAAoC;CACpC,4BAA4B;CAC5B,4BAA4B;CAC5B,kCAAkC;CAClC,+BAA+B;CAC/B,sBAAsB;CACtB,sBAAsB;CACtB,0BAA0B;CAC1B,+BAA+B;CAC/B,wBAAwB;CACxB,mCAAmC;CACnC,mCAAmC;CACnC,6BAA6B;CAC7B,gCAAgC;CAChC,0BAA0B;CAC1B,uCAAuC;CACvC,qBAAqB;CACrB,sCAAsC;CACtC,kCAAkC;CACnC;AAED,MAAa,SAA4C;CACvD,GAAG;CACH,+BAA+B;CAC/B,gCAAgC;CAChC,wBAAwB;CACxB,oCAAoC;CACpC,gCAAgC;CACjC"}
1
+ {"version":3,"file":"configs.js","names":[],"sources":["../src/configs.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\n\nexport const recommended: TSESLint.SharedConfig.RulesRecord = {\n 'tasty/known-property': 'warn',\n 'tasty/valid-value': 'error',\n 'tasty/valid-color-token': 'error',\n 'tasty/valid-custom-unit': 'error',\n 'tasty/valid-boolean-property': 'error',\n 'tasty/valid-state-key': 'error',\n 'tasty/valid-styles-structure': 'error',\n 'tasty/no-nested-state-map': 'error',\n 'tasty/no-important': 'error',\n 'tasty/valid-sub-element': 'error',\n 'tasty/valid-directional-modifier': 'error',\n 'tasty/valid-radius-shape': 'error',\n 'tasty/no-nested-selector': 'error',\n 'tasty/static-no-dynamic-values': 'error',\n 'tasty/static-valid-selector': 'error',\n 'tasty/valid-preset': 'error',\n 'tasty/valid-recipe': 'error',\n 'tasty/valid-transition': 'warn',\n 'tasty/require-default-state': 'error',\n 'tasty/no-own-at-root': 'warn',\n 'tasty/valid-default-state-order': 'warn',\n 'tasty/prefer-shorthand-property': 'warn',\n 'tasty/no-raw-color-values': 'warn',\n 'tasty/no-raw-transition-duration': 'warn',\n 'tasty/consistent-token-usage': 'warn',\n 'tasty/prefer-auto-calc': 'warn',\n 'tasty/prefer-custom-property-syntax': 'warn',\n 'tasty/prefer-hide': 'warn',\n 'tasty/prefer-directional-shorthand': 'warn',\n 'tasty/prefer-longhand-property': 'error',\n};\n\nexport const strict: TSESLint.SharedConfig.RulesRecord = {\n ...recommended,\n 'tasty/valid-custom-property': 'warn',\n 'tasty/no-unknown-state-alias': 'warn',\n 'tasty/no-styles-prop': 'warn',\n 'tasty/no-runtime-styles-mutation': 'warn',\n 'tasty/valid-state-definition': 'warn',\n};\n"],"mappings":";AAEA,MAAa,cAAiD;CAC5D,wBAAwB;CACxB,qBAAqB;CACrB,2BAA2B;CAC3B,2BAA2B;CAC3B,gCAAgC;CAChC,yBAAyB;CACzB,gCAAgC;CAChC,6BAA6B;CAC7B,sBAAsB;CACtB,2BAA2B;CAC3B,oCAAoC;CACpC,4BAA4B;CAC5B,4BAA4B;CAC5B,kCAAkC;CAClC,+BAA+B;CAC/B,sBAAsB;CACtB,sBAAsB;CACtB,0BAA0B;CAC1B,+BAA+B;CAC/B,wBAAwB;CACxB,mCAAmC;CACnC,mCAAmC;CACnC,6BAA6B;CAC7B,oCAAoC;CACpC,gCAAgC;CAChC,0BAA0B;CAC1B,uCAAuC;CACvC,qBAAqB;CACrB,sCAAsC;CACtC,kCAAkC;CACnC;AAED,MAAa,SAA4C;CACvD,GAAG;CACH,+BAA+B;CAC/B,gCAAgC;CAChC,wBAAwB;CACxB,oCAAoC;CACpC,gCAAgC;CACjC"}