tailwind-canonical 0.1.3 → 0.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 (42) hide show
  1. package/README.md +252 -23
  2. package/dist/cli/index.js +253 -23
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/analyzer.d.ts.map +1 -1
  5. package/dist/core/analyzer.js +17 -11
  6. package/dist/core/analyzer.js.map +1 -1
  7. package/dist/core/class-strings.d.ts +14 -0
  8. package/dist/core/class-strings.d.ts.map +1 -0
  9. package/dist/core/class-strings.js +127 -0
  10. package/dist/core/class-strings.js.map +1 -0
  11. package/dist/core/consistency.d.ts +46 -0
  12. package/dist/core/consistency.d.ts.map +1 -0
  13. package/dist/core/consistency.js +252 -0
  14. package/dist/core/consistency.js.map +1 -0
  15. package/dist/core/deduplicator.d.ts +2 -1
  16. package/dist/core/deduplicator.d.ts.map +1 -1
  17. package/dist/core/deduplicator.js +341 -75
  18. package/dist/core/deduplicator.js.map +1 -1
  19. package/dist/core/fixer.d.ts.map +1 -1
  20. package/dist/core/fixer.js +14 -18
  21. package/dist/core/fixer.js.map +1 -1
  22. package/dist/core/merger.d.ts +2 -1
  23. package/dist/core/merger.d.ts.map +1 -1
  24. package/dist/core/merger.js +5 -14
  25. package/dist/core/merger.js.map +1 -1
  26. package/dist/core/rules.d.ts +4 -0
  27. package/dist/core/rules.d.ts.map +1 -1
  28. package/dist/core/rules.js +43 -55
  29. package/dist/core/rules.js.map +1 -1
  30. package/dist/core/scanner.d.ts +2 -1
  31. package/dist/core/scanner.d.ts.map +1 -1
  32. package/dist/core/scanner.js +105 -6
  33. package/dist/core/scanner.js.map +1 -1
  34. package/dist/core/sorter.d.ts +6 -0
  35. package/dist/core/sorter.d.ts.map +1 -0
  36. package/dist/core/sorter.js +166 -0
  37. package/dist/core/sorter.js.map +1 -0
  38. package/dist/index.d.ts +7 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +4 -1
  41. package/dist/index.js.map +1 -1
  42. package/package.json +5 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tailwind-canonical
2
2
 
3
- Lint and auto-fix Tailwind CSS arbitrary values that have canonical equivalents.
3
+ Lint and auto-fix Tailwind CSS classes: arbitrary values → canonical, deduplication, shorthand collapsing, and class sorting.
4
4
 
5
5
  ## Install
6
6
 
@@ -13,24 +13,212 @@ yarn add -D tailwind-canonical
13
13
  ## CLI
14
14
 
