tailwind-canonical 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -21
- package/dist/cli/cli.d.ts +2 -12
- package/dist/cli/cli.js +241 -512
- package/dist/cli/format.d.ts +3 -0
- package/dist/cli/format.js +11 -0
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -1
- package/dist/cli/reporters.d.ts +69 -0
- package/dist/cli/reporters.js +333 -0
- package/dist/cli/types.d.ts +16 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/watch.d.ts +11 -0
- package/dist/cli/watch.js +33 -0
- package/dist/core/analyzer.d.ts +0 -1
- package/dist/core/analyzer.js +3 -3
- package/dist/core/class-strings.d.ts +0 -1
- package/dist/core/class-strings.js +13 -8
- package/dist/core/config.d.ts +2 -3
- package/dist/core/config.js +34 -44
- package/dist/core/consistency.d.ts +1 -2
- package/dist/core/consistency.js +1 -6
- package/dist/core/deduplicator.d.ts +0 -1
- package/dist/core/deduplicator.js +119 -175
- package/dist/core/fixer.d.ts +0 -1
- package/dist/core/fixer.js +0 -1
- package/dist/core/lexicon.d.ts +1 -1
- package/dist/core/lexicon.js +1 -2
- package/dist/core/merger.d.ts +0 -1
- package/dist/core/merger.js +41 -29
- package/dist/core/rules.d.ts +2 -1
- package/dist/core/rules.js +90 -57
- package/dist/core/scanner.d.ts +0 -1
- package/dist/core/scanner.js +23 -21
- package/dist/core/sorter.d.ts +0 -1
- package/dist/core/sorter.js +85 -10
- package/dist/core/suppressions.d.ts +1 -3
- package/dist/core/suppressions.js +17 -14
- package/dist/core/typos.d.ts +0 -1
- package/dist/core/typos.js +3 -3
- package/dist/eslint/plugin.d.ts +0 -1
- package/dist/eslint/plugin.js +64 -30
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/io/analyzer.d.ts +0 -1
- package/dist/io/analyzer.js +0 -1
- package/dist/io/config.d.ts +9 -1
- package/dist/io/config.js +66 -5
- package/dist/io/consistency.d.ts +0 -1
- package/dist/io/consistency.js +0 -1
- package/dist/io/deduplicator.d.ts +0 -1
- package/dist/io/deduplicator.js +0 -1
- package/dist/io/fixer.d.ts +0 -1
- package/dist/io/fixer.js +0 -1
- package/dist/io/merger.d.ts +0 -1
- package/dist/io/merger.js +0 -1
- package/dist/io/scanner.d.ts +0 -1
- package/dist/io/scanner.js +0 -1
- package/dist/io/sorter.d.ts +0 -1
- package/dist/io/sorter.js +0 -1
- package/dist/io/typos.d.ts +0 -1
- package/dist/io/typos.js +0 -1
- package/package.json +10 -9
- package/dist/cli/cli.d.ts.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/core/analyzer.d.ts.map +0 -1
- package/dist/core/analyzer.js.map +0 -1
- package/dist/core/class-strings.d.ts.map +0 -1
- package/dist/core/class-strings.js.map +0 -1
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/consistency.d.ts.map +0 -1
- package/dist/core/consistency.js.map +0 -1
- package/dist/core/deduplicator.d.ts.map +0 -1
- package/dist/core/deduplicator.js.map +0 -1
- package/dist/core/fixer.d.ts.map +0 -1
- package/dist/core/fixer.js.map +0 -1
- package/dist/core/lexicon.d.ts.map +0 -1
- package/dist/core/lexicon.js.map +0 -1
- package/dist/core/merger.d.ts.map +0 -1
- package/dist/core/merger.js.map +0 -1
- package/dist/core/rules.d.ts.map +0 -1
- package/dist/core/rules.js.map +0 -1
- package/dist/core/scanner.d.ts.map +0 -1
- package/dist/core/scanner.js.map +0 -1
- package/dist/core/sorter.d.ts.map +0 -1
- package/dist/core/sorter.js.map +0 -1
- package/dist/core/suppressions.d.ts.map +0 -1
- package/dist/core/suppressions.js.map +0 -1
- package/dist/core/typos.d.ts.map +0 -1
- package/dist/core/typos.js.map +0 -1
- package/dist/eslint/plugin.d.ts.map +0 -1
- package/dist/eslint/plugin.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/io/analyzer.d.ts.map +0 -1
- package/dist/io/analyzer.js.map +0 -1
- package/dist/io/config.d.ts.map +0 -1
- package/dist/io/config.js.map +0 -1
- package/dist/io/consistency.d.ts.map +0 -1
- package/dist/io/consistency.js.map +0 -1
- package/dist/io/deduplicator.d.ts.map +0 -1
- package/dist/io/deduplicator.js.map +0 -1
- package/dist/io/fixer.d.ts.map +0 -1
- package/dist/io/fixer.js.map +0 -1
- package/dist/io/merger.d.ts.map +0 -1
- package/dist/io/merger.js.map +0 -1
- package/dist/io/scanner.d.ts.map +0 -1
- package/dist/io/scanner.js.map +0 -1
- package/dist/io/sorter.d.ts.map +0 -1
- package/dist/io/sorter.js.map +0 -1
- package/dist/io/typos.d.ts.map +0 -1
- package/dist/io/typos.js.map +0 -1
package/dist/core/config.js
CHANGED
|
@@ -2,7 +2,9 @@ import { DEFAULT_SORT_ORDER } from './sorter.js';
|
|
|
2
2
|
const CONFIG_FILENAME = 'tailwind-canonical.config.ts';
|
|
3
3
|
export const CONFIG_FILENAMES = [
|
|
4
4
|
CONFIG_FILENAME,
|
|
5
|
+
'tailwind-canonical.config.mts',
|
|
5
6
|
'tailwind-canonical.config.js',
|
|
7
|
+
'tailwind-canonical.config.mjs',
|
|
6
8
|
];
|
|
7
9
|
const KNOWN_KEYS = [
|
|
8
10
|
'customTextTokens',
|
|
@@ -19,6 +21,7 @@ const KNOWN_KEYS = [
|
|
|
19
21
|
'rareScaleMaxFiles',
|
|
20
22
|
'rareScaleMaxCount',
|
|
21
23
|
'defaultCommand',
|
|
24
|
+
'tailwindVersion',
|
|
22
25
|
];
|
|
23
26
|
const ANALYZE_KEYS = [
|
|
24
27
|
'minRareScalePropertyOccurrences',
|
|
@@ -29,8 +32,7 @@ const ANALYZE_KEYS = [
|
|
|
29
32
|
'maxRareValues',
|
|
30
33
|
'maxPatterns',
|
|
31
34
|
];
|
|
32
|
-
const
|
|
33
|
-
const DEFAULT_COMMAND_KEYS = [
|
|
35
|
+
const DEFAULT_COMMAND_BOOLEAN_KEYS = [
|
|
34
36
|
'fix',
|
|
35
37
|
'merge',
|
|
36
38
|
'dedup',
|
|
@@ -39,21 +41,29 @@ const DEFAULT_COMMAND_KEYS = [
|
|
|
39
41
|
'analyze',
|
|
40
42
|
'typos',
|
|
41
43
|
'watch',
|
|
44
|
+
];
|
|
45
|
+
const DEFAULT_COMMAND_KEYS = [
|
|
46
|
+
...DEFAULT_COMMAND_BOOLEAN_KEYS,
|
|
42
47
|
'reporter',
|
|
43
48
|
'targets',
|
|
44
49
|
];
|
|
45
|
-
const DEFAULT_COMMAND_KEY_SET = new Set(DEFAULT_COMMAND_KEYS);
|
|
46
50
|
const REPORTERS = new Set(['text', 'json', 'sarif']);
|
|
47
|
-
const KNOWN_KEY_SET = new Set(KNOWN_KEYS);
|
|
48
51
|
const SORT_CATEGORIES = new Set(DEFAULT_SORT_ORDER);
|
|
49
|
-
function invalidConfig(filename, message) {
|
|
50
|
-
return new Error(`Invalid ${filename}: ${message}`);
|
|
51
|
-
}
|
|
52
52
|
function fail(filename, message) {
|
|
53
|
-
throw
|
|
53
|
+
throw new Error(`Invalid ${filename}: ${message}`);
|
|
54
|
+
}
|
|
55
|
+
function isPlainObject(value) {
|
|
56
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
function assertKnownKeys(cfg, known, label, filename) {
|
|
59
|
+
for (const key of Object.keys(cfg)) {
|
|
60
|
+
if (!known.includes(key)) {
|
|
61
|
+
fail(filename, `${label} "${key}" (expected one of: ${known.join(', ')})`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
function assertPxTokenMap(value, key, filename) {
|
|
56
|
-
if (
|
|
66
|
+
if (!isPlainObject(value)) {
|
|
57
67
|
fail(filename, `${key} must be an object mapping px numbers to token names`);
|
|
58
68
|
}
|
|
59
69
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -74,7 +84,7 @@ function assertRegExpArray(value, key, filename) {
|
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
function assertStringRecord(value, key, filename) {
|
|
77
|
-
if (
|
|
87
|
+
if (!isPlainObject(value)) {
|
|
78
88
|
fail(filename, `${key} must be an object mapping strings to strings`);
|
|
79
89
|
}
|
|
80
90
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -88,40 +98,20 @@ function assertPositiveInteger(value, key, filename) {
|
|
|
88
98
|
}
|
|
89
99
|
}
|
|
90
100
|
function assertAnalyzeConfig(value, filename) {
|
|
91
|
-
if (
|
|
101
|
+
if (!isPlainObject(value))
|
|
92
102
|
fail(filename, 'analyze must be an object');
|
|
93
|
-
|
|
94
|
-
const cfg = value;
|
|
95
|
-
for (const key of Object.keys(cfg)) {
|
|
96
|
-
if (!ANALYZE_KEY_SET.has(key)) {
|
|
97
|
-
fail(filename, `analyze contains unknown key "${key}" (expected one of: ${ANALYZE_KEYS.join(', ')})`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
103
|
+
assertKnownKeys(value, ANALYZE_KEYS, 'analyze contains unknown key', filename);
|
|
100
104
|
for (const key of ANALYZE_KEYS) {
|
|
101
|
-
if (key in
|
|
102
|
-
assertPositiveInteger(
|
|
105
|
+
if (key in value)
|
|
106
|
+
assertPositiveInteger(value[key], `analyze.${key}`, filename);
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
function assertDefaultCommandConfig(value, filename) {
|
|
106
|
-
if (
|
|
110
|
+
if (!isPlainObject(value))
|
|
107
111
|
fail(filename, 'defaultCommand must be an object');
|
|
108
|
-
}
|
|
109
112
|
const cfg = value;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
fail(filename, `defaultCommand contains unknown key "${key}" (expected one of: ${DEFAULT_COMMAND_KEYS.join(', ')})`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
for (const key of [
|
|
116
|
-
'fix',
|
|
117
|
-
'merge',
|
|
118
|
-
'dedup',
|
|
119
|
-
'sort',
|
|
120
|
-
'check',
|
|
121
|
-
'analyze',
|
|
122
|
-
'typos',
|
|
123
|
-
'watch',
|
|
124
|
-
]) {
|
|
113
|
+
assertKnownKeys(cfg, DEFAULT_COMMAND_KEYS, 'defaultCommand contains unknown key', filename);
|
|
114
|
+
for (const key of DEFAULT_COMMAND_BOOLEAN_KEYS) {
|
|
125
115
|
if (key in cfg && typeof cfg[key] !== 'boolean') {
|
|
126
116
|
fail(filename, `defaultCommand.${key} must be a boolean`);
|
|
127
117
|
}
|
|
@@ -150,11 +140,7 @@ export function validateConfig(input, filename = CONFIG_FILENAME) {
|
|
|
150
140
|
fail(filename, 'default export must be an object');
|
|
151
141
|
}
|
|
152
142
|
const cfg = input;
|
|
153
|
-
|
|
154
|
-
if (!KNOWN_KEY_SET.has(key)) {
|
|
155
|
-
fail(filename, `unknown key "${key}" (expected one of: ${KNOWN_KEYS.join(', ')})`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
143
|
+
assertKnownKeys(cfg, KNOWN_KEYS, 'unknown key', filename);
|
|
158
144
|
if ('customTextTokens' in cfg)
|
|
159
145
|
assertPxTokenMap(cfg.customTextTokens, 'customTextTokens', filename);
|
|
160
146
|
if ('customSpacingTokens' in cfg)
|
|
@@ -183,7 +169,11 @@ export function validateConfig(input, filename = CONFIG_FILENAME) {
|
|
|
183
169
|
assertPositiveInteger(cfg.rareScaleMaxCount, 'rareScaleMaxCount', filename);
|
|
184
170
|
if ('defaultCommand' in cfg)
|
|
185
171
|
assertDefaultCommandConfig(cfg.defaultCommand, filename);
|
|
172
|
+
if ('tailwindVersion' in cfg &&
|
|
173
|
+
cfg.tailwindVersion !== 3 &&
|
|
174
|
+
cfg.tailwindVersion !== 4) {
|
|
175
|
+
fail(filename, 'tailwindVersion must be 3 or 4');
|
|
176
|
+
}
|
|
186
177
|
return cfg;
|
|
187
178
|
}
|
|
188
|
-
export { loadConfig } from '../io/config.js';
|
|
189
|
-
//# sourceMappingURL=config.js.map
|
|
179
|
+
export { loadConfig, resolveInitFilename } from '../io/config.js';
|
|
@@ -23,7 +23,7 @@ export type ScaleInconsistency = {
|
|
|
23
23
|
property: string;
|
|
24
24
|
values: ScaleValue[];
|
|
25
25
|
};
|
|
26
|
-
type RareScaleValue = {
|
|
26
|
+
export type RareScaleValue = {
|
|
27
27
|
property: string;
|
|
28
28
|
value: string;
|
|
29
29
|
className: string;
|
|
@@ -60,4 +60,3 @@ export declare function analyzeConsistency(input: FileClasses[], options?: Consi
|
|
|
60
60
|
export declare function collectClasses(content: string, opts?: ClassStringOpts): string[];
|
|
61
61
|
export declare function toConsistencyOptions(config?: Config): ConsistencyOptions;
|
|
62
62
|
export { analyzeConsistencyFiles } from '../io/consistency.js';
|
|
63
|
-
//# sourceMappingURL=consistency.d.ts.map
|
package/dist/core/consistency.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { extractClassStrings, SINGLE_CLASS_REGEX, } from './class-strings.js';
|
|
2
|
-
import { COLOR_FAMILIES, parseColorClass, SCALE_PROPERTIES, TAILWIND_COLORS, } from './lexicon.js';
|
|
3
|
-
function stripVariants(cls) {
|
|
4
|
-
const idx = cls.lastIndexOf(':');
|
|
5
|
-
return idx === -1 ? cls : cls.slice(idx + 1);
|
|
6
|
-
}
|
|
2
|
+
import { COLOR_FAMILIES, parseColorClass, SCALE_PROPERTIES, stripVariants, TAILWIND_COLORS, } from './lexicon.js';
|
|
7
3
|
function parseColor(cls, families, knownColors) {
|
|
8
4
|
const parsed = parseColorClass(cls);
|
|
9
5
|
if (!parsed)
|
|
@@ -223,4 +219,3 @@ export function toConsistencyOptions(config = {}) {
|
|
|
223
219
|
};
|
|
224
220
|
}
|
|
225
221
|
export { analyzeConsistencyFiles } from '../io/consistency.js';
|
|
226
|
-
//# sourceMappingURL=consistency.js.map
|
|
@@ -30,53 +30,6 @@ const POSITION_GROUP = new Set([
|
|
|
30
30
|
'relative',
|
|
31
31
|
'sticky',
|
|
32
32
|
]);
|
|
33
|
-
const SIDE_MAP = {
|
|
34
|
-
p: ['t', 'b', 'l', 'r'],
|
|
35
|
-
m: ['t', 'b', 'l', 'r'],
|
|
36
|
-
border: ['t', 'b', 'l', 'r'],
|
|
37
|
-
inset: ['t', 'b', 'l', 'r'],
|
|
38
|
-
gap: ['t', 'b', 'l', 'r'],
|
|
39
|
-
'scroll-p': ['t', 'b', 'l', 'r'],
|
|
40
|
-
'scroll-m': ['t', 'b', 'l', 'r'],
|
|
41
|
-
px: ['l', 'r'],
|
|
42
|
-
mx: ['l', 'r'],
|
|
43
|
-
'border-x': ['l', 'r'],
|
|
44
|
-
'inset-x': ['l', 'r'],
|
|
45
|
-
'gap-x': ['l', 'r'],
|
|
46
|
-
'scroll-px': ['l', 'r'],
|
|
47
|
-
'scroll-mx': ['l', 'r'],
|
|
48
|
-
py: ['t', 'b'],
|
|
49
|
-
my: ['t', 'b'],
|
|
50
|
-
'border-y': ['t', 'b'],
|
|
51
|
-
'inset-y': ['t', 'b'],
|
|
52
|
-
'gap-y': ['t', 'b'],
|
|
53
|
-
'scroll-py': ['t', 'b'],
|
|
54
|
-
'scroll-my': ['t', 'b'],
|
|
55
|
-
pt: ['t'],
|
|
56
|
-
mt: ['t'],
|
|
57
|
-
'border-t': ['t'],
|
|
58
|
-
top: ['t'],
|
|
59
|
-
'scroll-pt': ['t'],
|
|
60
|
-
'scroll-mt': ['t'],
|
|
61
|
-
pb: ['b'],
|
|
62
|
-
mb: ['b'],
|
|
63
|
-
'border-b': ['b'],
|
|
64
|
-
bottom: ['b'],
|
|
65
|
-
'scroll-pb': ['b'],
|
|
66
|
-
'scroll-mb': ['b'],
|
|
67
|
-
pl: ['l'],
|
|
68
|
-
ml: ['l'],
|
|
69
|
-
'border-l': ['l'],
|
|
70
|
-
left: ['l'],
|
|
71
|
-
'scroll-pl': ['l'],
|
|
72
|
-
'scroll-ml': ['l'],
|
|
73
|
-
pr: ['r'],
|
|
74
|
-
mr: ['r'],
|
|
75
|
-
'border-r': ['r'],
|
|
76
|
-
right: ['r'],
|
|
77
|
-
'scroll-pr': ['r'],
|
|
78
|
-
'scroll-mr': ['r'],
|
|
79
|
-
};
|
|
80
33
|
const FAMILIES = [
|
|
81
34
|
{
|
|
82
35
|
kind: 'p',
|
|
@@ -160,6 +113,20 @@ const FAMILIES = [
|
|
|
160
113
|
regex: /^(scroll-m|scroll-mx|scroll-my|scroll-mt|scroll-mb|scroll-ml|scroll-mr)-(.+)$/,
|
|
161
114
|
},
|
|
162
115
|
];
|
|
116
|
+
const SIDE_MAP = {};
|
|
117
|
+
for (const f of FAMILIES) {
|
|
118
|
+
const entries = [
|
|
119
|
+
[f.full, ['t', 'b', 'l', 'r']],
|
|
120
|
+
[f.x, ['l', 'r']],
|
|
121
|
+
[f.y, ['t', 'b']],
|
|
122
|
+
[f.t, ['t']],
|
|
123
|
+
[f.b, ['b']],
|
|
124
|
+
[f.l, ['l']],
|
|
125
|
+
[f.r, ['r']],
|
|
126
|
+
];
|
|
127
|
+
for (const [prefix, sides] of entries)
|
|
128
|
+
SIDE_MAP[prefix] ??= sides;
|
|
129
|
+
}
|
|
163
130
|
function parseBoxClass(cls) {
|
|
164
131
|
for (const family of FAMILIES) {
|
|
165
132
|
const m = cls.match(family.regex);
|
|
@@ -207,39 +174,27 @@ function collapseBox(box, f) {
|
|
|
207
174
|
}
|
|
208
175
|
return parts.join(' ');
|
|
209
176
|
}
|
|
177
|
+
const CORNER_MAP = {
|
|
178
|
+
'': ['tl', 'tr', 'bl', 'br'],
|
|
179
|
+
t: ['tl', 'tr'],
|
|
180
|
+
b: ['bl', 'br'],
|
|
181
|
+
l: ['tl', 'bl'],
|
|
182
|
+
r: ['tr', 'br'],
|
|
183
|
+
tl: ['tl'],
|
|
184
|
+
tr: ['tr'],
|
|
185
|
+
bl: ['bl'],
|
|
186
|
+
br: ['br'],
|
|
187
|
+
};
|
|
210
188
|
function parseRoundedCorner(cls) {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
else if (side === 'b') {
|
|
221
|
-
c.bl = val;
|
|
222
|
-
c.br = val;
|
|
223
|
-
}
|
|
224
|
-
else if (side === 'l') {
|
|
225
|
-
c.tl = val;
|
|
226
|
-
c.bl = val;
|
|
227
|
-
}
|
|
228
|
-
else if (side === 'r') {
|
|
229
|
-
c.tr = val;
|
|
230
|
-
c.br = val;
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
c[side] = val;
|
|
234
|
-
}
|
|
235
|
-
return { corners: c };
|
|
236
|
-
}
|
|
237
|
-
const allM = cls.match(/^rounded(?:-(.+))?$/);
|
|
238
|
-
if (allM) {
|
|
239
|
-
const val = allM[1] ?? '';
|
|
240
|
-
return { corners: { tl: val, tr: val, bl: val, br: val } };
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
189
|
+
const m = cls.match(/^rounded-(tl|tr|bl|br|t|b|l|r)(?:-(.+))?$/) ??
|
|
190
|
+
cls.match(/^rounded()(?:-(.+))?$/);
|
|
191
|
+
if (!m)
|
|
192
|
+
return null;
|
|
193
|
+
const value = m[2] ?? '';
|
|
194
|
+
const corners = {};
|
|
195
|
+
for (const corner of CORNER_MAP[m[1]])
|
|
196
|
+
corners[corner] = value;
|
|
197
|
+
return corners;
|
|
243
198
|
}
|
|
244
199
|
function collapseCorners(c) {
|
|
245
200
|
const { tl, tr, bl, br } = c;
|
|
@@ -300,137 +255,127 @@ function collapseCorners(c) {
|
|
|
300
255
|
}
|
|
301
256
|
// ─── Responsive cascade collapse ─────────────────────────────────────────────
|
|
302
257
|
const RESPONSIVE_BREAKPOINTS = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
258
|
+
// Exhaustive by type: adding a breakpoint above fails the build until ranked.
|
|
259
|
+
const BP_RANK = {
|
|
260
|
+
'': 0,
|
|
261
|
+
sm: 1,
|
|
262
|
+
md: 2,
|
|
263
|
+
lg: 3,
|
|
264
|
+
xl: 4,
|
|
265
|
+
'2xl': 5,
|
|
266
|
+
};
|
|
307
267
|
function collapseResponsiveCascade(classes) {
|
|
308
|
-
// Last-wins keyword utilities (display, position) share a single CSS property
|
|
309
|
-
// even though their class names differ, so they must group by property — not
|
|
310
|
-
// by class name — or an intermediate override (e.g. md:block between flex and
|
|
311
|
-
// lg:flex) is missed and the restoring class is wrongly dropped.
|
|
312
|
-
const propertyKey = (base) => {
|
|
313
|
-
if (DISPLAY_GROUP.has(base))
|
|
314
|
-
return 'display';
|
|
315
|
-
if (POSITION_GROUP.has(base))
|
|
316
|
-
return 'position';
|
|
317
|
-
const dash = base.lastIndexOf('-');
|
|
318
|
-
return dash === -1 ? base : base.slice(0, dash);
|
|
319
|
-
};
|
|
320
268
|
const entries = classes.map((cls, idx) => {
|
|
321
269
|
for (const bp of RESPONSIVE_BREAKPOINTS) {
|
|
322
270
|
const prefix = `${bp}:`;
|
|
323
271
|
if (cls.startsWith(prefix) && !cls.slice(prefix.length).includes(':')) {
|
|
324
272
|
const base = cls.slice(prefix.length);
|
|
325
|
-
return { bp, base,
|
|
273
|
+
return { bp, base, idx };
|
|
326
274
|
}
|
|
327
275
|
}
|
|
328
|
-
return { bp: '', base: cls,
|
|
276
|
+
return { bp: '', base: cls, idx };
|
|
329
277
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
groups.set(e.key, [e]);
|
|
337
|
-
}
|
|
278
|
+
// Unknown variants cannot be ordered against the standard breakpoints safely.
|
|
279
|
+
if (entries.some((entry) => entry.base.includes(':')))
|
|
280
|
+
return classes;
|
|
281
|
+
if (!entries.some((entry) => entry.bp !== ''))
|
|
282
|
+
return classes;
|
|
283
|
+
const sorted = [...entries].sort((a, b) => BP_RANK[a.bp] - BP_RANK[b.bp] || a.idx - b.idx);
|
|
338
284
|
const toRemove = new Set();
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (entry.base === prev) {
|
|
346
|
-
toRemove.add(entry.idx);
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
prev = entry.base;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
285
|
+
let prev = null;
|
|
286
|
+
for (const entry of sorted) {
|
|
287
|
+
if (entry.base === prev)
|
|
288
|
+
toRemove.add(entry.idx);
|
|
289
|
+
else
|
|
290
|
+
prev = entry.base;
|
|
352
291
|
}
|
|
353
292
|
return classes.filter((_, i) => !toRemove.has(i));
|
|
354
293
|
}
|
|
355
294
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
295
|
+
// Survivors keep their slot; new shorthands take the slot of the first token
|
|
296
|
+
// they replace, so an unchanged group leaves the string byte-for-byte intact.
|
|
297
|
+
function placeCollapsed(slots, classes, indices, collapsed) {
|
|
298
|
+
const firstIndex = new Map();
|
|
299
|
+
for (const i of indices) {
|
|
300
|
+
if (!firstIndex.has(classes[i]))
|
|
301
|
+
firstIndex.set(classes[i], i);
|
|
302
|
+
}
|
|
303
|
+
const kept = new Set();
|
|
304
|
+
const fresh = [];
|
|
305
|
+
for (const token of collapsed.split(' ')) {
|
|
306
|
+
const i = firstIndex.get(token);
|
|
307
|
+
if (i === undefined)
|
|
308
|
+
fresh.push(token);
|
|
309
|
+
else
|
|
310
|
+
kept.add(i);
|
|
311
|
+
}
|
|
312
|
+
for (const i of indices)
|
|
313
|
+
slots[i] = kept.has(i) ? [classes[i]] : [];
|
|
314
|
+
if (fresh.length === 0)
|
|
315
|
+
return;
|
|
316
|
+
const anchor = indices.find((i) => !kept.has(i)) ?? indices[0];
|
|
317
|
+
slots[anchor] = [...slots[anchor], ...fresh];
|
|
318
|
+
}
|
|
319
|
+
function keepLastOf(slots, indices) {
|
|
320
|
+
for (const i of indices.slice(0, -1))
|
|
321
|
+
slots[i] = [];
|
|
359
322
|
}
|
|
360
323
|
export function deduplicateClasses(classStr) {
|
|
361
|
-
|
|
362
|
-
if (
|
|
324
|
+
const original = classStr.split(/\s+/).filter(Boolean);
|
|
325
|
+
if (original.length <= 1)
|
|
363
326
|
return classStr;
|
|
364
|
-
classes = collapseResponsiveCascade(
|
|
365
|
-
|
|
366
|
-
|
|
327
|
+
const classes = collapseResponsiveCascade(original);
|
|
328
|
+
const slots = classes.map((cls) => [cls]);
|
|
329
|
+
const displayIndices = [];
|
|
330
|
+
const positionIndices = [];
|
|
367
331
|
const seen = new Set();
|
|
368
|
-
const others = [];
|
|
369
332
|
const boxGroups = new Map();
|
|
370
333
|
const corners = { tl: null, tr: null, bl: null, br: null };
|
|
371
|
-
const
|
|
372
|
-
|
|
334
|
+
const cornerIndices = [];
|
|
335
|
+
classes.forEach((cls, idx) => {
|
|
373
336
|
if (DISPLAY_GROUP.has(cls)) {
|
|
374
|
-
|
|
375
|
-
|
|
337
|
+
displayIndices.push(idx);
|
|
338
|
+
return;
|
|
376
339
|
}
|
|
377
340
|
if (POSITION_GROUP.has(cls)) {
|
|
378
|
-
|
|
379
|
-
|
|
341
|
+
positionIndices.push(idx);
|
|
342
|
+
return;
|
|
380
343
|
}
|
|
381
344
|
const parsed = parseBoxClass(cls);
|
|
382
345
|
if (parsed) {
|
|
383
346
|
const { family, sides } = parsed;
|
|
384
347
|
let group = boxGroups.get(family.kind);
|
|
385
348
|
if (!group) {
|
|
386
|
-
group = { family, box: { t: '', b: '', l: '', r: '' },
|
|
349
|
+
group = { family, box: { t: '', b: '', l: '', r: '' }, indices: [] };
|
|
387
350
|
boxGroups.set(family.kind, group);
|
|
388
351
|
}
|
|
389
|
-
group.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (sides.b !== undefined)
|
|
393
|
-
group.box.b = sides.b;
|
|
394
|
-
if (sides.l !== undefined)
|
|
395
|
-
group.box.l = sides.l;
|
|
396
|
-
if (sides.r !== undefined)
|
|
397
|
-
group.box.r = sides.r;
|
|
398
|
-
continue;
|
|
352
|
+
group.indices.push(idx);
|
|
353
|
+
Object.assign(group.box, sides);
|
|
354
|
+
return;
|
|
399
355
|
}
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
corners.tl = c.tl;
|
|
406
|
-
if (c.tr !== undefined)
|
|
407
|
-
corners.tr = c.tr;
|
|
408
|
-
if (c.bl !== undefined)
|
|
409
|
-
corners.bl = c.bl;
|
|
410
|
-
if (c.br !== undefined)
|
|
411
|
-
corners.br = c.br;
|
|
412
|
-
continue;
|
|
356
|
+
const roundedCorners = parseRoundedCorner(cls);
|
|
357
|
+
if (roundedCorners) {
|
|
358
|
+
cornerIndices.push(idx);
|
|
359
|
+
Object.assign(corners, roundedCorners);
|
|
360
|
+
return;
|
|
413
361
|
}
|
|
414
|
-
if (
|
|
362
|
+
if (seen.has(cls))
|
|
363
|
+
slots[idx] = [];
|
|
364
|
+
else
|
|
415
365
|
seen.add(cls);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
result.push(displayWinner);
|
|
422
|
-
if (positionWinner)
|
|
423
|
-
result.push(positionWinner);
|
|
424
|
-
for (const { family, box, classes: groupClasses } of boxGroups.values()) {
|
|
425
|
-
if (groupClasses.length === 0)
|
|
426
|
-
continue;
|
|
427
|
-
pushCollapsed(result, collapseBox(box, family), groupClasses);
|
|
366
|
+
});
|
|
367
|
+
keepLastOf(slots, displayIndices);
|
|
368
|
+
keepLastOf(slots, positionIndices);
|
|
369
|
+
for (const { family, box, indices } of boxGroups.values()) {
|
|
370
|
+
placeCollapsed(slots, classes, indices, collapseBox(box, family));
|
|
428
371
|
}
|
|
429
|
-
if (
|
|
430
|
-
|
|
372
|
+
if (cornerIndices.length > 0) {
|
|
373
|
+
placeCollapsed(slots, classes, cornerIndices, collapseCorners(corners));
|
|
431
374
|
}
|
|
432
|
-
result.
|
|
433
|
-
|
|
375
|
+
const result = slots.flat();
|
|
376
|
+
const unchanged = result.length === original.length &&
|
|
377
|
+
result.every((cls, i) => cls === original[i]);
|
|
378
|
+
return unchanged ? classStr : result.join(' ');
|
|
434
379
|
}
|
|
435
380
|
export function dedupeContent(content, opts = {}) {
|
|
436
381
|
return replaceClassStrings(content, deduplicateClasses, {
|
|
@@ -439,4 +384,3 @@ export function dedupeContent(content, opts = {}) {
|
|
|
439
384
|
});
|
|
440
385
|
}
|
|
441
386
|
export { dedupeFile } from '../io/deduplicator.js';
|
|
442
|
-
//# sourceMappingURL=deduplicator.js.map
|
package/dist/core/fixer.d.ts
CHANGED
package/dist/core/fixer.js
CHANGED
package/dist/core/lexicon.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const TAILWIND_COLORS: Set<string>;
|
|
2
2
|
export declare const COLOR_FAMILIES: Record<string, string>;
|
|
3
|
+
export declare function stripVariants(cls: string): string;
|
|
3
4
|
/**
|
|
4
5
|
* Parses a color utility class into its property, color name and (optional)
|
|
5
6
|
* numeric shade. Strips leading variants (`hover:` …). Returns `null` when the
|
|
@@ -13,4 +14,3 @@ export declare function parseColorClass(cls: string): {
|
|
|
13
14
|
shade: string;
|
|
14
15
|
} | null;
|
|
15
16
|
export declare const SCALE_PROPERTIES: Set<string>;
|
|
16
|
-
//# sourceMappingURL=lexicon.d.ts.map
|
package/dist/core/lexicon.js
CHANGED
|
@@ -70,7 +70,7 @@ export const COLOR_FAMILIES = {
|
|
|
70
70
|
neutral: 'gray',
|
|
71
71
|
stone: 'gray',
|
|
72
72
|
};
|
|
73
|
-
function stripVariants(cls) {
|
|
73
|
+
export function stripVariants(cls) {
|
|
74
74
|
const idx = cls.lastIndexOf(':');
|
|
75
75
|
return idx === -1 ? cls : cls.slice(idx + 1);
|
|
76
76
|
}
|
|
@@ -136,4 +136,3 @@ export const SCALE_PROPERTIES = new Set([
|
|
|
136
136
|
'space-y',
|
|
137
137
|
'z',
|
|
138
138
|
]);
|
|
139
|
-
//# sourceMappingURL=lexicon.js.map
|
package/dist/core/merger.d.ts
CHANGED