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

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.
@@ -35,13 +35,16 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
35
35
  * Vue generates code like __VLS_ctx.clsx(...) for template expressions
36
36
  * where clsx is imported in the script section. We need to check if the
37
37
  * function name (not __VLS_ctx) is directly imported.
38
+ *
39
+ * Also handles namespace imports: __VLS_ctx.utils.clsx(...) for `import * as utils from 'clsx'`
38
40
  */
39
41
  shouldValidateFunctionCall(callExpression, utilityFunctions, context) {
40
- // First, check if this is a __VLS_ctx.functionName() pattern
42
+ // First, check if this is a __VLS_ctx.functionName() or __VLS_ctx.namespace.functionName() pattern
41
43
  if (context) {
42
44
  const expr = callExpression.expression;
43
45
  if (context.typescript.isPropertyAccessExpression(expr)) {
44
46
  const objectExpr = expr.expression;
47
+ // Pattern 1: __VLS_ctx.functionName() - direct import
45
48
  if (context.typescript.isIdentifier(objectExpr) && objectExpr.text === '__VLS_ctx') {
46
49
  const functionName = expr.name.text;
47
50
  // Check each utility function configuration
@@ -60,6 +63,31 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
60
63
  }
61
64
  return false;
62
65
  }
66
+ // Pattern 2: __VLS_ctx.namespace.functionName() - namespace import
67
+ // e.g., import * as utils from 'clsx' -> __VLS_ctx.utils.clsx()
68
+ if (context.typescript.isPropertyAccessExpression(objectExpr)) {
69
+ const namespaceRoot = objectExpr.expression;
70
+ if (context.typescript.isIdentifier(namespaceRoot) &&
71
+ namespaceRoot.text === '__VLS_ctx') {
72
+ const namespaceName = objectExpr.name.text; // e.g., 'utils'
73
+ const functionName = expr.name.text; // e.g., 'clsx'
74
+ // Check each utility function configuration
75
+ for (const utilityFunc of utilityFunctions) {
76
+ if (typeof utilityFunc === 'string') {
77
+ if (utilityFunc === functionName) {
78
+ return true;
79
+ }
80
+ }
81
+ else if (utilityFunc.name === functionName) {
82
+ // Check if namespace is imported from expected module
83
+ if (this.isNamespaceImportedFrom(namespaceName, utilityFunc.from, context)) {
84
+ return true;
85
+ }
86
+ }
87
+ }
88
+ return false;
89
+ }
90
+ }
63
91
  }
64
92
  }
65
93
  // Fall back to base implementation for non-Vue patterns
@@ -67,17 +95,12 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
67
95
  }
68
96
  canHandle(node, context) {
69
97
  // We handle call expressions that look like Vue's generated element calls
70
- // Pattern: __VLS_asFunctionalElement(...)({ ...{ class: ... } })
98
+ // Pattern 1 (intrinsic elements): __VLS_asFunctionalElement(...)({ ...{ class: ... } })
99
+ // Pattern 2 (custom components): __VLS_1({ colorStyles: ... }, ...)
71
100
  if (!context.typescript.isCallExpression(node)) {
72
101
  return false;
73
102
  }
74
- // Check if this is a chained call (the outer call to the element function)
75
- // The pattern is: func(...)({...}) where the result of func(...) is called again
76
- const expression = node.expression;
77
- if (!context.typescript.isCallExpression(expression)) {
78
- return false;
79
- }
80
- // Check if the arguments contain an object with spread that has a 'class' property
103
+ // Check if the arguments contain an object with class properties
81
104
  if (node.arguments.length === 0) {
82
105
  return false;
83
106
  }
@@ -85,10 +108,28 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
85
108
  if (!context.typescript.isObjectLiteralExpression(firstArg)) {
86
109
  return false;
87
110
  }
88
- // Look for spread assignments with 'class' property
89
- return this.hasClassSpreadProperty(firstArg, context);
111
+ const expression = node.expression;
112
+ // Pattern 1: Chained call (intrinsic elements)
113
+ // func(...)({...}) where the result of func(...) is called again
114
+ if (context.typescript.isCallExpression(expression)) {
115
+ // Look for spread assignments with class property
116
+ return this.hasClassSpreadProperty(firstArg, context);
117
+ }
118
+ // Pattern 2: Identifier call (custom components)
119
+ // __VLS_N({ classAttribute: ... }, ...)
120
+ if (context.typescript.isIdentifier(expression)) {
121
+ const name = expression.text;
122
+ // Vue generates __VLS_0, __VLS_1, etc. for component instances
123
+ if (name.startsWith('__VLS_')) {
124
+ // Check for direct class attribute properties
125
+ return this.hasClassDirectProperty(firstArg, context);
126
+ }
127
+ }
128
+ return false;
90
129
  }