15
15
  ```bash
16
- # Check
16
+ # Check for non-canonical arbitrary values
17
17
  npx tailwind-canonical ./src
18
18
 
19
- # Auto-fix
19
+ # Glob patterns (quote to prevent shell expansion)
20
+ npx tailwind-canonical 'src/**/*.tsx'
21
+ npx tailwind-canonical 'src/**/*.{tsx,ts}'
22
+ npx tailwind-canonical 'src/**/*.tsx' '!src/generated/**'
23
+
24
+ # Auto-fix: arbitrary → canonical
20
25
  npx tailwind-canonical --fix ./src
26
+
27
+ # Deduplicate and collapse shorthands
28
+ npx tailwind-canonical --dedup ./src
29
+
30
+ # Sort classes into canonical order
31
+ npx tailwind-canonical --sort ./src
32
+
33
+ # Resolve conflicts with tailwind-merge (requires: pnpm add -D tailwind-merge)
34
+ npx tailwind-canonical --merge ./src
35
+
36
+ # Combine: fix → dedup → merge → sort
37
+ npx tailwind-canonical --fix --dedup --merge --sort ./src
38
+
39
+ # Cross-file consistency analysis
40
+ npx tailwind-canonical --analyze ./src
41
+
42
+ # Watch mode: re-run on every file save
43
+ npx tailwind-canonical --watch --fix --sort ./src
44
+
45
+ # Structured output for CI (check mode only)
46
+ npx tailwind-canonical --reporter json ./src
47
+ npx tailwind-canonical --reporter sarif ./src
48
+ ```
49
+
50
+ ## What each flag does
51
+
52
+ | Flag | What it fixes | Example |
53
+ |---|---|---|
54
+ | `--fix` | Arbitrary values → canonical tokens | `text-[12px]` → `text-xs` |
55
+ | `--dedup` | Redundant classes, conflicts, shorthand collapse | `flex block` → `block`, `px-4 py-4` → `p-4`, `border-t-2 border-b-2` → `border-y-2` |
56
+ | `--sort` | Canonical class order | `text-sm flex p-4` → `flex p-4 text-sm` |
57
+ | `--merge` | tailwind-merge conflict resolution | `bg-red-500 bg-blue-500` → `bg-blue-500` |
58
+ | `--analyze` | Cross-file consistency (color variants, scale drift, repeated patterns) | `Warning: 3 red color variants used for text: text-red-500, text-rose-500, text-red-600` |
59
+ | `--watch` | Re-run on every file save (debounced 50ms) | `[12:34:01] src/Button.tsx — 2 changes applied` |
60
+ | `--reporter json` | JSON output (check mode) or fix summary | machine-readable for CI pipelines |
61
+ | `--reporter sarif` | SARIF 2.1.0 output (check mode) | GitHub Code Scanning / VS Code |
62
+
63
+ ## Structured output (`--reporter`)
64
+
65
+ ```bash
66
+ # JSON — check mode: outputs findings, exits 1 if any found
67
+ npx tailwind-canonical --reporter json ./src
68
+ ```
69
+ ```json
70
+ {
71
+ "files": 3,
72
+ "total": 2,
73
+ "findings": [
74
+ { "file": "src/Button.tsx", "line": 12, "col": 17, "original": "text-[14px]", "canonical": "text-sm", "isCustomToken": false }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ ```bash
80
+ # JSON — fix mode: outputs summary of changes
81
+ npx tailwind-canonical --fix --reporter json ./src
82
+ ```
83
+ ```json
84
+ { "files": 3, "changedFiles": ["src/Button.tsx"], "fixed": 1, "deduped": 0, "merged": 0, "sorted": 0 }
85
+ ```
86
+
87
+ ```bash
88
+ # SARIF — compatible with GitHub Code Scanning and VS Code Problem Matcher
89
+ npx tailwind-canonical --reporter sarif ./src > results.sarif
90
+ ```
91
+
92
+ ## Cross-file consistency (`--analyze`)
93
+
94
+ Detects semantic inconsistencies visible only at project scale — it never modifies files.
95
+
96
+ ```bash
97
+ npx tailwind-canonical --analyze ./src
98
+ # Warning: 3 red color variants used for text: text-red-500 (4), text-rose-500 (1), text-red-600 (2)
99
+ # Warning: px inconsistency: px-4 (8 files) vs px-3 (2 files)
100
+ # Warning: z inconsistency: z-[100] (3 files) vs z-[200] (1 file) vs z-[50] (2 files)
101
+ ```
102
+
103
+ Three detectors:
104
+
105
+ | Detector | What it flags |
106
+ |---|---|
107
+ | Color variants | Multiple shades/colors of the same hue family used for one property (`text`, `bg`, `border`, …) |
108
+ | Scale inconsistency | Competing values for the same spacing / `gap` / `z` property across files |
109
+ | Repeated patterns | Identical class combinations recurring across 3+ files |
110
+
111
+ Pairs with `--reporter json` for machine-readable output. Exits `1` when any inconsistency is found.
112
+
113
+ ```bash
114
+ npx tailwind-canonical --analyze --reporter json ./src
115
+ ```
116
+ ```json
117
+ {
118
+ "filesAnalyzed": 42,
119
+ "colorVariants": [
120
+ { "property": "text", "family": "red",
121
+ "variants": [
122
+ { "token": "red-500", "count": 4, "files": ["src/Alert.tsx"] },
123
+ { "token": "rose-500", "count": 1, "files": ["src/Toast.tsx"] }
124
+ ] }
125
+ ],
126
+ "scaleInconsistencies": [
127
+ { "property": "px",
128
+ "values": [
129
+ { "value": "4", "count": 8, "files": ["src/Button.tsx"] },
130
+ { "value": "3", "count": 2, "files": ["src/IconButton.tsx"] }
131
+ ] }
132
+ ],
133
+ "combinations": []
134
+ }
135
+ ```
136
+
137
+ ## Canonical class order (`--sort`)
138
+
139
+ `layout → position → display → flex/grid → sizing → border → spacing → typography → colors → effects → transitions → transforms → interactivity → a11y → variants`
140
+
141
+ ```
142
+ // before
143
+ className="text-sm bg-red-500 flex h-10 w-full p-4 rounded"
144
+
145
+ // after
146
+ className="flex h-10 w-full rounded p-4 text-sm bg-red-500"
147
+ ```
148
+
149
+ Variants (`hover:`, `sm:`, `focus:`) are sorted after base classes, with responsive breakpoints before state variants.
150
+
151
+ ### Custom order (`sortOrder`)
152
+
153
+ Override the category order via config. Categories you omit (and unknown classes) sort to the end, preserving their original relative order. Variants are always sorted after base classes regardless of order.
154
+
155
+ ```js
156
+ // tailwind-canonical.config.js
157
+ export default {
158
+ sortOrder: [
159
+ 'display',
160
+ 'position',
161
+ 'sizing',
162
+ 'spacing',
163
+ 'border',
164
+ 'typography',
165
+ 'colors',
166
+ 'effects',
167
+ ],
168
+ }
21
169
  ```
22
170
 
23
- ## Output
171
+ Valid category names: `layout`, `position`, `inset`, `display`, `flex-grid`, `sizing`, `border`, `spacing`, `typography`, `colors`, `effects`, `transitions`, `transforms`, `interactivity`, `accessibility`. Omit `sortOrder` to use the default order above.
24
172
 
173
+ ## Deduplication (`--dedup`)
174
+
175
+ ### Exact duplicates and conflicts
176
+
177
+ ```
178
+ flex flex flex → flex
179
+ flex block → block (last wins)
180
+ relative absolute → absolute
25
181
  ```
26
- src/components/badge.tsx:12:18 text-[11px] → text-2xs [custom token]
27
- src/components/card.tsx:34:5 h-[64px] → h-16
28
- src/components/card.tsx:41:5 text-[12px] → text-xs
29
182
 
30
- ✖ Found 3 non-canonical classes
31
- Run with --fix to auto-replace
183
+ ### Padding / margin shorthand collapse
184
+
185
+ ```
186
+ px-4 py-4 → p-4
187
+ p-4 px-2 → py-4 px-2
188
+ pt-2 pb-2 → py-2
189
+ pl-4 pr-4 → px-4
190
+ mx-4 my-4 → m-4
32
191
  ```
33
192
 
193
+ ### Border shorthand collapse
194
+
195
+ ```
196
+ border-t-2 border-b-2 border-l-2 border-r-2 → border-2
197
+ border-t-2 border-b-2 → border-y-2
198
+ border-l-4 border-r-4 → border-x-4
199
+ ```
200
+
201
+ ### Inset shorthand collapse
202
+
203
+ ```
204
+ top-4 right-4 bottom-4 left-4 → inset-4
205
+ top-4 bottom-4 → inset-y-4
206
+ left-2 right-2 → inset-x-2
207
+ ```
208
+
209
+ ## Arbitrary value detection (`--fix`)
210
+
211
+ | Arbitrary | Canonical | Notes |
212
+ |---|---|---|
213
+ | `text-[12px]` | `text-xs` | Built-in |
214
+ | `text-[0.75rem]` | `text-xs` | rem values |
215
+ | `h-[64px]` | `h-16` | Spacing scale ÷4 |
216
+ | `w-[50%]` | `w-1/2` | Percentage fractions |
217
+ | `opacity-[0.5]` | `opacity-50` | Opacity scale |
218
+ | `text-[11px]` | `text-2xs` | Custom token |
219
+
220
+ Non-divisible values (`h-[22px]`, `px-[7px]`) are left untouched.
221
+
34
222
  ## Config
35
223
 
36
224
  Create `tailwind-canonical.config.js` at the root:
@@ -42,9 +230,22 @@ export default {
42
230
  11: '2xs',
43
231
  13: 'xxs',
44
232
  },
233
+ customSpacingTokens: {
234
+ 14: '3.5',
235
+ },
236
+ // Support non-React attribute patterns (default: ['className'])
237
+ attributeNames: ['className', 'class', ':class', 'tw'],
238
+ // Support utility function wrappers
239
+ functionNames: ['cn', 'clsx', 'cva'],
45
240
  }
46
241
  ```
