tailwind-canonical 0.5.0 → 0.7.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 (106) hide show
  1. package/README.md +183 -14
  2. package/dist/cli/cli.d.ts +2 -1
  3. package/dist/cli/cli.js +208 -75
  4. package/dist/cli/index.d.ts +0 -1
  5. package/dist/cli/index.js +0 -1
  6. package/dist/core/analyzer.d.ts +0 -1
  7. package/dist/core/analyzer.js +0 -1
  8. package/dist/core/class-strings.d.ts +0 -1
  9. package/dist/core/class-strings.js +0 -1
  10. package/dist/core/config.d.ts +2 -3
  11. package/dist/core/config.js +11 -2
  12. package/dist/core/consistency.d.ts +0 -1
  13. package/dist/core/consistency.js +1 -6
  14. package/dist/core/deduplicator.d.ts +0 -1
  15. package/dist/core/deduplicator.js +10 -6
  16. package/dist/core/fixer.d.ts +0 -1
  17. package/dist/core/fixer.js +0 -1
  18. package/dist/core/lexicon.d.ts +1 -1
  19. package/dist/core/lexicon.js +1 -2
  20. package/dist/core/merger.d.ts +0 -1
  21. package/dist/core/merger.js +35 -29
  22. package/dist/core/rules.d.ts +3 -1
  23. package/dist/core/rules.js +56 -1
  24. package/dist/core/scanner.d.ts +0 -1
  25. package/dist/core/scanner.js +0 -1
  26. package/dist/core/sorter.d.ts +0 -1
  27. package/dist/core/sorter.js +5 -3
  28. package/dist/core/suppressions.d.ts +0 -1
  29. package/dist/core/suppressions.js +0 -1
  30. package/dist/core/typos.d.ts +0 -1
  31. package/dist/core/typos.js +0 -1
  32. package/dist/eslint/plugin.d.ts +0 -1
  33. package/dist/eslint/plugin.js +2 -2
  34. package/dist/index.d.ts +0 -1
  35. package/dist/index.js +0 -1
  36. package/dist/io/analyzer.d.ts +0 -1
  37. package/dist/io/analyzer.js +0 -1
  38. package/dist/io/config.d.ts +9 -1
  39. package/dist/io/config.js +93 -7
  40. package/dist/io/consistency.d.ts +0 -1
  41. package/dist/io/consistency.js +0 -1
  42. package/dist/io/deduplicator.d.ts +0 -1
  43. package/dist/io/deduplicator.js +0 -1
  44. package/dist/io/fixer.d.ts +0 -1
  45. package/dist/io/fixer.js +0 -1
  46. package/dist/io/merger.d.ts +0 -1
  47. package/dist/io/merger.js +0 -1
  48. package/dist/io/scanner.d.ts +0 -1
  49. package/dist/io/scanner.js +0 -1
  50. package/dist/io/sorter.d.ts +0 -1
  51. package/dist/io/sorter.js +0 -1
  52. package/dist/io/typos.d.ts +0 -1
  53. package/dist/io/typos.js +0 -1
  54. package/package.json +11 -10
  55. package/dist/cli/cli.d.ts.map +0 -1
  56. package/dist/cli/cli.js.map +0 -1
  57. package/dist/cli/index.d.ts.map +0 -1
  58. package/dist/cli/index.js.map +0 -1
  59. package/dist/core/analyzer.d.ts.map +0 -1
  60. package/dist/core/analyzer.js.map +0 -1
  61. package/dist/core/class-strings.d.ts.map +0 -1
  62. package/dist/core/class-strings.js.map +0 -1
  63. package/dist/core/config.d.ts.map +0 -1
  64. package/dist/core/config.js.map +0 -1
  65. package/dist/core/consistency.d.ts.map +0 -1
  66. package/dist/core/consistency.js.map +0 -1
  67. package/dist/core/deduplicator.d.ts.map +0 -1
  68. package/dist/core/deduplicator.js.map +0 -1
  69. package/dist/core/fixer.d.ts.map +0 -1
  70. package/dist/core/fixer.js.map +0 -1
  71. package/dist/core/lexicon.d.ts.map +0 -1
  72. package/dist/core/lexicon.js.map +0 -1
  73. package/dist/core/merger.d.ts.map +0 -1
  74. package/dist/core/merger.js.map +0 -1
  75. package/dist/core/rules.d.ts.map +0 -1
  76. package/dist/core/rules.js.map +0 -1
  77. package/dist/core/scanner.d.ts.map +0 -1
  78. package/dist/core/scanner.js.map +0 -1
  79. package/dist/core/sorter.d.ts.map +0 -1
  80. package/dist/core/sorter.js.map +0 -1
  81. package/dist/core/suppressions.d.ts.map +0 -1
  82. package/dist/core/suppressions.js.map +0 -1
  83. package/dist/core/typos.d.ts.map +0 -1
  84. package/dist/core/typos.js.map +0 -1
  85. package/dist/eslint/plugin.d.ts.map +0 -1
  86. package/dist/eslint/plugin.js.map +0 -1
  87. package/dist/index.d.ts.map +0 -1
  88. package/dist/index.js.map +0 -1
  89. package/dist/io/analyzer.d.ts.map +0 -1
  90. package/dist/io/analyzer.js.map +0 -1
  91. package/dist/io/config.d.ts.map +0 -1
  92. package/dist/io/config.js.map +0 -1
  93. package/dist/io/consistency.d.ts.map +0 -1
  94. package/dist/io/consistency.js.map +0 -1
  95. package/dist/io/deduplicator.d.ts.map +0 -1
  96. package/dist/io/deduplicator.js.map +0 -1
  97. package/dist/io/fixer.d.ts.map +0 -1
  98. package/dist/io/fixer.js.map +0 -1
  99. package/dist/io/merger.d.ts.map +0 -1
  100. package/dist/io/merger.js.map +0 -1
  101. package/dist/io/scanner.d.ts.map +0 -1
  102. package/dist/io/scanner.js.map +0 -1
  103. package/dist/io/sorter.d.ts.map +0 -1
  104. package/dist/io/sorter.js.map +0 -1
  105. package/dist/io/typos.d.ts.map +0 -1
  106. package/dist/io/typos.js.map +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lint and auto-fix Tailwind CSS classes: arbitrary values → canonical, deduplication, shorthand collapsing, and class sorting.
