tailwind-typescript-plugin 1.4.0-beta.23 → 1.4.0-beta.25

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 (94) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/lib/core/interfaces.d.ts +17 -1
  3. package/lib/core/interfaces.d.ts.map +1 -1
  4. package/lib/extractors/BaseExtractor.d.ts +7 -1
  5. package/lib/extractors/BaseExtractor.d.ts.map +1 -1
  6. package/lib/extractors/BaseExtractor.js +10 -0
  7. package/lib/extractors/BaseExtractor.js.map +1 -1
  8. package/lib/extractors/BaseExtractor.spec.d.ts +2 -0
  9. package/lib/extractors/BaseExtractor.spec.d.ts.map +1 -0
  10. package/lib/extractors/BaseExtractor.spec.js +421 -0
  11. package/lib/extractors/BaseExtractor.spec.js.map +1 -0
  12. package/lib/extractors/CvaExtractor.spec.d.ts +2 -0
  13. package/lib/extractors/CvaExtractor.spec.d.ts.map +1 -0
  14. package/lib/extractors/CvaExtractor.spec.js +1177 -0
  15. package/lib/extractors/CvaExtractor.spec.js.map +1 -0
  16. package/lib/extractors/ExpressionExtractor.spec.d.ts +2 -0
  17. package/lib/extractors/ExpressionExtractor.spec.d.ts.map +1 -0
  18. package/lib/extractors/ExpressionExtractor.spec.js +316 -0
  19. package/lib/extractors/ExpressionExtractor.spec.js.map +1 -0
  20. package/lib/extractors/JsxAttributeExtractor.d.ts +5 -0
  21. package/lib/extractors/JsxAttributeExtractor.d.ts.map +1 -1
  22. package/lib/extractors/JsxAttributeExtractor.js +6 -0
  23. package/lib/extractors/JsxAttributeExtractor.js.map +1 -1
  24. package/lib/extractors/JsxAttributeExtractor.spec.d.ts +2 -0
  25. package/lib/extractors/JsxAttributeExtractor.spec.d.ts.map +1 -0
  26. package/lib/extractors/JsxAttributeExtractor.spec.js +430 -0
  27. package/lib/extractors/JsxAttributeExtractor.spec.js.map +1 -0
  28. package/lib/extractors/TailwindVariantsExtractor.spec.d.ts +2 -0
  29. package/lib/extractors/TailwindVariantsExtractor.spec.d.ts.map +1 -0
  30. package/lib/extractors/TailwindVariantsExtractor.spec.js +1407 -0
  31. package/lib/extractors/TailwindVariantsExtractor.spec.js.map +1 -0
  32. package/lib/extractors/TemplateExpressionExtractor.spec.d.ts +2 -0
  33. package/lib/extractors/TemplateExpressionExtractor.spec.d.ts.map +1 -0
  34. package/lib/extractors/TemplateExpressionExtractor.spec.js +240 -0
  35. package/lib/extractors/TemplateExpressionExtractor.spec.js.map +1 -0
  36. package/lib/extractors/VariableReferenceExtractor.spec.d.ts +2 -0
  37. package/lib/extractors/VariableReferenceExtractor.spec.d.ts.map +1 -0
  38. package/lib/extractors/VariableReferenceExtractor.spec.js +138 -0
  39. package/lib/extractors/VariableReferenceExtractor.spec.js.map +1 -0
  40. package/lib/extractors/VueAttributeExtractor.d.ts +6 -0
  41. package/lib/extractors/VueAttributeExtractor.d.ts.map +1 -1
  42. package/lib/extractors/VueAttributeExtractor.js +7 -0
  43. package/lib/extractors/VueAttributeExtractor.js.map +1 -1
  44. package/lib/plugin/TailwindTypescriptPlugin.d.ts +9 -0
  45. package/lib/plugin/TailwindTypescriptPlugin.d.ts.map +1 -1
  46. package/lib/plugin/TailwindTypescriptPlugin.js +36 -11
  47. package/lib/plugin/TailwindTypescriptPlugin.js.map +1 -1
  48. package/lib/services/ClassNameExtractionService.d.ts +8 -3
  49. package/lib/services/ClassNameExtractionService.d.ts.map +1 -1
  50. package/lib/services/ClassNameExtractionService.js +26 -25
  51. package/lib/services/ClassNameExtractionService.js.map +1 -1
  52. package/lib/services/ClassNameExtractionService.spec.js +0 -7
  53. package/lib/services/ClassNameExtractionService.spec.js.map +1 -1
  54. package/lib/services/ConfigSchemaValidator.d.ts +40 -0
  55. package/lib/services/ConfigSchemaValidator.d.ts.map +1 -0
  56. package/lib/services/ConfigSchemaValidator.js +139 -0
  57. package/lib/services/ConfigSchemaValidator.js.map +1 -0
  58. package/lib/services/ConfigSchemaValidator.spec.d.ts +2 -0
  59. package/lib/services/ConfigSchemaValidator.spec.d.ts.map +1 -0
  60. package/lib/services/ConfigSchemaValidator.spec.js +344 -0
  61. package/lib/services/ConfigSchemaValidator.spec.js.map +1 -0
  62. package/lib/services/ConflictClassDetection.spec.js +48 -0
  63. package/lib/services/ConflictClassDetection.spec.js.map +1 -1
  64. package/lib/services/DiagnosticService.spec.d.ts +2 -0
  65. package/lib/services/DiagnosticService.spec.d.ts.map +1 -0
  66. package/lib/services/DiagnosticService.spec.js +259 -0
  67. package/lib/services/DiagnosticService.spec.js.map +1 -0
  68. package/lib/services/FileDiagnosticCache.spec.d.ts +2 -0
  69. package/lib/services/FileDiagnosticCache.spec.d.ts.map +1 -0
  70. package/lib/services/FileDiagnosticCache.spec.js +213 -0
  71. package/lib/services/FileDiagnosticCache.spec.js.map +1 -0
  72. package/lib/services/PerformanceCache.spec.d.ts +2 -0
  73. package/lib/services/PerformanceCache.spec.d.ts.map +1 -0
  74. package/lib/services/PerformanceCache.spec.js +168 -0
  75. package/lib/services/PerformanceCache.spec.js.map +1 -0
  76. package/lib/services/PluginConfigService.d.ts +19 -0
  77. package/lib/services/PluginConfigService.d.ts.map +1 -1
  78. package/lib/services/PluginConfigService.js +29 -0
  79. package/lib/services/PluginConfigService.js.map +1 -1
  80. package/lib/services/ValidationService.spec.d.ts +2 -0
  81. package/lib/services/ValidationService.spec.d.ts.map +1 -0
  82. package/lib/services/ValidationService.spec.js +289 -0
  83. package/lib/services/ValidationService.spec.js.map +1 -0
  84. package/lib/utils/FrameworkDetector.d.ts +1 -2
  85. package/lib/utils/FrameworkDetector.d.ts.map +1 -1
  86. package/lib/utils/FrameworkDetector.js +0 -5
  87. package/lib/utils/FrameworkDetector.js.map +1 -1
  88. package/lib/utils/FrameworkDetector.spec.js +1 -9
  89. package/lib/utils/FrameworkDetector.spec.js.map +1 -1
  90. package/package.json +10 -3
  91. package/lib/extractors/SvelteAttributeExtractor.d.ts +0 -17
  92. package/lib/extractors/SvelteAttributeExtractor.d.ts.map +0 -1
  93. package/lib/extractors/SvelteAttributeExtractor.js +0 -27
  94. package/lib/extractors/SvelteAttributeExtractor.js.map +0 -1
