tailwind-canonical 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fahari Hamada Sidi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # tailwind-canonical
2
+
3
+ Lint and auto-fix Tailwind CSS arbitrary values that have canonical equivalents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add -D tailwind-canonical
9
+ npm install -D tailwind-canonical
10
+ yarn add -D tailwind-canonical
11
+ ```
12
+
13
+ ## CLI
14
+
15
+ ```bash
16
+ # Check
17
+ npx tailwind-canonical ./src
18
+
19
+ # Auto-fix
20
+ npx tailwind-canonical --fix ./src
21
+ ```
22
+
23
+ ## Output
24
+
25
+ ```
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
+
30
+ ✖ Found 3 non-canonical classes
31
+ Run with --fix to auto-replace
32
+ ```
33
+
34
+ ## Config
35
+
36
+ Create `tailwind-canonical.config.js` at the root:
37
+
38
+ ```js
39
+ export default {
40
+ customTextTokens: {
41
+ 10: '3xs',
42
+ 11: '2xs',
43
+ 13: 'xxs',
44
+ },
45
+ }
46
+ ```
47
+
48
+ ## ESLint plugin
49
+
50
+ ```js
51
+ // eslint.config.js
52
+ import tailwindCanonical from 'tailwind-canonical/eslint'
53
+
54
+ export default [
55
+ {
56
+ plugins: { 'tailwind-canonical': tailwindCanonical },
57
+ rules: {
58
+ 'tailwind-canonical/no-arbitrary-canonical': 'warn',
59
+ },
60
+ },
61
+ ]
62
+ ```
63
+
64
+ ## Pre-commit hook (Husky)
65
+
66
+ ```bash
67
+ # .husky/pre-commit
68
+ npx tailwind-canonical ./src ./app
69
+ ```
70
+
71
+ ## What gets flagged
72
+
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 |
82
+
83
+ Non-divisible values (`h-[22px]`, `px-[7px]`) are left untouched.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import { analyzeFile } from '../core/analyzer.js';
3
+ import { fixFile } from '../core/fixer.js';
4
+ import { scanFiles } from '../core/scanner.js';
5
+ const args = process.argv.slice(2);
6
+ const fix = args.includes('--fix');
7
+ const targets = args.filter((a) => !a.startsWith('--'));
8
+ if (targets.length === 0) {
9
+ console.error('Usage: tailwind-canonical [--fix] <dir|file> [dir|file...]');
10
+ process.exit(1);
11
+ }
12
+ let config = {};
13
+ try {
14
+ const { default: userConfig } = await import(new URL(`file://${process.cwd()}/tailwind-canonical.config.js`).href);
15
+ config = userConfig;
16
+ }
17
+ catch { }
18
+ const files = targets.flatMap((t) => scanFiles(t));
19
+ let totalFindings = 0;
20
+ let totalFixed = 0;
21
+ for (const file of files) {
22
+ if (fix) {
23
+ const count = fixFile(file, config);
24
+ if (count > 0) {
25
+ console.log(` fixed ${file} (${count} replacement${count > 1 ? 's' : ''})`);
26
+ totalFixed += count;
27
+ }
28
+ }
29
+ else {
30
+ const findings = analyzeFile(file, config);
31
+ for (const f of findings) {
32
+ const tag = f.suggestion.isCustomToken ? ' [custom token]' : '';
33
+ console.log(` ${f.file}:${f.line}:${f.col} ${f.suggestion.original} → ${f.suggestion.canonical}${tag}`);
34
+ }
35
+ totalFindings += findings.length;
36
+ }
37
+ }
38
+ if (fix) {
39
+ console.log(`\n✓ Fixed ${totalFixed} occurrence${totalFixed !== 1 ? 's' : ''} across ${files.length} files`);
40
+ }
41
+ else if (totalFindings > 0) {
42
+ console.log(`\n✖ Found ${totalFindings} non-canonical class${totalFindings !== 1 ? 'es' : ''}`);
43
+ console.log(' Run with --fix to auto-replace\n');
44
+ process.exit(1);
45
+ }
46
+ else {
47
+ console.log('✓ No non-canonical classes found');
48
+ }
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,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,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AAExD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IACzB,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,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;AACtB,CAAC;AAAC,MAAM,CAAC,CAAA,CAAC;AAEV,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,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,OAAO,CAAC,GAAG,CACT,YAAY,IAAI,KAAK,KAAK,eAAe,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CACjE,CAAC;YACF,UAAU,IAAI,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,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;QACJ,CAAC;QACD,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC;IACnC,CAAC;AACH,CAAC;AAED,IAAI,GAAG,EAAE,CAAC;IACR,OAAO,CAAC,GAAG,CACT,aAAa,UAAU,cAAc,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,MAAM,QAAQ,CAChG,CAAC;AACJ,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"}
@@ -0,0 +1,9 @@
1
+ import { type Config, type Suggestion } from './rules.js';
2
+ export type Finding = {
3
+ file: string;
4
+ line: number;
5
+ col: number;
6
+ suggestion: Suggestion;
7
+ };
8
+ export declare function analyzeFile(filePath: string, config?: Config): Finding[];
9
+ //# sourceMappingURL=analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/core/analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAoB,MAAM,YAAY,CAAC;AAE5E,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AA6CF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,EAAE,CAY5E"}
@@ -0,0 +1,38 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { suggestCanonical } from './rules.js';
3
+ const CLASS_REGEX = /className(?:Name)?\s*=\s*(?:"([^"]+)"|'([^']+)'|`([^`]+)`|\{([^}]+)\})/g;
4
+ const SINGLE_CLASS_REGEX = /[^\s"'`{}]+/g;
5
+ function extractClasses(content) {
6
+ const found = [];
7
+ CLASS_REGEX.lastIndex = 0;
8
+ for (let match = CLASS_REGEX.exec(content); match !== null; match = CLASS_REGEX.exec(content)) {
9
+ const raw = match[1] ?? match[2] ?? match[3] ?? match[4] ?? '';
10
+ SINGLE_CLASS_REGEX.lastIndex = 0;
11
+ for (let clsMatch = SINGLE_CLASS_REGEX.exec(raw); clsMatch !== null; clsMatch = SINGLE_CLASS_REGEX.exec(raw)) {
12
+ found.push({
13
+ cls: clsMatch[0],
14
+ index: match.index + match[0].indexOf(raw) + clsMatch.index,
15
+ });
16
+ }
17
+ }
18
+ return found;
19
+ }
20
+ function indexToLineCol(content, index) {
21
+ const before = content.slice(0, index);
22
+ const line = before.split('\n').length;
23
+ const col = index - before.lastIndexOf('\n');
24
+ return { line, col };
25
+ }
26
+ export function analyzeFile(filePath, config = {}) {
27
+ const content = readFileSync(filePath, 'utf8');
28
+ const findings = [];
29
+ for (const { cls, index } of extractClasses(content)) {
30
+ const suggestion = suggestCanonical(cls, config);
31
+ if (!suggestion)
32
+ continue;
33
+ const { line, col } = indexToLineCol(content, index);
34
+ findings.push({ file: filePath, line, col, suggestion });
35
+ }
36
+ return findings;
37
+ }
38
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/core/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAgC,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAS5E,MAAM,WAAW,GACf,yEAAyE,CAAC;AAC5E,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C,SAAS,cAAc,CACrB,OAAe;IAEf,MAAM,KAAK,GAA0C,EAAE,CAAC;IAExD,WAAW,CAAC,SAAS,GAAG,CAAC,CAAC;IAE1B,KACE,IAAI,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EACrC,KAAK,KAAK,IAAI,EACd,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EACjC,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/D,kBAAkB,CAAC,SAAS,GAAG,CAAC,CAAC;QACjC,KACE,IAAI,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAC3C,QAAQ,KAAK,IAAI,EACjB,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EACvC,CAAC;YACD,KAAK,CAAC,IAAI,CAAC;gBACT,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK;aAC5D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CACrB,OAAe,EACf,KAAa;IAEb,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IACvC,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAiB,EAAE;IAC/D,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,MAAM,UAAU,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACrD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Config } from './rules.js';
2
+ export declare function fixFile(filePath: string, config?: Config): number;
3
+ //# sourceMappingURL=fixer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixer.d.ts","sourceRoot":"","sources":["../../src/core/fixer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAoB,MAAM,YAAY,CAAC;AAE3D,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,MAAM,CAyBrE"}
@@ -0,0 +1,27 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { suggestCanonical } from './rules.js';
3
+ export function fixFile(filePath, config = {}) {
4
+ let content = readFileSync(filePath, 'utf8');
5
+ let count = 0;
6
+ const CLASS_ATTR_REGEX = /className\s*=\s*(?:"([^"]+)"|'([^']+)'|`([^`]+)`)/g;
7
+ content = content.replace(CLASS_ATTR_REGEX, (full, dq, sq, bt) => {
8
+ const raw = dq ?? sq ?? bt ?? '';
9
+ const quote = dq !== undefined ? '"' : sq !== undefined ? "'" : '`';
10
+ const fixed = raw.replace(/[^\s]+/g, (cls) => {
11
+ const suggestion = suggestCanonical(cls, config);
12
+ if (suggestion) {
13
+ count++;
14
+ return suggestion.canonical;
15
+ }
16
+ return cls;
17
+ });
18
+ return full
19
+ .replace(raw, fixed)
20
+ .replace(/["'`]/, quote)
21
+ .replace(/["'`]$/, quote);
22
+ });
23
+ if (count > 0)
24
+ writeFileSync(filePath, content, 'utf8');
25
+ return count;
26
+ }
27
+ //# sourceMappingURL=fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixer.js","sourceRoot":"","sources":["../../src/core/fixer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAe,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,SAAiB,EAAE;IAC3D,IAAI,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,gBAAgB,GAAG,oDAAoD,CAAC;IAE9E,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QAC/D,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACpE,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,GAAW,EAAE,EAAE;YACnD,MAAM,UAAU,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACjD,IAAI,UAAU,EAAE,CAAC;gBACf,KAAK,EAAE,CAAC;gBACR,OAAO,UAAU,CAAC,SAAS,CAAC;YAC9B,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QACH,OAAO,IAAI;aACR,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC;aACnB,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;aACvB,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,IAAI,KAAK,GAAG,CAAC;QAAE,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACxD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,12 @@
1
+ export type Suggestion = {
2
+ original: string;
3
+ canonical: string;
4
+ isCustomToken: boolean;
5
+ };
6
+ export type Config = {
7
+ customTextTokens?: Record<number, string>;
8
+ customSpacingTokens?: Record<number, string>;
9
+ ignorePatterns?: RegExp[];
10
+ };
11
+ export declare function suggestCanonical(cls: string, config?: Config): Suggestion | null;
12
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/core/rules.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AA6CF,MAAM,MAAM,MAAM,GAAG;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAW,GAClB,UAAU,GAAG,IAAI,CAyFnB"}
@@ -0,0 +1,124 @@
1
+ const TEXT_SIZE_MAP = {
2
+ 8: '3xs',
3
+ 9: '3xs',
4
+ 10: '3xs',
5
+ 11: '2xs',
6
+ 12: 'xs',
7
+ 13: 'xxs',
8
+ 14: 'sm',
9
+ 16: 'base',
10
+ 18: 'lg',
11
+ 20: 'xl',
12
+ 24: '2xl',
13
+ 30: '3xl',
14
+ 36: '4xl',
15
+ 48: '5xl',
16
+ 60: '6xl',
17
+ 72: '7xl',
18
+ };
19
+ const BUILT_IN_TEXT = {
20
+ 12: 'xs',
21
+ 14: 'sm',
22
+ 16: 'base',
23
+ 18: 'lg',
24
+ 20: 'xl',
25
+ 24: '2xl',
26
+ 30: '3xl',
27
+ 36: '4xl',
28
+ 48: '5xl',
29
+ 60: '6xl',
30
+ 72: '7xl',
31
+ };
32
+ const ROUNDED_MAP = {
33
+ 2: 'sm',
34
+ 4: 'sm',
35
+ 6: 'md',
36
+ 8: 'lg',
37
+ 12: 'xl',
38
+ 16: '2xl',
39
+ 24: '3xl',
40
+ };
41
+ export function suggestCanonical(cls, config = {}) {
42
+ const textTokens = { ...TEXT_SIZE_MAP, ...config.customTextTokens };
43
+ const spacingTokens = config.customSpacingTokens ?? {};
44
+ const textMatch = cls.match(/^(text)-\[(\d+)px\]$/);
45
+ if (textMatch) {
46
+ const px = parseInt(textMatch[2], 10);
47
+ const token = textTokens[px];
48
+ if (!token)
49
+ return null;
50
+ const isCustomToken = !BUILT_IN_TEXT[px];
51
+ return { original: cls, canonical: `text-${token}`, isCustomToken };
52
+ }
53
+ const spacingPrefixes = [
54
+ 'h',
55
+ 'w',
56
+ 'p',
57
+ 'px',
58
+ 'py',
59
+ 'pt',
60
+ 'pb',
61
+ 'pl',
62
+ 'pr',
63
+ 'm',
64
+ 'mx',
65
+ 'my',
66
+ 'mt',
67
+ 'mb',
68
+ 'ml',
69
+ 'mr',
70
+ 'gap',
71
+ 'gap-x',
72
+ 'gap-y',
73
+ 'top',
74
+ 'left',
75
+ 'right',
76
+ 'bottom',
77
+ 'inset',
78
+ 'size',
79
+ 'min-h',
80
+ 'max-h',
81
+ 'min-w',
82
+ 'max-w',
83
+ 'translate-x',
84
+ 'translate-y',
85
+ 'space-x',
86
+ 'space-y',
87
+ ];
88
+ const spacingMatch = cls.match(new RegExp(`^(${spacingPrefixes.join('|')})-\\[(\\d+)px\\]$`));
89
+ if (spacingMatch) {
90
+ const prefix = spacingMatch[1];
91
+ const px = parseInt(spacingMatch[2], 10);
92
+ if (spacingTokens[px]) {
93
+ return {
94
+ original: cls,
95
+ canonical: `${prefix}-${spacingTokens[px]}`,
96
+ isCustomToken: true,
97
+ };
98
+ }
99
+ if (px % 4 === 0) {
100
+ const unit = px / 4;
101
+ return {
102
+ original: cls,
103
+ canonical: `${prefix}-${unit}`,
104
+ isCustomToken: false,
105
+ };
106
+ }
107
+ return null;
108
+ }
109
+ const roundedMatch = cls.match(/^(rounded|rounded-[a-z]+)-\[(\d+)px\]$/);
110
+ if (roundedMatch) {
111
+ const prefix = roundedMatch[1];
112
+ const px = parseInt(roundedMatch[2], 10);
113
+ const token = ROUNDED_MAP[px];
114
+ if (!token)
115
+ return null;
116
+ return {
117
+ original: cls,
118
+ canonical: `${prefix.replace(/-$/, '')}-${token}`,
119
+ isCustomToken: false,
120
+ };
121
+ }
122
+ return null;
123
+ }
124
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/core/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export type ScanOptions = {
2
+ extensions?: string[];
3
+ ignore?: string[];
4
+ };
5
+ export declare function scanFiles(dir: string, options?: ScanOptions): string[];
6
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,EAAE,CAuB1E"}
@@ -0,0 +1,36 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const DEFAULT_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js', '.vue', '.svelte'];
4
+ const DEFAULT_IGNORE = [
5
+ 'node_modules',
6
+ '.next',
7
+ 'dist',
8
+ '.git',
9
+ 'build',
10
+ 'coverage',
11
+ ];
12
+ export function scanFiles(dir, options = {}) {
13
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
14
+ const ignore = options.ignore ?? DEFAULT_IGNORE;
15
+ const files = [];
16
+ function walk(current) {
17
+ const entries = readdirSync(current, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ if (ignore.includes(entry.name))
20
+ continue;
21
+ const full = join(current, entry.name);
22
+ if (entry.isDirectory()) {
23
+ walk(full);
24
+ }
25
+ else if (extensions.some((ext) => entry.name.endsWith(ext))) {
26
+ files.push(full);
27
+ }
28
+ }
29
+ }
30
+ const stat = statSync(dir);
31
+ if (stat.isFile())
32
+ return extensions.some((ext) => dir.endsWith(ext)) ? [dir] : [];
33
+ walk(dir);
34
+ return files;
35
+ }
36
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAC7E,MAAM,cAAc,GAAG;IACrB,cAAc;IACd,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,UAAU;CACX,CAAC;AAOF,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,UAAuB,EAAE;IAC9D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,kBAAkB,CAAC;IAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;IAChD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,SAAS,IAAI,CAAC,OAAe;QAC3B,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,CAAC,IAAI,CAAC,CAAC;YACb,CAAC;iBAAM,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC9D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,MAAM,EAAE;QACf,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { type Config } from '../core/rules.js';
2
+ type RuleContext = {
3
+ options: [Config?];
4
+ report: (descriptor: {
5
+ node: unknown;
6
+ message: string;
7
+ fix?: (fixer: {
8
+ replaceText: (node: unknown, text: string) => unknown;
9
+ }) => unknown;
10
+ }) => void;
11
+ };
12
+ declare const _default: {
13
+ rules: {
14
+ 'no-arbitrary-canonical': {
15
+ meta: {
16
+ type: "suggestion";
17
+ fixable: "code";
18
+ schema: {
19
+ type: string;
20
+ properties: {
21
+ customTextTokens: {
22
+ type: string;
23
+ };
24
+ customSpacingTokens: {
25
+ type: string;
26
+ };
27
+ };
28
+ additionalProperties: boolean;
29
+ }[];
30
+ messages: {
31
+ useCanonical: string;
32
+ };
33
+ };
34
+ create(context: RuleContext): {
35
+ Literal: (node: {
36
+ value: unknown;
37
+ raw?: string;
38
+ type: string;
39
+ }) => void;
40
+ TemplateLiteral(node: {
41
+ quasis: Array<{
42
+ value: {
43
+ raw: string;
44
+ };
45
+ type: string;
46
+ }>;
47
+ }): void;
48
+ };
49
+ };
50
+ };
51
+ configs: {
52
+ recommended: {
53
+ plugins: string[];
54
+ rules: {
55
+ 'tailwind-canonical/no-arbitrary-canonical': string;
56
+ };
57
+ };
58
+ };
59
+ };
60
+ export default _default;
61
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/eslint/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAoB,MAAM,kBAAkB,CAAC;AAEjE,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IACnB,MAAM,EAAE,CAAC,UAAU,EAAE;QACnB,IAAI,EAAE,OAAO,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE;YACZ,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;SACvD,KAAK,OAAO,CAAC;KACf,KAAK,IAAI,CAAC;CACZ,CAAC;;;;;;;;;;;;;;;;;;;;;;;4BAqBgB,WAAW;gCAGG;oBAC1B,KAAK,EAAE,OAAO,CAAC;oBACf,GAAG,CAAC,EAAE,MAAM,CAAC;oBACb,IAAI,EAAE,MAAM,CAAC;iBACd;sCAqBuB;oBACpB,MAAM,EAAE,KAAK,CAAC;wBACZ,KAAK,EAAE;4BAAE,GAAG,EAAE,MAAM,CAAA;yBAAE,CAAC;wBACvB,IAAI,EAAE,MAAM,CAAC;qBACd,CAAC,CAAC;iBACJ;;;;;;;;;;;;;AASP,wBAYE"}
@@ -0,0 +1,65 @@
1
+ import { suggestCanonical } from '../core/rules.js';
2
+ const noArbitraryCanonical = {
3
+ meta: {
4
+ type: 'suggestion',
5
+ fixable: 'code',
6
+ schema: [
7
+ {
8
+ type: 'object',
9
+ properties: {
10
+ customTextTokens: { type: 'object' },
11
+ customSpacingTokens: { type: 'object' },
12
+ },
13
+ additionalProperties: false,
14
+ },
15
+ ],
16
+ messages: {
17
+ useCanonical: "Use canonical class '{{canonical}}' instead of '{{original}}'",
18
+ },
19
+ },
20
+ create(context) {
21
+ const config = context.options[0] ?? {};
22
+ function checkLiteral(node) {
23
+ if (typeof node.value !== 'string')
24
+ return;
25
+ const classes = node.value.split(/\s+/);
26
+ for (const cls of classes) {
27
+ const suggestion = suggestCanonical(cls, config);
28
+ if (!suggestion)
29
+ continue;
30
+ context.report({
31
+ node,
32
+ message: `Use canonical class '${suggestion.canonical}' instead of '${suggestion.original}'`,
33
+ fix(fixer) {
34
+ const fixed = node.value;
35
+ const newVal = fixed.replace(cls, suggestion.canonical);
36
+ const quote = node.raw?.startsWith('"') ? '"' : "'";
37
+ return fixer.replaceText(node, `${quote}${newVal}${quote}`);
38
+ },
39
+ });
40
+ }
41
+ }
42
+ return {
43
+ Literal: checkLiteral,
44
+ TemplateLiteral(node) {
45
+ for (const quasi of node.quasis) {
46
+ checkLiteral({ value: quasi.value.raw, type: 'Literal' });
47
+ }
48
+ },
49
+ };
50
+ },
51
+ };
52
+ export default {
53
+ rules: {
54
+ 'no-arbitrary-canonical': noArbitraryCanonical,
55
+ },
56
+ configs: {
57
+ recommended: {
58
+ plugins: ['tailwind-canonical'],
59
+ rules: {
60
+ 'tailwind-canonical/no-arbitrary-canonical': 'warn',
61
+ },
62
+ },
63
+ },
64
+ };
65
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/eslint/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAajE,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE;QACJ,IAAI,EAAE,YAAqB;QAC3B,OAAO,EAAE,MAAe;QACxB,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;oBACpC,mBAAmB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;iBACxC;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;QACD,QAAQ,EAAE;YACR,YAAY,EACV,+DAA+D;SAClE;KACF;IACD,MAAM,CAAC,OAAoB;QACzB,MAAM,MAAM,GAAW,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEhD,SAAS,YAAY,CAAC,IAIrB;YACC,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;gBAAE,OAAO;YAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACxC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,MAAM,UAAU,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACjD,IAAI,CAAC,UAAU;oBAAE,SAAS;gBAC1B,OAAO,CAAC,MAAM,CAAC;oBACb,IAAI;oBACJ,OAAO,EAAE,wBAAwB,UAAU,CAAC,SAAS,iBAAiB,UAAU,CAAC,QAAQ,GAAG;oBAC5F,GAAG,CAAC,KAAK;wBACP,MAAM,KAAK,GAAG,IAAI,CAAC,KAAe,CAAC;wBACnC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;wBACxD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;wBACpD,OAAO,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC;oBAC9D,CAAC;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO;YACL,OAAO,EAAE,YAAY;YACrB,eAAe,CAAC,IAKf;gBACC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChC,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe;IACb,KAAK,EAAE;QACL,wBAAwB,EAAE,oBAAoB;KAC/C;IACD,OAAO,EAAE;QACP,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,oBAAoB,CAAC;YAC/B,KAAK,EAAE;gBACL,2CAA2C,EAAE,MAAM;aACpD;SACF;KACF;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ export type { Finding } from './core/analyzer.js';
2
+ export { analyzeFile } from './core/analyzer.js';
3
+ export { fixFile } from './core/fixer.js';
4
+ export type { Config, Suggestion } from './core/rules.js';
5
+ export { suggestCanonical } from './core/rules.js';
6
+ export { scanFiles } from './core/scanner.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { analyzeFile } from './core/analyzer.js';
2
+ export { fixFile } from './core/fixer.js';
3
+ export { suggestCanonical } from './core/rules.js';
4
+ export { scanFiles } from './core/scanner.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "tailwind-canonical",
3
+ "version": "0.1.0",
4
+ "description": "Lint and auto-fix Tailwind CSS arbitrary values that have canonical equivalents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./eslint": {
14
+ "import": "./dist/eslint/plugin.js",
15
+ "types": "./dist/eslint/plugin.d.ts"
16
+ }
17
+ },
18
+ "bin": {
19
+ "tailwind-canonical": "./dist/cli/index.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/peak-lab/tailwind-canonical.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/peak-lab/tailwind-canonical/issues"
32
+ },
33
+ "homepage": "https://github.com/peak-lab/tailwind-canonical#readme",
34
+ "author": "Fahari Hamada Sidi",
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "keywords": [
42
+ "tailwind",
43
+ "tailwindcss",
44
+ "linter",
45
+ "canonical",
46
+ "arbitrary-values",
47
+ "eslint-plugin"
48
+ ],
49
+ "license": "MIT",
50
+ "devDependencies": {
51
+ "@biomejs/biome": "^2.4.16",
52
+ "@types/node": "^22.0.0",
53
+ "lefthook": "^2.1.8",
54
+ "tsx": "^4.0.0",
55
+ "typescript": "^6.0.0"
56
+ },
57
+ "peerDependencies": {
58
+ "eslint": ">=8.0.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "eslint": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "scripts": {
66
+ "build": "tsc",
67
+ "dev": "tsc --watch",
68
+ "test": "node --import=tsx/esm --test \"src/**/*.test.ts\"",
69
+ "typecheck": "tsc --noEmit",
70
+ "lint": "biome check src/",
71
+ "lint:fix": "biome check --write src/"
72
+ }
73
+ }