47
242
 
243
+ `customTextTokens` merges with the built-in text size map. `customSpacingTokens` supplements the default ÷4 spacing logic.
244
+
245
+ `attributeNames` controls which HTML/JSX attributes are scanned (default: `['className']`). Use `['class']` for plain HTML/PHP/Jinja templates, `[':class']` for Vue, `['tw']` for styled-components.
246
+
247
+ `functionNames` enables scanning inside utility function calls like `cn(...)` and `clsx(...)`.
248
+
48
249
  ## ESLint plugin
49
250
 
50
251
  ```js
@@ -56,28 +257,56 @@ export default [
56
257
  plugins: { 'tailwind-canonical': tailwindCanonical },
57
258
  rules: {
58
259
  'tailwind-canonical/no-arbitrary-canonical': 'warn',
260
+ // Optional: flag tailwind-merge conflicts (requires tailwind-merge peer dep)
261
+ 'tailwind-canonical/no-conflicting-classes': 'warn',
59
262
  },
60
263
  },
61
264
  ]
62
265
  ```
63
266
 
64
- ## Pre-commit hook (Husky)
267
+ ## Pre-commit hook (Husky / Lefthook)
268
+
269
+ ```yaml
270
+ # Lefthook: lefthook.yml
271
+ pre-commit:
272
+ parallel: true
273
+ commands:
274
+ tailwind:
275
+ glob: "src/**/*.{tsx,jsx}"
276
+ run: npx tailwind-canonical --fix --dedup --sort {staged_files}
277
+ stage_fixed: true
278
+ lint:
279
+ glob: "src/**/*.{ts,tsx,js,jsx,json}"
280
+ run: npx biome check --write --no-errors-on-unmatched {staged_files}
281
+ stage_fixed: true
282
+ ```
283
+
284
+ ```bash
285
+ # Husky: .husky/pre-commit
286
+ npx tailwind-canonical --fix --dedup --sort ./src ./app
287
+ ```
288
+
289
+ ## Dead code detection (knip)
65
290
 