@@ -0,0 +1,1177 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const ts = __importStar(require("typescript/lib/tsserverlibrary"));
37
+ const CvaExtractor_1 = require("./CvaExtractor");
38
+ describe('CvaExtractor', () => {
39
+ let extractor;
40
+ const createContext = (code, overrides = {}) => {
41
+ const sourceFile = ts.createSourceFile('test.tsx', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
42
+ return {
43
+ typescript: ts,
44
+ sourceFile,
45
+ utilityFunctions: [],
46
+ ...overrides
47
+ };
48
+ };
49
+ /**
50
+ * Creates a context with a full TypeScript program and TypeChecker.
51
+ * This allows testing code paths that require type information.
52
+ */
53
+ const createContextWithTypeChecker = (code, overrides = {}) => {
54
+ const fileName = '/test.tsx';
55
+ // Create a virtual file system
56
+ const files = {
57
+ [fileName]: code,
58
+ // Add a minimal cva type declaration
59
+ '/node_modules/class-variance-authority/index.d.ts': `
60
+ export declare function cva<T>(base?: string, config?: T): (...args: any[]) => string;
61
+ `
62
+ };
63
+ // Create a compiler host
64
+ const compilerHost = {
65
+ getSourceFile: (name, languageVersion) => {
66
+ const content = files[name];
67
+ if (content !== undefined) {
68
+ return ts.createSourceFile(name, content, languageVersion, true);
69
+ }
70
+ return undefined;
71
+ },
72
+ getDefaultLibFileName: () => '/lib.d.ts',
73
+ writeFile: () => { },
74
+ getCurrentDirectory: () => '/',
75
+ getCanonicalFileName: (f) => f,
76
+ useCaseSensitiveFileNames: () => true,
77
+ getNewLine: () => '\n',
78
+ fileExists: (name) => name in files,
79
+ readFile: (name) => files[name],
80
+ directoryExists: () => true,
81
+ getDirectories: () => []
82
+ };
83
+ // Create a program
84
+ const program = ts.createProgram([fileName], {
85
+ target: ts.ScriptTarget.Latest,
86
+ module: ts.ModuleKind.ESNext,
87
+ jsx: ts.JsxEmit.React,
88
+ strict: true,
89
+ moduleResolution: ts.ModuleResolutionKind.NodeJs
90
+ }, compilerHost);
91
+ const sourceFile = program.getSourceFile(fileName);
92
+ const typeChecker = program.getTypeChecker();
93
+ return {
94
+ typescript: ts,
95
+ sourceFile,
96
+ typeChecker,
97
+ utilityFunctions: [],
98
+ ...overrides
99
+ };
100
+ };
101
+ const findCallExpression = (sourceFile) => {
102
+ let result;
103
+ const visit = (node) => {
104
+ if (ts.isCallExpression(node)) {
105
+ result = node;
106
+ return;
107
+ }
108
+ ts.forEachChild(node, visit);
109
+ };
110
+ visit(sourceFile);
111
+ return result;
112
+ };
113
+ const findAllCallExpressions = (sourceFile) => {
114
+ const results = [];
115
+ const visit = (node) => {
116
+ if (ts.isCallExpression(node)) {
117
+ results.push(node);
118
+ }
119
+ ts.forEachChild(node, visit);
120
+ };
121
+ visit(sourceFile);
122
+ return results;
123
+ };
124
+ const findLastCallExpression = (sourceFile) => {
125
+ const calls = findAllCallExpressions(sourceFile);
126
+ return calls[calls.length - 1];
127
+ };
128
+ beforeEach(() => {
129
+ extractor = new CvaExtractor_1.CvaExtractor();
130
+ });
131
+ describe('canHandle', () => {
132
+ it('should return true for call expressions', () => {
133
+ const code = "import { cva } from 'class-variance-authority'; cva('flex');";
134
+ const context = createContext(code);
135
+ const callExpr = findCallExpression(context.sourceFile);
136
+ expect(extractor.canHandle(callExpr, context)).toBe(true);
137
+ });
138
+ it('should return false for non-call expressions', () => {
139
+ const code = 'const x = "flex";';
140
+ const context = createContext(code);
141
+ expect(extractor.canHandle(context.sourceFile.statements[0], context)).toBe(false);
142
+ });
143
+ });
144
+ describe('extract - type guards', () => {
145
+ it('should return empty array for non-call expression node', () => {
146
+ const code = 'const x = "flex";';
147
+ const context = createContext(code);
148
+ const classes = extractor.extract(context.sourceFile.statements[0], context);
149
+ expect(classes).toHaveLength(0);
150
+ });
151
+ });
152
+ describe('extract - basic cva calls', () => {
153
+ it('should extract classes from cva base string', () => {
154
+ const code = `
155
+ import { cva } from 'class-variance-authority';
156
+ const button = cva('flex items-center');
157
+ `;
158
+ const context = createContext(code);
159
+ const callExpr = findCallExpression(context.sourceFile);
160
+ const classes = extractor.extract(callExpr, context);
161
+ expect(classes.map(c => c.className)).toContain('flex');
162
+ expect(classes.map(c => c.className)).toContain('items-center');
163
+ });
164
+ it('should extract classes from cva base array', () => {
165
+ const code = `
166
+ import { cva } from 'class-variance-authority';
167
+ const button = cva(['flex', 'items-center', 'justify-center']);
168
+ `;
169
+ const context = createContext(code);
170
+ const callExpr = findCallExpression(context.sourceFile);
171
+ const classes = extractor.extract(callExpr, context);
172
+ expect(classes.map(c => c.className)).toContain('flex');
173
+ expect(classes.map(c => c.className)).toContain('items-center');
174
+ expect(classes.map(c => c.className)).toContain('justify-center');
175
+ });
176
+ it('should return empty array when no cva import', () => {
177
+ const code = `
178
+ const button = cva('flex items-center');
179
+ `;
180
+ const context = createContext(code);
181
+ const callExpr = findCallExpression(context.sourceFile);
182
+ const classes = extractor.extract(callExpr, context);
183
+ expect(classes).toHaveLength(0);
184
+ });
185
+ it('should return empty array for cva call with no arguments', () => {
186
+ const code = `
187
+ import { cva } from 'class-variance-authority';
188
+ const button = cva();
189
+ `;
190
+ const context = createContext(code);
191
+ const callExpr = findCallExpression(context.sourceFile);
192
+ const classes = extractor.extract(callExpr, context);
193
+ expect(classes).toHaveLength(0);
194
+ });
195
+ });
196
+ describe('extract - cva variants', () => {
197
+ it('should extract classes from variants object', () => {
198
+ const code = `
199
+ import { cva } from 'class-variance-authority';
200
+ const button = cva('base', {
201
+ variants: {
202
+ intent: {
203
+ primary: 'bg-blue-500 text-white',
204
+ secondary: 'bg-gray-500'
205
+ }
206
+ }
207
+ });
208
+ `;
209
+ const context = createContext(code);
210
+ const callExpr = findCallExpression(context.sourceFile);
211
+ const classes = extractor.extract(callExpr, context);
212
+ expect(classes.map(c => c.className)).toContain('base');
213
+ expect(classes.map(c => c.className)).toContain('bg-blue-500');
214
+ expect(classes.map(c => c.className)).toContain('text-white');
215
+ expect(classes.map(c => c.className)).toContain('bg-gray-500');
216
+ });
217
+ it('should extract classes from variant arrays', () => {
218
+ const code = `
219
+ import { cva } from 'class-variance-authority';
220
+ const button = cva('base', {
221
+ variants: {
222
+ size: {
223
+ sm: ['text-sm', 'py-1', 'px-2'],
224
+ lg: ['text-lg', 'py-3', 'px-4']
225
+ }
226
+ }
227
+ });
228
+ `;
229
+ const context = createContext(code);
230
+ const callExpr = findCallExpression(context.sourceFile);
231
+ const classes = extractor.extract(callExpr, context);
232
+ expect(classes.map(c => c.className)).toContain('text-sm');
233
+ expect(classes.map(c => c.className)).toContain('py-1');
234
+ expect(classes.map(c => c.className)).toContain('text-lg');
235
+ expect(classes.map(c => c.className)).toContain('py-3');
236
+ });
237
+ it('should handle boolean variants with true/false keys', () => {
238
+ const code = `
239
+ import { cva } from 'class-variance-authority';
240
+ const button = cva('base', {
241
+ variants: {
242
+ disabled: {
243
+ true: 'opacity-50 cursor-not-allowed',
244
+ false: 'cursor-pointer'
245
+ }
246
+ }
247
+ });
248
+ `;
249
+ const context = createContext(code);
250
+ const callExpr = findCallExpression(context.sourceFile);
251
+ const classes = extractor.extract(callExpr, context);
252
+ expect(classes.map(c => c.className)).toContain('opacity-50');
253
+ expect(classes.map(c => c.className)).toContain('cursor-not-allowed');
254
+ expect(classes.map(c => c.className)).toContain('cursor-pointer');
255
+ });
256
+ it('should handle null variant values', () => {
257
+ const code = `
258
+ import { cva } from 'class-variance-authority';
259
+ const button = cva('base', {
260
+ variants: {
261
+ intent: {
262
+ primary: 'bg-blue-500',
263
+ none: null
264
+ }
265
+ }
266
+ });
267
+ `;
268
+ const context = createContext(code);
269
+ const callExpr = findCallExpression(context.sourceFile);
270
+ const classes = extractor.extract(callExpr, context);
271
+ expect(classes.map(c => c.className)).toContain('bg-blue-500');
272
+ // null should be skipped
273
+ expect(classes.map(c => c.className)).not.toContain('null');
274
+ });
275
+ it('should handle non-object variant initializers', () => {
276
+ const code = `
277
+ import { cva } from 'class-variance-authority';
278
+ const button = cva('base', {
279
+ variants: 'not-an-object'
280
+ });
281
+ `;
282
+ const context = createContext(code);
283
+ const callExpr = findCallExpression(context.sourceFile);
284
+ // Should not crash
285
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
286
+ });
287
+ });
288
+ describe('extract - compoundVariants', () => {
289
+ it('should extract classes from compoundVariants with class property', () => {
290
+ const code = `
291
+ import { cva } from 'class-variance-authority';
292
+ const button = cva('base', {
293
+ variants: {
294
+ intent: { primary: 'bg-blue-500' }
295
+ },
296
+ compoundVariants: [
297
+ {
298
+ intent: 'primary',
299
+ class: 'font-bold uppercase'
300
+ }
301
+ ]
302
+ });
303
+ `;
304
+ const context = createContext(code);
305
+ const callExpr = findCallExpression(context.sourceFile);
306
+ const classes = extractor.extract(callExpr, context);
307
+ expect(classes.map(c => c.className)).toContain('font-bold');
308
+ expect(classes.map(c => c.className)).toContain('uppercase');
309
+ });
310
+ it('should extract classes from compoundVariants with className property', () => {
311
+ const code = `
312
+ import { cva } from 'class-variance-authority';
313
+ const button = cva('base', {
314
+ compoundVariants: [
315
+ {
316
+ intent: 'primary',
317
+ className: 'shadow-lg'
318
+ }
319
+ ]
320
+ });
321
+ `;
322
+ const context = createContext(code);
323
+ const callExpr = findCallExpression(context.sourceFile);
324
+ const classes = extractor.extract(callExpr, context);
325
+ expect(classes.map(c => c.className)).toContain('shadow-lg');
326
+ });
327
+ it('should handle compoundVariants with array values', () => {
328
+ const code = `
329
+ import { cva } from 'class-variance-authority';
330
+ const button = cva('base', {
331
+ compoundVariants: [
332
+ {
333
+ intent: 'primary',
334
+ class: ['font-bold', 'uppercase']
335
+ }
336
+ ]
337
+ });
338
+ `;
339
+ const context = createContext(code);
340
+ const callExpr = findCallExpression(context.sourceFile);
341
+ const classes = extractor.extract(callExpr, context);
342
+ expect(classes.map(c => c.className)).toContain('font-bold');
343
+ expect(classes.map(c => c.className)).toContain('uppercase');
344
+ });
345
+ it('should handle non-array compoundVariants', () => {
346
+ const code = `
347
+ import { cva } from 'class-variance-authority';
348
+ const button = cva('base', {
349
+ compoundVariants: 'not-an-array'
350
+ });
351
+ `;
352
+ const context = createContext(code);
353
+ const callExpr = findCallExpression(context.sourceFile);
354
+ // Should not crash
355
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
356
+ });
357
+ });
358
+ describe('extract - defaultVariants', () => {
359
+ it('should skip defaultVariants property', () => {
360
+ const code = `
361
+ import { cva } from 'class-variance-authority';
362
+ const button = cva('base', {
363
+ variants: {
364
+ intent: { primary: 'bg-blue-500' }
365
+ },
366
+ defaultVariants: {
367
+ intent: 'primary'
368
+ }
369
+ });
370
+ `;
371
+ const context = createContext(code);
372
+ const callExpr = findCallExpression(context.sourceFile);
373
+ const classes = extractor.extract(callExpr, context);
374
+ // Should not include 'primary' as a class (it's a variant value, not a class)
375
+ expect(classes.map(c => c.className)).not.toContain('primary');
376
+ });
377
+ });
378
+ describe('extract - import aliasing', () => {
379
+ it('should handle aliased imports', () => {
380
+ const code = `
381
+ import { cva as createVariants } from 'class-variance-authority';
382
+ const button = createVariants('flex items-center');
383
+ `;
384
+ const context = createContext(code);
385
+ const callExpr = findCallExpression(context.sourceFile);
386
+ const classes = extractor.extract(callExpr, context);
387
+ expect(classes.map(c => c.className)).toContain('flex');
388
+ expect(classes.map(c => c.className)).toContain('items-center');
389
+ });
390
+ it('should handle member expression calls', () => {
391
+ const code = `
392
+ import { cva } from 'class-variance-authority';
393
+ const utils = { cva };
394
+ const button = utils.cva('flex items-center');
395
+ `;
396
+ const context = createContext(code);
397
+ const calls = findAllCallExpressions(context.sourceFile);
398
+ // Find the utils.cva call
399
+ const cvaCall = calls.find(c => c.getText().includes('utils.cva'));
400
+ if (cvaCall) {
401
+ const classes = extractor.extract(cvaCall, context);
402
+ expect(classes.map(c => c.className)).toContain('flex');
403
+ }
404
+ });
405
+ });
406
+ describe('extract - template literals', () => {
407
+ it('should extract classes from no-substitution template literal', () => {
408
+ const code = `
409
+ import { cva } from 'class-variance-authority';
410
+ const button = cva(\`flex items-center\`);
411
+ `;
412
+ const context = createContext(code);
413
+ const callExpr = findCallExpression(context.sourceFile);
414
+ const classes = extractor.extract(callExpr, context);
415
+ expect(classes.map(c => c.className)).toContain('flex');
416
+ expect(classes.map(c => c.className)).toContain('items-center');
417
+ });
418
+ it('should extract static parts from template expression', () => {
419
+ const code = `
420
+ import { cva } from 'class-variance-authority';
421
+ const dynamic = 'test';
422
+ const button = cva(\`flex \${dynamic} items-center\`);
423
+ `;
424
+ const context = createContext(code);
425
+ const calls = findAllCallExpressions(context.sourceFile);
426
+ const cvaCall = calls.find(c => c.getText().includes('cva('));
427
+ if (cvaCall) {
428
+ const classes = extractor.extract(cvaCall, context);
429
+ expect(classes.map(c => c.className)).toContain('flex');
430
+ expect(classes.map(c => c.className)).toContain('items-center');
431
+ }
432
+ });
433
+ });
434
+ describe('extract - expression types in values', () => {
435
+ it('should extract from ternary expression in base', () => {
436
+ const code = `
437
+ import { cva } from 'class-variance-authority';
438
+ const condition = true;
439
+ const button = cva(condition ? 'flex' : 'block');
440
+ `;
441
+ const context = createContext(code);
442
+ const calls = findAllCallExpressions(context.sourceFile);
443
+ const cvaCall = calls.find(c => c.getText().includes('cva('));
444
+ if (cvaCall) {
445
+ const classes = extractor.extract(cvaCall, context);
446
+ expect(classes.map(c => c.className)).toContain('flex');
447
+ expect(classes.map(c => c.className)).toContain('block');
448
+ }
449
+ });
450
+ it('should extract from binary expression (logical AND)', () => {
451
+ const code = `
452
+ import { cva } from 'class-variance-authority';
453
+ const condition = true;
454
+ const button = cva(condition && 'flex');
455
+ `;
456
+ const context = createContext(code);
457
+ const calls = findAllCallExpressions(context.sourceFile);
458
+ const cvaCall = calls.find(c => c.getText().includes('cva('));
459
+ if (cvaCall) {
460
+ const classes = extractor.extract(cvaCall, context);
461
+ expect(classes.map(c => c.className)).toContain('flex');
462
+ }
463
+ });
464
+ it('should extract from parenthesized expression', () => {
465
+ const code = `
466
+ import { cva } from 'class-variance-authority';
467
+ const button = cva(('flex items-center'));
468
+ `;
469
+ const context = createContext(code);
470
+ const callExpr = findCallExpression(context.sourceFile);
471
+ const classes = extractor.extract(callExpr, context);
472
+ expect(classes.map(c => c.className)).toContain('flex');
473
+ expect(classes.map(c => c.className)).toContain('items-center');
474
+ });
475
+ it('should extract from as expression', () => {
476
+ const code = `
477
+ import { cva } from 'class-variance-authority';
478
+ const button = cva('flex items-center' as string);
479
+ `;
480
+ const context = createContext(code);
481
+ const callExpr = findCallExpression(context.sourceFile);
482
+ const classes = extractor.extract(callExpr, context);
483
+ expect(classes.map(c => c.className)).toContain('flex');
484
+ expect(classes.map(c => c.className)).toContain('items-center');
485
+ });
486
+ it('should extract from non-null expression', () => {
487
+ const code = `
488
+ import { cva } from 'class-variance-authority';
489
+ const maybeClasses = 'flex items-center';
490
+ const button = cva(maybeClasses!);
491
+ `;
492
+ const context = createContext(code);
493
+ const calls = findAllCallExpressions(context.sourceFile);
494
+ const cvaCall = calls.find(c => c.getText().includes('cva('));
495
+ // Should not crash
496
+ expect(() => {
497
+ if (cvaCall)
498
+ extractor.extract(cvaCall, context);
499
+ }).not.toThrow();
500
+ });
501
+ });
502
+ describe('extract - array with expressions', () => {
503
+ it('should extract from array with ternary expressions', () => {
504
+ const code = `
505
+ import { cva } from 'class-variance-authority';
506
+ const condition = true;
507
+ const button = cva(['flex', condition ? 'visible' : 'hidden']);
508
+ `;
509
+ const context = createContext(code);
510
+ const calls = findAllCallExpressions(context.sourceFile);
511
+ const cvaCall = calls.find(c => c.getText().includes('cva('));
512
+ if (cvaCall) {
513
+ const classes = extractor.extract(cvaCall, context);
514
+ expect(classes.map(c => c.className)).toContain('flex');
515
+ expect(classes.map(c => c.className)).toContain('visible');
516
+ expect(classes.map(c => c.className)).toContain('hidden');
517
+ }
518
+ });
519
+ });
520
+ describe('extract - isVariant marking', () => {
521
+ it('should mark base classes without isVariant', () => {
522
+ const code = `
523
+ import { cva } from 'class-variance-authority';
524
+ const button = cva('base-class', {
525
+ variants: {
526
+ intent: {
527
+ primary: 'variant-class'
528
+ }
529
+ }
530
+ });
531
+ `;
532
+ const context = createContext(code);
533
+ const callExpr = findCallExpression(context.sourceFile);
534
+ const classes = extractor.extract(callExpr, context);
535
+ const baseClass = classes.find(c => c.className === 'base-class');
536
+ const variantClass = classes.find(c => c.className === 'variant-class');
537
+ expect(baseClass?.isVariant).toBeFalsy();
538
+ expect(variantClass?.isVariant).toBe(true);
539
+ });
540
+ it('should mark compoundVariant classes with isVariant', () => {
541
+ const code = `
542
+ import { cva } from 'class-variance-authority';
543
+ const button = cva('base', {
544
+ compoundVariants: [
545
+ { intent: 'primary', class: 'compound-class' }
546
+ ]
547
+ });
548
+ `;
549
+ const context = createContext(code);
550
+ const callExpr = findCallExpression(context.sourceFile);
551
+ const classes = extractor.extract(callExpr, context);
552
+ const compoundClass = classes.find(c => c.className === 'compound-class');
553
+ expect(compoundClass?.isVariant).toBe(true);
554
+ });
555
+ });
556
+ describe('extract - attributeId', () => {
557
+ it('should set attributeId for all classes', () => {
558
+ const code = `
559
+ import { cva } from 'class-variance-authority';
560
+ const button = cva('flex items-center');
561
+ `;
562
+ const context = createContext(code);
563
+ const callExpr = findCallExpression(context.sourceFile);
564
+ const classes = extractor.extract(callExpr, context);
565
+ expect(classes[0].attributeId).toBeDefined();
566
+ expect(classes[0].attributeId).toMatch(/^cva:\d+-\d+$/);
567
+ expect(classes[0].attributeId).toBe(classes[1].attributeId);
568
+ });
569
+ });
570
+ describe('extract - property name handling', () => {
571
+ it('should handle string literal property names', () => {
572
+ const code = `
573
+ import { cva } from 'class-variance-authority';
574
+ const button = cva('base', {
575
+ 'variants': {
576
+ 'intent': {
577
+ 'primary': 'bg-blue-500'
578
+ }
579
+ }
580
+ });
581
+ `;
582
+ const context = createContext(code);
583
+ const callExpr = findCallExpression(context.sourceFile);
584
+ const classes = extractor.extract(callExpr, context);
585
+ expect(classes.map(c => c.className)).toContain('bg-blue-500');
586
+ });
587
+ });
588
+ describe('extract - config object validation', () => {
589
+ it('should handle non-object config argument', () => {
590
+ const code = `
591
+ import { cva } from 'class-variance-authority';
592
+ const button = cva('base', 'not-an-object');
593
+ `;
594
+ const context = createContext(code);
595
+ const callExpr = findCallExpression(context.sourceFile);
596
+ const classes = extractor.extract(callExpr, context);
597
+ expect(classes.map(c => c.className)).toContain('base');
598
+ });
599
+ it('should handle spread elements in config', () => {
600
+ const code = `
601
+ import { cva } from 'class-variance-authority';
602
+ const button = cva('base', {
603
+ ...otherConfig,
604
+ variants: { intent: { primary: 'bg-blue-500' } }
605
+ });
606
+ `;
607
+ const context = createContext(code);
608
+ const callExpr = findCallExpression(context.sourceFile);
609
+ // Should not crash on spread elements
610
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
611
+ });
612
+ });
613
+ describe('caching', () => {
614
+ it('should cache import detection results', () => {
615
+ const code = `
616
+ import { cva } from 'class-variance-authority';
617
+ const button = cva('flex');
618
+ `;
619
+ const context = createContext(code);
620
+ const callExpr = findCallExpression(context.sourceFile);
621
+ // Call twice to test caching
622
+ extractor.extract(callExpr, context);
623
+ const classes = extractor.extract(callExpr, context);
624
+ expect(classes.map(c => c.className)).toContain('flex');
625
+ });
626
+ it('should clear cache when clearCache is called', () => {
627
+ const code = `
628
+ import { cva } from 'class-variance-authority';
629
+ const button = cva('flex');
630
+ `;
631
+ const context = createContext(code);
632
+ const callExpr = findCallExpression(context.sourceFile);
633
+ extractor.extract(callExpr, context);
634
+ extractor.clearCache();
635
+ // Should still work after cache clear
636
+ const classes = extractor.extract(callExpr, context);
637
+ expect(classes.map(c => c.className)).toContain('flex');
638
+ });
639
+ });
640
+ describe('edge cases', () => {
641
+ it('should return empty array for non-cva call expression', () => {
642
+ const code = `
643
+ import { cva } from 'class-variance-authority';
644
+ const x = otherFunction('flex');
645
+ `;
646
+ const context = createContext(code);
647
+ const callExpr = findCallExpression(context.sourceFile);
648
+ const classes = extractor.extract(callExpr, context);
649
+ expect(classes).toHaveLength(0);
650
+ });
651
+ it('should handle import without import clause', () => {
652
+ const code = `
653
+ import 'class-variance-authority';
654
+ const button = cva('flex');
655
+ `;
656
+ const context = createContext(code);
657
+ const callExpr = findCallExpression(context.sourceFile);
658
+ const classes = extractor.extract(callExpr, context);
659
+ expect(classes).toHaveLength(0);
660
+ });
661
+ it('should handle empty variants object', () => {
662
+ const code = `
663
+ import { cva } from 'class-variance-authority';
664
+ const button = cva('base', { variants: {} });
665
+ `;
666
+ const context = createContext(code);
667
+ const callExpr = findCallExpression(context.sourceFile);
668
+ const classes = extractor.extract(callExpr, context);
669
+ expect(classes.map(c => c.className)).toContain('base');
670
+ });
671
+ it('should handle unknown config properties', () => {
672
+ const code = `
673
+ import { cva } from 'class-variance-authority';
674
+ const button = cva('base', {
675
+ unknownProp: 'some-class'
676
+ });
677
+ `;
678
+ const context = createContext(code);
679
+ const callExpr = findCallExpression(context.sourceFile);
680
+ const classes = extractor.extract(callExpr, context);
681
+ // Unknown props are processed as potential class containers
682
+ expect(classes.map(c => c.className)).toContain('some-class');
683
+ });
684
+ });
685
+ describe('extract with TypeChecker - cva function calls', () => {
686
+ it('should extract class from cva-created function call with class property', () => {
687
+ const code = `
688
+ import { cva } from 'class-variance-authority';
689
+ const button = cva('base-class', {
690
+ variants: {
691
+ intent: {
692
+ primary: 'bg-blue-500'
693
+ }
694
+ }
695
+ });
696
+ const result = button({ intent: 'primary', class: 'extra-class' });
697
+ `;
698
+ const context = createContextWithTypeChecker(code);
699
+ const callExpr = findLastCallExpression(context.sourceFile);
700
+ const classes = extractor.extract(callExpr, context);
701
+ expect(classes.map(c => c.className)).toContain('extra-class');
702
+ });
703
+ it('should extract class from cva-created function call with className property', () => {
704
+ const code = `
705
+ import { cva } from 'class-variance-authority';
706
+ const button = cva('base-class');
707
+ const result = button({ className: 'override-class' });
708
+ `;
709
+ const context = createContextWithTypeChecker(code);
710
+ const callExpr = findLastCallExpression(context.sourceFile);
711
+ const classes = extractor.extract(callExpr, context);
712
+ expect(classes.map(c => c.className)).toContain('override-class');
713
+ });
714
+ it('should extract multiple classes from cva function call', () => {
715
+ const code = `
716
+ import { cva } from 'class-variance-authority';
717
+ const button = cva('base');
718
+ const result = button({ class: 'flex items-center gap-2' });
719
+ `;
720
+ const context = createContextWithTypeChecker(code);
721
+ const callExpr = findLastCallExpression(context.sourceFile);
722
+ const classes = extractor.extract(callExpr, context);
723
+ expect(classes.map(c => c.className)).toContain('flex');
724
+ expect(classes.map(c => c.className)).toContain('items-center');
725
+ expect(classes.map(c => c.className)).toContain('gap-2');
726
+ });
727
+ it('should return empty array when cva function call has no arguments', () => {
728
+ const code = `
729
+ import { cva } from 'class-variance-authority';
730
+ const button = cva('base');
731
+ const result = button();
732
+ `;
733
+ const context = createContextWithTypeChecker(code);
734
+ const callExpr = findLastCallExpression(context.sourceFile);
735
+ const classes = extractor.extract(callExpr, context);
736
+ expect(classes).toHaveLength(0);
737
+ });
738
+ it('should return empty array when cva function call arg is not object literal', () => {
739
+ const code = `
740
+ import { cva } from 'class-variance-authority';
741
+ const button = cva('base');
742
+ const options = { class: 'flex' };
743
+ const result = button(options);
744
+ `;
745
+ const context = createContextWithTypeChecker(code);
746
+ const callExpr = findLastCallExpression(context.sourceFile);
747
+ const classes = extractor.extract(callExpr, context);
748
+ // Variable reference - can't extract without resolving
749
+ expect(classes).toHaveLength(0);
750
+ });
751
+ it('should skip utility functions even with TypeChecker', () => {
752
+ const code = `
753
+ import { cva } from 'class-variance-authority';
754
+ const button = cva('base');
755
+ const result = button({ class: 'flex' });
756
+ `;
757
+ const context = createContextWithTypeChecker(code, {
758
+ utilityFunctions: ['button']
759
+ });
760
+ const callExpr = findLastCallExpression(context.sourceFile);
761
+ const classes = extractor.extract(callExpr, context);
762
+ // button is marked as utility function, should not extract
763
+ expect(classes).toHaveLength(0);
764
+ });
765
+ it('should cache symbol lookup results', () => {
766
+ const code = `
767
+ import { cva } from 'class-variance-authority';
768
+ const button = cva('base');
769
+ const result1 = button({ class: 'flex' });
770
+ const result2 = button({ class: 'grid' });
771
+ `;
772
+ const context = createContextWithTypeChecker(code);
773
+ const calls = findAllCallExpressions(context.sourceFile);
774
+ const buttonCall1 = calls[1]; // First button() call
775
+ const buttonCall2 = calls[2]; // Second button() call
776
+ // Both calls should work
777
+ const classes1 = extractor.extract(buttonCall1, context);
778
+ const classes2 = extractor.extract(buttonCall2, context);
779
+ expect(classes1.map(c => c.className)).toContain('flex');
780
+ expect(classes2.map(c => c.className)).toContain('grid');
781
+ });
782
+ it('should set attributeId for cva function call classes', () => {
783
+ const code = `
784
+ import { cva } from 'class-variance-authority';
785
+ const button = cva('base');
786
+ const result = button({ class: 'flex items-center' });
787
+ `;
788
+ const context = createContextWithTypeChecker(code);
789
+ const callExpr = findLastCallExpression(context.sourceFile);
790
+ const classes = extractor.extract(callExpr, context);
791
+ expect(classes[0].attributeId).toBeDefined();
792
+ expect(classes[0].attributeId).toMatch(/^cva-call:\d+-\d+$/);
793
+ });
794
+ it('should not extract from non-cva function calls', () => {
795
+ const code = `
796
+ import { cva } from 'class-variance-authority';
797
+ const notCva = () => {};
798
+ const result = notCva({ class: 'flex' });
799
+ `;
800
+ const context = createContextWithTypeChecker(code);
801
+ const callExpr = findLastCallExpression(context.sourceFile);
802
+ const classes = extractor.extract(callExpr, context);
803
+ expect(classes).toHaveLength(0);
804
+ });
805
+ it('should handle property access expression calls', () => {
806
+ const code = `
807
+ import { cva } from 'class-variance-authority';
808
+ const variants = {
809
+ button: cva('base-class')
810
+ };
811
+ const result = variants.button({ class: 'extra-class' });
812
+ `;
813
+ const context = createContextWithTypeChecker(code);
814
+ const callExpr = findLastCallExpression(context.sourceFile);
815
+ // This tests the property access expression path in isCvaCreatedFunctionCall
816
+ // The result depends on whether TypeChecker can resolve the symbol
817
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
818
+ });
819
+ it('should skip non-identifier and non-property-access expressions', () => {
820
+ const code = `
821
+ import { cva } from 'class-variance-authority';
822
+ const getButton = () => cva('base');
823
+ const result = getButton()({ class: 'flex' });
824
+ `;
825
+ const context = createContextWithTypeChecker(code);
826
+ const calls = findAllCallExpressions(context.sourceFile);
827
+ const outerCall = calls[calls.length - 1]; // getButton()({ class: 'flex' })
828
+ const classes = extractor.extract(outerCall, context);
829
+ // Call expression as the expression (not identifier) - should return empty
830
+ expect(classes).toHaveLength(0);
831
+ });
832
+ it('should handle symbol without declarations', () => {
833
+ // This is an edge case that's hard to trigger, but we can at least
834
+ // verify the code doesn't crash
835
+ const code = `
836
+ import { cva } from 'class-variance-authority';
837
+ const button = cva('base');
838
+ button({ class: 'flex' });
839
+ `;
840
+ const context = createContextWithTypeChecker(code);
841
+ const callExpr = findLastCallExpression(context.sourceFile);
842
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
843
+ });
844
+ });
845
+ describe('extract with TypeChecker - edge cases', () => {
846
+ it('should handle class property with array value', () => {
847
+ const code = `
848
+ import { cva } from 'class-variance-authority';
849
+ const button = cva('base');
850
+ const result = button({ class: ['flex', 'items-center'] });
851
+ `;
852
+ const context = createContextWithTypeChecker(code);
853
+ const callExpr = findLastCallExpression(context.sourceFile);
854
+ const classes = extractor.extract(callExpr, context);
855
+ expect(classes.map(c => c.className)).toContain('flex');
856
+ expect(classes.map(c => c.className)).toContain('items-center');
857
+ });
858
+ it('should skip non-property-assignment in function call arg', () => {
859
+ const code = `
860
+ import { cva } from 'class-variance-authority';
861
+ const button = cva('base');
862
+ const extra = 'flex';
863
+ const result = button({ class: 'grid', extra });
864
+ `;
865
+ const context = createContextWithTypeChecker(code);
866
+ const callExpr = findLastCallExpression(context.sourceFile);
867
+ const classes = extractor.extract(callExpr, context);
868
+ // Should only extract 'grid' from class property
869
+ expect(classes.map(c => c.className)).toContain('grid');
870
+ });
871
+ it('should handle aliased cva import with TypeChecker', () => {
872
+ const code = `
873
+ import { cva as createVariant } from 'class-variance-authority';
874
+ const button = createVariant('base-class');
875
+ const result = button({ class: 'extra-class' });
876
+ `;
877
+ const context = createContextWithTypeChecker(code);
878
+ const callExpr = findLastCallExpression(context.sourceFile);
879
+ const classes = extractor.extract(callExpr, context);
880
+ expect(classes.map(c => c.className)).toContain('extra-class');
881
+ });
882
+ });
883
+ describe('100% coverage - edge cases', () => {
884
+ it('should return false for cva call with call expression (not identifier/property access)', () => {
885
+ // Line 88: return false when expression is neither identifier nor property access
886
+ const code = `
887
+ import { cva } from 'class-variance-authority';
888
+ const getCva = () => cva;
889
+ const button = getCva()('base-class');
890
+ `;
891
+ const context = createContext(code);
892
+ const calls = findAllCallExpressions(context.sourceFile);
893
+ // getCva()('base-class') - the outer call has a call expression as its expression
894
+ const outerCall = calls[calls.length - 1];
895
+ const classes = extractor.extract(outerCall, context);
896
+ // Should not match as cva call since expression is a call expression
897
+ expect(classes).toHaveLength(0);
898
+ });
899
+ it('should skip computed property names in config (line 197)', () => {
900
+ const code = `
901
+ import { cva } from 'class-variance-authority';
902
+ const prop = 'variants';
903
+ const button = cva('base', { [prop]: { size: { sm: 'text-sm' } } });
904
+ `;
905
+ const context = createContext(code);
906
+ const callExpr = findCallExpression(context.sourceFile);
907
+ const classes = extractor.extract(callExpr, context);
908
+ // Base class should be extracted, but computed property is skipped
909
+ expect(classes.map(c => c.className)).toContain('base');
910
+ });
911
+ it('should skip shorthand properties in variants (line 246)', () => {
912
+ const code = `
913
+ import { cva } from 'class-variance-authority';
914
+ const size = { sm: 'text-sm' };
915
+ const button = cva('base', { variants: { size } });
916
+ `;
917
+ const context = createContext(code);
918
+ const callExpr = findCallExpression(context.sourceFile);
919
+ const classes = extractor.extract(callExpr, context);
920
+ // Only base should be extracted
921
+ expect(classes.map(c => c.className)).toContain('base');
922
+ });
923
+ it('should skip spread in variant options (line 254)', () => {
924
+ const code = `
925
+ import { cva } from 'class-variance-authority';
926
+ const options = { sm: 'text-sm' };
927
+ const button = cva('base', {
928
+ variants: {
929
+ size: {
930
+ ...options,
931
+ lg: 'text-lg'
932
+ }
933
+ }
934
+ });
935
+ `;
936
+ const context = createContext(code);
937
+ const callExpr = findCallExpression(context.sourceFile);
938
+ const classes = extractor.extract(callExpr, context);
939
+ expect(classes.map(c => c.className)).toContain('base');
940
+ expect(classes.map(c => c.className)).toContain('text-lg');
941
+ });
942
+ it('should skip non-object elements in compoundVariants (line 296)', () => {
943
+ const code = `
944
+ import { cva } from 'class-variance-authority';
945
+ const button = cva('base', {
946
+ compoundVariants: [
947
+ 'not-an-object',
948
+ { intent: 'primary', class: 'font-bold' }
949
+ ]
950
+ });
951
+ `;
952
+ const context = createContext(code);
953
+ const callExpr = findCallExpression(context.sourceFile);
954
+ const classes = extractor.extract(callExpr, context);
955
+ expect(classes.map(c => c.className)).toContain('base');
956
+ expect(classes.map(c => c.className)).toContain('font-bold');
957
+ });
958
+ it('should skip spread in compoundVariants properties (line 302)', () => {
959
+ const code = `
960
+ import { cva } from 'class-variance-authority';
961
+ const conditions = { intent: 'primary' };
962
+ const button = cva('base', {
963
+ compoundVariants: [
964
+ {
965
+ ...conditions,
966
+ class: 'font-bold'
967
+ }
968
+ ]
969
+ });
970
+ `;
971
+ const context = createContext(code);
972
+ const callExpr = findCallExpression(context.sourceFile);
973
+ const classes = extractor.extract(callExpr, context);
974
+ expect(classes.map(c => c.className)).toContain('font-bold');
975
+ });
976
+ it('should handle identifier in extractFromValue (lines 340-341)', () => {
977
+ // This tests the identifier branch in extractFromValue
978
+ const code = `
979
+ import { cva } from 'class-variance-authority';
980
+ const myBase = 'flex items-center';
981
+ const button = cva(myBase);
982
+ `;
983
+ const context = createContext(code);
984
+ const callExpr = findCallExpression(context.sourceFile);
985
+ // Without TypeChecker, variable references won't be resolved
986
+ // but the code path should be hit
987
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
988
+ });
989
+ it('should handle identifier in array (lines 355-357)', () => {
990
+ const code = `
991
+ import { cva } from 'class-variance-authority';
992
+ const extraClass = 'extra';
993
+ const button = cva(['flex', extraClass, 'items-center']);
994
+ `;
995
+ const context = createContext(code);
996
+ const callExpr = findCallExpression(context.sourceFile);
997
+ const classes = extractor.extract(callExpr, context);
998
+ expect(classes.map(c => c.className)).toContain('flex');
999
+ expect(classes.map(c => c.className)).toContain('items-center');
1000
+ });
1001
+ it('should handle return [] at end of extractFromValue (line 461)', () => {
1002
+ // This tests when node doesn't match any known expression type
1003
+ const code = `
1004
+ import { cva } from 'class-variance-authority';
1005
+ const button = cva('base', {
1006
+ variants: {
1007
+ size: {
1008
+ sm: someFunction()
1009
+ }
1010
+ }
1011
+ });
1012
+ `;
1013
+ const context = createContext(code);
1014
+ const callExpr = findCallExpression(context.sourceFile);
1015
+ // someFunction() is a CallExpression which isn't handled in extractFromValue
1016
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
1017
+ });
1018
+ it('should return false when symbol has no declarations (line 525)', () => {
1019
+ // This is hard to trigger directly, but we can test through a function
1020
+ // that references something TypeChecker can't resolve
1021
+ const code = `
1022
+ import { cva } from 'class-variance-authority';
1023
+ declare const button: ReturnType<typeof cva>;
1024
+ const result = button({ class: 'flex' });
1025
+ `;
1026
+ const context = createContextWithTypeChecker(code);
1027
+ const callExpr = findLastCallExpression(context.sourceFile);
1028
+ // Should not crash even if symbol resolution is tricky
1029
+ expect(() => extractor.extract(callExpr, context)).not.toThrow();
1030
+ });
1031
+ it('should return null for numeric literal property name (line 610)', () => {
1032
+ const code = `
1033
+ import { cva } from 'class-variance-authority';
1034
+ const button = cva('base', {
1035
+ variants: {
1036
+ size: {
1037
+ 100: 'text-xs'
1038
+ }
1039
+ }
1040
+ });
1041
+ `;
1042
+ const context = createContext(code);
1043
+ const callExpr = findCallExpression(context.sourceFile);
1044
+ const classes = extractor.extract(callExpr, context);
1045
+ // Numeric keys should still work (they're treated as identifiers)
1046
+ expect(classes.map(c => c.className)).toContain('base');
1047
+ expect(classes.map(c => c.className)).toContain('text-xs');
1048
+ });
1049
+ it('should handle export default cva pattern (lines 541-544)', () => {
1050
+ // Export assignments are a specific declaration type
1051
+ const code = `
1052
+ import { cva } from 'class-variance-authority';
1053
+ const button = cva('base');
1054
+ export default button;
1055
+ const result = button({ class: 'flex' });
1056
+ `;
1057
+ const context = createContextWithTypeChecker(code);
1058
+ const callExpr = findLastCallExpression(context.sourceFile);
1059
+ const classes = extractor.extract(callExpr, context);
1060
+ expect(classes.map(c => c.className)).toContain('flex');
1061
+ });
1062
+ it('should return false when TypeChecker returns no symbol (line 503)', () => {
1063
+ // Create a scenario where getSymbolAtLocation returns undefined
1064
+ const code = `
1065
+ import { cva } from 'class-variance-authority';
1066
+ const result = undefinedFunction({ class: 'flex' });
1067
+ `;
1068
+ const context = createContextWithTypeChecker(code);
1069
+ const callExpr = findLastCallExpression(context.sourceFile);
1070
+ const classes = extractor.extract(callExpr, context);
1071
+ expect(classes).toHaveLength(0);
1072
+ });
1073
+ it('should handle non-identifier/non-property-access in isCvaCreatedFunctionCall (line 486)', () => {
1074
+ // When the call expression is something like fn()()
1075
+ const code = `
1076
+ import { cva } from 'class-variance-authority';
1077
+ const getVariant = () => cva('base');
1078
+ const result = getVariant()({ class: 'flex' });
1079
+ `;
1080
+ const context = createContextWithTypeChecker(code);
1081
+ const calls = findAllCallExpressions(context.sourceFile);
1082
+ // The outer call getVariant()({ class: 'flex' }) has call expression as its expression
1083
+ const outerCall = calls[calls.length - 1];
1084
+ const classes = extractor.extract(outerCall, context);
1085
+ expect(classes).toHaveLength(0);
1086
+ });
1087
+ it('should handle type assertion expression in .ts file', () => {
1088
+ // Create context with .ts extension to test isTypeAssertionExpression
1089
+ const fileName = '/test.ts';
1090
+ const code = `
1091
+ import { cva } from 'class-variance-authority';
1092
+ const button = cva(<string>'base-class');
1093
+ `;
1094
+ const files = {
1095
+ [fileName]: code,
1096
+ '/node_modules/class-variance-authority/index.d.ts': `
1097
+ export declare function cva<T>(base?: string, config?: T): (...args: any[]) => string;
1098
+ `
1099
+ };
1100
+ const compilerHost = {
1101
+ getSourceFile: (name, languageVersion) => {
1102
+ const content = files[name];
1103
+ if (content !== undefined) {
1104
+ return ts.createSourceFile(name, content, languageVersion, true, name.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
1105
+ }
1106
+ return undefined;
1107
+ },
1108
+ getDefaultLibFileName: () => '/lib.d.ts',
1109
+ writeFile: () => { },
1110
+ getCurrentDirectory: () => '/',
1111
+ getCanonicalFileName: (f) => f,
1112
+ useCaseSensitiveFileNames: () => true,
1113
+ getNewLine: () => '\n',
1114
+ fileExists: (name) => name in files,
1115
+ readFile: (name) => files[name],
1116
+ directoryExists: () => true,
1117
+ getDirectories: () => []
1118
+ };
1119
+ const program = ts.createProgram([fileName], {
1120
+ target: ts.ScriptTarget.Latest,
1121
+ module: ts.ModuleKind.ESNext,
1122
+ strict: true,
1123
+ moduleResolution: ts.ModuleResolutionKind.NodeJs
1124
+ }, compilerHost);
1125
+ const sourceFile = program.getSourceFile(fileName);
1126
+ const context = {
1127
+ typescript: ts,
1128
+ sourceFile,
1129
+ typeChecker: program.getTypeChecker(),
1130
+ utilityFunctions: []
1131
+ };
1132
+ const callExpr = findCallExpression(sourceFile);
1133
+ const classes = extractor.extract(callExpr, context);
1134
+ expect(classes.map(c => c.className)).toContain('base-class');
1135
+ });
1136
+ it('should return false in isCvaCall for element access expression (line 88)', () => {
1137
+ // Element access expression like cvaFunctions['default']() is neither identifier nor property access
1138
+ const code = `
1139
+ import { cva } from 'class-variance-authority';
1140
+ const cvaFunctions = { default: cva };
1141
+ const button = cvaFunctions['default']('base-class');
1142
+ `;
1143
+ const context = createContext(code);
1144
+ const calls = findAllCallExpressions(context.sourceFile);
1145
+ // cvaFunctions['default']('base-class') has element access expression
1146
+ const lastCall = calls[calls.length - 1];
1147
+ const classes = extractor.extract(lastCall, context);
1148
+ // Element access expressions return false in isCvaCall
1149
+ expect(classes).toHaveLength(0);
1150
+ });
1151
+ it('should handle identifier variable reference in array with TypeChecker (line 357)', () => {
1152
+ const code = `
1153
+ import { cva } from 'class-variance-authority';
1154
+ const myClass = 'my-custom-class';
1155
+ const button = cva(['flex', myClass, 'items-center']);
1156
+ `;
1157
+ const context = createContextWithTypeChecker(code);
1158
+ const callExpr = findCallExpression(context.sourceFile);
1159
+ const classes = extractor.extract(callExpr, context);
1160
+ // With TypeChecker, we might be able to resolve some variable references
1161
+ expect(classes.map(c => c.className)).toContain('flex');
1162
+ expect(classes.map(c => c.className)).toContain('items-center');
1163
+ });
1164
+ it('should handle export default with direct cva call', () => {
1165
+ // Test export default cva(...) pattern
1166
+ const code = `
1167
+ import { cva } from 'class-variance-authority';
1168
+ export default cva('exported-base');
1169
+ `;
1170
+ const context = createContextWithTypeChecker(code);
1171
+ const callExpr = findCallExpression(context.sourceFile);
1172
+ const classes = extractor.extract(callExpr, context);
1173
+ expect(classes.map(c => c.className)).toContain('exported-base');
1174
+ });
1175
+ });
1176
+ });
1177
+ //# sourceMappingURL=CvaExtractor.spec.js.map