4
4
 
5
- > Compatible with Tailwind CSS v3.3+ and v4 — zero `tailwindcss` dependency (conventions are built in). Custom theme tokens are supported via config.
5
+ > Compatible with Tailwind CSS v4 out of the box, and with v3.3+ via [`tailwindVersion: 3`](#tailwind-v3-vs-v4-tailwindversion) — the ÷4 spacing rule assumes v4's dynamic scale, so v3 projects must set it. Zero `tailwindcss` dependency (conventions are built in). Custom theme tokens are supported via config.
6
6
 
7
7
  ## Install
8
8
 
@@ -12,9 +12,47 @@ npm install -D tailwind-canonical
12
12
  yarn add -D tailwind-canonical
13
13
  ```
14
14
 
15
+ ## Quick start
16
+
17
+ First scan to see what needs fixing:
18
+ ```bash
19
+ npx tailwind-canonical ./src
20
+ ```
21
+
22
+ Create a config file to make the full pipeline re-runnable:
23
+ ```ts
24
+ // tailwind-canonical.config.ts
25
+ export default {
26
+ defaultCommand: {
27
+ fix: true,
28
+ dedup: true,
29
+ sort: true,
30
+ typos: true,
31
+ targets: ['./src'],
32
+ },
33
+ }
34
+ ```
35
+
36
+ Now run the whole pipeline with a single command:
37
+ ```bash
38
+ npx tailwind-canonical
39
+ ```
40
+
41
+ For ESLint integration, add to your flat config:
42
+ ```js
43
+ import tailwindCanonical from 'tailwind-canonical/eslint'
44
+
45
+ export default [tailwindCanonical.configs.recommended]
46
+ ```
47
+
48
+ For pre-commit automation, see the [Pre-commit hook](#pre-commit-hook-husky--lefthook) section below.
49
+
15
50
  ## CLI
16
51
 
17
52
  ```bash
53
+ # Scaffold a config in the current directory (.ts, or .js/.mjs on older Node)
54
+ npx tailwind-canonical init
55
+
18
56
  # Check for non-canonical arbitrary values
19
57
  npx tailwind-canonical ./src
20
58
 
@@ -38,6 +76,9 @@ npx tailwind-canonical --merge ./src
38
76
  # Combine: fix → dedup → merge → sort
39
77
  npx tailwind-canonical --fix --dedup --merge --sort ./src
40
78
 
79
+ # Dry-run: report what the transforms would change, write nothing
80
+ npx tailwind-canonical --check --fix --dedup --sort ./src
81
+
41
82
  # Cross-file consistency analysis
42
83
  npx tailwind-canonical --analyze ./src
43
84
 
@@ -63,6 +104,7 @@ An unmatched target exits `1` with `No files matched: <targets>`.
63
104
  | `--dedup` | Redundant classes, conflicts, shorthand collapse | `flex block` → `block`, `px-4 py-4` → `p-4`, `border-t-2 border-b-2` → `border-y-2` |
64
105
  | `--sort` | Canonical class order | `text-sm flex p-4` → `flex p-4 text-sm` |
65
106
  | `--merge` | tailwind-merge conflict resolution | `bg-red-500 bg-blue-500` → `bg-blue-500` |
107
+ | `--check` | Dry-run: report what the combined transforms would change, write nothing (exit 1 on pending changes) | `would fix src/Button.tsx (1 replacement)` |
66
108
  | `--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` |
67
109
  | `--watch` | Re-run on every file save (debounced 50ms) | `[12:34:01] src/Button.tsx — 2 changes applied` |
68
110
  | `--typos` | Likely misspelled color names (read-only; chains after transforms) | `text-gry-500` → `text-gray-500` [typo] |
@@ -98,6 +140,15 @@ npx tailwind-canonical --fix --reporter json ./src
98
140
  npx tailwind-canonical --reporter sarif ./src > results.sarif
99
141
  ```
100
142
 
143
+ **GitHub Actions integration:**
144
+ ```yaml
145
+ - run: npx tailwind-canonical --reporter sarif ./src > results.sarif
146
+ continue-on-error: true
147
+ - uses: github/codeql-action/upload-sarif@v3
148
+ with:
149
+ sarif_file: results.sarif
150
+ ```
151
+
101
152
  ## Typo detection (`--typos`)
102
153
 
103
154
  Flags likely misspelled Tailwind color names (Levenshtein distance 1, low false-positive bias). Read-only.
@@ -124,6 +175,23 @@ npx tailwind-canonical --fix --dedup --sort --typos ./src
124
175
 
125
176
  Exit code follows the typo scan (`1` on findings). With `--reporter json` the transform summary gains `typoTotal` and `typos` fields. `--analyze` remains exclusive: combining it with any other mode runs analyze only and warns. `--watch` is not supported with `--typos` (warned, single pass runs instead).
126
177
 
178
+ ## Dry-run mode (`--check`)
179
+
180
+ Combine `--check` with any of `--fix`/`--dedup`/`--merge`/`--sort` to report what those transforms would change without writing anything to disk. Useful as a CI gate:
181
+
182
+ ```yaml
183
+ # CI
184
+ - run: npx tailwind-canonical --check --fix --dedup --sort ./src
185
+ ```
186
+
187
+ ```bash
188
+ npx tailwind-canonical --check --fix --dedup --sort ./src
189
+ # would fix src/Button.tsx (1 replacement)
190
+ # ✖ 1 pending change across 1 file (run without --check to apply)
191
+ ```
192
+
193
+ Exits `1` when any file would change (blocking the pipeline), `0` when the tree is already canonical. `--check` requires at least one transform flag; `--check` alone errors. `--analyze` takes priority over `--check` (warned, ignored). `--watch` is not supported with `--check` (warned, single pass runs instead). `--typos` still chains after the check pass since it's read-only. With `--reporter json` the transform report gains `"check": true`.
194
+
127
195
  ## Suppression comments
128
196
 
129
197
  Skip specific lines or blocks from check and all transforms (`--fix`, `--dedup`, `--merge`, `--sort`):
@@ -288,6 +356,52 @@ left-2 right-2 → inset-x-2
288
356
 
289
357
  Non-divisible values (`h-[22px]`, `px-[7px]`) are left untouched.
290
358
 
359
+ ### Tailwind v3 vs v4 (`tailwindVersion`)
360
+
361
+ The ÷4 spacing rule assumes the **v4** scale, where every step is derived from
362
+ `calc(var(--spacing) * n)` and any integer works. v3 ships a discrete scale, so
363
+ `p-[52px]` → `p-13` compiles to nothing at all on v3 and the padding silently
364
+ disappears. Set `tailwindVersion: 3` on a v3 project:
365
+
366
+ ```ts
367
+ export default {
368
+ tailwindVersion: 3, // default: 4
369
+ }
370
+ ```
371
+
372
+ In v3 mode, suggestions are restricted to what v3.3 actually generates — the
373
+ scale differs per utility, not just per family:
374
+
375
+ | Class | `tailwindVersion: 4` | `tailwindVersion: 3` |
376
+ |---|---|---|
377
+ | `p-[16px]` | `p-4` | `p-4` |
378
+ | `p-[52px]` | `p-13` | untouched — not on the v3 scale |
379
+ | `max-w-[16px]` | `max-w-4` | untouched — scale arrived after v3.3 |
380
+ | `max-w-[0px]` | `max-w-0` | `max-w-0` — the zero step does exist |
381
+ | `size-[16px]` | `size-4` | untouched — no scale at all in v3.3 |
382
+ | `max-h-[16px]` | `max-h-4` | `max-h-4` |
383
+ | `max-w-[50%]` | `max-w-1/2` | untouched — no fractions in v3.3 |
384
+ | `top-[20%]` | `top-1/5` | untouched — v3.3 stops at quarters here |
385
+ | `top-[25%]` | `top-1/4` | `top-1/4` |
386
+ | `w-[8.333333%]` | `w-1/12` | `w-1/12` — `w` carries the full set |
387
+
388
+ Spacing steps: `size-*` has no numeric scale in v3.3, and `min-w-*`, `max-w-*`
389
+ and `min-h-*` only ship the zero step — they gained the scale afterwards.
390
+ Fractions: `w` carries all thirteen, `h` stops at sixths, `inset`/`top`/`left`/
391
+ `right`/`bottom`/`translate-x`/`translate-y` only have halves, thirds and
392
+ quarters, and `min-w`/`max-w`/`min-h`/`max-h` have none.
393
+
394
+ `text-*`, `rounded-*` and `opacity-*` are identical in both majors and are
395
+ never gated. `customSpacingTokens` stays your decision and is applied in both
396
+ modes. All of the above was measured against `tailwindcss@3.3.7`, the
397
+ advertised floor.
398
+
399
+ **On v3.4**, keep `tailwindVersion: 3`. v3.4 added the numeric scale to
400
+ `size-*`, `min-w-*`, `max-w-*` and `min-h-*`, but its spacing scale is still
401
+ discrete — `p-13` does not exist there either. So v3 mode is the safe setting
402
+ and merely leaves a few v3.4-valid suggestions on the table; `tailwindVersion:
403
+ 4` would emit steps v3.4 cannot generate.
404
+
291
405
  ## Config
292
406
 
293
407
  Create `tailwind-canonical.config.ts` at the root:
@@ -301,6 +415,8 @@ export default {
301
415
  customSpacingTokens: {
302
416
  14: '3.5',
303
417
  },
418
+ // Target Tailwind major: 3 restricts suggestions to the v3 scale (default: 4)
419
+ tailwindVersion: 4,
304
420
  // Support non-React attribute patterns (default: ['className'])
305
421
  attributeNames: ['className', 'class', ':class', 'tw'],
306
422
  // Support utility function wrappers
@@ -311,6 +427,7 @@ export default {
311
427
  dedup: true,
312
428
  merge: true,
313
429
  sort: true,
430
+ check: false, // dry-run mode: report changes without writing (useful for CI)
314
431
  targets: ['./src'],
315
432
  },
316
433
  // Tune --analyze reporting
@@ -338,7 +455,7 @@ export default {
338
455
  above, running `tailwind-canonical` is equivalent to
339
456
  `tailwind-canonical --fix --dedup --merge --sort ./src`. Explicit CLI flags and
340
457
  targets still override the config, so `tailwind-canonical --typos ./app` runs
341
- only typo detection on `./app`.
458
+ only typo detection on `./app`. Supported keys: `fix`, `dedup`, `merge`, `sort`, `check`, `analyze`, `typos`, `watch`, `reporter`, `targets`.
342
459
 
343
460
  `analyze` controls project-scale consistency reporting. `maxScaleGroups`,
344
461
  `maxScaleValues`, `maxRareValues`, and `maxPatterns` keep terminal output
@@ -351,8 +468,21 @@ files and at most 3 times.
351
468
  The older top-level rare-value keys are still accepted for compatibility, but
352
469
  new configs should prefer `analyze`.
353
470
 
354
- `tailwind-canonical.config.js` is still loaded as a fallback when no TypeScript
355
- config exists.
471
+ Accepted filenames, in resolution order: `tailwind-canonical.config.ts`,
472
+ `.mts`, `.js`, `.mjs`.
473
+
474
+ A `.ts` or `.mts` config needs Node's type stripping, which is on by default
475
+ from Node 22.18 and 23.6, and available behind `--experimental-strip-types`
476
+ from 22.6. Short of that use `.mjs`, or `.js` when your nearest `package.json`
477
+ declares `"type": "module"`; both load on every supported Node. `tailwind-
478
+ canonical init` scaffolds a filename that loads on a bare `node` — it will not
479
+ write `.ts` just because the process it runs in happens to carry the flag.
480
+
481
+ The config is resolved upward from the current directory: if no config file is
482
+ found in `cwd`, tailwind-canonical checks each parent directory in turn, until
483
+ it finds one, reaches a `.git` directory, or reaches the filesystem root. This
484
+ makes the CLI monorepo-friendly — a single config at the repo root applies to
485
+ every package.
356
486
 
357
487
  ## ESLint plugin
358
488
 
@@ -401,6 +531,12 @@ It **accepts but ignores**: `functionNames`, `attributeNames`, `sortOrder`.
401
531
  These are CLI-only options, allowed in the schema purely so a single shared
402
532
  config object can be passed without errors.
403
533
 
534
+ ## Known limits
535
+
536
+ - **`--watch` does not pick up files created after start** — the watcher initializes a file list once and monitors only existing files for changes. New files in the target directories are not detected until the watcher is restarted.
537
+ - **`--typos` covers Tailwind color names only** — color-name detection uses Levenshtein distance 1 against the built-in Tailwind color palette. Custom colors from your config are supported via `extraColors` in the config, but typo detection does not extend to properties, function names, or arbitrary modifiers.
538
+ - **`--merge` requires `tailwind-merge` as a peer dependency** — the plugin is optional, so the CLI will error if you use `--merge` without installing `tailwind-merge`. Install with `pnpm add -D tailwind-merge`.
539
+
404
540
  ## Pre-commit hook (Husky / Lefthook)
405
541
 
406
542
  ```yaml
@@ -423,6 +559,17 @@ pre-commit:
423
559
  npx tailwind-canonical --fix --dedup --sort ./src ./app
424
560
  ```
425
561
 
562
+ ### lint-staged
563
+
564
+ ```jsonc
565
+ // package.json
566
+ {
567
+ "lint-staged": {
568
+ "*.{jsx,tsx}": ["tailwind-canonical --fix --dedup --sort --typos"]
569
+ }
570
+ }
571
+ ```
572
+
426
573
  ## Dead code detection (knip)
427
574
 
428
575
  ```bash
@@ -435,15 +582,37 @@ Detects unused exports, files, and dependencies. Config in `knip.json`. Enforced
435
582
 
436
583
  ```ts
437
584
  import {
438
- suggestCanonical, // pure: class string → suggestion or null
439
- analyzeFile, // find non-canonical classes in a file
440
- fixFile, // apply --fix in-place
441
- deduplicateClasses, // pure: deduplicate a class string
442
- dedupeFile, // apply --dedup in-place
443
- sortClasses, // pure: sort a class string
444
- sortFile, // apply --sort in-place
445
- mergeFile, // apply --merge in-place (requires tailwind-merge)
446
- scanFiles, // recursive file scanner (sync, dir/file targets)
447
- resolveTargets, // async glob resolver with negation + brace expansion
585
+ suggestCanonical, // pure: class string → suggestion or null
586
+ analyzeFile, // find non-canonical classes in a file
587
+ fixFile, // apply --fix in-place
588
+ deduplicateClasses, // pure: deduplicate a class string
589
+ dedupeFile, // apply --dedup in-place
590
+ sortClasses, // pure: sort a class string
591
+ sortFile, // apply --sort in-place
592
+ DEFAULT_SORT_ORDER, // default sort category order (override via config sortOrder)
593
+ mergeFile, // apply --merge in-place (requires tailwind-merge)
594
+ scanFiles, // recursive file scanner (sync, dir/file targets)
595
+ resolveTargets, // async glob resolver with negation + brace expansion
596
+ loadConfig, // load + validate tailwind-canonical.config.{ts,mts,js,mjs} for a cwd
597
+ analyzeConsistency, // pure: cross-file consistency analysis (--analyze)
598
+ analyzeTyposFile, // find likely color-name typos in a file
599
+ detectTypo, // pure: check one class for a likely color-name typo
600
+ } from 'tailwind-canonical'
601
+
602
+ import type {
603
+ Config, // shared config object accepted across the API
604
+ Suggestion, // suggestCanonical's non-null return shape
605
+ Finding, // one entry returned by analyzeFile
606
+ ScanOptions, // extensions/ignore options for scanFiles/resolveTargets
607
+ SortCategory, // one entry of DEFAULT_SORT_ORDER / config.sortOrder
608
+ TypoFinding, // one entry returned by analyzeTyposFile
609
+ ConsistencyOptions, // options accepted by analyzeConsistency
610
+ ConsistencyReport, // analyzeConsistency's return shape
611
+ FileClasses, // per-file input shape for analyzeConsistency
612
+ ColorVariant, // one variant inside a ConsistencyReport color group
613
+ ColorVariantGroup, // one color-family group in ConsistencyReport.colorVariants
614
+ ScaleInconsistency, // one entry in ConsistencyReport.scaleInconsistencies
615
+ ScaleValue, // one value inside a ScaleInconsistency
616
+ ClassCombination, // one repeated-pattern entry in ConsistencyReport.combinations
448
617
  } from 'tailwind-canonical'
449
618
  ```
package/dist/cli/cli.d.ts CHANGED
@@ -16,6 +16,7 @@ type Flags = {
16
16
  watch: boolean;
17
17
  analyze: boolean;
18
18
  typos: boolean;
19
+ check: boolean;
19
20
  help: boolean;
20
21
  version: boolean;
21
22
  reporter: Reporter;
@@ -24,6 +25,7 @@ type Flags = {
24
25
  hasExplicitMode: boolean;
25
26
  hasExplicitReporter: boolean;
26
27
  hasExplicitWatch: boolean;
28
+ hasExplicitCheck: boolean;
27
29
  };
28
30
  export declare function parseArgs(argv: string[]): Flags;
29
31
  /**
@@ -36,4 +38,3 @@ export declare function parseArgs(argv: string[]): Flags;
36
38
  export declare function flagWarnings(flags: Flags): string[];
37
39
  export declare function run(argv: string[], cwd: string, sink?: Sink): Promise<RunResult>;
38
40
  export {};
39
- //# sourceMappingURL=cli.d.ts.map