91
130
  hasClassSpreadProperty(obj, context) {
131
+ // Build set of class attribute names to check
132
+ const classAttributeNames = new Set(['class', ...(context.classAttributes || [])]);
92
133
  for (const prop of obj.properties) {
93
134
  if (context.typescript.isSpreadAssignment(prop)) {
94
135
  const spreadExpr = prop.expression;
@@ -96,7 +137,7 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
96
137
  for (const innerProp of spreadExpr.properties) {
97
138
  if (context.typescript.isPropertyAssignment(innerProp)) {
98
139
  const name = innerProp.name;
99
- if (context.typescript.isIdentifier(name) && name.text === 'class') {
140
+ if (context.typescript.isIdentifier(name) && classAttributeNames.has(name.text)) {
100
141
  return true;
101
142
  }
102
143
  }
@@ -106,6 +147,22 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
106
147
  }
107
148
  return false;
108
149
  }
150
+ /**
151
+ * Check if an object has direct class attribute properties (for custom components).
152
+ * Vue generates direct properties like: { colorStyles: "bg-blue-500" }
153
+ */
154
+ hasClassDirectProperty(obj, context) {
155
+ const classAttributeNames = new Set(['class', ...(context.classAttributes || [])]);
156
+ for (const prop of obj.properties) {
157
+ if (context.typescript.isPropertyAssignment(prop)) {
158
+ const name = prop.name;
159
+ if (context.typescript.isIdentifier(name) && classAttributeNames.has(name.text)) {
160
+ return true;
161
+ }
162
+ }
163
+ }
164
+ return false;
165
+ }
109
166
  extract(node, context) {
110
167
  const classNames = [];
111
168
  if (!context.typescript.isCallExpression(node)) {
@@ -115,26 +172,44 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
115
172
  if (!firstArg || !context.typescript.isObjectLiteralExpression(firstArg)) {
116
173
  return classNames;
117
174
  }
118
- // Find spread assignments with 'class' property
175
+ // Build set of class attribute names to check
176
+ const classAttributeNames = new Set(['class', ...(context.classAttributes || [])]);
177
+ // Process all properties in the object literal
119
178
  for (const prop of firstArg.properties) {
120
- if (!context.typescript.isSpreadAssignment(prop)) {
121
- continue;
122
- }
123
- const spreadExpr = prop.expression;
124
- if (!context.typescript.isObjectLiteralExpression(spreadExpr)) {
125
- continue;
179
+ // Handle spread assignments: ...{ class: "..." }
180
+ if (context.typescript.isSpreadAssignment(prop)) {
181
+ const spreadExpr = prop.expression;
182
+ if (context.typescript.isObjectLiteralExpression(spreadExpr)) {
183
+ for (const innerProp of spreadExpr.properties) {
184
+ if (!context.typescript.isPropertyAssignment(innerProp)) {
185
+ continue;
186
+ }
187
+ const name = innerProp.name;
188
+ if (!context.typescript.isIdentifier(name)) {
189
+ continue;
190
+ }
191
+ // Check if this is a class attribute
192
+ if (!classAttributeNames.has(name.text)) {
193
+ continue;
194
+ }
195
+ const value = innerProp.initializer;
196
+ const attributeId = `${innerProp.getStart()}-${innerProp.getEnd()}`;
197
+ classNames.push(...this.extractClassesFromValue(value, context, attributeId));
198
+ }
199
+ }
126
200
  }
127
- for (const innerProp of spreadExpr.properties) {
128
- if (!context.typescript.isPropertyAssignment(innerProp)) {
201
+ // Handle direct property assignments: colorStyles: "..."
202
+ else if (context.typescript.isPropertyAssignment(prop)) {
203
+ const name = prop.name;
204
+ if (!context.typescript.isIdentifier(name)) {
129
205
  continue;
130
206
  }
131
- const name = innerProp.name;
132
- if (!context.typescript.isIdentifier(name) || name.text !== 'class') {
207
+ // Check if this is a class attribute (custom attributes like colorStyles)
208
+ if (!classAttributeNames.has(name.text)) {
133
209
  continue;
134
210
  }
135
- const value = innerProp.initializer;
136
- const attributeId = `${innerProp.getStart()}-${innerProp.getEnd()}`;
137
- // Handle different class value types
211
+ const value = prop.initializer;
212
+ const attributeId = `${prop.getStart()}-${prop.getEnd()}`;
138
213
  classNames.push(...this.extractClassesFromValue(value, context, attributeId));
139
214
  }
140
215
  }
@@ -180,47 +255,8 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
180
255
  }
181
256
  }
182
257
  if (objectExpr) {
183
- for (const prop of objectExpr.properties) {
184
- if (context.typescript.isPropertyAssignment(prop)) {
185
- const propName = prop.name;
186
- let className;
187
- let start;
188
- // Handle string literal keys: { 'bg-red-500': true }
189
- if (context.typescript.isStringLiteral(propName)) {
190
- className = propName.text;
191
- start = propName.getStart() + 1; // Skip opening quote
192
- }
193
- // Handle identifier keys: { flex: true }
194
- else if (context.typescript.isIdentifier(propName)) {
195
- className = propName.text;
196
- start = propName.getStart();
197
- }
198
- if (className && start !== undefined) {
199
- classNames.push({
200
- className,
201
- absoluteStart: start,
202
- length: className.length,
203
- line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
204
- file: context.sourceFile.fileName,
205
- attributeId
206
- });
207
- }
208
- }
209
- // Handle shorthand properties: { flex }
210
- else if (context.typescript.isShorthandPropertyAssignment(prop)) {
211
- const className = prop.name.text;
212
- const start = prop.name.getStart();
213
- classNames.push({
214
- className,
215
- absoluteStart: start,
216
- length: className.length,
217
- line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
218
- file: context.sourceFile.fileName,
219
- attributeId
220
- });
221
- }
222
- }
223
- return classNames;
258
+ // Use extractFromObjectExpression which handles computed property names
259
+ return this.extractFromObjectExpression(objectExpr, context, attributeId);
224
260
  }
225
261
  // Array literal: class: ['flex', 'items-center']
226
262
  // Vue wraps expressions in parentheses: class: (['flex', 'items-center'])
@@ -235,8 +271,8 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
235
271
  }
236
272
  }
237
273
  if (arrayExpr) {
238
- const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
239
- return addAttributeId(this.expressionExtractor.extract(arrayExpr, context));
274
+ // Process array elements directly to handle __VLS_ctx references
275
+ return this.extractFromArrayExpression(arrayExpr, context, attributeId);
240
276
  }
241
277
  // Template literal or other expressions - delegate to expression extractor
242
278
  // Vue wraps expressions in parentheses: class: (`flex items-center`)
@@ -268,9 +304,84 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
268
304
  callExpr = inner;
269
305
  }
270
306
  }
271
- if (callExpr && this.shouldValidateFunctionCall(callExpr, context.utilityFunctions, context)) {
272
- const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
273
- return addAttributeId(this.expressionExtractor.extract(callExpr, context));
307
+ if (callExpr) {
308
+ // Check if it's a utility function (clsx, cn, etc.) - extract all arguments
309
+ if (this.shouldValidateFunctionCall(callExpr, context.utilityFunctions, context)) {
310
+ const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
311
+ return addAttributeId(this.expressionExtractor.extract(callExpr, context));
312
+ }
313
+ // Check for CVA/TV function calls with class override: button({ class: '...' })
314
+ // These are __VLS_ctx.functionName({ class: '...' }) patterns
315
+ const classOverrideClasses = this.extractFromCvaTvClassOverride(callExpr, context, attributeId);
316
+ if (classOverrideClasses.length > 0) {
317
+ return classOverrideClasses;
318
+ }
319
+ }
320
+ // Handle conditional (ternary) expressions: class: (isActive ? 'flex' : 'hidden')
321
+ // Vue wraps expressions in parentheses: class: (__VLS_ctx.isActive ? 'active' : 'inactive')
322
+ let conditionalExpr;
323
+ if (context.typescript.isConditionalExpression(value)) {
324
+ conditionalExpr = value;
325
+ }
326
+ else if (context.typescript.isParenthesizedExpression(value)) {
327
+ const inner = value.expression;
328
+ if (context.typescript.isConditionalExpression(inner)) {
329
+ conditionalExpr = inner;
330
+ }
331
+ }
332
+ if (conditionalExpr) {
333
+ return this.extractFromConditionalExpression(conditionalExpr, context, attributeId);
334
+ }
335
+ // Handle binary expressions: class: (isActive && 'flex')
336
+ // Vue wraps expressions in parentheses: class: (__VLS_ctx.isActive && 'active')
337
+ let binaryExpr;
338
+ if (context.typescript.isBinaryExpression(value)) {
339
+ binaryExpr = value;
340
+ }
341
+ else if (context.typescript.isParenthesizedExpression(value)) {
342
+ const inner = value.expression;
343
+ if (context.typescript.isBinaryExpression(inner)) {
344
+ binaryExpr = inner;
345
+ }
346
+ }
347
+ if (binaryExpr) {
348
+ return this.extractFromBinaryExpression(binaryExpr, context, attributeId);
349
+ }
350
+ // Handle type assertions: class: ('invalid-class' as string)
351
+ // Vue wraps expressions: class: (('invalid-class' as string))
352
+ let asExpr;
353
+ if (context.typescript.isAsExpression(value)) {
354
+ asExpr = value;
355
+ }
356
+ else if (context.typescript.isParenthesizedExpression(value)) {
357
+ let inner = value.expression;
358
+ // Double unwrap for nested parentheses
359
+ if (context.typescript.isParenthesizedExpression(inner)) {
360
+ inner = inner.expression;
361
+ }
362
+ if (context.typescript.isAsExpression(inner)) {
363
+ asExpr = inner;
364
+ }
365
+ }
366
+ if (asExpr) {
367
+ return this.extractClassesFromValue(asExpr.expression, context, attributeId);
368
+ }
369
+ // Handle non-null assertions: class: (someClass!)
370
+ let nonNullExpr;
371
+ if (context.typescript.isNonNullExpression(value)) {
372
+ nonNullExpr = value;
373
+ }
374
+ else if (context.typescript.isParenthesizedExpression(value)) {
375
+ let inner = value.expression;
376
+ if (context.typescript.isParenthesizedExpression(inner)) {
377
+ inner = inner.expression;
378
+ }
379
+ if (context.typescript.isNonNullExpression(inner)) {
380
+ nonNullExpr = inner;
381
+ }
382
+ }
383
+ if (nonNullExpr) {
384
+ return this.extractClassesFromValue(nonNullExpr.expression, context, attributeId);
274
385
  }
275
386
  // Handle __VLS_ctx.propertyName patterns for variable/computed/function references
276
387
  // Vue generates: class: (__VLS_ctx.myClass) for :class="myClass"
@@ -278,6 +389,402 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
278
389
  if (resolvedClasses.length > 0) {
279
390
  return resolvedClasses;
280
391
  }
392
+ // Handle props.propertyName patterns with default values from withDefaults
393
+ // Vue generates: class: (props.buttonClass) for :class="props.buttonClass"
394
+ const propsDefaultClasses = this.extractFromPropsWithDefaults(value, context, attributeId);
395
+ if (propsDefaultClasses.length > 0) {
396
+ return propsDefaultClasses;
397
+ }
398
+ return classNames;
399
+ }
400
+ /**
401
+ * Extract classes from an array expression, handling __VLS_ctx references.
402
+ * This method processes array elements directly to support variable references.
403
+ */
404
+ extractFromArrayExpression(arrayExpr, context, attributeId) {
405
+ const { typescript } = context;
406
+ const classNames = [];
407
+ for (const element of arrayExpr.elements) {
408
+ if (element === undefined)
409
+ continue;
410
+ // String literal: 'flex'
411
+ if (typescript.isStringLiteral(element)) {
412
+ const fullText = element.text;
413
+ if (fullText.length > 0) {
414
+ const stringContentStart = element.getStart() + 1;
415
+ let offset = 0;
416
+ const parts = fullText.split(/(\s+)/);
417
+ for (const part of parts) {
418
+ if (part && !/^\s+$/.test(part)) {
419
+ classNames.push({
420
+ className: part,
421
+ absoluteStart: stringContentStart + offset,
422
+ length: part.length,
423
+ line: context.sourceFile.getLineAndCharacterOfPosition(stringContentStart + offset)
424
+ .line + 1,
425
+ file: context.sourceFile.fileName,
426
+ attributeId
427
+ });
428
+ }
429
+ offset += part.length;
430
+ }
431
+ }
432
+ }
433
+ // Object literal: { 'bg-red-500': isActive }
434
+ else if (typescript.isObjectLiteralExpression(element)) {
435
+ classNames.push(...this.extractFromObjectExpression(element, context, attributeId));
436
+ }
437
+ // Spread element: ...classes
438
+ else if (typescript.isSpreadElement(element)) {
439
+ // Try to resolve __VLS_ctx reference
440
+ const vlsResults = this.extractFromVlsCtxReference(element.expression, context, attributeId);
441
+ if (vlsResults.length > 0) {
442
+ classNames.push(...vlsResults);
443
+ }
444
+ }
445
+ // Handle __VLS_ctx.variable references: __VLS_ctx.myClass
446
+ else if (typescript.isPropertyAccessExpression(element)) {
447
+ const vlsResults = this.extractFromVlsCtxReference(element, context, attributeId);
448
+ if (vlsResults.length > 0) {
449
+ classNames.push(...vlsResults);
450
+ }
451
+ }
452
+ // Handle parenthesized expressions: (__VLS_ctx.myVar)
453
+ else if (typescript.isParenthesizedExpression(element)) {
454
+ const inner = element.expression;
455
+ if (typescript.isPropertyAccessExpression(inner)) {
456
+ const vlsResults = this.extractFromVlsCtxReference(inner, context, attributeId);
457
+ if (vlsResults.length > 0) {
458
+ classNames.push(...vlsResults);
459
+ }
460
+ }
461
+ else if (typescript.isArrayLiteralExpression(inner)) {
462
+ // Nested array in parentheses
463
+ classNames.push(...this.extractFromArrayExpression(inner, context, attributeId));
464
+ }
465
+ }
466
+ // Handle nested arrays recursively
467
+ else if (typescript.isArrayLiteralExpression(element)) {
468
+ classNames.push(...this.extractFromArrayExpression(element, context, attributeId));
469
+ }
470
+ // Handle ternary/conditional expressions
471
+ else if (typescript.isConditionalExpression(element)) {
472
+ // Extract from both branches
473
+ classNames.push(...this.extractFromConditionalElement(element, context, attributeId));
474
+ }
475
+ }
476
+ return classNames;
477
+ }
478
+ /**
479
+ * Extract classes from an object expression, handling computed property names.
480
+ */
481
+ extractFromObjectExpression(objExpr, context, attributeId) {
482
+ const { typescript } = context;
483
+ const classNames = [];
484
+ for (const prop of objExpr.properties) {
485
+ if (typescript.isPropertyAssignment(prop)) {
486
+ const propName = prop.name;
487
+ let className;
488
+ let start;
489
+ if (typescript.isStringLiteral(propName)) {
490
+ className = propName.text;
491
+ start = propName.getStart() + 1;
492
+ }
493
+ else if (typescript.isIdentifier(propName)) {
494
+ className = propName.text;
495
+ start = propName.getStart();
496
+ }
497
+ // Handle computed property names: { [__VLS_ctx.myVar]: true }
498
+ else if (typescript.isComputedPropertyName(propName)) {
499
+ let computedExpr = propName.expression;
500
+ // Unwrap parentheses
501
+ if (typescript.isParenthesizedExpression(computedExpr)) {
502
+ computedExpr = computedExpr.expression;
503
+ }
504
+ // Resolve __VLS_ctx.variable pattern
505
+ if (typescript.isPropertyAccessExpression(computedExpr)) {
506
+ const vlsResults = this.extractFromVlsCtxReference(computedExpr, context, attributeId);
507
+ if (vlsResults.length > 0) {
508
+ classNames.push(...vlsResults);
509
+ continue;
510
+ }
511
+ }
512
+ }
513
+ if (className && start !== undefined) {
514
+ classNames.push({
515
+ className,
516
+ absoluteStart: start,
517
+ length: className.length,
518
+ line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
519
+ file: context.sourceFile.fileName,
520
+ attributeId
521
+ });
522
+ }
523
+ }
524
+ else if (typescript.isShorthandPropertyAssignment(prop)) {
525
+ const className = prop.name.text;
526
+ const start = prop.name.getStart();
527
+ classNames.push({
528
+ className,
529
+ absoluteStart: start,
530
+ length: className.length,
531
+ line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
532
+ file: context.sourceFile.fileName,
533
+ attributeId
534
+ });
535
+ }
536
+ }
537
+ return classNames;
538
+ }
539
+ /**
540
+ * Extract classes from a conditional (ternary) expression in array context.
541
+ */
542
+ extractFromConditionalElement(conditional, context, attributeId) {
543
+ return this.extractFromConditionalExpression(conditional, context, attributeId);
544
+ }
545
+ /**
546
+ * Extract classes from a conditional (ternary) expression.
547
+ * Handles: isActive ? 'flex' : 'hidden'
548
+ */
549
+ extractFromConditionalExpression(conditional, context, attributeId) {
550
+ const classNames = [];
551
+ // Use the ternary's position as a unique identifier (like ExpressionExtractor)
552
+ const ternaryId = conditional.getStart();
553
+ // Extract from true branch with conditionalBranchId
554
+ const whenTrue = conditional.whenTrue;
555
+ classNames.push(...this.extractFromBranchExpression(whenTrue, context, attributeId, `ternary:true:${ternaryId}`));
556
+ // Extract from false branch with conditionalBranchId
557
+ const whenFalse = conditional.whenFalse;
558
+ classNames.push(...this.extractFromBranchExpression(whenFalse, context, attributeId, `ternary:false:${ternaryId}`));
559
+ return classNames;
560
+ }
561
+ /**
562
+ * Extract classes from a binary expression.
563
+ * Handles: isActive && 'flex', isDisabled || 'fallback'
564
+ */
565
+ extractFromBinaryExpression(binary, context, attributeId) {
566
+ const classNames = [];
567
+ // Extract from left operand (for patterns like: 'flex' || fallback)
568
+ classNames.push(...this.extractFromBranchExpression(binary.left, context, attributeId));
569
+ // Extract from right operand (for patterns like: isActive && 'flex')
570
+ classNames.push(...this.extractFromBranchExpression(binary.right, context, attributeId));
571
+ return classNames;
572
+ }
573
+ /**
574
+ * Extract classes from a branch expression (ternary branch or binary operand).
575
+ * Handles string literals, nested ternaries, nested binaries, and VLS references.
576
+ */
577
+ extractFromBranchExpression(expr, context, attributeId, conditionalBranchId) {
578
+ const { typescript } = context;
579
+ const classNames = [];
580
+ // Helper to add conditionalBranchId to extracted classes
581
+ const addBranchId = (classes) => conditionalBranchId ? classes.map(c => ({ ...c, conditionalBranchId })) : classes;
582
+ // String literal: 'flex items-center'
583
+ if (typescript.isStringLiteral(expr)) {
584
+ classNames.push(...addBranchId(this.extractClassesFromStringLiteral(expr, context, attributeId)));
585
+ }
586
+ // Nested ternary: condition ? 'a' : (nested ? 'b' : 'c')
587
+ else if (typescript.isConditionalExpression(expr)) {
588
+ classNames.push(...this.extractFromConditionalExpression(expr, context, attributeId));
589
+ }
590
+ // Nested binary: isA && isB && 'class'
591
+ else if (typescript.isBinaryExpression(expr)) {
592
+ classNames.push(...addBranchId(this.extractFromBinaryExpression(expr, context, attributeId)));
593
+ }
594
+ // Parenthesized expression
595
+ else if (typescript.isParenthesizedExpression(expr)) {
596
+ classNames.push(...this.extractFromBranchExpression(expr.expression, context, attributeId, conditionalBranchId));
597
+ }
598
+ // VLS ctx reference: __VLS_ctx.myClass
599
+ else if (typescript.isPropertyAccessExpression(expr)) {
600
+ const vlsResults = this.extractFromVlsCtxReference(expr, context, attributeId);
601
+ classNames.push(...addBranchId(vlsResults));
602
+ }
603
+ // Template literal: `flex ${something}`
604
+ else if (typescript.isTemplateExpression(expr) ||
605
+ typescript.isNoSubstitutionTemplateLiteral(expr)) {
606
+ const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
607
+ classNames.push(...addBranchId(addAttributeId(this.expressionExtractor.extract(expr, context))));
608
+ }
609
+ return classNames;
610
+ }
611
+ /**
612
+ * Extract classes from CVA/TV function calls with class override.
613
+ * Handles patterns like: button({ color: 'primary', class: 'invalid-class' })
614
+ *
615
+ * Only extracts from functions that are defined using cva() or tv(),
616
+ * not from arbitrary custom functions.
617
+ */
618
+ extractFromCvaTvClassOverride(callExpr, context, attributeId) {
619
+ const { typescript } = context;
620
+ const classNames = [];
621
+ // Check if this is a __VLS_ctx.functionName pattern
622
+ const calleeExpr = callExpr.expression;
623
+ if (!typescript.isPropertyAccessExpression(calleeExpr)) {
624
+ return classNames;
625
+ }
626
+ const objectExpr = calleeExpr.expression;
627
+ if (!typescript.isIdentifier(objectExpr) || objectExpr.text !== '__VLS_ctx') {
628
+ return classNames;
629
+ }
630
+ const functionName = calleeExpr.name;
631
+ if (!typescript.isIdentifier(functionName)) {
632
+ return classNames;
633
+ }
634
+ // Check if this function is defined using cva() or tv()
635
+ // by looking at its definition
636
+ if (!this.isCvaTvFunction(functionName, context)) {
637
+ return classNames;
638
+ }
639
+ // Look for object argument with class/className property
640
+ if (callExpr.arguments.length === 0) {
641
+ return classNames;
642
+ }
643
+ const firstArg = callExpr.arguments[0];
644
+ if (!typescript.isObjectLiteralExpression(firstArg)) {
645
+ return classNames;
646
+ }
647
+ // Find class or className property
648
+ for (const prop of firstArg.properties) {
649
+ if (!typescript.isPropertyAssignment(prop)) {
650
+ continue;
651
+ }
652
+ const propName = prop.name;
653
+ if (!typescript.isIdentifier(propName)) {
654
+ continue;
655
+ }
656
+ if (propName.text !== 'class' && propName.text !== 'className') {
657
+ continue;
658
+ }
659
+ // Extract classes from the property value
660
+ const value = prop.initializer;
661
+ if (typescript.isStringLiteral(value)) {
662
+ classNames.push(...this.extractClassesFromStringLiteral(value, context, attributeId));
663
+ }
664
+ else if (typescript.isArrayLiteralExpression(value)) {
665
+ classNames.push(...this.extractFromArrayExpression(value, context, attributeId));
666
+ }
667
+ else if (typescript.isTemplateExpression(value) ||
668
+ typescript.isNoSubstitutionTemplateLiteral(value)) {
669
+ const addAttrId = (classes) => classes.map(c => ({ ...c, attributeId }));
670
+ classNames.push(...addAttrId(this.expressionExtractor.extract(value, context)));
671
+ }
672
+ }
673
+ return classNames;
674
+ }
675
+ /**
676
+ * Check if a function is defined using cva() or tv().
677
+ * Resolves the function symbol and checks if its initializer is a cva/tv call.
678
+ */
679
+ isCvaTvFunction(functionName, context) {
680
+ const { typescript, typeChecker } = context;
681
+ if (!typeChecker) {
682
+ return false;
683
+ }
684
+ // Get the symbol for the function name
685
+ const symbol = typeChecker.getSymbolAtLocation(functionName);
686
+ if (!symbol) {
687
+ return false;
688
+ }
689
+ const declarations = symbol.getDeclarations();
690
+ if (!declarations || declarations.length === 0) {
691
+ return false;
692
+ }
693
+ for (const declaration of declarations) {
694
+ // Check PropertySignature -> typeof reference in Volar 3.x
695
+ if (typescript.isPropertySignature(declaration) && declaration.type) {
696
+ if (typescript.isTypeQueryNode(declaration.type)) {
697
+ const exprName = declaration.type.exprName;
698
+ if (typescript.isIdentifier(exprName)) {
699
+ // Resolve the actual variable
700
+ const varSymbol = typeChecker.getSymbolAtLocation(exprName);
701
+ if (varSymbol) {
702
+ const varDeclarations = varSymbol.getDeclarations();
703
+ if (varDeclarations) {
704
+ for (const varDecl of varDeclarations) {
705
+ if (typescript.isVariableDeclaration(varDecl) && varDecl.initializer) {
706
+ if (this.isCallToCvaOrTv(varDecl.initializer, context)) {
707
+ return true;
708
+ }
709
+ }
710
+ }
711
+ }
712
+ }
713
+ }
714
+ }
715
+ }
716
+ // Check direct variable declaration
717
+ else if (typescript.isVariableDeclaration(declaration) && declaration.initializer) {
718
+ if (this.isCallToCvaOrTv(declaration.initializer, context)) {
719
+ return true;
720
+ }
721
+ }
722
+ // Check property assignment in Vue's return
723
+ else if (typescript.isPropertyAssignment(declaration)) {
724
+ let expr = declaration.initializer;
725
+ if (typescript.isAsExpression(expr)) {
726
+ expr = expr.expression;
727
+ }
728
+ if (typescript.isIdentifier(expr)) {
729
+ const refSymbol = typeChecker.getSymbolAtLocation(expr);
730
+ if (refSymbol) {
731
+ const refDeclarations = refSymbol.getDeclarations();
732
+ if (refDeclarations) {
733
+ for (const refDecl of refDeclarations) {
734
+ if (typescript.isVariableDeclaration(refDecl) && refDecl.initializer) {
735
+ if (this.isCallToCvaOrTv(refDecl.initializer, context)) {
736
+ return true;
737
+ }
738
+ }
739
+ }
740
+ }
741
+ }
742
+ }
743
+ }
744
+ }
745
+ return false;
746
+ }
747
+ /**
748
+ * Check if an expression is a call to cva() or tv().
749
+ */
750
+ isCallToCvaOrTv(expr, context) {
751
+ const { typescript } = context;
752
+ if (!typescript.isCallExpression(expr)) {
753
+ return false;
754
+ }
755
+ const callee = expr.expression;
756
+ // Direct call: cva(...) or tv(...)
757
+ if (typescript.isIdentifier(callee)) {
758
+ const name = callee.text;
759
+ return name === 'cva' || name === 'tv' || name === 'tvLite';
760
+ }
761
+ return false;
762
+ }
763
+ /**
764
+ * Extract classes from a string literal with attributeId.
765
+ */
766
+ extractClassesFromStringLiteral(literal, context, attributeId) {
767
+ const classNames = [];
768
+ const fullText = literal.text;
769
+ if (fullText.length === 0) {
770
+ return classNames;
771
+ }
772
+ const stringContentStart = literal.getStart() + 1;
773
+ let offset = 0;
774
+ const parts = fullText.split(/(\s+)/);
775
+ for (const part of parts) {
776
+ if (part && !/^\s+$/.test(part)) {
777
+ classNames.push({
778
+ className: part,
779
+ absoluteStart: stringContentStart + offset,
780
+ length: part.length,
781
+ line: context.sourceFile.getLineAndCharacterOfPosition(stringContentStart + offset).line + 1,
782
+ file: context.sourceFile.fileName,
783
+ attributeId
784
+ });
785
+ }
786
+ offset += part.length;
787
+ }
281
788
  return classNames;
282
789
  }
283
790
  /**
@@ -329,6 +836,84 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
329
836
  return [];
330
837
  }
331
838
  const objectExpr = expr.expression;
839
+ // Handle nested property access: __VLS_ctx.obj.property
840
+ // e.g., __VLS_ctx.slotProps.buttonClass
841
+ if (typescript.isPropertyAccessExpression(objectExpr)) {
842
+ const nestedObject = objectExpr.expression;
843
+ if (typescript.isIdentifier(nestedObject) && nestedObject.text === '__VLS_ctx') {
844
+ // This is __VLS_ctx.something.somethingElse
845
+ const middlePropName = objectExpr.name;
846
+ const finalPropName = expr.name;
847
+ if (typescript.isIdentifier(middlePropName) && typescript.isIdentifier(finalPropName)) {
848
+ // Resolve the middle property (e.g., slotProps) to get its type/value
849
+ const middleSymbol = typeChecker.getSymbolAtLocation(middlePropName);
850
+ if (middleSymbol) {
851
+ const middleDeclarations = middleSymbol.getDeclarations();
852
+ if (middleDeclarations) {
853
+ for (const middleDecl of middleDeclarations) {
854
+ // Handle variable declaration: const slotProps = { buttonClass: '...' }
855
+ if (typescript.isVariableDeclaration(middleDecl) && middleDecl.initializer) {
856
+ if (typescript.isObjectLiteralExpression(middleDecl.initializer)) {
857
+ // Find the property in the object literal
858
+ for (const prop of middleDecl.initializer.properties) {
859
+ if (typescript.isPropertyAssignment(prop)) {
860
+ const propName = prop.name;
861
+ if (typescript.isIdentifier(propName) &&
862
+ propName.text === finalPropName.text) {
863
+ // Found the property, extract classes from its value
864
+ // Keep original positions from the class string
865
+ const classes = this.extractFromExpression(prop.initializer, context, attributeId);
866
+ return classes.map(c => ({
867
+ ...c,
868
+ attributeId
869
+ }));
870
+ }
871
+ }
872
+ }
873
+ }
874
+ }
875
+ // Handle property signature in Volar's generated types
876
+ else if (typescript.isPropertySignature(middleDecl) && middleDecl.type) {
877
+ if (typescript.isTypeQueryNode(middleDecl.type)) {
878
+ const exprName = middleDecl.type.exprName;
879
+ if (typescript.isIdentifier(exprName)) {
880
+ const varSymbol = typeChecker.getSymbolAtLocation(exprName);
881
+ if (varSymbol) {
882
+ const varDeclarations = varSymbol.getDeclarations();
883
+ if (varDeclarations) {
884
+ for (const varDecl of varDeclarations) {
885
+ if (typescript.isVariableDeclaration(varDecl) &&
886
+ varDecl.initializer &&
887
+ typescript.isObjectLiteralExpression(varDecl.initializer)) {
888
+ // Find the property
889
+ for (const prop of varDecl.initializer.properties) {
890
+ if (typescript.isPropertyAssignment(prop)) {
891
+ const pName = prop.name;
892
+ if (typescript.isIdentifier(pName) &&
893
+ pName.text === finalPropName.text) {
894
+ // Keep original positions from the class string
895
+ const classes = this.extractFromExpression(prop.initializer, context, attributeId);
896
+ return classes.map(c => ({
897
+ ...c,
898
+ attributeId
899
+ }));
900
+ }
901
+ }
902
+ }
903
+ }
904
+ }
905
+ }
906
+ }
907
+ }
908
+ }
909
+ }
910
+ }
911
+ }
912
+ }
913
+ }
914
+ }
915
+ return [];
916
+ }
332
917
  if (!typescript.isIdentifier(objectExpr) || objectExpr.text !== '__VLS_ctx') {
333
918
  return [];
334
919
  }
@@ -362,13 +947,20 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
362
947
  if (typescript.isVariableDeclaration(declaration)) {
363
948
  const initializer = declaration.initializer;
364
949
  if (initializer) {
365
- // Check if this is a computed() call
950
+ // Check if this is a computed() or inject() call
366
951
  if (typescript.isCallExpression(initializer)) {
952
+ // Handle computed() calls
367
953
  const computedClasses = this.extractFromComputedCall(initializer, context, attributeId, templatePosition, templateLength);
368
954
  if (computedClasses.length > 0) {
369
955
  classNames.push(...computedClasses);
370
956
  continue;
371
957
  }
958
+ // Handle inject() calls with default value: inject('key', 'default-classes')
959
+ const injectClasses = this.extractFromInjectCall(initializer, context, attributeId);
960
+ if (injectClasses.length > 0) {
961
+ classNames.push(...injectClasses);
962
+ continue;
963
+ }
372
964
  }
373
965
  // For regular variables, extract classes from the initializer
374
966
  // Keep original position from string literal
@@ -443,7 +1035,7 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
443
1035
  if (varDeclarations) {
444
1036
  for (const varDecl of varDeclarations) {
445
1037
  if (typescript.isVariableDeclaration(varDecl) && varDecl.initializer) {
446
- // Check if it's a computed() call - use ORIGINAL position
1038
+ // Check if it's a computed() or inject() call - use ORIGINAL position
447
1039
  // so errors point to actual class strings in script
448
1040
  if (typescript.isCallExpression(varDecl.initializer)) {
449
1041
  const computedClasses = this.extractFromComputedCall(varDecl.initializer, context, attributeId
@@ -452,6 +1044,11 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
452
1044
  if (computedClasses.length > 0) {
453
1045
  return computedClasses;
454
1046
  }
1047
+ // Check for inject() calls with default value
1048
+ const injectClasses = this.extractFromInjectCall(varDecl.initializer, context, attributeId);
1049
+ if (injectClasses.length > 0) {
1050
+ return injectClasses;
1051
+ }
455
1052
  }
456
1053
  // For string literals, use ORIGINAL position from script
457
1054
  // This makes errors point to the actual invalid class
@@ -502,6 +1099,11 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
502
1099
  if (computedClasses.length > 0) {
503
1100
  return computedClasses;
504
1101
  }
1102
+ // Check for inject() calls with default value
1103
+ const injectClasses = this.extractFromInjectCall(decl.initializer, context, attributeId);
1104
+ if (injectClasses.length > 0) {
1105
+ return injectClasses;
1106
+ }
505
1107
  }
506
1108
  // Otherwise extract from the initializer directly
507
1109
  const classes = this.extractFromExpression(decl.initializer, context);
@@ -726,13 +1328,19 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
726
1328
  if (typescript.isVariableDeclaration(declaration)) {
727
1329
  const init = declaration.initializer;
728
1330
  if (init) {
729
- // Check for computed() calls
1331
+ // Check for computed() or inject() calls
730
1332
  if (typescript.isCallExpression(init)) {
731
1333
  const computedClasses = this.extractFromComputedCall(init, context, attributeId, templatePosition, templateLength);
732
1334
  if (computedClasses.length > 0) {
733
1335
  classNames.push(...computedClasses);
734
1336
  continue;
735
1337
  }
1338
+ // Check for inject() calls with default value
1339
+ const injectClasses = this.extractFromInjectCall(init, context, attributeId);
1340
+ if (injectClasses.length > 0) {
1341
+ classNames.push(...injectClasses);
1342
+ continue;
1343
+ }
736
1344
  }
737
1345
  // Extract classes from the initializer - keep original position
738
1346
  classNames.push(...addAttributeId(this.extractFromExpression(init, context)));
@@ -772,6 +1380,27 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
772
1380
  }
773
1381
  return [];
774
1382
  }
1383
+ /**
1384
+ * Extract classes from an inject() call with a default value.
1385
+ * Handles: const classes = inject('key', 'flex items-center')
1386
+ */
1387
+ extractFromInjectCall(callExpr, context, attributeId) {
1388
+ const { typescript } = context;
1389
+ // Check if this is a call to 'inject'
1390
+ const calleeExpr = callExpr.expression;
1391
+ if (!typescript.isIdentifier(calleeExpr) || calleeExpr.text !== 'inject') {
1392
+ return [];
1393
+ }
1394
+ // inject() needs at least 2 arguments for us to extract the default value
1395
+ // inject(key, defaultValue) or inject(key, defaultValue, treatDefaultAsFactory)
1396
+ if (callExpr.arguments.length < 2) {
1397
+ return [];
1398
+ }
1399
+ const defaultValue = callExpr.arguments[1];
1400
+ // Extract classes from the default value
1401
+ const classes = this.extractFromExpression(defaultValue, context, attributeId);
1402
+ return classes.map(c => ({ ...c, attributeId }));
1403
+ }
775
1404
  /**
776
1405
  * Extract classes from a function declaration's return statements.
777
1406
  * Handles: function getClasses() { return ['flex', 'items-center']; }
@@ -820,8 +1449,11 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
820
1449
  }
821
1450
  /**
822
1451
  * Extract classes from an expression (array, object, string, etc.)
1452
+ * @param expr The expression to extract from
1453
+ * @param context The extraction context
1454
+ * @param attributeId Optional attribute ID for tracking
823
1455
  */
824
- extractFromExpression(expr, context) {
1456
+ extractFromExpression(expr, context, attributeId) {
825
1457
  const { typescript } = context;
826
1458
  const classNames = [];
827
1459
  // String literal: 'flex items-center'
@@ -854,13 +1486,36 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
854
1486
  if (element === undefined)
855
1487
  continue;
856
1488
  if (typescript.isStringLiteral(element)) {
857
- classNames.push(...this.extractFromExpression(element, context));
1489
+ classNames.push(...this.extractFromExpression(element, context, attributeId));
858
1490
  }
859
1491
  else if (typescript.isObjectLiteralExpression(element)) {
860
- classNames.push(...this.extractFromExpression(element, context));
1492
+ classNames.push(...this.extractFromExpression(element, context, attributeId));
861
1493
  }
862
1494
  else if (typescript.isSpreadElement(element)) {
863
- classNames.push(...this.extractFromExpression(element.expression, context));
1495
+ classNames.push(...this.extractFromExpression(element.expression, context, attributeId));
1496
+ }
1497
+ // Handle __VLS_ctx.variable references in arrays
1498
+ else if (typescript.isPropertyAccessExpression(element)) {
1499
+ if (attributeId) {
1500
+ const vlsResults = this.extractFromVlsCtxReference(element, context, attributeId);
1501
+ if (vlsResults.length > 0) {
1502
+ classNames.push(...vlsResults);
1503
+ }
1504
+ }
1505
+ }
1506
+ // Handle parenthesized expressions: (__VLS_ctx.myVar)
1507
+ else if (typescript.isParenthesizedExpression(element)) {
1508
+ const inner = element.expression;
1509
+ if (typescript.isPropertyAccessExpression(inner) && attributeId) {
1510
+ const vlsResults = this.extractFromVlsCtxReference(inner, context, attributeId);
1511
+ if (vlsResults.length > 0) {
1512
+ classNames.push(...vlsResults);
1513
+ }
1514
+ }
1515
+ }
1516
+ // Handle nested arrays recursively
1517
+ else if (typescript.isArrayLiteralExpression(element)) {
1518
+ classNames.push(...this.extractFromExpression(element, context, attributeId));
864
1519
  }
865
1520
  }
866
1521
  return classNames;
@@ -880,13 +1535,30 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
880
1535
  className = propName.text;
881
1536
  start = propName.getStart();
882
1537
  }
1538
+ // Handle computed property names: { [__VLS_ctx.myVar]: true }
1539
+ else if (typescript.isComputedPropertyName(propName)) {
1540
+ let computedExpr = propName.expression;
1541
+ // Unwrap parentheses: { [(__VLS_ctx.myVar)]: true }
1542
+ if (typescript.isParenthesizedExpression(computedExpr)) {
1543
+ computedExpr = computedExpr.expression;
1544
+ }
1545
+ // Resolve __VLS_ctx.variable pattern
1546
+ if (typescript.isPropertyAccessExpression(computedExpr) && attributeId) {
1547
+ const vlsResults = this.extractFromVlsCtxReference(computedExpr, context, attributeId);
1548
+ if (vlsResults.length > 0) {
1549
+ classNames.push(...vlsResults);
1550
+ continue;
1551
+ }
1552
+ }
1553
+ }
883
1554
  if (className && start !== undefined) {
884
1555
  classNames.push({
885
1556
  className,
886
1557
  absoluteStart: start,
887
1558
  length: className.length,
888
1559
  line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
889
- file: context.sourceFile.fileName
1560
+ file: context.sourceFile.fileName,
1561
+ attributeId
890
1562
  });
891
1563
  }
892
1564
  }
@@ -898,14 +1570,115 @@ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
898
1570
  absoluteStart: start,
899
1571
  length: className.length,
900
1572
  line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
901
- file: context.sourceFile.fileName
1573
+ file: context.sourceFile.fileName,
1574
+ attributeId
902
1575
  });