66
291
  ```bash
67
- # .husky/pre-commit
68
- npx tailwind-canonical ./src ./app
292
+ pnpm knip
69
293
  ```
70
294
 
71
- ## What gets flagged
295
+ Detects unused exports, files, and dependencies. Config in `knip.json`.
72
296
 
73
- | Arbitrary | Canonical | Notes |
74
- |---|---|---|
75
- | `text-[12px]` | `text-xs` | Built-in |
76
- | `text-[14px]` | `text-sm` | Built-in |
77
- | `text-[11px]` | `text-2xs` | Custom token |
78
- | `h-[64px]` | `h-16` | Spacing scale ÷4 |
79
- | `w-[32px]` | `w-8` | Spacing scale ÷4 |
80
- | `min-h-[56px]` | `min-h-14` | Spacing scale ÷4 |
81
- | `max-w-[280px]` | `max-w-70` | Spacing scale ÷4 |
297
+ ## Programmatic API
82
298
 
83
- Non-divisible values (`h-[22px]`, `px-[7px]`) are left untouched.
299
+ ```ts
300
+ import {
301
+ suggestCanonical, // pure: class string → suggestion or null
302
+ analyzeFile, // find non-canonical classes in a file
303
+ fixFile, // apply --fix in-place
304
+ deduplicateClasses, // pure: deduplicate a class string
305
+ dedupeFile, // apply --dedup in-place
306
+ sortClasses, // pure: sort a class string
307
+ sortFile, // apply --sort in-place
308
+ mergeFile, // apply --merge in-place (requires tailwind-merge)
309
+ scanFiles, // recursive file scanner (sync, dir/file targets)
310
+ resolveTargets, // async glob resolver with negation + brace expansion
311
+ } from 'tailwind-canonical'
312
+ ```
package/dist/cli/index.js CHANGED
@@ -1,16 +1,39 @@
1
1
  #!/usr/bin/env node
2
+ import { watch as fsWatch } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
2
4
  import { analyzeFile } from '../core/analyzer.js';
5
+ import { analyzeConsistencyFiles } from '../core/consistency.js';
3
6
  import { dedupeFile } from '../core/deduplicator.js';
4
7
  import { fixFile } from '../core/fixer.js';
5
8
  import { mergeFile } from '../core/merger.js';
6
- import { scanFiles } from '../core/scanner.js';
9
+ import { resolveTargets } from '../core/scanner.js';
10
+ import { sortFile } from '../core/sorter.js';
11
+ function pluralize(n, word) {
12
+ return `${n} ${word}${n !== 1 ? 's' : ''}`;
13
+ }
14
+ function timestamp() {
15
+ const d = new Date();
16
+ return `[${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}]`;
17
+ }
18
+ const SARIF_SCHEMA = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json';
7
19
  const args = process.argv.slice(2);
8
20
  const fix = args.includes('--fix');
9
21
  const merge = args.includes('--merge');
10
22
  const dedup = args.includes('--dedup');
11
- const targets = args.filter((a) => !a.startsWith('--'));
23
+ const sort = args.includes('--sort');
24
+ const watch = args.includes('--watch');
25
+ const analyze = args.includes('--analyze');
26
+ const reporterIdx = args.indexOf('--reporter');
27
+ const reporter = reporterIdx !== -1 &&
28
+ (args[reporterIdx + 1] === 'json' || args[reporterIdx + 1] === 'sarif')
29
+ ? args[reporterIdx + 1]
30
+ : 'text';
31
+ const targets = args.filter((a, i) => !a.startsWith('--') &&
32
+ args[i - 1] !== '--reporter' &&
33
+ a !== 'json' &&
34
+ a !== 'sarif');
12
35
  if (targets.length === 0) {
13
- console.error('Usage: tailwind-canonical [--fix] [--merge] [--dedup] <dir|file> [dir|file...]');
36
+ console.error('Usage: tailwind-canonical [--fix] [--merge] [--dedup] [--sort] [--analyze] [--watch] [--reporter json|sarif] <dir|file> [dir|file...]');
14
37
  process.exit(1);
15
38
  }
