@culur/prettier-plugin-tailwindcss-group 1.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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +558 -0
- package/dist/index.d.mts +673 -0
- package/dist/index.mjs +4194 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +66 -0
- package/src/__tests__/comments.test.ts +171 -0
- package/src/__tests__/plugin-official.test.ts +32 -0
- package/src/__tests__/readme.test.ts +13 -0
- package/src/__tests__/utils/ast-estree.ts +22 -0
- package/src/__tests__/utils/ast-md.ts +101 -0
- package/src/__tests__/utils/test-format.ts +35 -0
- package/src/ast/ast-array-expression.test.ts +33 -0
- package/src/ast/ast-array-expression.ts +20 -0
- package/src/ast/ast-call-expression.test.ts +17 -0
- package/src/ast/ast-call-expression.ts +5 -0
- package/src/ast/ast-comment.test.ts +45 -0
- package/src/ast/ast-comment.ts +25 -0
- package/src/ast/ast-common.ts +29 -0
- package/src/ast/ast-jsx.test.ts +56 -0
- package/src/ast/ast-jsx.ts +53 -0
- package/src/ast/ast-literal.test.ts +47 -0
- package/src/ast/ast-literal.ts +17 -0
- package/src/ast/ast-property.test.ts +96 -0
- package/src/ast/ast-property.ts +32 -0
- package/src/ast/index.ts +8 -0
- package/src/ast/key.test.ts +21 -0
- package/src/ast/key.ts +4 -0
- package/src/group-classes/group-classes.bucket.ts +61 -0
- package/src/group-classes/group-classes.format.ts +39 -0
- package/src/group-classes/group-classes.test.ts +126 -0
- package/src/group-classes/group-classes.ts +34 -0
- package/src/group-classes/group-classes.types.ts +5 -0
- package/src/handle-node/handle-cva-class-value.test.ts +133 -0
- package/src/handle-node/handle-cva-class-value.ts +47 -0
- package/src/handle-node/handle-expressions.test.ts +140 -0
- package/src/handle-node/handle-expressions.ts +63 -0
- package/src/handle-node/handle-string-literal.test.ts +135 -0
- package/src/handle-node/handle-string-literal.ts +53 -0
- package/src/handle-targets/index.ts +3 -0
- package/src/handle-targets/target-cva-base.test.ts +103 -0
- package/src/handle-targets/target-cva-base.ts +17 -0
- package/src/handle-targets/target-cva-compound-variants.test.ts +239 -0
- package/src/handle-targets/target-cva-compound-variants.ts +45 -0
- package/src/handle-targets/target-cva-variants.test.ts +178 -0
- package/src/handle-targets/target-cva-variants.ts +50 -0
- package/src/handle-targets/target-cva.test.ts +126 -0
- package/src/handle-targets/target-cva.ts +33 -0
- package/src/handle-targets/target-jsx.test.ts +242 -0
- package/src/handle-targets/target-jsx.ts +65 -0
- package/src/handle-targets/target-object-properties.test.ts +140 -0
- package/src/handle-targets/target-object-properties.ts +41 -0
- package/src/index.ts +13 -0
- package/src/options.test.ts +73 -0
- package/src/options.ts +94 -0
- package/src/orders/class-groups.test.ts +74 -0
- package/src/orders/class-groups.ts +178 -0
- package/src/orders/class-order.tailwind-merge.ts +26 -0
- package/src/orders/modifier-order.prettier-plugin-tailwindcss.test.ts +33 -0
- package/src/orders/modifier-order.prettier-plugin-tailwindcss.ts +23 -0
- package/src/prettier.d.ts +21 -0
- package/src/printers.test.ts +58 -0
- package/src/printers.ts +29 -0
- package/src/printers.walk-ast.test.ts +58 -0
- package/src/printers.walk-ast.ts +41 -0
- package/src/utils/split-classes.ts +3 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import dedent from 'dedent';
|
|
2
|
+
import { describe } from 'vitest';
|
|
3
|
+
import { testFormat } from '../__tests__/utils/test-format';
|
|
4
|
+
|
|
5
|
+
describe('target-cva-compound-variants: bypassing invalid or missing compoundVariants', () => {
|
|
6
|
+
testFormat(
|
|
7
|
+
'ignores config without compoundVariants',
|
|
8
|
+
{ classNameThreshold: 4 },
|
|
9
|
+
dedent /* ts */ `
|
|
10
|
+
const b = cva('base', {
|
|
11
|
+
variants: { size: { sm: 'text-sm' } },
|
|
12
|
+
});
|
|
13
|
+
`,
|
|
14
|
+
dedent /* ts */ `
|
|
15
|
+
const b = cva('base', {
|
|
16
|
+
variants: { size: { sm: 'text-sm' } },
|
|
17
|
+
});
|
|
18
|
+
`,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
testFormat(
|
|
22
|
+
'ignores non-array compoundVariants property',
|
|
23
|
+
{ classNameThreshold: 4 },
|
|
24
|
+
dedent /* ts */ `
|
|
25
|
+
const b = cva('base', {
|
|
26
|
+
compoundVariants: 'invalid-string',
|
|
27
|
+
});
|
|
28
|
+
`,
|
|
29
|
+
dedent /* ts */ `
|
|
30
|
+
const b = cva('base', {
|
|
31
|
+
compoundVariants: 'invalid-string',
|
|
32
|
+
});
|
|
33
|
+
`,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
testFormat(
|
|
37
|
+
'skips non-object elements in compoundVariants array',
|
|
38
|
+
{ classNameThreshold: 4 },
|
|
39
|
+
dedent /* ts */ `
|
|
40
|
+
const b = cva('base', {
|
|
41
|
+
compoundVariants: [null, undefined, 'string-elem'],
|
|
42
|
+
});
|
|
43
|
+
`,
|
|
44
|
+
dedent /* ts */ `
|
|
45
|
+
const b = cva('base', {
|
|
46
|
+
compoundVariants: [null, undefined, 'string-elem'],
|
|
47
|
+
});
|
|
48
|
+
`,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
testFormat(
|
|
52
|
+
'ignores non-class properties inside compoundVariants objects',
|
|
53
|
+
{ classNameThreshold: 1 },
|
|
54
|
+
dedent /* ts */ `
|
|
55
|
+
const b = cva('base', {
|
|
56
|
+
compoundVariants: [
|
|
57
|
+
{
|
|
58
|
+
intent: 'primary',
|
|
59
|
+
size: 'medium',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
`,
|
|
64
|
+
dedent /* ts */ `
|
|
65
|
+
const b = cva('base', {
|
|
66
|
+
compoundVariants: [
|
|
67
|
+
{
|
|
68
|
+
intent: 'primary',
|
|
69
|
+
size: 'medium',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
`,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
testFormat(
|
|
77
|
+
'skips non-transformable class property values',
|
|
78
|
+
{ classNameThreshold: 1 },
|
|
79
|
+
dedent /* ts */ `
|
|
80
|
+
const b = cva('base', {
|
|
81
|
+
compoundVariants: [
|
|
82
|
+
{
|
|
83
|
+
intent: 'primary',
|
|
84
|
+
class: someIdentifier,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
`,
|
|
89
|
+
dedent /* ts */ `
|
|
90
|
+
const b = cva('base', {
|
|
91
|
+
compoundVariants: [
|
|
92
|
+
{
|
|
93
|
+
intent: 'primary',
|
|
94
|
+
class: someIdentifier,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
`,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('target-cva-compound-variants: formatting "class" property', () => {
|
|
103
|
+
testFormat(
|
|
104
|
+
'formats StringLiteral "class" when > threshold',
|
|
105
|
+
{ classNameThreshold: 4 },
|
|
106
|
+
dedent /* ts */ `
|
|
107
|
+
const b = cva('base', {
|
|
108
|
+
compoundVariants: [
|
|
109
|
+
{
|
|
110
|
+
intent: 'primary',
|
|
111
|
+
class:
|
|
112
|
+
'flex items-center justify-center text-sm font-bold bg-white',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
`,
|
|
117
|
+
dedent /* ts */ `
|
|
118
|
+
const b = cva('base', {
|
|
119
|
+
compoundVariants: [
|
|
120
|
+
{
|
|
121
|
+
intent: 'primary',
|
|
122
|
+
class: [
|
|
123
|
+
'flex items-center justify-center',
|
|
124
|
+
'text-sm font-bold',
|
|
125
|
+
'bg-white',
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
`,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
testFormat(
|
|
134
|
+
'formats CallExpression in "class"',
|
|
135
|
+
{ classNameThreshold: 4 },
|
|
136
|
+
dedent /* ts */ `
|
|
137
|
+
const b = cva('base', {
|
|
138
|
+
compoundVariants: [
|
|
139
|
+
{
|
|
140
|
+
intent: 'primary',
|
|
141
|
+
class: cn('flex items-center text-sm font-bold bg-white'),
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
`,
|
|
146
|
+
dedent /* ts */ `
|
|
147
|
+
const b = cva('base', {
|
|
148
|
+
compoundVariants: [
|
|
149
|
+
{
|
|
150
|
+
intent: 'primary',
|
|
151
|
+
class: cn('flex items-center', 'text-sm font-bold', 'bg-white'),
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
`,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
testFormat(
|
|
159
|
+
'formats ArrayExpression in "class"',
|
|
160
|
+
{ classNameThreshold: 4 },
|
|
161
|
+
dedent /* ts */ `
|
|
162
|
+
const b = cva('base', {
|
|
163
|
+
compoundVariants: [
|
|
164
|
+
{
|
|
165
|
+
intent: 'primary',
|
|
166
|
+
class: ['flex items-center text-sm font-bold bg-white'],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
`,
|
|
171
|
+
dedent /* ts */ `
|
|
172
|
+
const b = cva('base', {
|
|
173
|
+
compoundVariants: [
|
|
174
|
+
{
|
|
175
|
+
intent: 'primary',
|
|
176
|
+
class: ['flex items-center', 'text-sm font-bold', 'bg-white'],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
`,
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('target-cva-compound-variants: formatting "className" property', () => {
|
|
185
|
+
testFormat(
|
|
186
|
+
'formats StringLiteral "className" when > threshold',
|
|
187
|
+
{ classNameThreshold: 4 },
|
|
188
|
+
dedent /* ts */ `
|
|
189
|
+
const b = cva('base', {
|
|
190
|
+
compoundVariants: [
|
|
191
|
+
{
|
|
192
|
+
intent: 'primary',
|
|
193
|
+
className:
|
|
194
|
+
'flex items-center justify-center text-sm font-bold bg-white',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
`,
|
|
199
|
+
dedent /* ts */ `
|
|
200
|
+
const b = cva('base', {
|
|
201
|
+
compoundVariants: [
|
|
202
|
+
{
|
|
203
|
+
intent: 'primary',
|
|
204
|
+
className: [
|
|
205
|
+
'flex items-center justify-center',
|
|
206
|
+
'text-sm font-bold',
|
|
207
|
+
'bg-white',
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
`,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
testFormat(
|
|
216
|
+
'leaves StringLiteral "className" unchanged when <= threshold',
|
|
217
|
+
{ classNameThreshold: 10 },
|
|
218
|
+
dedent /* ts */ `
|
|
219
|
+
const b = cva('base', {
|
|
220
|
+
compoundVariants: [
|
|
221
|
+
{
|
|
222
|
+
intent: 'primary',
|
|
223
|
+
className: 'flex items-center',
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
});
|
|
227
|
+
`,
|
|
228
|
+
dedent /* ts */ `
|
|
229
|
+
const b = cva('base', {
|
|
230
|
+
compoundVariants: [
|
|
231
|
+
{
|
|
232
|
+
intent: 'primary',
|
|
233
|
+
className: 'flex items-center',
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
`,
|
|
238
|
+
);
|
|
239
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ObjectExpression } from 'estree-jsx';
|
|
2
|
+
import type { PluginOptions } from '../prettier';
|
|
3
|
+
import {
|
|
4
|
+
isArrayExpression,
|
|
5
|
+
isCallExpression,
|
|
6
|
+
isObjectExpression,
|
|
7
|
+
isStaticProperty,
|
|
8
|
+
isStringLiteral,
|
|
9
|
+
} from '../ast';
|
|
10
|
+
import { handleCvaClassValue } from '../handle-node/handle-cva-class-value';
|
|
11
|
+
|
|
12
|
+
/** Processes `compoundVariants[*].class` and `compoundVariants[*].className` in `cva(...)`. */
|
|
13
|
+
export function handleTargetCvaCompoundVariants(
|
|
14
|
+
config: ObjectExpression,
|
|
15
|
+
options: PluginOptions,
|
|
16
|
+
): void {
|
|
17
|
+
// Find the 'compoundVariants' property on the cva config object
|
|
18
|
+
const cvProp = config.properties.find(property =>
|
|
19
|
+
isStaticProperty(property, 'compoundVariants'),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Return early if 'compoundVariants' is missing or not an array
|
|
23
|
+
if (!cvProp || !isArrayExpression(cvProp.value)) return;
|
|
24
|
+
|
|
25
|
+
// Process each compound variant object entry
|
|
26
|
+
for (const element of cvProp.value.elements) {
|
|
27
|
+
if (!element || !isObjectExpression(element)) continue;
|
|
28
|
+
|
|
29
|
+
for (const prop of element.properties) {
|
|
30
|
+
// Only target 'class' or 'className' properties containing transformable class expressions
|
|
31
|
+
if (
|
|
32
|
+
(!isStaticProperty(prop, 'class') &&
|
|
33
|
+
!isStaticProperty(prop, 'className')) ||
|
|
34
|
+
(!isStringLiteral(prop.value) &&
|
|
35
|
+
!isCallExpression(prop.value) &&
|
|
36
|
+
!isArrayExpression(prop.value))
|
|
37
|
+
) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Transform class value (string literal, function call, or array)
|
|
42
|
+
prop.value = handleCvaClassValue(prop.value, options);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import dedent from 'dedent';
|
|
2
|
+
import { describe } from 'vitest';
|
|
3
|
+
import { testFormat } from '../__tests__/utils/test-format';
|
|
4
|
+
|
|
5
|
+
describe('target-cva-variants: bypassing invalid or missing variants', () => {
|
|
6
|
+
testFormat(
|
|
7
|
+
'ignores config without variants property',
|
|
8
|
+
{ classNameThreshold: 4 },
|
|
9
|
+
dedent /* ts */ `
|
|
10
|
+
const b = cva('base', {
|
|
11
|
+
defaultVariants: { size: 'sm' },
|
|
12
|
+
});
|
|
13
|
+
`,
|
|
14
|
+
dedent /* ts */ `
|
|
15
|
+
const b = cva('base', {
|
|
16
|
+
defaultVariants: { size: 'sm' },
|
|
17
|
+
});
|
|
18
|
+
`,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
testFormat(
|
|
22
|
+
'ignores non-object variants property',
|
|
23
|
+
{ classNameThreshold: 4 },
|
|
24
|
+
dedent /* ts */ `
|
|
25
|
+
const b = cva('base', {
|
|
26
|
+
variants: 'invalid-string',
|
|
27
|
+
});
|
|
28
|
+
`,
|
|
29
|
+
dedent /* ts */ `
|
|
30
|
+
const b = cva('base', {
|
|
31
|
+
variants: 'invalid-string',
|
|
32
|
+
});
|
|
33
|
+
`,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
testFormat(
|
|
37
|
+
'skips variant groups that are not objects',
|
|
38
|
+
{ classNameThreshold: 4 },
|
|
39
|
+
dedent /* ts */ `
|
|
40
|
+
const b = cva('base', {
|
|
41
|
+
variants: {
|
|
42
|
+
size: 'not-an-object',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
`,
|
|
46
|
+
dedent /* ts */ `
|
|
47
|
+
const b = cva('base', {
|
|
48
|
+
variants: {
|
|
49
|
+
size: 'not-an-object',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
testFormat(
|
|
56
|
+
'skips options with non-transformable values',
|
|
57
|
+
{ classNameThreshold: 1 },
|
|
58
|
+
dedent /* ts */ `
|
|
59
|
+
const b = cva('base', {
|
|
60
|
+
variants: {
|
|
61
|
+
size: {
|
|
62
|
+
sm: dynamicVar,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
`,
|
|
67
|
+
dedent /* ts */ `
|
|
68
|
+
const b = cva('base', {
|
|
69
|
+
variants: {
|
|
70
|
+
size: {
|
|
71
|
+
sm: dynamicVar,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
`,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('target-cva-variants: formatting variant option values', () => {
|
|
80
|
+
testFormat(
|
|
81
|
+
'converts StringLiteral option to StringArrayExpression when > threshold',
|
|
82
|
+
{ classNameThreshold: 4 },
|
|
83
|
+
dedent /* ts */ `
|
|
84
|
+
const b = cva('base', {
|
|
85
|
+
variants: {
|
|
86
|
+
size: {
|
|
87
|
+
sm: 'flex items-center justify-center text-sm font-bold bg-white shadow-md',
|
|
88
|
+
lg: 'flex',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
`,
|
|
93
|
+
dedent /* ts */ `
|
|
94
|
+
const b = cva('base', {
|
|
95
|
+
variants: {
|
|
96
|
+
size: {
|
|
97
|
+
sm: [
|
|
98
|
+
'flex items-center justify-center',
|
|
99
|
+
'text-sm font-bold',
|
|
100
|
+
'bg-white',
|
|
101
|
+
'shadow-md',
|
|
102
|
+
],
|
|
103
|
+
lg: 'flex',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
`,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
testFormat(
|
|
111
|
+
'simplifies variant StringArrayExpression to StringLiteral when <= threshold',
|
|
112
|
+
{ classNameThreshold: 10 },
|
|
113
|
+
dedent /* ts */ `
|
|
114
|
+
const b = cva('base', {
|
|
115
|
+
variants: {
|
|
116
|
+
size: {
|
|
117
|
+
sm: ['flex', 'items-center'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
`,
|
|
122
|
+
dedent /* ts */ `
|
|
123
|
+
const b = cva('base', {
|
|
124
|
+
variants: {
|
|
125
|
+
size: {
|
|
126
|
+
sm: 'flex items-center',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
`,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
testFormat(
|
|
134
|
+
'formats CallExpression option in variants',
|
|
135
|
+
{ classNameThreshold: 4 },
|
|
136
|
+
dedent /* ts */ `
|
|
137
|
+
const b = cva('base', {
|
|
138
|
+
variants: {
|
|
139
|
+
size: {
|
|
140
|
+
sm: cn('flex items-center text-sm font-bold bg-white'),
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
`,
|
|
145
|
+
dedent /* ts */ `
|
|
146
|
+
const b = cva('base', {
|
|
147
|
+
variants: {
|
|
148
|
+
size: {
|
|
149
|
+
sm: cn('flex items-center', 'text-sm font-bold', 'bg-white'),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
`,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
testFormat(
|
|
157
|
+
'preserves dynamic variant option untouched',
|
|
158
|
+
{ classNameThreshold: 1 },
|
|
159
|
+
dedent /* ts */ `
|
|
160
|
+
const b = cva('base', {
|
|
161
|
+
variants: {
|
|
162
|
+
size: {
|
|
163
|
+
sm: cn(isActive && 'flex'),
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
`,
|
|
168
|
+
dedent /* ts */ `
|
|
169
|
+
const b = cva('base', {
|
|
170
|
+
variants: {
|
|
171
|
+
size: {
|
|
172
|
+
sm: cn(isActive && 'flex'),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
`,
|
|
177
|
+
);
|
|
178
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ObjectExpression } from 'estree-jsx';
|
|
2
|
+
import type { PluginOptions } from '../prettier';
|
|
3
|
+
import {
|
|
4
|
+
isArrayExpression,
|
|
5
|
+
isCallExpression,
|
|
6
|
+
isObjectExpression,
|
|
7
|
+
isStaticProperty,
|
|
8
|
+
isStringLiteral,
|
|
9
|
+
} from '../ast';
|
|
10
|
+
import { handleCvaClassValue } from '../handle-node/handle-cva-class-value';
|
|
11
|
+
|
|
12
|
+
/** Processes `variants[*][*]` (all leaf option values) in `cva(...)`. */
|
|
13
|
+
export function handleTargetCvaVariants(
|
|
14
|
+
config: ObjectExpression,
|
|
15
|
+
options: PluginOptions,
|
|
16
|
+
): void {
|
|
17
|
+
// Find the 'variants' property on the cva config object
|
|
18
|
+
const variantsProp = config.properties.find(p =>
|
|
19
|
+
isStaticProperty(p, 'variants'),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Return early if 'variants' is missing or not an object
|
|
23
|
+
if (!variantsProp || !isObjectExpression(variantsProp.value)) return;
|
|
24
|
+
|
|
25
|
+
// Traverse each variant group (e.g. `intent: { primary: "...", secondary: "..." }`)
|
|
26
|
+
for (const variantProp of variantsProp.value.properties) {
|
|
27
|
+
if (
|
|
28
|
+
!isStaticProperty(variantProp) ||
|
|
29
|
+
!isObjectExpression(variantProp.value)
|
|
30
|
+
) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Traverse each option within the variant (e.g. `primary: "..."`)
|
|
35
|
+
for (const optionProp of variantProp.value.properties) {
|
|
36
|
+
// Only process properties with valid static keys and transformable values
|
|
37
|
+
if (
|
|
38
|
+
!isStaticProperty(optionProp) ||
|
|
39
|
+
(!isStringLiteral(optionProp.value) &&
|
|
40
|
+
!isCallExpression(optionProp.value) &&
|
|
41
|
+
!isArrayExpression(optionProp.value))
|
|
42
|
+
) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Transform option class value (string literal, function call, or array)
|
|
47
|
+
optionProp.value = handleCvaClassValue(optionProp.value, options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import dedent from 'dedent';
|
|
2
|
+
import { describe } from 'vitest';
|
|
3
|
+
import { testFormat } from '../__tests__/utils/test-format';
|
|
4
|
+
|
|
5
|
+
describe('target-cva: validation and bypass', () => {
|
|
6
|
+
testFormat(
|
|
7
|
+
'ignores non-cva function calls',
|
|
8
|
+
{ classNameThreshold: 2 },
|
|
9
|
+
dedent /* ts */ `
|
|
10
|
+
const b = myOtherFn('flex items-center text-sm font-bold');
|
|
11
|
+
`,
|
|
12
|
+
dedent /* ts */ `
|
|
13
|
+
const b = myOtherFn('flex items-center text-sm font-bold');
|
|
14
|
+
`,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
testFormat(
|
|
18
|
+
'ignores aliased/renamed cva calls (e.g. createVariants or classVarianceAuthority)',
|
|
19
|
+
{ classNameThreshold: 4 },
|
|
20
|
+
dedent /* ts */ `
|
|
21
|
+
const button = createVariants(
|
|
22
|
+
'flex items-center justify-center text-sm font-bold bg-white shadow-md',
|
|
23
|
+
);
|
|
24
|
+
`,
|
|
25
|
+
dedent /* ts */ `
|
|
26
|
+
const button = createVariants(
|
|
27
|
+
'flex items-center justify-center text-sm font-bold bg-white shadow-md',
|
|
28
|
+
);
|
|
29
|
+
`,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
testFormat(
|
|
33
|
+
'ignores cva() calls with no arguments',
|
|
34
|
+
{ classNameThreshold: 2 },
|
|
35
|
+
dedent /* ts */ ` const b = cva(); `,
|
|
36
|
+
dedent /* ts */ ` const b = cva(); `,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
testFormat(
|
|
40
|
+
'handles cva(base, ...spreadConfig) by processing only base',
|
|
41
|
+
{ classNameThreshold: 4 },
|
|
42
|
+
dedent /* ts */ `
|
|
43
|
+
const b = cva(
|
|
44
|
+
'flex items-center justify-center text-sm font-bold bg-white',
|
|
45
|
+
...spreadConfig,
|
|
46
|
+
);
|
|
47
|
+
`,
|
|
48
|
+
dedent /* ts */ `
|
|
49
|
+
const b = cva(
|
|
50
|
+
['flex items-center justify-center', 'text-sm font-bold', 'bg-white'],
|
|
51
|
+
...spreadConfig,
|
|
52
|
+
);
|
|
53
|
+
`,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
testFormat(
|
|
57
|
+
'handles cva(base, identifierConfig) by processing only base',
|
|
58
|
+
{ classNameThreshold: 4 },
|
|
59
|
+
dedent /* ts */ `
|
|
60
|
+
const b = cva(
|
|
61
|
+
'flex items-center justify-center text-sm font-bold bg-white',
|
|
62
|
+
config,
|
|
63
|
+
);
|
|
64
|
+
`,
|
|
65
|
+
dedent /* ts */ `
|
|
66
|
+
const b = cva(
|
|
67
|
+
['flex items-center justify-center', 'text-sm font-bold', 'bg-white'],
|
|
68
|
+
config,
|
|
69
|
+
);
|
|
70
|
+
`,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('target-cva: full cva processing (base + config)', () => {
|
|
75
|
+
testFormat(
|
|
76
|
+
'formats both base and variants in a complete cva definition',
|
|
77
|
+
{ classNameThreshold: 4 },
|
|
78
|
+
dedent /* ts */ `
|
|
79
|
+
const b = cva(
|
|
80
|
+
'flex items-center justify-center text-sm font-bold bg-white',
|
|
81
|
+
{
|
|
82
|
+
variants: {
|
|
83
|
+
intent: {
|
|
84
|
+
primary:
|
|
85
|
+
'flex items-center justify-center text-sm font-bold bg-white shadow-md',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
compoundVariants: [
|
|
89
|
+
{
|
|
90
|
+
intent: 'primary',
|
|
91
|
+
class:
|
|
92
|
+
'flex items-center justify-center text-sm font-bold bg-white',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
`,
|
|
98
|
+
dedent /* ts */ `
|
|
99
|
+
const b = cva(
|
|
100
|
+
['flex items-center justify-center', 'text-sm font-bold', 'bg-white'],
|
|
101
|
+
{
|
|
102
|
+
variants: {
|
|
103
|
+
intent: {
|
|
104
|
+
primary: [
|
|
105
|
+
'flex items-center justify-center',
|
|
106
|
+
'text-sm font-bold',
|
|
107
|
+
'bg-white',
|
|
108
|
+
'shadow-md',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
compoundVariants: [
|
|
113
|
+
{
|
|
114
|
+
intent: 'primary',
|
|
115
|
+
class: [
|
|
116
|
+
'flex items-center justify-center',
|
|
117
|
+
'text-sm font-bold',
|
|
118
|
+
'bg-white',
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
`,
|
|
125
|
+
);
|
|
126
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Node } from 'estree-jsx';
|
|
2
|
+
import type { PluginOptions } from '../prettier';
|
|
3
|
+
import { isCallExpression, isIdentifier, isObjectExpression } from '../ast';
|
|
4
|
+
import { handleTargetCvaBase } from './target-cva-base';
|
|
5
|
+
import { handleTargetCvaCompoundVariants } from './target-cva-compound-variants';
|
|
6
|
+
import { handleTargetCvaVariants } from './target-cva-variants';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handles a `cva(...)` CallExpression, transforming static classes in
|
|
10
|
+
* `params[0]`, `params[1].variants`, and `params[1].compoundVariants`.
|
|
11
|
+
*/
|
|
12
|
+
export function handleTargetCVA(node: Node, options: PluginOptions): boolean {
|
|
13
|
+
// Validate that node is a CallExpression calling `cva(...)` with at least 1 argument
|
|
14
|
+
if (
|
|
15
|
+
!isCallExpression(node) ||
|
|
16
|
+
!isIdentifier(node.callee, 'cva') ||
|
|
17
|
+
node.arguments.length < 1
|
|
18
|
+
) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Process params[0] — base classes
|
|
23
|
+
handleTargetCvaBase(node, options);
|
|
24
|
+
|
|
25
|
+
// Process params[1] — config object (variants & compoundVariants)
|
|
26
|
+
const config = node.arguments[1];
|
|
27
|
+
if (config && config.type !== 'SpreadElement' && isObjectExpression(config)) {
|
|
28
|
+
handleTargetCvaVariants(config, options);
|
|
29
|
+
handleTargetCvaCompoundVariants(config, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return true;
|
|
33
|
+
}
|