903
1576
  }
904
1577
  }
905
1578
  return classNames;
906
1579
  }
1580
+ // Handle conditional (ternary) expressions: isActive ? 'flex' : 'hidden'
1581
+ if (typescript.isConditionalExpression(expr)) {
1582
+ // Extract from both branches recursively
1583
+ classNames.push(...this.extractFromExpression(expr.whenTrue, context, attributeId));
1584
+ classNames.push(...this.extractFromExpression(expr.whenFalse, context, attributeId));
1585
+ return classNames;
1586
+ }
1587
+ // Handle binary expressions: isActive && 'flex', isDisabled || 'fallback'
1588
+ if (typescript.isBinaryExpression(expr)) {
1589
+ classNames.push(...this.extractFromExpression(expr.left, context, attributeId));
1590
+ classNames.push(...this.extractFromExpression(expr.right, context, attributeId));
1591
+ return classNames;
1592
+ }
1593
+ // Handle parenthesized expressions: ('flex items-center')
1594
+ if (typescript.isParenthesizedExpression(expr)) {
1595
+ return this.extractFromExpression(expr.expression, context, attributeId);
1596
+ }
1597
+ // Handle type assertions: 'flex' as string, 'flex' as const
1598
+ if (typescript.isAsExpression(expr)) {
1599
+ return this.extractFromExpression(expr.expression, context, attributeId);
1600
+ }
1601
+ // Handle non-null assertions: someValue!
1602
+ if (typescript.isNonNullExpression(expr)) {
1603
+ return this.extractFromExpression(expr.expression, context, attributeId);
1604
+ }
1605
+ // Handle template literals
1606
+ if (typescript.isTemplateExpression(expr) || typescript.isNoSubstitutionTemplateLiteral(expr)) {
1607
+ const addAttrId = (classes) => attributeId ? classes.map(c => ({ ...c, attributeId })) : classes;
1608
+ return addAttrId(this.expressionExtractor.extract(expr, context));
1609
+ }
907
1610
  return classNames;