16
39
  if (merge) {
@@ -26,53 +49,260 @@ let config = {};
26
49
  try {
27
50
  const { default: userConfig } = await import(new URL(`file://${process.cwd()}/tailwind-canonical.config.js`).href);
28
51
  config = userConfig;
52
+ // biome-ignore lint/suspicious/noEmptyBlockStatements: config file is optional
29
53
  }
30
54
  catch { }
31
- const files = targets.flatMap((t) => scanFiles(t));
55
+ async function processFile(file) {
56
+ let count = 0;
57
+ if (fix)
58
+ count += fixFile(file, config);
59
+ if (dedup)
60
+ count += dedupeFile(file, {
61
+ functionNames: config.functionNames,
62
+ attributeNames: config.attributeNames,
63
+ });
64
+ if (merge)
65
+ count += await mergeFile(file, {
66
+ functionNames: config.functionNames,
67
+ attributeNames: config.attributeNames,
68
+ });
69
+ if (sort)
70
+ count += sortFile(file, {
71
+ functionNames: config.functionNames,
72
+ attributeNames: config.attributeNames,
73
+ }, config.sortOrder);
74
+ return count;
75
+ }
76
+ const files = await resolveTargets(targets);
77
+ if (analyze) {
78
+ const report = analyzeConsistencyFiles(files, config);
79
+ const issueCount = report.colorVariants.length +
80
+ report.scaleInconsistencies.length +
81
+ report.combinations.length;
82
+ if (reporter === 'json') {
83
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
84
+ }
85
+ else {
86
+ for (const group of report.colorVariants) {
87
+ const tokens = group.variants
88
+ .map((v) => `${group.property}-${v.token} (${v.count})`)
89
+ .join(', ');
90
+ console.log(` Warning: ${group.variants.length} ${group.family} color variants used for ${group.property}: ${tokens}`);
91
+ }
92
+ for (const scale of report.scaleInconsistencies) {
93
+ const values = scale.values
94
+ .map((v) => `${scale.property}-${v.value} (${pluralize(v.files.length, 'file')})`)
95
+ .join(' vs ');
96
+ console.log(` Warning: ${scale.property} inconsistency: ${values}`);
97
+ }
98
+ for (const combo of report.combinations) {
99
+ console.log(` Pattern: "${combo.classes.join(' ')}" repeated in ${pluralize(combo.files.length, 'file')}`);
100
+ }
101
+ if (issueCount === 0) {
102
+ console.log('✓ No cross-file inconsistencies found');
103
+ }
104
+ else {
105
+ console.log(`\n✖ Found ${pluralize(issueCount, 'consistency issue')} across ${pluralize(report.filesAnalyzed, 'file')}`);
106
+ }
107
+ }
108
+ if (issueCount > 0)
109
+ process.exit(1);
110
+ process.exit(0);
111
+ }
32
112
  let totalFindings = 0;
33
113
  let totalFixed = 0;
34
114
  let totalMerged = 0;
35
115
  let totalDeduped = 0;
116
+ let totalSorted = 0;
117
+ const allFindings = [];
118
+ const changedFiles = [];
36
119
  for (const file of files) {
37
120
  if (fix) {
38
121
  const count = fixFile(file, config);
39
122
  if (count > 0) {
40
- console.log(` fixed ${file} (${count} replacement${count > 1 ? 's' : ''})`);
41
123
  totalFixed += count;
124
+ changedFiles.push(file);
125
+ if (reporter === 'text') {
126
+ console.log(` fixed ${file} (${pluralize(count, 'replacement')})`);
127
+ }
42
128
  }
43
129
  }
44
130
  if (dedup) {
45
- const count = dedupeFile(file);
131
+ const count = dedupeFile(file, {
132
+ functionNames: config.functionNames,
133
+ attributeNames: config.attributeNames,
134
+ });
46
135
  if (count > 0) {
47
- console.log(` deduped ${file} (${count} class string${count > 1 ? 's' : ''})`);
48
136
  totalDeduped += count;
137
+ if (!changedFiles.includes(file))
138
+ changedFiles.push(file);
139
+ if (reporter === 'text') {
140
+ console.log(` deduped ${file} (${pluralize(count, 'class string')})`);
141
+ }
49
142
  }
50
143
  }
51
144
  if (merge) {
52
- const count = await mergeFile(file);
145
+ const count = await mergeFile(file, {
146
+ functionNames: config.functionNames,
147
+ attributeNames: config.attributeNames,
148
+ });
53
149
  if (count > 0) {
54
- console.log(` merged ${file} (${count} conflict${count > 1 ? 's' : ''})`);
55
150
  totalMerged += count;
151
+ if (!changedFiles.includes(file))
152
+ changedFiles.push(file);
153
+ if (reporter === 'text') {
154
+ console.log(` merged ${file} (${pluralize(count, 'conflict')})`);
155
+ }
56
156
  }
57
157
  }
58
- if (!fix && !dedup && !merge) {
59
- const findings = analyzeFile(file, config);
60
- for (const f of findings) {
61
- const tag = f.suggestion.isCustomToken ? ' [custom token]' : '';
62
- console.log(` ${f.file}:${f.line}:${f.col} ${f.suggestion.original} → ${f.suggestion.canonical}${tag}`);
158
+ if (sort) {
159
+ const count = sortFile(file, {
160
+ functionNames: config.functionNames,
161
+ attributeNames: config.attributeNames,
162
+ }, config.sortOrder);
163
+ if (count > 0) {
164
+ totalSorted += count;
165
+ if (!changedFiles.includes(file))
166
+ changedFiles.push(file);
167
+ if (reporter === 'text') {
168
+ console.log(` sorted ${file} (${pluralize(count, 'class string')})`);
169
+ }
63
170
  }
171
+ }
172
+ if (!(fix || dedup || merge || sort)) {
173
+ const findings = analyzeFile(file, config);
174
+ allFindings.push(...findings);
64
175
  totalFindings += findings.length;
176
+ if (reporter === 'text' && !watch) {
177
+ for (const f of findings) {
178
+ const tag = f.suggestion.isCustomToken ? ' [custom token]' : '';
179
+ console.log(` ${f.file}:${f.line}:${f.col} ${f.suggestion.original} → ${f.suggestion.canonical}${tag}`);
180
+ }
181
+ }
65
182
  }
66
183
  }
67
- if (fix || dedup || merge) {
68
- const parts = [];
69
- if (fix)
70
- parts.push(`${totalFixed} replacement${totalFixed !== 1 ? 's' : ''}`);
71
- if (dedup)
72
- parts.push(`${totalDeduped} dedup${totalDeduped !== 1 ? 's' : ''}`);
73
- if (merge)
74
- parts.push(`${totalMerged} conflict${totalMerged !== 1 ? 's' : ''} merged`);
75
- console.log(`\n✓ Fixed ${parts.join(', ')} across ${files.length} file${files.length !== 1 ? 's' : ''}`);
184
+ if (watch) {
185
+ const fileSet = new Set(files);
186
+ const timers = new Map();
187
+ const dirs = new Set(files.map((f) => dirname(f)));
188
+ console.log(`Watching ${pluralize(files.length, 'file')}... (Ctrl+C to stop)`);
189
+ for (const dir of dirs) {
190
+ fsWatch(dir, { recursive: true }, (_, filename) => {
191
+ if (!filename)
192
+ return;
193
+ const full = resolve(dir, filename);
194
+ if (!fileSet.has(full))
195
+ return;
196
+ clearTimeout(timers.get(full));
197
+ timers.set(full, setTimeout(() => {
198
+ if (fix || dedup || merge || sort) {
199
+ processFile(full)
200
+ .then((count) => {
201
+ if (count > 0)
202
+ console.log(`${timestamp()} ${full} — ${pluralize(count, 'change')} applied`);
203
+ })
204
+ // biome-ignore lint/suspicious/noEmptyBlockStatements: watcher errors are silent by design
205
+ .catch(() => { });
206
+ }
207
+ else {
208
+ const findings = analyzeFile(full, config);
209
+ if (findings.length > 0) {
210
+ console.log(`${timestamp()} ${full} — ${pluralize(findings.length, 'finding')}`);
211
+ for (const f of findings) {
212
+ const tag = f.suggestion.isCustomToken ? ' [custom token]' : '';
213
+ console.log(` ${f.line}:${f.col} ${f.suggestion.original} → ${f.suggestion.canonical}${tag}`);
214
+ }
215
+ }
216
+ }
217
+ }, 50));
218
+ });
219
+ }
220
+ process.on('SIGINT', () => {
221
+ console.log('\nWatcher stopped.');
222
+ process.exit(0);
223
+ });
224
+ }
225
+ else if (fix || dedup || merge || sort) {
226
+ if (reporter === 'json') {
227
+ process.stdout.write(`${JSON.stringify({
228
+ files: files.length,
229
+ changedFiles,
230
+ fixed: totalFixed,
231
+ deduped: totalDeduped,
232
+ merged: totalMerged,
233
+ sorted: totalSorted,
234
+ }, null, 2)}\n`);
235
+ }
236
+ else {
237
+ const parts = [];
238
+ if (fix)
239
+ parts.push(pluralize(totalFixed, 'replacement'));
240
+ if (dedup)
241
+ parts.push(pluralize(totalDeduped, 'dedup'));
242
+ if (merge)
243
+ parts.push(`${pluralize(totalMerged, 'conflict')} merged`);
244
+ if (sort)
245
+ parts.push(`${totalSorted} sorted`);
246
+ console.log(`\n✓ Fixed ${parts.join(', ')} across ${files.length} file${files.length !== 1 ? 's' : ''}`);
247
+ }
248
+ }
249
+ else if (reporter === 'json') {
250
+ process.stdout.write(`${JSON.stringify({
251
+ files: files.length,
252
+ total: totalFindings,
253
+ findings: allFindings.map((f) => ({
254
+ file: f.file,
255
+ line: f.line,
256
+ col: f.col,
257
+ original: f.suggestion.original,
258
+ canonical: f.suggestion.canonical,
259
+ isCustomToken: f.suggestion.isCustomToken,
260
+ })),
261
+ }, null, 2)}\n`);
262
+ if (totalFindings > 0)
263
+ process.exit(1);
264
+ }
265
+ else if (reporter === 'sarif') {
266
+ const sarifOutput = {
267
+ $schema: SARIF_SCHEMA,
268
+ version: '2.1.0',
269
+ runs: [
270
+ {
271
+ tool: {
272
+ driver: {
273
+ name: 'tailwind-canonical',
274
+ informationUri: 'https://github.com/peak-lab/tailwind-canonical',
275
+ rules: [
276
+ {
277
+ id: 'no-arbitrary-canonical',
278
+ name: 'NoArbitraryCanonical',
279
+ shortDescription: {
280
+ text: 'Arbitrary value has a canonical Tailwind equivalent',
281
+ },
282
+ },
283
+ ],
284
+ },
285
+ },
286
+ results: allFindings.map((f) => ({
287
+ ruleId: 'no-arbitrary-canonical',
288
+ message: {
289
+ text: `${f.suggestion.original} → ${f.suggestion.canonical}`,
290
+ },
291
+ locations: [
292
+ {
293
+ physicalLocation: {
294
+ artifactLocation: { uri: f.file },
295
+ region: { startLine: f.line, startColumn: f.col },
296
+ },
297
+ },
298
+ ],
299
+ })),
300
+ },
301
+ ],
302
+ };
303
+ process.stdout.write(`${JSON.stringify(sarifOutput, null, 2)}\n`);
304
+ if (totalFindings > 0)
305
+ process.exit(1);
76
306
  }
77
307
  else if (totalFindings > 0) {
78
308
  console.log(`\n✖ Found ${totalFindings} non-canonical class${totalFindings !== 1 ? 'es' : ''}`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAgB,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,SAAS,SAAS,CAAC,CAAS,EAAE,IAAY;IACxC,OAAO,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;AACrJ,CAAC;AAED,MAAM,YAAY,GAChB,gGAAgG,CAAC;AAEnG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACnC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AACvC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AACvC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACrC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AACvC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAE3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;AAC/C,MAAM,QAAQ,GACZ,WAAW,KAAK,CAAC,CAAC;IAClB,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC;IACrE,CAAC,CAAE,IAAI,CAAC,WAAW,GAAG,CAAC,CAAsB;IAC7C,CAAC,CAAC,MAAM,CAAC;AAEb,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CACzB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;IACnB,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,YAAY;IAC5B,CAAC,KAAK,MAAM;IACZ,CAAC,KAAK,OAAO,CAChB,CAAC;AAEF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IACzB,OAAO,CAAC,KAAK,CACX,uIAAuI,CACxI,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,KAAK,EAAE,CAAC;IACV,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CACX,6DAA6D,CAC9D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,MAAM,GAAW,EAAE,CAAC;AACxB,IAAI,CAAC;IACH,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAC1C,IAAI,GAAG,CAAC,UAAU,OAAO,CAAC,GAAG,EAAE,+BAA+B,CAAC,CAAC,IAAI,CACrE,CAAC;IACF,MAAM,GAAG,UAAU,CAAC;IACpB,+EAA+E;AACjF,CAAC;AAAC,MAAM,CAAC,CAAA,CAAC;AAEV,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG;QAAE,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,KAAK;QACP,KAAK,IAAI,UAAU,CAAC,IAAI,EAAE;YACxB,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,CAAC,CAAC;IACL,IAAI,KAAK;QACP,KAAK,IAAI,MAAM,SAAS,CAAC,IAAI,EAAE;YAC7B,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,CAAC,CAAC;IACL,IAAI,IAAI;QACN,KAAK,IAAI,QAAQ,CACf,IAAI,EACJ;YACE,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,EACD,MAAM,CAAC,SAAS,CACjB,CAAC;IACJ,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;AAE5C,IAAI,OAAO,EAAE,CAAC;IACZ,MAAM,MAAM,GAAG,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,UAAU,GACd,MAAM,CAAC,aAAa,CAAC,MAAM;QAC3B,MAAM,CAAC,oBAAoB,CAAC,MAAM;QAClC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;IAE7B,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ;iBAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC;iBACvD,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,CAAC,GAAG,CACT,cAAc,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,4BAA4B,KAAK,CAAC,QAAQ,KAAK,MAAM,EAAE,CAC3G,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,oBAAoB,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;iBACxB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CACxE;iBACA,IAAI,CAAC,MAAM,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,QAAQ,mBAAmB,MAAM,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxC,OAAO,CAAC,GAAG,CACT,eAAe,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAC/F,CAAC;QACJ,CAAC;QACD,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CACT,aAAa,SAAS,CAAC,UAAU,EAAE,mBAAmB,CAAC,WAAW,SAAS,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,EAAE,CAC5G,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB,IAAI,UAAU,GAAG,CAAC,CAAC;AACnB,IAAI,WAAW,GAAG,CAAC,CAAC;AACpB,IAAI,YAAY,GAAG,CAAC,CAAC;AACrB,IAAI,WAAW,GAAG,CAAC,CAAC;AACpB,MAAM,WAAW,GAAc,EAAE,CAAC;AAClC,MAAM,YAAY,GAAa,EAAE,CAAC;AAElC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;IACzB,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,UAAU,IAAI,KAAK,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE;YAC7B,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,CAAC,CAAC;QACH,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,YAAY,IAAI,KAAK,CAAC;YACtB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1D,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE;YAClC,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,CAAC,CAAC;QACH,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,WAAW,IAAI,KAAK,CAAC;YACrB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1D,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,KAAK,GAAG,QAAQ,CACpB,IAAI,EACJ;YACE,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,cAAc,EAAE,MAAM,CAAC,cAAc;SACtC,EACD,MAAM,CAAC,SAAS,CACjB,CAAC;QACF,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,WAAW,IAAI,KAAK,CAAC;YACrB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1D,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;QAC9B,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC;QACjC,IAAI,QAAQ,KAAK,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAClC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChE,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,QAAQ,MAAM,CAAC,CAAC,UAAU,CAAC,SAAS,GAAG,GAAG,EAAE,CAC7F,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,IAAI,KAAK,EAAE,CAAC;IACV,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyC,CAAC;IAChE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3D,OAAO,CAAC,GAAG,CACT,YAAY,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,sBAAsB,CAClE,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE;YAChD,IAAI,CAAC,QAAQ;gBAAE,OAAO;YACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YACpC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC/B,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,GAAG,CACR,IAAI,EACJ,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;oBAClC,WAAW,CAAC,IAAI,CAAC;yBACd,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;wBACd,IAAI,KAAK,GAAG,CAAC;4BACX,OAAO,CAAC,GAAG,CACT,GAAG,SAAS,EAAE,IAAI,IAAI,MAAM,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CACjE,CAAC;oBACN,CAAC,CAAC;wBACF,2FAA2F;yBAC1F,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACrB,CAAC;qBAAM,CAAC;oBACN,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBAC3C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACxB,OAAO,CAAC,GAAG,CACT,GAAG,SAAS,EAAE,IAAI,IAAI,MAAM,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CACpE,CAAC;wBACF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;4BACzB,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;4BAChE,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,QAAQ,MAAM,CAAC,CAAC,UAAU,CAAC,SAAS,GAAG,GAAG,EAAE,CACnF,CAAC;wBACJ,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC,EAAE,EAAE,CAAC,CACP,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;IACzC,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CACf;YACE,KAAK,EAAE,KAAK,CAAC,MAAM;YACnB,YAAY;YACZ,KAAK,EAAE,UAAU;YACjB,OAAO,EAAE,YAAY;YACrB,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,WAAW;SACpB,EACD,IAAI,EACJ,CAAC,CACF,IAAI,CACN,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC;QAC1D,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;QACtE,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,SAAS,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CACT,aAAa,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC5F,CAAC;IACJ,CAAC;AACH,CAAC;KAAM,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CACf;QACE,KAAK,EAAE,KAAK,CAAC,MAAM;QACnB,KAAK,EAAE,aAAa;QACpB,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ;YAC/B,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,SAAS;YACjC,aAAa,EAAE,CAAC,CAAC,UAAU,CAAC,aAAa;SAC1C,CAAC,CAAC;KACJ,EACD,IAAI,EACJ,CAAC,CACF,IAAI,CACN,CAAC;IACF,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;KAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;IAChC,MAAM,WAAW,GAAG;QAClB,OAAO,EAAE,YAAY;QACrB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,oBAAoB;wBAC1B,cAAc,EAAE,gDAAgD;wBAChE,KAAK,EAAE;4BACL;gCACE,EAAE,EAAE,wBAAwB;gCAC5B,IAAI,EAAE,sBAAsB;gCAC5B,gBAAgB,EAAE;oCAChB,IAAI,EAAE,qDAAqD;iCAC5D;6BACF;yBACF;qBACF;iBACF;gBACD,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC/B,MAAM,EAAE,wBAAwB;oBAChC,OAAO,EAAE;wBACP,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,QAAQ,MAAM,CAAC,CAAC,UAAU,CAAC,SAAS,EAAE;qBAC7D;oBACD,SAAS,EAAE;wBACT;4BACE,gBAAgB,EAAE;gCAChB,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE;gCACjC,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE;6BAClD;yBACF;qBACF;iBACF,CAAC,CAAC;aACJ;SACF;KACF,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAClE,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;KAAM,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;IAC7B,OAAO,CAAC,GAAG,CACT,aAAa,aAAa,uBAAuB,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACnF,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAClD,CAAC"}