908
1611
  }
1612
+ /**
1613
+ * Extract classes from props.propertyName patterns with default values.
1614
+ * Vue generates __VLS_defaults for withDefaults() calls.
1615
+ *
1616
+ * Generated code pattern:
1617
+ * const __VLS_defaults = { buttonClass: 'flex items-center' };
1618
+ * ...{ class: (props.buttonClass) }
1619
+ */
1620
+ extractFromPropsWithDefaults(value, context, attributeId) {
1621
+ const { typescript, typeChecker } = context;
1622
+ if (!typeChecker) {
1623
+ return [];
1624
+ }
1625
+ // Unwrap parentheses: (props.buttonClass) -> props.buttonClass
1626
+ let expr = value;
1627
+ if (typescript.isParenthesizedExpression(expr)) {
1628
+ expr = expr.expression;
1629
+ }
1630
+ // Check for props.propertyName pattern
1631
+ if (!typescript.isPropertyAccessExpression(expr)) {
1632
+ return [];
1633
+ }
1634
+ const objectExpr = expr.expression;
1635
+ if (!typescript.isIdentifier(objectExpr) || objectExpr.text !== 'props') {
1636
+ return [];
1637
+ }
1638
+ const propertyName = expr.name;
1639
+ if (!typescript.isIdentifier(propertyName)) {
1640
+ return [];
1641
+ }
1642
+ // Look for __VLS_defaults in the source file
1643
+ const defaultsValue = this.findVlsDefaultsProperty(propertyName.text, context);
1644
+ if (defaultsValue) {
1645
+ return this.extractFromExpression(defaultsValue, context, attributeId);
1646
+ }
1647
+ return [];
1648
+ }
1649
+ /**
1650
+ * Find a property value in __VLS_defaults object.
1651
+ */
1652
+ findVlsDefaultsProperty(propertyName, context) {
1653
+ const { typescript, sourceFile } = context;
1654
+ // Walk through the source file to find __VLS_defaults
1655
+ let result;
1656
+ const visitor = (node) => {
1657
+ if (result)
1658
+ return;
1659
+ if (typescript.isVariableDeclaration(node)) {
1660
+ const name = node.name;
1661
+ if (typescript.isIdentifier(name) &&
1662
+ name.text === '__VLS_defaults' &&
1663
+ node.initializer &&
1664
+ typescript.isObjectLiteralExpression(node.initializer)) {
1665
+ // Found __VLS_defaults, look for the property
1666
+ for (const prop of node.initializer.properties) {
1667
+ if (typescript.isPropertyAssignment(prop)) {
1668
+ const propName = prop.name;
1669
+ if (typescript.isIdentifier(propName) && propName.text === propertyName) {
1670
+ result = prop.initializer;
1671
+ return;
1672
+ }
1673
+ }
1674
+ }
1675
+ }
1676
+ }
1677
+ typescript.forEachChild(node, visitor);
1678
+ };
1679
+ typescript.forEachChild(sourceFile, visitor);
1680
+ return result;
1681
+ }
909
1682
  }
910
1683
  exports.VueAttributeExtractor = VueAttributeExtractor;
911
1684
  //# sourceMappingURL=VueAttributeExtractor.js.map