tailwind-typescript-plugin 1.4.1-beta.3 → 1.4.1-beta.31

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 (152) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +369 -71
  3. package/lib/core/interfaces.d.ts +40 -13
  4. package/lib/core/interfaces.d.ts.map +1 -1
  5. package/lib/core/types.d.ts +112 -1
  6. package/lib/core/types.d.ts.map +1 -1
  7. package/lib/extractors/BaseExtractor.d.ts +56 -3
  8. package/lib/extractors/BaseExtractor.d.ts.map +1 -1
  9. package/lib/extractors/BaseExtractor.js +194 -5
  10. package/lib/extractors/BaseExtractor.js.map +1 -1
  11. package/lib/extractors/BaseExtractor.spec.d.ts +2 -0
  12. package/lib/extractors/BaseExtractor.spec.d.ts.map +1 -0
  13. package/lib/extractors/BaseExtractor.spec.js +421 -0
  14. package/lib/extractors/BaseExtractor.spec.js.map +1 -0
  15. package/lib/extractors/CvaExtractor.js +1 -1
  16. package/lib/extractors/CvaExtractor.js.map +1 -1
  17. package/lib/extractors/CvaExtractor.spec.d.ts +2 -0
  18. package/lib/extractors/CvaExtractor.spec.d.ts.map +1 -0
  19. package/lib/extractors/CvaExtractor.spec.js +1177 -0
  20. package/lib/extractors/CvaExtractor.spec.js.map +1 -0
  21. package/lib/extractors/ExpressionExtractor.d.ts.map +1 -1
  22. package/lib/extractors/ExpressionExtractor.js +35 -5
  23. package/lib/extractors/ExpressionExtractor.js.map +1 -1
  24. package/lib/extractors/ExpressionExtractor.spec.d.ts +2 -0
  25. package/lib/extractors/ExpressionExtractor.spec.d.ts.map +1 -0
  26. package/lib/extractors/ExpressionExtractor.spec.js +316 -0
  27. package/lib/extractors/ExpressionExtractor.spec.js.map +1 -0
  28. package/lib/extractors/JsxAttributeExtractor.d.ts +7 -1
  29. package/lib/extractors/JsxAttributeExtractor.d.ts.map +1 -1
  30. package/lib/extractors/JsxAttributeExtractor.js +21 -8
  31. package/lib/extractors/JsxAttributeExtractor.js.map +1 -1
  32. package/lib/extractors/JsxAttributeExtractor.spec.d.ts +2 -0
  33. package/lib/extractors/JsxAttributeExtractor.spec.d.ts.map +1 -0
  34. package/lib/extractors/JsxAttributeExtractor.spec.js +430 -0
  35. package/lib/extractors/JsxAttributeExtractor.spec.js.map +1 -0
  36. package/lib/extractors/TailwindVariantsExtractor.d.ts.map +1 -1
  37. package/lib/extractors/TailwindVariantsExtractor.js +1 -5
  38. package/lib/extractors/TailwindVariantsExtractor.js.map +1 -1
  39. package/lib/extractors/TailwindVariantsExtractor.spec.d.ts +2 -0
  40. package/lib/extractors/TailwindVariantsExtractor.spec.d.ts.map +1 -0
  41. package/lib/extractors/TailwindVariantsExtractor.spec.js +1407 -0
  42. package/lib/extractors/TailwindVariantsExtractor.spec.js.map +1 -0
  43. package/lib/extractors/TemplateExpressionExtractor.spec.d.ts +2 -0
  44. package/lib/extractors/TemplateExpressionExtractor.spec.d.ts.map +1 -0
  45. package/lib/extractors/TemplateExpressionExtractor.spec.js +240 -0
  46. package/lib/extractors/TemplateExpressionExtractor.spec.js.map +1 -0
  47. package/lib/extractors/VariableReferenceExtractor.d.ts.map +1 -1
  48. package/lib/extractors/VariableReferenceExtractor.js +21 -0
  49. package/lib/extractors/VariableReferenceExtractor.js.map +1 -1
  50. package/lib/extractors/VariableReferenceExtractor.spec.d.ts +2 -0
  51. package/lib/extractors/VariableReferenceExtractor.spec.d.ts.map +1 -0
  52. package/lib/extractors/VariableReferenceExtractor.spec.js +138 -0
  53. package/lib/extractors/VariableReferenceExtractor.spec.js.map +1 -0
  54. package/lib/extractors/VueAttributeExtractor.d.ts +202 -0
  55. package/lib/extractors/VueAttributeExtractor.d.ts.map +1 -0
  56. package/lib/extractors/VueAttributeExtractor.js +1691 -0
  57. package/lib/extractors/VueAttributeExtractor.js.map +1 -0
  58. package/lib/extractors/VueExpressionExtractor.d.ts +34 -0
  59. package/lib/extractors/VueExpressionExtractor.d.ts.map +1 -0
  60. package/lib/extractors/VueExpressionExtractor.js +171 -0
  61. package/lib/extractors/VueExpressionExtractor.js.map +1 -0
  62. package/lib/infrastructure/TailwindValidator.css-vars.spec.js +1 -11
  63. package/lib/infrastructure/TailwindValidator.css-vars.spec.js.map +1 -1
  64. package/lib/infrastructure/TailwindValidator.d.ts +10 -3
  65. package/lib/infrastructure/TailwindValidator.d.ts.map +1 -1
  66. package/lib/infrastructure/TailwindValidator.js +68 -28
  67. package/lib/infrastructure/TailwindValidator.js.map +1 -1
  68. package/lib/infrastructure/TailwindValidator.spec.js +50 -17
  69. package/lib/infrastructure/TailwindValidator.spec.js.map +1 -1
  70. package/lib/plugin/TailwindTypescriptPlugin.d.ts +22 -1
  71. package/lib/plugin/TailwindTypescriptPlugin.d.ts.map +1 -1
  72. package/lib/plugin/TailwindTypescriptPlugin.js +133 -50
  73. package/lib/plugin/TailwindTypescriptPlugin.js.map +1 -1
  74. package/lib/services/ClassNameExtractionService.d.ts +27 -6
  75. package/lib/services/ClassNameExtractionService.d.ts.map +1 -1
  76. package/lib/services/ClassNameExtractionService.js +80 -17
  77. package/lib/services/ClassNameExtractionService.js.map +1 -1
  78. package/lib/services/ClassNameExtractionService.spec.d.ts +2 -0
  79. package/lib/services/ClassNameExtractionService.spec.d.ts.map +1 -0
  80. package/lib/services/ClassNameExtractionService.spec.js +215 -0
  81. package/lib/services/ClassNameExtractionService.spec.js.map +1 -0
  82. package/lib/services/CodeActionService.spec.js +1 -2
  83. package/lib/services/CodeActionService.spec.js.map +1 -1
  84. package/lib/services/CompletionService.d.ts +121 -0
  85. package/lib/services/CompletionService.d.ts.map +1 -0
  86. package/lib/services/CompletionService.js +573 -0
  87. package/lib/services/CompletionService.js.map +1 -0
  88. package/lib/services/CompletionService.spec.d.ts +2 -0
  89. package/lib/services/CompletionService.spec.d.ts.map +1 -0
  90. package/lib/services/CompletionService.spec.js +1182 -0
  91. package/lib/services/CompletionService.spec.js.map +1 -0
  92. package/lib/services/ConfigSchemaValidator.d.ts +40 -0
  93. package/lib/services/ConfigSchemaValidator.d.ts.map +1 -0
  94. package/lib/services/ConfigSchemaValidator.js +139 -0
  95. package/lib/services/ConfigSchemaValidator.js.map +1 -0
  96. package/lib/services/ConfigSchemaValidator.spec.d.ts +2 -0
  97. package/lib/services/ConfigSchemaValidator.spec.d.ts.map +1 -0
  98. package/lib/services/ConfigSchemaValidator.spec.js +344 -0
  99. package/lib/services/ConfigSchemaValidator.spec.js.map +1 -0
  100. package/lib/services/ConflictClassDetection.spec.js +53 -5
  101. package/lib/services/ConflictClassDetection.spec.js.map +1 -1
  102. package/lib/services/DiagnosticService.d.ts +8 -8
  103. package/lib/services/DiagnosticService.d.ts.map +1 -1
  104. package/lib/services/DiagnosticService.js +29 -13
  105. package/lib/services/DiagnosticService.js.map +1 -1
  106. package/lib/services/DiagnosticService.spec.d.ts +2 -0
  107. package/lib/services/DiagnosticService.spec.d.ts.map +1 -0
  108. package/lib/services/DiagnosticService.spec.js +259 -0
  109. package/lib/services/DiagnosticService.spec.js.map +1 -0
  110. package/lib/services/DuplicateClassDetection.spec.js +20 -21
  111. package/lib/services/DuplicateClassDetection.spec.js.map +1 -1
  112. package/lib/services/FileDiagnosticCache.spec.d.ts +2 -0
  113. package/lib/services/FileDiagnosticCache.spec.d.ts.map +1 -0
  114. package/lib/services/FileDiagnosticCache.spec.js +213 -0
  115. package/lib/services/FileDiagnosticCache.spec.js.map +1 -0
  116. package/lib/services/PerformanceCache.spec.d.ts +2 -0
  117. package/lib/services/PerformanceCache.spec.d.ts.map +1 -0
  118. package/lib/services/PerformanceCache.spec.js +168 -0
  119. package/lib/services/PerformanceCache.spec.js.map +1 -0
  120. package/lib/services/PluginConfigService.d.ts +66 -15
  121. package/lib/services/PluginConfigService.d.ts.map +1 -1
  122. package/lib/services/PluginConfigService.js +230 -73
  123. package/lib/services/PluginConfigService.js.map +1 -1
  124. package/lib/services/PluginConfigService.spec.d.ts +2 -0
  125. package/lib/services/PluginConfigService.spec.d.ts.map +1 -0
  126. package/lib/services/PluginConfigService.spec.js +93 -0
  127. package/lib/services/PluginConfigService.spec.js.map +1 -0
  128. package/lib/services/ValidationService.d.ts +7 -5
  129. package/lib/services/ValidationService.d.ts.map +1 -1
  130. package/lib/services/ValidationService.js +40 -43
  131. package/lib/services/ValidationService.js.map +1 -1
  132. package/lib/services/ValidationService.spec.d.ts +2 -0
  133. package/lib/services/ValidationService.spec.d.ts.map +1 -0
  134. package/lib/services/ValidationService.spec.js +289 -0
  135. package/lib/services/ValidationService.spec.js.map +1 -0
  136. package/lib/utils/FrameworkDetector.d.ts +23 -0
  137. package/lib/utils/FrameworkDetector.d.ts.map +1 -0
  138. package/lib/utils/FrameworkDetector.js +47 -0
  139. package/lib/utils/FrameworkDetector.js.map +1 -0
  140. package/lib/utils/FrameworkDetector.spec.d.ts +2 -0
  141. package/lib/utils/FrameworkDetector.spec.d.ts.map +1 -0
  142. package/lib/utils/FrameworkDetector.spec.js +67 -0
  143. package/lib/utils/FrameworkDetector.spec.js.map +1 -0
  144. package/package.json +11 -4
  145. package/lib/extractors/StringLiteralExtractor.d.ts +0 -12
  146. package/lib/extractors/StringLiteralExtractor.d.ts.map +0 -1
  147. package/lib/extractors/StringLiteralExtractor.js +0 -21
  148. package/lib/extractors/StringLiteralExtractor.js.map +0 -1
  149. package/lib/services/ClassNameExtractionService.original.d.ts +0 -20
  150. package/lib/services/ClassNameExtractionService.original.d.ts.map +0 -1
  151. package/lib/services/ClassNameExtractionService.original.js +0 -48
  152. package/lib/services/ClassNameExtractionService.original.js.map +0 -1
@@ -0,0 +1,1691 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VueAttributeExtractor = void 0;
4
+ const BaseExtractor_1 = require("./BaseExtractor");
5
+ const VueExpressionExtractor_1 = require("./VueExpressionExtractor");
6
+ /**
7
+ * Extracts class names from Vue template class attributes
8
+ *
9
+ * When @vue/language-tools (Volar) transforms Vue SFC templates, it generates
10
+ * TypeScript code using function calls with object spreads:
11
+ *
12
+ * ```typescript
13
+ * __VLS_asFunctionalElement(__VLS_intrinsicElements.div)({
14
+ * ...{ class: "flex items-center" },
15
+ * });
16
+ * ```
17
+ *
18
+ * For dynamic classes:
19
+ * ```typescript
20
+ * __VLS_asFunctionalElement(__VLS_intrinsicElements.div)({
21
+ * ...{ class: ({ 'bg-red-500': isActive }) },
22
+ * });
23
+ * ```
24
+ *
25
+ * This extractor handles these patterns to extract class names.
26
+ */
27
+ class VueAttributeExtractor extends BaseExtractor_1.BaseExtractor {
28
+ constructor() {
29
+ super();
30
+ this.expressionExtractor = new VueExpressionExtractor_1.VueExpressionExtractor();
31
+ }
32
+ /**
33
+ * Fast filter: Vue patterns are always CallExpression nodes (~95% node skip rate)
34
+ * Volar transforms templates into: __VLS_asFunctionalElement(...)({...})
35
+ */
36
+ getNodeFilter() {
37
+ return (node, typescript) => typescript.isCallExpression(node);
38
+ }
39
+ /**
40
+ * Override to handle Vue's __VLS_ctx pattern.
41
+ *
42
+ * Vue generates code like __VLS_ctx.clsx(...) for template expressions
43
+ * where clsx is imported in the script section. We need to check if the
44
+ * function name (not __VLS_ctx) is directly imported.
45
+ *
46
+ * Also handles namespace imports: __VLS_ctx.utils.clsx(...) for `import * as utils from 'clsx'`
47
+ */
48
+ shouldValidateFunctionCall(callExpression, utilityFunctions, context) {
49
+ // First, check if this is a __VLS_ctx.functionName() or __VLS_ctx.namespace.functionName() pattern
50
+ if (context) {
51
+ const expr = callExpression.expression;
52
+ if (context.typescript.isPropertyAccessExpression(expr)) {
53
+ const objectExpr = expr.expression;
54
+ // Pattern 1: __VLS_ctx.functionName() - direct import
55
+ if (context.typescript.isIdentifier(objectExpr) && objectExpr.text === '__VLS_ctx') {
56
+ const functionName = expr.name.text;
57
+ // Check each utility function configuration
58
+ for (const utilityFunc of utilityFunctions) {
59
+ if (typeof utilityFunc === 'string') {
60
+ if (utilityFunc === functionName) {
61
+ return true;
62
+ }
63
+ }
64
+ else if (utilityFunc.name === functionName) {
65
+ // Check if the function is directly imported from expected module
66
+ if (this.isImportedFrom(functionName, utilityFunc.from, context)) {
67
+ return true;
68
+ }
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ // Pattern 2: __VLS_ctx.namespace.functionName() - namespace import
74
+ // e.g., import * as utils from 'clsx' -> __VLS_ctx.utils.clsx()
75
+ if (context.typescript.isPropertyAccessExpression(objectExpr)) {
76
+ const namespaceRoot = objectExpr.expression;
77
+ if (context.typescript.isIdentifier(namespaceRoot) &&
78
+ namespaceRoot.text === '__VLS_ctx') {
79
+ const namespaceName = objectExpr.name.text; // e.g., 'utils'
80
+ const functionName = expr.name.text; // e.g., 'clsx'
81
+ // Check each utility function configuration
82
+ for (const utilityFunc of utilityFunctions) {
83
+ if (typeof utilityFunc === 'string') {
84
+ if (utilityFunc === functionName) {
85
+ return true;
86
+ }
87
+ }
88
+ else if (utilityFunc.name === functionName) {
89
+ // Check if namespace is imported from expected module
90
+ if (this.isNamespaceImportedFrom(namespaceName, utilityFunc.from, context)) {
91
+ return true;
92
+ }
93
+ }
94
+ }
95
+ return false;
96
+ }
97
+ }
98
+ }
99
+ }
100
+ // Fall back to base implementation for non-Vue patterns
101
+ return super.shouldValidateFunctionCall(callExpression, utilityFunctions, context);
102
+ }
103
+ canHandle(node, context) {
104
+ // We handle call expressions that look like Vue's generated element calls
105
+ // Pattern 1 (intrinsic elements): __VLS_asFunctionalElement(...)({ ...{ class: ... } })
106
+ // Pattern 2 (custom components): __VLS_1({ colorStyles: ... }, ...)
107
+ if (!context.typescript.isCallExpression(node)) {
108
+ return false;
109
+ }
110
+ // Check if the arguments contain an object with class properties
111
+ if (node.arguments.length === 0) {
112
+ return false;
113
+ }
114
+ const firstArg = node.arguments[0];
115
+ if (!context.typescript.isObjectLiteralExpression(firstArg)) {
116
+ return false;
117
+ }
118
+ const expression = node.expression;
119
+ // Pattern 1: Chained call (intrinsic elements)
120
+ // func(...)({...}) where the result of func(...) is called again
121
+ if (context.typescript.isCallExpression(expression)) {
122
+ // Look for spread assignments with class property
123
+ return this.hasClassSpreadProperty(firstArg, context);
124
+ }
125
+ // Pattern 2: Identifier call (custom components)
126
+ // __VLS_N({ classAttribute: ... }, ...)
127
+ if (context.typescript.isIdentifier(expression)) {
128
+ const name = expression.text;
129
+ // Vue generates __VLS_0, __VLS_1, etc. for component instances
130
+ if (name.startsWith('__VLS_')) {
131
+ // Check for direct class attribute properties
132
+ return this.hasClassDirectProperty(firstArg, context);
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ hasClassSpreadProperty(obj, context) {
138
+ // Build set of class attribute names to check
139
+ const classAttributeNames = new Set(['class', ...(context.classAttributes || [])]);
140
+ for (const prop of obj.properties) {
141
+ if (context.typescript.isSpreadAssignment(prop)) {
142
+ const spreadExpr = prop.expression;
143
+ if (context.typescript.isObjectLiteralExpression(spreadExpr)) {
144
+ for (const innerProp of spreadExpr.properties) {
145
+ if (context.typescript.isPropertyAssignment(innerProp)) {
146
+ const name = innerProp.name;
147
+ if (context.typescript.isIdentifier(name) && classAttributeNames.has(name.text)) {
148
+ return true;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+ /**
158
+ * Check if an object has direct class attribute properties (for custom components).
159
+ * Vue generates direct properties like: { colorStyles: "bg-blue-500" }
160
+ */
161
+ hasClassDirectProperty(obj, context) {
162
+ const classAttributeNames = new Set(['class', ...(context.classAttributes || [])]);
163
+ for (const prop of obj.properties) {
164
+ if (context.typescript.isPropertyAssignment(prop)) {
165
+ const name = prop.name;
166
+ if (context.typescript.isIdentifier(name) && classAttributeNames.has(name.text)) {
167
+ return true;
168
+ }
169
+ }
170
+ }
171
+ return false;
172
+ }
173
+ extract(node, context) {
174
+ const classNames = [];
175
+ if (!context.typescript.isCallExpression(node)) {
176
+ return classNames;
177
+ }
178
+ const firstArg = node.arguments[0];
179
+ if (!firstArg || !context.typescript.isObjectLiteralExpression(firstArg)) {
180
+ return classNames;
181
+ }
182
+ // Build set of class attribute names to check
183
+ const classAttributeNames = new Set(['class', ...(context.classAttributes || [])]);
184
+ // Process all properties in the object literal
185
+ for (const prop of firstArg.properties) {
186
+ // Handle spread assignments: ...{ class: "..." }
187
+ if (context.typescript.isSpreadAssignment(prop)) {
188
+ const spreadExpr = prop.expression;
189
+ if (context.typescript.isObjectLiteralExpression(spreadExpr)) {
190
+ for (const innerProp of spreadExpr.properties) {
191
+ if (!context.typescript.isPropertyAssignment(innerProp)) {
192
+ continue;
193
+ }
194
+ const name = innerProp.name;
195
+ if (!context.typescript.isIdentifier(name)) {
196
+ continue;
197
+ }
198
+ // Check if this is a class attribute
199
+ if (!classAttributeNames.has(name.text)) {
200
+ continue;
201
+ }
202
+ const value = innerProp.initializer;
203
+ const attributeId = `${innerProp.getStart()}-${innerProp.getEnd()}`;
204
+ classNames.push(...this.extractClassesFromValue(value, context, attributeId));
205
+ }
206
+ }
207
+ }
208
+ // Handle direct property assignments: colorStyles: "..."
209
+ else if (context.typescript.isPropertyAssignment(prop)) {
210
+ const name = prop.name;
211
+ if (!context.typescript.isIdentifier(name)) {
212
+ continue;
213
+ }
214
+ // Check if this is a class attribute (custom attributes like colorStyles)
215
+ if (!classAttributeNames.has(name.text)) {
216
+ continue;
217
+ }
218
+ const value = prop.initializer;
219
+ const attributeId = `${prop.getStart()}-${prop.getEnd()}`;
220
+ classNames.push(...this.extractClassesFromValue(value, context, attributeId));
221
+ }
222
+ }
223
+ return classNames;
224
+ }
225
+ extractClassesFromValue(value, context, attributeId) {
226
+ const classNames = [];
227
+ // Static string literal: class: "flex items-center"
228
+ if (context.typescript.isStringLiteral(value)) {
229
+ const fullText = value.text;
230
+ if (fullText.length === 0) {
231
+ return classNames;
232
+ }
233
+ const stringContentStart = value.getStart() + 1;
234
+ let offset = 0;
235
+ const parts = fullText.split(/(\s+)/);
236
+ for (const part of parts) {
237
+ if (part && !/^\s+$/.test(part)) {
238
+ classNames.push({
239
+ className: part,
240
+ absoluteStart: stringContentStart + offset,
241
+ length: part.length,
242
+ line: context.sourceFile.getLineAndCharacterOfPosition(stringContentStart + offset).line +
243
+ 1,
244
+ file: context.sourceFile.fileName,
245
+ attributeId
246
+ });
247
+ }
248
+ offset += part.length;
249
+ }
250
+ return classNames;
251
+ }
252
+ // Object literal for dynamic classes: class: { 'bg-red-500': isActive }
253
+ // Or wrapped in parentheses: class: ({ 'bg-red-500': isActive })
254
+ let objectExpr;
255
+ if (context.typescript.isObjectLiteralExpression(value)) {
256
+ objectExpr = value;
257
+ }
258
+ else if (context.typescript.isParenthesizedExpression(value)) {
259
+ const inner = value.expression;
260
+ if (context.typescript.isObjectLiteralExpression(inner)) {
261
+ objectExpr = inner;
262
+ }
263
+ }
264
+ if (objectExpr) {
265
+ // Use extractFromObjectExpression which handles computed property names
266
+ return this.extractFromObjectExpression(objectExpr, context, attributeId);
267
+ }
268
+ // Array literal: class: ['flex', 'items-center']
269
+ // Vue wraps expressions in parentheses: class: (['flex', 'items-center'])
270
+ let arrayExpr;
271
+ if (context.typescript.isArrayLiteralExpression(value)) {
272
+ arrayExpr = value;
273
+ }
274
+ else if (context.typescript.isParenthesizedExpression(value)) {
275
+ const inner = value.expression;
276
+ if (context.typescript.isArrayLiteralExpression(inner)) {
277
+ arrayExpr = inner;
278
+ }
279
+ }
280
+ if (arrayExpr) {
281
+ // Process array elements directly to handle __VLS_ctx references
282
+ return this.extractFromArrayExpression(arrayExpr, context, attributeId);
283
+ }
284
+ // Template literal or other expressions - delegate to expression extractor
285
+ // Vue wraps expressions in parentheses: class: (`flex items-center`)
286
+ let templateExpr;
287
+ if (context.typescript.isTemplateExpression(value) ||
288
+ context.typescript.isNoSubstitutionTemplateLiteral(value)) {
289
+ templateExpr = value;
290
+ }
291
+ else if (context.typescript.isParenthesizedExpression(value)) {
292
+ const inner = value.expression;
293
+ if (context.typescript.isTemplateExpression(inner) ||
294
+ context.typescript.isNoSubstitutionTemplateLiteral(inner)) {
295
+ templateExpr = inner;
296
+ }
297
+ }
298
+ if (templateExpr) {
299
+ const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
300
+ return addAttributeId(this.expressionExtractor.extract(templateExpr, context));
301
+ }
302
+ // Call expression (utility functions like cn, clsx)
303
+ // Vue wraps expressions in parentheses: class: (__VLS_ctx.clsx(...))
304
+ let callExpr;
305
+ if (context.typescript.isCallExpression(value)) {
306
+ callExpr = value;
307
+ }
308
+ else if (context.typescript.isParenthesizedExpression(value)) {
309
+ const inner = value.expression;
310
+ if (context.typescript.isCallExpression(inner)) {
311
+ callExpr = inner;
312
+ }
313
+ }
314
+ if (callExpr) {
315
+ // Check if it's a utility function (clsx, cn, etc.) - extract all arguments
316
+ if (this.shouldValidateFunctionCall(callExpr, context.utilityFunctions, context)) {
317
+ const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
318
+ return addAttributeId(this.expressionExtractor.extract(callExpr, context));
319
+ }
320
+ // Check for CVA/TV function calls with class override: button({ class: '...' })
321
+ // These are __VLS_ctx.functionName({ class: '...' }) patterns
322
+ const classOverrideClasses = this.extractFromCvaTvClassOverride(callExpr, context, attributeId);
323
+ if (classOverrideClasses.length > 0) {
324
+ return classOverrideClasses;
325
+ }
326
+ }
327
+ // Handle conditional (ternary) expressions: class: (isActive ? 'flex' : 'hidden')
328
+ // Vue wraps expressions in parentheses: class: (__VLS_ctx.isActive ? 'active' : 'inactive')
329
+ let conditionalExpr;
330
+ if (context.typescript.isConditionalExpression(value)) {
331
+ conditionalExpr = value;
332
+ }
333
+ else if (context.typescript.isParenthesizedExpression(value)) {
334
+ const inner = value.expression;
335
+ if (context.typescript.isConditionalExpression(inner)) {
336
+ conditionalExpr = inner;
337
+ }
338
+ }
339
+ if (conditionalExpr) {
340
+ return this.extractFromConditionalExpression(conditionalExpr, context, attributeId);
341
+ }
342
+ // Handle binary expressions: class: (isActive && 'flex')
343
+ // Vue wraps expressions in parentheses: class: (__VLS_ctx.isActive && 'active')
344
+ let binaryExpr;
345
+ if (context.typescript.isBinaryExpression(value)) {
346
+ binaryExpr = value;
347
+ }
348
+ else if (context.typescript.isParenthesizedExpression(value)) {
349
+ const inner = value.expression;
350
+ if (context.typescript.isBinaryExpression(inner)) {
351
+ binaryExpr = inner;
352
+ }
353
+ }
354
+ if (binaryExpr) {
355
+ return this.extractFromBinaryExpression(binaryExpr, context, attributeId);
356
+ }
357
+ // Handle type assertions: class: ('invalid-class' as string)
358
+ // Vue wraps expressions: class: (('invalid-class' as string))
359
+ let asExpr;
360
+ if (context.typescript.isAsExpression(value)) {
361
+ asExpr = value;
362
+ }
363
+ else if (context.typescript.isParenthesizedExpression(value)) {
364
+ let inner = value.expression;
365
+ // Double unwrap for nested parentheses
366
+ if (context.typescript.isParenthesizedExpression(inner)) {
367
+ inner = inner.expression;
368
+ }
369
+ if (context.typescript.isAsExpression(inner)) {
370
+ asExpr = inner;
371
+ }
372
+ }
373
+ if (asExpr) {
374
+ return this.extractClassesFromValue(asExpr.expression, context, attributeId);
375
+ }
376
+ // Handle non-null assertions: class: (someClass!)
377
+ let nonNullExpr;
378
+ if (context.typescript.isNonNullExpression(value)) {
379
+ nonNullExpr = value;
380
+ }
381
+ else if (context.typescript.isParenthesizedExpression(value)) {
382
+ let inner = value.expression;
383
+ if (context.typescript.isParenthesizedExpression(inner)) {
384
+ inner = inner.expression;
385
+ }
386
+ if (context.typescript.isNonNullExpression(inner)) {
387
+ nonNullExpr = inner;
388
+ }
389
+ }
390
+ if (nonNullExpr) {
391
+ return this.extractClassesFromValue(nonNullExpr.expression, context, attributeId);
392
+ }
393
+ // Handle __VLS_ctx.propertyName patterns for variable/computed/function references
394
+ // Vue generates: class: (__VLS_ctx.myClass) for :class="myClass"
395
+ const resolvedClasses = this.extractFromVlsCtxReference(value, context, attributeId);
396
+ if (resolvedClasses.length > 0) {
397
+ return resolvedClasses;
398
+ }
399
+ // Handle props.propertyName patterns with default values from withDefaults
400
+ // Vue generates: class: (props.buttonClass) for :class="props.buttonClass"
401
+ const propsDefaultClasses = this.extractFromPropsWithDefaults(value, context, attributeId);
402
+ if (propsDefaultClasses.length > 0) {
403
+ return propsDefaultClasses;
404
+ }
405
+ return classNames;
406
+ }
407
+ /**
408
+ * Extract classes from an array expression, handling __VLS_ctx references.
409
+ * This method processes array elements directly to support variable references.
410
+ */
411
+ extractFromArrayExpression(arrayExpr, context, attributeId) {
412
+ const { typescript } = context;
413
+ const classNames = [];
414
+ for (const element of arrayExpr.elements) {
415
+ if (element === undefined)
416
+ continue;
417
+ // String literal: 'flex'
418
+ if (typescript.isStringLiteral(element)) {
419
+ const fullText = element.text;
420
+ if (fullText.length > 0) {
421
+ const stringContentStart = element.getStart() + 1;
422
+ let offset = 0;
423
+ const parts = fullText.split(/(\s+)/);
424
+ for (const part of parts) {
425
+ if (part && !/^\s+$/.test(part)) {
426
+ classNames.push({
427
+ className: part,
428
+ absoluteStart: stringContentStart + offset,
429
+ length: part.length,
430
+ line: context.sourceFile.getLineAndCharacterOfPosition(stringContentStart + offset)
431
+ .line + 1,
432
+ file: context.sourceFile.fileName,
433
+ attributeId
434
+ });
435
+ }
436
+ offset += part.length;
437
+ }
438
+ }
439
+ }
440
+ // Object literal: { 'bg-red-500': isActive }
441
+ else if (typescript.isObjectLiteralExpression(element)) {
442
+ classNames.push(...this.extractFromObjectExpression(element, context, attributeId));
443
+ }
444
+ // Spread element: ...classes
445
+ else if (typescript.isSpreadElement(element)) {
446
+ // Try to resolve __VLS_ctx reference
447
+ const vlsResults = this.extractFromVlsCtxReference(element.expression, context, attributeId);
448
+ if (vlsResults.length > 0) {
449
+ classNames.push(...vlsResults);
450
+ }
451
+ }
452
+ // Handle __VLS_ctx.variable references: __VLS_ctx.myClass
453
+ else if (typescript.isPropertyAccessExpression(element)) {
454
+ const vlsResults = this.extractFromVlsCtxReference(element, context, attributeId);
455
+ if (vlsResults.length > 0) {
456
+ classNames.push(...vlsResults);
457
+ }
458
+ }
459
+ // Handle parenthesized expressions: (__VLS_ctx.myVar)
460
+ else if (typescript.isParenthesizedExpression(element)) {
461
+ const inner = element.expression;
462
+ if (typescript.isPropertyAccessExpression(inner)) {
463
+ const vlsResults = this.extractFromVlsCtxReference(inner, context, attributeId);
464
+ if (vlsResults.length > 0) {
465
+ classNames.push(...vlsResults);
466
+ }
467
+ }
468
+ else if (typescript.isArrayLiteralExpression(inner)) {
469
+ // Nested array in parentheses
470
+ classNames.push(...this.extractFromArrayExpression(inner, context, attributeId));
471
+ }
472
+ }
473
+ // Handle nested arrays recursively
474
+ else if (typescript.isArrayLiteralExpression(element)) {
475
+ classNames.push(...this.extractFromArrayExpression(element, context, attributeId));
476
+ }
477
+ // Handle ternary/conditional expressions
478
+ else if (typescript.isConditionalExpression(element)) {
479
+ // Extract from both branches
480
+ classNames.push(...this.extractFromConditionalElement(element, context, attributeId));
481
+ }
482
+ }
483
+ return classNames;
484
+ }
485
+ /**
486
+ * Extract classes from an object expression, handling computed property names.
487
+ */
488
+ extractFromObjectExpression(objExpr, context, attributeId) {
489
+ const { typescript } = context;
490
+ const classNames = [];
491
+ for (const prop of objExpr.properties) {
492
+ if (typescript.isPropertyAssignment(prop)) {
493
+ const propName = prop.name;
494
+ let className;
495
+ let start;
496
+ if (typescript.isStringLiteral(propName)) {
497
+ className = propName.text;
498
+ start = propName.getStart() + 1;
499
+ }
500
+ else if (typescript.isIdentifier(propName)) {
501
+ className = propName.text;
502
+ start = propName.getStart();
503
+ }
504
+ // Handle computed property names: { [__VLS_ctx.myVar]: true }
505
+ else if (typescript.isComputedPropertyName(propName)) {
506
+ let computedExpr = propName.expression;
507
+ // Unwrap parentheses
508
+ if (typescript.isParenthesizedExpression(computedExpr)) {
509
+ computedExpr = computedExpr.expression;
510
+ }
511
+ // Resolve __VLS_ctx.variable pattern
512
+ if (typescript.isPropertyAccessExpression(computedExpr)) {
513
+ const vlsResults = this.extractFromVlsCtxReference(computedExpr, context, attributeId);
514
+ if (vlsResults.length > 0) {
515
+ classNames.push(...vlsResults);
516
+ continue;
517
+ }
518
+ }
519
+ }
520
+ if (className && start !== undefined) {
521
+ classNames.push({
522
+ className,
523
+ absoluteStart: start,
524
+ length: className.length,
525
+ line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
526
+ file: context.sourceFile.fileName,
527
+ attributeId
528
+ });
529
+ }
530
+ }
531
+ else if (typescript.isShorthandPropertyAssignment(prop)) {
532
+ const className = prop.name.text;
533
+ const start = prop.name.getStart();
534
+ classNames.push({
535
+ className,
536
+ absoluteStart: start,
537
+ length: className.length,
538
+ line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
539
+ file: context.sourceFile.fileName,
540
+ attributeId
541
+ });
542
+ }
543
+ }
544
+ return classNames;
545
+ }
546
+ /**
547
+ * Extract classes from a conditional (ternary) expression in array context.
548
+ */
549
+ extractFromConditionalElement(conditional, context, attributeId) {
550
+ return this.extractFromConditionalExpression(conditional, context, attributeId);
551
+ }
552
+ /**
553
+ * Extract classes from a conditional (ternary) expression.
554
+ * Handles: isActive ? 'flex' : 'hidden'
555
+ */
556
+ extractFromConditionalExpression(conditional, context, attributeId) {
557
+ const classNames = [];
558
+ // Use the ternary's position as a unique identifier (like ExpressionExtractor)
559
+ const ternaryId = conditional.getStart();
560
+ // Extract from true branch with conditionalBranchId
561
+ const whenTrue = conditional.whenTrue;
562
+ classNames.push(...this.extractFromBranchExpression(whenTrue, context, attributeId, `ternary:true:${ternaryId}`));
563
+ // Extract from false branch with conditionalBranchId
564
+ const whenFalse = conditional.whenFalse;
565
+ classNames.push(...this.extractFromBranchExpression(whenFalse, context, attributeId, `ternary:false:${ternaryId}`));
566
+ return classNames;
567
+ }
568
+ /**
569
+ * Extract classes from a binary expression.
570
+ * Handles: isActive && 'flex', isDisabled || 'fallback'
571
+ */
572
+ extractFromBinaryExpression(binary, context, attributeId) {
573
+ const classNames = [];
574
+ // Extract from left operand (for patterns like: 'flex' || fallback)
575
+ classNames.push(...this.extractFromBranchExpression(binary.left, context, attributeId));
576
+ // Extract from right operand (for patterns like: isActive && 'flex')
577
+ classNames.push(...this.extractFromBranchExpression(binary.right, context, attributeId));
578
+ return classNames;
579
+ }
580
+ /**
581
+ * Extract classes from a branch expression (ternary branch or binary operand).
582
+ * Handles string literals, nested ternaries, nested binaries, and VLS references.
583
+ */
584
+ extractFromBranchExpression(expr, context, attributeId, conditionalBranchId) {
585
+ const { typescript } = context;
586
+ const classNames = [];
587
+ // Helper to add conditionalBranchId to extracted classes
588
+ const addBranchId = (classes) => conditionalBranchId ? classes.map(c => ({ ...c, conditionalBranchId })) : classes;
589
+ // String literal: 'flex items-center'
590
+ if (typescript.isStringLiteral(expr)) {
591
+ classNames.push(...addBranchId(this.extractClassesFromStringLiteral(expr, context, attributeId)));
592
+ }
593
+ // Nested ternary: condition ? 'a' : (nested ? 'b' : 'c')
594
+ else if (typescript.isConditionalExpression(expr)) {
595
+ classNames.push(...this.extractFromConditionalExpression(expr, context, attributeId));
596
+ }
597
+ // Nested binary: isA && isB && 'class'
598
+ else if (typescript.isBinaryExpression(expr)) {
599
+ classNames.push(...addBranchId(this.extractFromBinaryExpression(expr, context, attributeId)));
600
+ }
601
+ // Parenthesized expression
602
+ else if (typescript.isParenthesizedExpression(expr)) {
603
+ classNames.push(...this.extractFromBranchExpression(expr.expression, context, attributeId, conditionalBranchId));
604
+ }
605
+ // VLS ctx reference: __VLS_ctx.myClass
606
+ else if (typescript.isPropertyAccessExpression(expr)) {
607
+ const vlsResults = this.extractFromVlsCtxReference(expr, context, attributeId);
608
+ classNames.push(...addBranchId(vlsResults));
609
+ }
610
+ // Template literal: `flex ${something}`
611
+ else if (typescript.isTemplateExpression(expr) ||
612
+ typescript.isNoSubstitutionTemplateLiteral(expr)) {
613
+ const addAttributeId = (classes) => classes.map(c => ({ ...c, attributeId }));
614
+ classNames.push(...addBranchId(addAttributeId(this.expressionExtractor.extract(expr, context))));
615
+ }
616
+ return classNames;
617
+ }
618
+ /**
619
+ * Extract classes from CVA/TV function calls with class override.
620
+ * Handles patterns like: button({ color: 'primary', class: 'invalid-class' })
621
+ *
622
+ * Only extracts from functions that are defined using cva() or tv(),
623
+ * not from arbitrary custom functions.
624
+ */
625
+ extractFromCvaTvClassOverride(callExpr, context, attributeId) {
626
+ const { typescript } = context;
627
+ const classNames = [];
628
+ // Check if this is a __VLS_ctx.functionName pattern
629
+ const calleeExpr = callExpr.expression;
630
+ if (!typescript.isPropertyAccessExpression(calleeExpr)) {
631
+ return classNames;
632
+ }
633
+ const objectExpr = calleeExpr.expression;
634
+ if (!typescript.isIdentifier(objectExpr) || objectExpr.text !== '__VLS_ctx') {
635
+ return classNames;
636
+ }
637
+ const functionName = calleeExpr.name;
638
+ if (!typescript.isIdentifier(functionName)) {
639
+ return classNames;
640
+ }
641
+ // Check if this function is defined using cva() or tv()
642
+ // by looking at its definition
643
+ if (!this.isCvaTvFunction(functionName, context)) {
644
+ return classNames;
645
+ }
646
+ // Look for object argument with class/className property
647
+ if (callExpr.arguments.length === 0) {
648
+ return classNames;
649
+ }
650
+ const firstArg = callExpr.arguments[0];
651
+ if (!typescript.isObjectLiteralExpression(firstArg)) {
652
+ return classNames;
653
+ }
654
+ // Find class or className property
655
+ for (const prop of firstArg.properties) {
656
+ if (!typescript.isPropertyAssignment(prop)) {
657
+ continue;
658
+ }
659
+ const propName = prop.name;
660
+ if (!typescript.isIdentifier(propName)) {
661
+ continue;
662
+ }
663
+ if (propName.text !== 'class' && propName.text !== 'className') {
664
+ continue;
665
+ }
666
+ // Extract classes from the property value
667
+ const value = prop.initializer;
668
+ if (typescript.isStringLiteral(value)) {
669
+ classNames.push(...this.extractClassesFromStringLiteral(value, context, attributeId));
670
+ }
671
+ else if (typescript.isArrayLiteralExpression(value)) {
672
+ classNames.push(...this.extractFromArrayExpression(value, context, attributeId));
673
+ }
674
+ else if (typescript.isTemplateExpression(value) ||
675
+ typescript.isNoSubstitutionTemplateLiteral(value)) {
676
+ const addAttrId = (classes) => classes.map(c => ({ ...c, attributeId }));
677
+ classNames.push(...addAttrId(this.expressionExtractor.extract(value, context)));
678
+ }
679
+ }
680
+ return classNames;
681
+ }
682
+ /**
683
+ * Check if a function is defined using cva() or tv().
684
+ * Resolves the function symbol and checks if its initializer is a cva/tv call.
685
+ */
686
+ isCvaTvFunction(functionName, context) {
687
+ const { typescript, typeChecker } = context;
688
+ if (!typeChecker) {
689
+ return false;
690
+ }
691
+ // Get the symbol for the function name
692
+ const symbol = typeChecker.getSymbolAtLocation(functionName);
693
+ if (!symbol) {
694
+ return false;
695
+ }
696
+ const declarations = symbol.getDeclarations();
697
+ if (!declarations || declarations.length === 0) {
698
+ return false;
699
+ }
700
+ for (const declaration of declarations) {
701
+ // Check PropertySignature -> typeof reference in Volar 3.x
702
+ if (typescript.isPropertySignature(declaration) && declaration.type) {
703
+ if (typescript.isTypeQueryNode(declaration.type)) {
704
+ const exprName = declaration.type.exprName;
705
+ if (typescript.isIdentifier(exprName)) {
706
+ // Resolve the actual variable
707
+ const varSymbol = typeChecker.getSymbolAtLocation(exprName);
708
+ if (varSymbol) {
709
+ const varDeclarations = varSymbol.getDeclarations();
710
+ if (varDeclarations) {
711
+ for (const varDecl of varDeclarations) {
712
+ if (typescript.isVariableDeclaration(varDecl) && varDecl.initializer) {
713
+ if (this.isCallToCvaOrTv(varDecl.initializer, context)) {
714
+ return true;
715
+ }
716
+ }
717
+ }
718
+ }
719
+ }
720
+ }
721
+ }
722
+ }
723
+ // Check direct variable declaration
724
+ else if (typescript.isVariableDeclaration(declaration) && declaration.initializer) {
725
+ if (this.isCallToCvaOrTv(declaration.initializer, context)) {
726
+ return true;
727
+ }
728
+ }
729
+ // Check property assignment in Vue's return
730
+ else if (typescript.isPropertyAssignment(declaration)) {
731
+ let expr = declaration.initializer;
732
+ if (typescript.isAsExpression(expr)) {
733
+ expr = expr.expression;
734
+ }
735
+ if (typescript.isIdentifier(expr)) {
736
+ const refSymbol = typeChecker.getSymbolAtLocation(expr);
737
+ if (refSymbol) {
738
+ const refDeclarations = refSymbol.getDeclarations();
739
+ if (refDeclarations) {
740
+ for (const refDecl of refDeclarations) {
741
+ if (typescript.isVariableDeclaration(refDecl) && refDecl.initializer) {
742
+ if (this.isCallToCvaOrTv(refDecl.initializer, context)) {
743
+ return true;
744
+ }
745
+ }
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
751
+ }
752
+ return false;
753
+ }
754
+ /**
755
+ * Check if an expression is a call to cva() or tv().
756
+ */
757
+ isCallToCvaOrTv(expr, context) {
758
+ const { typescript } = context;
759
+ if (!typescript.isCallExpression(expr)) {
760
+ return false;
761
+ }
762
+ const callee = expr.expression;
763
+ // Direct call: cva(...) or tv(...)
764
+ if (typescript.isIdentifier(callee)) {
765
+ const name = callee.text;
766
+ return name === 'cva' || name === 'tv' || name === 'tvLite';
767
+ }
768
+ return false;
769
+ }
770
+ /**
771
+ * Extract classes from a string literal with attributeId.
772
+ */
773
+ extractClassesFromStringLiteral(literal, context, attributeId) {
774
+ const classNames = [];
775
+ const fullText = literal.text;
776
+ if (fullText.length === 0) {
777
+ return classNames;
778
+ }
779
+ const stringContentStart = literal.getStart() + 1;
780
+ let offset = 0;
781
+ const parts = fullText.split(/(\s+)/);
782
+ for (const part of parts) {
783
+ if (part && !/^\s+$/.test(part)) {
784
+ classNames.push({
785
+ className: part,
786
+ absoluteStart: stringContentStart + offset,
787
+ length: part.length,
788
+ line: context.sourceFile.getLineAndCharacterOfPosition(stringContentStart + offset).line + 1,
789
+ file: context.sourceFile.fileName,
790
+ attributeId
791
+ });
792
+ }
793
+ offset += part.length;
794
+ }
795
+ return classNames;
796
+ }
797
+ /**
798
+ * Extract classes from __VLS_ctx patterns by resolving variable/function references.
799
+ *
800
+ * Vue's generated code transforms template expressions in two ways:
801
+ * - :class="myClass" becomes class: (__VLS_ctx.myClass) - property access
802
+ * - :class="getClasses()" becomes class: (__VLS_ctx.getClasses()) - call expression
803
+ *
804
+ * The symbol resolves to a property assignment in Vue's generated code like:
805
+ * `return { myClass: myClass as typeof myClass }` or
806
+ * `return { getClasses: getClasses as typeof getClasses }`
807
+ * We need to follow the reference chain to find the actual declaration.
808
+ *
809
+ * IMPORTANT: We use the TEMPLATE position (the propertyName in __VLS_ctx.propertyName)
810
+ * for diagnostics because Volar only maps diagnostics from the template-generated section,
811
+ * not from the script section. Script section positions have valid mappings but Volar
812
+ * doesn't apply them for diagnostics.
813
+ */
814
+ extractFromVlsCtxReference(value, context, attributeId) {
815
+ const { typescript, typeChecker } = context;
816
+ if (!typeChecker) {
817
+ return [];
818
+ }
819
+ // Unwrap parenthesized expression if present
820
+ let expr = value;
821
+ if (typescript.isParenthesizedExpression(expr)) {
822
+ expr = expr.expression;
823
+ }
824
+ // Handle call expressions: __VLS_ctx.getClasses()
825
+ if (typescript.isCallExpression(expr)) {
826
+ const calleeExpr = expr.expression;
827
+ if (typescript.isPropertyAccessExpression(calleeExpr)) {
828
+ const objectExpr = calleeExpr.expression;
829
+ if (typescript.isIdentifier(objectExpr) && objectExpr.text === '__VLS_ctx') {
830
+ const functionName = calleeExpr.name;
831
+ if (typescript.isIdentifier(functionName)) {
832
+ // Use the function name position in template for diagnostics
833
+ const templatePosition = functionName.getStart();
834
+ const templateLength = functionName.text.length;
835
+ return this.extractFromVlsCtxFunctionCall(functionName, context, attributeId, templatePosition, templateLength);
836
+ }
837
+ }
838
+ }
839
+ return [];
840
+ }
841
+ // Check if this is a __VLS_ctx.propertyName pattern (property access)
842
+ if (!typescript.isPropertyAccessExpression(expr)) {
843
+ return [];
844
+ }
845
+ const objectExpr = expr.expression;
846
+ // Handle nested property access: __VLS_ctx.obj.property
847
+ // e.g., __VLS_ctx.slotProps.buttonClass
848
+ if (typescript.isPropertyAccessExpression(objectExpr)) {
849
+ const nestedObject = objectExpr.expression;
850
+ if (typescript.isIdentifier(nestedObject) && nestedObject.text === '__VLS_ctx') {
851
+ // This is __VLS_ctx.something.somethingElse
852
+ const middlePropName = objectExpr.name;
853
+ const finalPropName = expr.name;
854
+ if (typescript.isIdentifier(middlePropName) && typescript.isIdentifier(finalPropName)) {
855
+ // Resolve the middle property (e.g., slotProps) to get its type/value
856
+ const middleSymbol = typeChecker.getSymbolAtLocation(middlePropName);
857
+ if (middleSymbol) {
858
+ const middleDeclarations = middleSymbol.getDeclarations();
859
+ if (middleDeclarations) {
860
+ for (const middleDecl of middleDeclarations) {
861
+ // Handle variable declaration: const slotProps = { buttonClass: '...' }
862
+ if (typescript.isVariableDeclaration(middleDecl) && middleDecl.initializer) {
863
+ if (typescript.isObjectLiteralExpression(middleDecl.initializer)) {
864
+ // Find the property in the object literal
865
+ for (const prop of middleDecl.initializer.properties) {
866
+ if (typescript.isPropertyAssignment(prop)) {
867
+ const propName = prop.name;
868
+ if (typescript.isIdentifier(propName) &&
869
+ propName.text === finalPropName.text) {
870
+ // Found the property, extract classes from its value
871
+ // Keep original positions from the class string
872
+ const classes = this.extractFromExpression(prop.initializer, context, attributeId);
873
+ return classes.map(c => ({
874
+ ...c,
875
+ attributeId
876
+ }));
877
+ }
878
+ }
879
+ }
880
+ }
881
+ }
882
+ // Handle property signature in Volar's generated types
883
+ else if (typescript.isPropertySignature(middleDecl) && middleDecl.type) {
884
+ if (typescript.isTypeQueryNode(middleDecl.type)) {
885
+ const exprName = middleDecl.type.exprName;
886
+ if (typescript.isIdentifier(exprName)) {
887
+ const varSymbol = typeChecker.getSymbolAtLocation(exprName);
888
+ if (varSymbol) {
889
+ const varDeclarations = varSymbol.getDeclarations();
890
+ if (varDeclarations) {
891
+ for (const varDecl of varDeclarations) {
892
+ if (typescript.isVariableDeclaration(varDecl) &&
893
+ varDecl.initializer &&
894
+ typescript.isObjectLiteralExpression(varDecl.initializer)) {
895
+ // Find the property
896
+ for (const prop of varDecl.initializer.properties) {
897
+ if (typescript.isPropertyAssignment(prop)) {
898
+ const pName = prop.name;
899
+ if (typescript.isIdentifier(pName) &&
900
+ pName.text === finalPropName.text) {
901
+ // Keep original positions from the class string
902
+ const classes = this.extractFromExpression(prop.initializer, context, attributeId);
903
+ return classes.map(c => ({
904
+ ...c,
905
+ attributeId
906
+ }));
907
+ }
908
+ }
909
+ }
910
+ }
911
+ }
912
+ }
913
+ }
914
+ }
915
+ }
916
+ }
917
+ }
918
+ }
919
+ }
920
+ }
921
+ }
922
+ return [];
923
+ }
924
+ if (!typescript.isIdentifier(objectExpr) || objectExpr.text !== '__VLS_ctx') {
925
+ return [];
926
+ }
927
+ // Get the property name identifier (e.g., 'myClass' from __VLS_ctx.myClass)
928
+ const propertyName = expr.name;
929
+ // Vue's generated code uses regular identifiers, not private identifiers
930
+ if (!typescript.isIdentifier(propertyName)) {
931
+ return [];
932
+ }
933
+ // IMPORTANT: Use the template position (propertyName) for diagnostics
934
+ // This position has a valid Volar mapping that will be applied in the IDE
935
+ const templatePosition = propertyName.getStart();
936
+ const templateLength = propertyName.text.length;
937
+ // Use the type checker to resolve the symbol
938
+ const symbol = typeChecker.getSymbolAtLocation(propertyName);
939
+ if (!symbol) {
940
+ return [];
941
+ }
942
+ const declarations = symbol.getDeclarations();
943
+ if (!declarations || declarations.length === 0) {
944
+ return [];
945
+ }
946
+ const classNames = [];
947
+ // Keep original positions - diagnostic will point to where class is defined
948
+ const addAttributeId = (classes) => classes.map(c => ({
949
+ ...c,
950
+ attributeId
951
+ }));
952
+ for (const declaration of declarations) {
953
+ // Handle variable declarations: const myClass = 'flex items-center'
954
+ if (typescript.isVariableDeclaration(declaration)) {
955
+ const initializer = declaration.initializer;
956
+ if (initializer) {
957
+ // Check if this is a computed() or inject() call
958
+ if (typescript.isCallExpression(initializer)) {
959
+ // Handle computed() calls
960
+ const computedClasses = this.extractFromComputedCall(initializer, context, attributeId, templatePosition, templateLength);
961
+ if (computedClasses.length > 0) {
962
+ classNames.push(...computedClasses);
963
+ continue;
964
+ }
965
+ // Handle inject() calls with default value: inject('key', 'default-classes')
966
+ const injectClasses = this.extractFromInjectCall(initializer, context, attributeId);
967
+ if (injectClasses.length > 0) {
968
+ classNames.push(...injectClasses);
969
+ continue;
970
+ }
971
+ }
972
+ // For regular variables, extract classes from the initializer
973
+ // Keep original position from string literal
974
+ classNames.push(...addAttributeId(this.extractFromExpression(initializer, context)));
975
+ }
976
+ }
977
+ // Handle function declarations: function getClasses() { return [...] }
978
+ else if (typescript.isFunctionDeclaration(declaration)) {
979
+ const funcClasses = this.extractFromFunctionDeclaration(declaration, context, attributeId, templatePosition, templateLength);
980
+ classNames.push(...funcClasses);
981
+ }
982
+ // Handle property assignments in Vue's generated code:
983
+ // `return { myClass: myClass as typeof myClass }`
984
+ // The declaration is the PropertyAssignment, and we need to follow the reference
985
+ else if (typescript.isPropertyAssignment(declaration)) {
986
+ const initializer = declaration.initializer;
987
+ // Follow the reference: `myClass as typeof myClass` or just `myClass`
988
+ const resolvedClasses = this.resolvePropertyAssignmentClasses(initializer, context, attributeId, templatePosition, templateLength);
989
+ classNames.push(...resolvedClasses);
990
+ }
991
+ // Handle shorthand property assignments: `return { myClass }`
992
+ else if (typescript.isShorthandPropertyAssignment(declaration)) {
993
+ // The name itself is the reference to the variable
994
+ const resolvedClasses = this.resolveIdentifierClasses(declaration.name, context, attributeId, templatePosition, templateLength);
995
+ classNames.push(...resolvedClasses);
996
+ }
997
+ // Handle property signatures in Volar 3.x:
998
+ // type __VLS_SetupExposed = { myClass: typeof myClass; }
999
+ // The type is a string literal type that contains the class value
1000
+ else if (typescript.isPropertySignature(declaration)) {
1001
+ const resolvedClasses = this.extractFromPropertySignatureType(propertyName, context, attributeId);
1002
+ classNames.push(...resolvedClasses);
1003
+ }
1004
+ }
1005
+ return classNames;
1006
+ }
1007
+ /**
1008
+ * Extract classes from a PropertySignature by getting the type.
1009
+ *
1010
+ * In Volar 3.x, script variables are exposed via a type like:
1011
+ * type __VLS_SetupExposed = { myClass: typeof myClass; }
1012
+ *
1013
+ * We prioritize finding the actual variable declaration to use original positions,
1014
+ * so errors point to the actual class strings in the script section.
1015
+ */
1016
+ extractFromPropertySignatureType(identifier, context, attributeId) {
1017
+ const { typescript, typeChecker } = context;
1018
+ if (!typeChecker) {
1019
+ return [];
1020
+ }
1021
+ // For computed/functions, we need to use template position since
1022
+ // the actual class values are in dynamic expressions
1023
+ const templatePosition = identifier.getStart();
1024
+ const templateLength = identifier.text.length;
1025
+ // PRIORITY 1: Try to find the actual variable declaration via PropertySignature's typeof
1026
+ // This allows us to use original positions for string literals in the script section
1027
+ const symbol = typeChecker.getSymbolAtLocation(identifier);
1028
+ if (symbol) {
1029
+ const declarations = symbol.getDeclarations();
1030
+ if (declarations) {
1031
+ for (const decl of declarations) {
1032
+ if (typescript.isPropertySignature(decl) && decl.type) {
1033
+ // Check if the type is a TypeQuery (typeof expression)
1034
+ if (typescript.isTypeQueryNode(decl.type)) {
1035
+ // The exprName contains the identifier we need to resolve
1036
+ const exprName = decl.type.exprName;
1037
+ if (typescript.isIdentifier(exprName)) {
1038
+ // Get the symbol for this identifier
1039
+ const varSymbol = typeChecker.getSymbolAtLocation(exprName);
1040
+ if (varSymbol) {
1041
+ const varDeclarations = varSymbol.getDeclarations();
1042
+ if (varDeclarations) {
1043
+ for (const varDecl of varDeclarations) {
1044
+ if (typescript.isVariableDeclaration(varDecl) && varDecl.initializer) {
1045
+ // Check if it's a computed() or inject() call - use ORIGINAL position
1046
+ // so errors point to actual class strings in script
1047
+ if (typescript.isCallExpression(varDecl.initializer)) {
1048
+ const computedClasses = this.extractFromComputedCall(varDecl.initializer, context, attributeId
1049
+ // Don't pass templatePosition - use original positions
1050
+ );
1051
+ if (computedClasses.length > 0) {
1052
+ return computedClasses;
1053
+ }
1054
+ // Check for inject() calls with default value
1055
+ const injectClasses = this.extractFromInjectCall(varDecl.initializer, context, attributeId);
1056
+ if (injectClasses.length > 0) {
1057
+ return injectClasses;
1058
+ }
1059
+ }
1060
+ // For string literals, use ORIGINAL position from script
1061
+ // This makes errors point to the actual invalid class
1062
+ const classes = this.extractFromExpression(varDecl.initializer, context);
1063
+ if (classes.length > 0) {
1064
+ return classes.map(c => ({ ...c, attributeId }));
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ }
1076
+ // PRIORITY 2: Fall back to type-based extraction with template position
1077
+ // This handles cases where we can't find the actual declaration
1078
+ const type = typeChecker.getTypeAtLocation(identifier);
1079
+ // Check if it's a string literal type (simple string variable)
1080
+ if (type.isStringLiteral()) {
1081
+ const classValue = type.value;
1082
+ return this.parseClassString(classValue, identifier, context, attributeId);
1083
+ }
1084
+ // For union types (e.g., ternary expressions), collect all string literal types
1085
+ if (type.isUnion()) {
1086
+ const classNames = [];
1087
+ for (const unionType of type.types) {
1088
+ if (unionType.isStringLiteral()) {
1089
+ classNames.push(...this.parseClassString(unionType.value, identifier, context, attributeId));
1090
+ }
1091
+ }
1092
+ if (classNames.length > 0) {
1093
+ return classNames;
1094
+ }
1095
+ }
1096
+ // PRIORITY 3: Fallback via type.getSymbol() for non-PropertySignature cases
1097
+ const typeSymbol = type.getSymbol();
1098
+ if (typeSymbol) {
1099
+ const declarations = typeSymbol.getDeclarations();
1100
+ if (declarations) {
1101
+ for (const decl of declarations) {
1102
+ if (typescript.isVariableDeclaration(decl) && decl.initializer) {
1103
+ // Check if it's a computed() call
1104
+ if (typescript.isCallExpression(decl.initializer)) {
1105
+ const computedClasses = this.extractFromComputedCall(decl.initializer, context, attributeId, templatePosition, templateLength);
1106
+ if (computedClasses.length > 0) {
1107
+ return computedClasses;
1108
+ }
1109
+ // Check for inject() calls with default value
1110
+ const injectClasses = this.extractFromInjectCall(decl.initializer, context, attributeId);
1111
+ if (injectClasses.length > 0) {
1112
+ return injectClasses;
1113
+ }
1114
+ }
1115
+ // Otherwise extract from the initializer directly
1116
+ const classes = this.extractFromExpression(decl.initializer, context);
1117
+ return classes.map(c => ({
1118
+ ...c,
1119
+ attributeId,
1120
+ absoluteStart: templatePosition,
1121
+ length: templateLength,
1122
+ line: context.sourceFile.getLineAndCharacterOfPosition(templatePosition).line + 1
1123
+ }));
1124
+ }
1125
+ }
1126
+ }
1127
+ }
1128
+ return [];
1129
+ }
1130
+ /**
1131
+ * Parse a class string into ClassNameInfo array.
1132
+ * Uses the identifier position for diagnostics (mapped by Volar).
1133
+ */
1134
+ parseClassString(classValue, identifier, context, attributeId) {
1135
+ const classNames = [];
1136
+ if (!classValue || classValue.length === 0) {
1137
+ return classNames;
1138
+ }
1139
+ // Use identifier position for all diagnostics (Volar will map this)
1140
+ const position = identifier.getStart();
1141
+ const line = context.sourceFile.getLineAndCharacterOfPosition(position).line + 1;
1142
+ const parts = classValue.split(/\s+/);
1143
+ for (const part of parts) {
1144
+ if (part && part.trim()) {
1145
+ classNames.push({
1146
+ className: part.trim(),
1147
+ absoluteStart: position,
1148
+ length: identifier.text.length,
1149
+ line,
1150
+ file: context.sourceFile.fileName,
1151
+ attributeId
1152
+ });
1153
+ }
1154
+ }
1155
+ return classNames;
1156
+ }
1157
+ /**
1158
+ * Extract classes from a __VLS_ctx.functionName() call by resolving the function.
1159
+ */
1160
+ extractFromVlsCtxFunctionCall(functionName, context, attributeId, templatePosition, templateLength) {
1161
+ const { typescript, typeChecker } = context;
1162
+ if (!typeChecker) {
1163
+ return [];
1164
+ }
1165
+ // Get the symbol for the function name
1166
+ const symbol = typeChecker.getSymbolAtLocation(functionName);
1167
+ if (!symbol) {
1168
+ return [];
1169
+ }
1170
+ const declarations = symbol.getDeclarations();
1171
+ if (!declarations || declarations.length === 0) {
1172
+ return [];
1173
+ }
1174
+ const classNames = [];
1175
+ for (const declaration of declarations) {
1176
+ // Handle property assignment in Vue's return: `return { getClasses: getClasses as typeof getClasses }`
1177
+ if (typescript.isPropertyAssignment(declaration)) {
1178
+ const initializer = declaration.initializer;
1179
+ // Unwrap type assertion: `getClasses as typeof getClasses` -> `getClasses`
1180
+ let expr = initializer;
1181
+ if (typescript.isAsExpression(expr)) {
1182
+ expr = expr.expression;
1183
+ }
1184
+ // Resolve the identifier to the actual function
1185
+ if (typescript.isIdentifier(expr)) {
1186
+ const funcClasses = this.resolveFunctionIdentifier(expr, context, attributeId, templatePosition, templateLength);
1187
+ classNames.push(...funcClasses);
1188
+ }
1189
+ }
1190
+ // Handle shorthand property: `return { getClasses }`
1191
+ else if (typescript.isShorthandPropertyAssignment(declaration)) {
1192
+ const funcClasses = this.resolveFunctionIdentifier(declaration.name, context, attributeId, templatePosition, templateLength);
1193
+ classNames.push(...funcClasses);
1194
+ }
1195
+ // Handle property signature in Volar 3.x:
1196
+ // type __VLS_SetupExposed = { getClasses: typeof getClasses; }
1197
+ else if (typescript.isPropertySignature(declaration)) {
1198
+ // The type is `typeof getClasses`, which refers to the actual function
1199
+ // We need to get the type and find the function declaration
1200
+ const funcClasses = this.extractFromPropertySignatureFunction(functionName, context, attributeId);
1201
+ classNames.push(...funcClasses);
1202
+ }
1203
+ }
1204
+ return classNames;
1205
+ }
1206
+ /**
1207
+ * Extract classes from a function referenced via PropertySignature.
1208
+ * In Volar 3.x: `type __VLS_SetupExposed = { getClasses: typeof getClasses; }`
1209
+ */
1210
+ extractFromPropertySignatureFunction(functionName, context, attributeId) {
1211
+ const { typescript, typeChecker } = context;
1212
+ if (!typeChecker) {
1213
+ return [];
1214
+ }
1215
+ // Get the type at the function name location
1216
+ const type = typeChecker.getTypeAtLocation(functionName);
1217
+ // Try to find the actual function via type's symbol FIRST
1218
+ // This gives us access to the function body for analysis
1219
+ const typeSymbol = type.getSymbol();
1220
+ if (typeSymbol) {
1221
+ const funcDeclarations = typeSymbol.getDeclarations();
1222
+ if (funcDeclarations) {
1223
+ for (const decl of funcDeclarations) {
1224
+ if (typescript.isFunctionDeclaration(decl) && decl.body) {
1225
+ // Don't pass templatePosition - use original positions
1226
+ // so errors point to actual class strings in script
1227
+ return this.extractFromFunctionBody(decl.body, context, attributeId);
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ // Fallback: try to extract from return type if it's a tuple of literals
1233
+ const callSignatures = type.getCallSignatures();
1234
+ if (callSignatures.length === 0) {
1235
+ return [];
1236
+ }
1237
+ const returnType = callSignatures[0].getReturnType();
1238
+ // For array types like string[], check if elements are string literal types
1239
+ if (typeChecker.isArrayType(returnType)) {
1240
+ const typeArgs = returnType.typeArguments;
1241
+ if (typeArgs && typeArgs.length > 0) {
1242
+ const elementType = typeArgs[0];
1243
+ return this.extractClassesFromType(elementType, functionName, context, attributeId);
1244
+ }
1245
+ }
1246
+ return [];
1247
+ }
1248
+ /**
1249
+ * Extract classes from a TypeScript type (for union types, string literals, etc.)
1250
+ */
1251
+ extractClassesFromType(type, identifier, context, attributeId) {
1252
+ const { typeChecker } = context;
1253
+ if (!typeChecker) {
1254
+ return [];
1255
+ }
1256
+ const classNames = [];
1257
+ // For union types, collect all string literal types
1258
+ if (type.isUnion()) {
1259
+ for (const unionType of type.types) {
1260
+ if (unionType.isStringLiteral()) {
1261
+ classNames.push(...this.parseClassString(unionType.value, identifier, context, attributeId));
1262
+ }
1263
+ }
1264
+ }
1265
+ // For single string literal type
1266
+ else if (type.isStringLiteral()) {
1267
+ classNames.push(...this.parseClassString(type.value, identifier, context, attributeId));
1268
+ }
1269
+ return classNames;
1270
+ }
1271
+ /**
1272
+ * Resolve a function identifier to its declaration and extract classes from return statements.
1273
+ */
1274
+ resolveFunctionIdentifier(identifier, context, attributeId, templatePosition, templateLength) {
1275
+ const { typescript, typeChecker } = context;
1276
+ if (!typeChecker) {
1277
+ return [];
1278
+ }
1279
+ const symbol = typeChecker.getSymbolAtLocation(identifier);
1280
+ if (!symbol) {
1281
+ return [];
1282
+ }
1283
+ const declarations = symbol.getDeclarations();
1284
+ if (!declarations || declarations.length === 0) {
1285
+ return [];
1286
+ }
1287
+ const classNames = [];
1288
+ for (const declaration of declarations) {
1289
+ if (typescript.isFunctionDeclaration(declaration)) {
1290
+ classNames.push(...this.extractFromFunctionDeclaration(declaration, context, attributeId, templatePosition, templateLength));
1291
+ }
1292
+ }
1293
+ return classNames;
1294
+ }
1295
+ /**
1296
+ * Resolve classes from a property assignment initializer.
1297
+ * Handles patterns like `myClass as typeof myClass` or just `myClass`.
1298
+ */
1299
+ resolvePropertyAssignmentClasses(initializer, context, attributeId, templatePosition, templateLength) {
1300
+ const { typescript } = context;
1301
+ // Unwrap type assertions: `myClass as typeof myClass` -> `myClass`
1302
+ let expr = initializer;
1303
+ if (typescript.isAsExpression(expr)) {
1304
+ expr = expr.expression;
1305
+ }
1306
+ // If it's an identifier, resolve it to the actual variable
1307
+ if (typescript.isIdentifier(expr)) {
1308
+ return this.resolveIdentifierClasses(expr, context, attributeId, templatePosition, templateLength);
1309
+ }
1310
+ return [];
1311
+ }
1312
+ /**
1313
+ * Resolve classes from an identifier by finding its declaration.
1314
+ */
1315
+ resolveIdentifierClasses(identifier, context, attributeId, templatePosition, templateLength) {
1316
+ const { typescript, typeChecker } = context;
1317
+ if (!typeChecker) {
1318
+ return [];
1319
+ }
1320
+ const symbol = typeChecker.getSymbolAtLocation(identifier);
1321
+ if (!symbol) {
1322
+ return [];
1323
+ }
1324
+ const declarations = symbol.getDeclarations();
1325
+ if (!declarations || declarations.length === 0) {
1326
+ return [];
1327
+ }
1328
+ const classNames = [];
1329
+ // Keep original positions - diagnostic will point to where class is defined
1330
+ const addAttributeId = (classes) => classes.map(c => ({
1331
+ ...c,
1332
+ attributeId
1333
+ }));
1334
+ for (const declaration of declarations) {
1335
+ if (typescript.isVariableDeclaration(declaration)) {
1336
+ const init = declaration.initializer;
1337
+ if (init) {
1338
+ // Check for computed() or inject() calls
1339
+ if (typescript.isCallExpression(init)) {
1340
+ const computedClasses = this.extractFromComputedCall(init, context, attributeId, templatePosition, templateLength);
1341
+ if (computedClasses.length > 0) {
1342
+ classNames.push(...computedClasses);
1343
+ continue;
1344
+ }
1345
+ // Check for inject() calls with default value
1346
+ const injectClasses = this.extractFromInjectCall(init, context, attributeId);
1347
+ if (injectClasses.length > 0) {
1348
+ classNames.push(...injectClasses);
1349
+ continue;
1350
+ }
1351
+ }
1352
+ // Extract classes from the initializer - keep original position
1353
+ classNames.push(...addAttributeId(this.extractFromExpression(init, context)));
1354
+ }
1355
+ }
1356
+ else if (typescript.isFunctionDeclaration(declaration)) {
1357
+ classNames.push(...this.extractFromFunctionDeclaration(declaration, context, attributeId, templatePosition, templateLength));
1358
+ }
1359
+ }
1360
+ return classNames;
1361
+ }
1362
+ /**
1363
+ * Extract classes from a computed() call expression.
1364
+ * Handles: const classes = computed(() => ['flex', 'items-center'])
1365
+ */
1366
+ extractFromComputedCall(callExpr, context, attributeId, templatePosition, templateLength) {
1367
+ const { typescript } = context;
1368
+ // Check if this is a call to 'computed'
1369
+ const calleeExpr = callExpr.expression;
1370
+ if (!typescript.isIdentifier(calleeExpr) || calleeExpr.text !== 'computed') {
1371
+ return [];
1372
+ }
1373
+ // Get the callback argument
1374
+ if (callExpr.arguments.length === 0) {
1375
+ return [];
1376
+ }
1377
+ const callback = callExpr.arguments[0];
1378
+ // Handle arrow functions: computed(() => [...])
1379
+ if (typescript.isArrowFunction(callback)) {
1380
+ return this.extractFromFunctionBody(callback.body, context, attributeId, templatePosition, templateLength);
1381
+ }
1382
+ // Handle regular functions: computed(function() { return [...] })
1383
+ if (typescript.isFunctionExpression(callback)) {
1384
+ if (callback.body) {
1385
+ return this.extractFromFunctionBody(callback.body, context, attributeId, templatePosition, templateLength);
1386
+ }
1387
+ }
1388
+ return [];
1389
+ }
1390
+ /**
1391
+ * Extract classes from an inject() call with a default value.
1392
+ * Handles: const classes = inject('key', 'flex items-center')
1393
+ */
1394
+ extractFromInjectCall(callExpr, context, attributeId) {
1395
+ const { typescript } = context;
1396
+ // Check if this is a call to 'inject'
1397
+ const calleeExpr = callExpr.expression;
1398
+ if (!typescript.isIdentifier(calleeExpr) || calleeExpr.text !== 'inject') {
1399
+ return [];
1400
+ }
1401
+ // inject() needs at least 2 arguments for us to extract the default value
1402
+ // inject(key, defaultValue) or inject(key, defaultValue, treatDefaultAsFactory)
1403
+ if (callExpr.arguments.length < 2) {
1404
+ return [];
1405
+ }
1406
+ const defaultValue = callExpr.arguments[1];
1407
+ // Extract classes from the default value
1408
+ const classes = this.extractFromExpression(defaultValue, context, attributeId);
1409
+ return classes.map(c => ({ ...c, attributeId }));
1410
+ }
1411
+ /**
1412
+ * Extract classes from a function declaration's return statements.
1413
+ * Handles: function getClasses() { return ['flex', 'items-center']; }
1414
+ */
1415
+ extractFromFunctionDeclaration(funcDecl, context, attributeId, templatePosition, templateLength) {
1416
+ if (!funcDecl.body) {
1417
+ return [];
1418
+ }
1419
+ return this.extractFromFunctionBody(funcDecl.body, context, attributeId, templatePosition, templateLength);
1420
+ }
1421
+ /**
1422
+ * Extract classes from a function body (block or expression).
1423
+ * Handles both arrow function expressions and function blocks.
1424
+ */
1425
+ extractFromFunctionBody(body, context, attributeId, templatePosition, templateLength) {
1426
+ const { typescript } = context;
1427
+ const classNames = [];
1428
+ // If template position is provided, override positions for Volar mapping
1429
+ const processClasses = (classes) => {
1430
+ if (templatePosition !== undefined && templateLength !== undefined) {
1431
+ return classes.map(c => ({
1432
+ ...c,
1433
+ attributeId,
1434
+ absoluteStart: templatePosition,
1435
+ length: templateLength,
1436
+ line: context.sourceFile.getLineAndCharacterOfPosition(templatePosition).line + 1
1437
+ }));
1438
+ }
1439
+ return classes.map(c => ({ ...c, attributeId }));
1440
+ };
1441
+ // Handle concise arrow function: () => ['flex', 'items-center']
1442
+ if (!typescript.isBlock(body)) {
1443
+ // The body is an expression, extract classes from it
1444
+ classNames.push(...processClasses(this.extractFromExpression(body, context)));
1445
+ return classNames;
1446
+ }
1447
+ // Handle block body: look for return statements
1448
+ const visitNode = (node) => {
1449
+ if (typescript.isReturnStatement(node) && node.expression) {
1450
+ classNames.push(...processClasses(this.extractFromExpression(node.expression, context)));
1451
+ }
1452
+ typescript.forEachChild(node, visitNode);
1453
+ };
1454
+ typescript.forEachChild(body, visitNode);
1455
+ return classNames;
1456
+ }
1457
+ /**
1458
+ * Extract classes from an expression (array, object, string, etc.)
1459
+ * @param expr The expression to extract from
1460
+ * @param context The extraction context
1461
+ * @param attributeId Optional attribute ID for tracking
1462
+ */
1463
+ extractFromExpression(expr, context, attributeId) {
1464
+ const { typescript } = context;
1465
+ const classNames = [];
1466
+ // String literal: 'flex items-center'
1467
+ if (typescript.isStringLiteral(expr)) {
1468
+ const fullText = expr.text;
1469
+ if (fullText.length === 0) {
1470
+ return classNames;
1471
+ }
1472
+ const stringContentStart = expr.getStart() + 1;
1473
+ let offset = 0;
1474
+ const parts = fullText.split(/(\s+)/);
1475
+ for (const part of parts) {
1476
+ if (part && !/^\s+$/.test(part)) {
1477
+ classNames.push({
1478
+ className: part,
1479
+ absoluteStart: stringContentStart + offset,
1480
+ length: part.length,
1481
+ line: context.sourceFile.getLineAndCharacterOfPosition(stringContentStart + offset).line +
1482
+ 1,
1483
+ file: context.sourceFile.fileName
1484
+ });
1485
+ }
1486
+ offset += part.length;
1487
+ }
1488
+ return classNames;
1489
+ }
1490
+ // Array literal: ['flex', 'items-center', { 'bg-red-500': isActive }]
1491
+ if (typescript.isArrayLiteralExpression(expr)) {
1492
+ for (const element of expr.elements) {
1493
+ if (element === undefined)
1494
+ continue;
1495
+ if (typescript.isStringLiteral(element)) {
1496
+ classNames.push(...this.extractFromExpression(element, context, attributeId));
1497
+ }
1498
+ else if (typescript.isObjectLiteralExpression(element)) {
1499
+ classNames.push(...this.extractFromExpression(element, context, attributeId));
1500
+ }
1501
+ else if (typescript.isSpreadElement(element)) {
1502
+ classNames.push(...this.extractFromExpression(element.expression, context, attributeId));
1503
+ }
1504
+ // Handle __VLS_ctx.variable references in arrays
1505
+ else if (typescript.isPropertyAccessExpression(element)) {
1506
+ if (attributeId) {
1507
+ const vlsResults = this.extractFromVlsCtxReference(element, context, attributeId);
1508
+ if (vlsResults.length > 0) {
1509
+ classNames.push(...vlsResults);
1510
+ }
1511
+ }
1512
+ }
1513
+ // Handle parenthesized expressions: (__VLS_ctx.myVar)
1514
+ else if (typescript.isParenthesizedExpression(element)) {
1515
+ const inner = element.expression;
1516
+ if (typescript.isPropertyAccessExpression(inner) && attributeId) {
1517
+ const vlsResults = this.extractFromVlsCtxReference(inner, context, attributeId);
1518
+ if (vlsResults.length > 0) {
1519
+ classNames.push(...vlsResults);
1520
+ }
1521
+ }
1522
+ }
1523
+ // Handle nested arrays recursively
1524
+ else if (typescript.isArrayLiteralExpression(element)) {
1525
+ classNames.push(...this.extractFromExpression(element, context, attributeId));
1526
+ }
1527
+ }
1528
+ return classNames;
1529
+ }
1530
+ // Object literal: { 'flex': true, 'bg-red-500': isActive }
1531
+ if (typescript.isObjectLiteralExpression(expr)) {
1532
+ for (const prop of expr.properties) {
1533
+ if (typescript.isPropertyAssignment(prop)) {
1534
+ const propName = prop.name;
1535
+ let className;
1536
+ let start;
1537
+ if (typescript.isStringLiteral(propName)) {
1538
+ className = propName.text;
1539
+ start = propName.getStart() + 1;
1540
+ }
1541
+ else if (typescript.isIdentifier(propName)) {
1542
+ className = propName.text;
1543
+ start = propName.getStart();
1544
+ }
1545
+ // Handle computed property names: { [__VLS_ctx.myVar]: true }
1546
+ else if (typescript.isComputedPropertyName(propName)) {
1547
+ let computedExpr = propName.expression;
1548
+ // Unwrap parentheses: { [(__VLS_ctx.myVar)]: true }
1549
+ if (typescript.isParenthesizedExpression(computedExpr)) {
1550
+ computedExpr = computedExpr.expression;
1551
+ }
1552
+ // Resolve __VLS_ctx.variable pattern
1553
+ if (typescript.isPropertyAccessExpression(computedExpr) && attributeId) {
1554
+ const vlsResults = this.extractFromVlsCtxReference(computedExpr, context, attributeId);
1555
+ if (vlsResults.length > 0) {
1556
+ classNames.push(...vlsResults);
1557
+ continue;
1558
+ }
1559
+ }
1560
+ }
1561
+ if (className && start !== undefined) {
1562
+ classNames.push({
1563
+ className,
1564
+ absoluteStart: start,
1565
+ length: className.length,
1566
+ line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
1567
+ file: context.sourceFile.fileName,
1568
+ attributeId
1569
+ });
1570
+ }
1571
+ }
1572
+ else if (typescript.isShorthandPropertyAssignment(prop)) {
1573
+ const className = prop.name.text;
1574
+ const start = prop.name.getStart();
1575
+ classNames.push({
1576
+ className,
1577
+ absoluteStart: start,
1578
+ length: className.length,
1579
+ line: context.sourceFile.getLineAndCharacterOfPosition(start).line + 1,
1580
+ file: context.sourceFile.fileName,
1581
+ attributeId
1582
+ });
1583
+ }
1584
+ }
1585
+ return classNames;
1586
+ }
1587
+ // Handle conditional (ternary) expressions: isActive ? 'flex' : 'hidden'
1588
+ if (typescript.isConditionalExpression(expr)) {
1589
+ // Extract from both branches recursively
1590
+ classNames.push(...this.extractFromExpression(expr.whenTrue, context, attributeId));
1591
+ classNames.push(...this.extractFromExpression(expr.whenFalse, context, attributeId));
1592
+ return classNames;
1593
+ }
1594
+ // Handle binary expressions: isActive && 'flex', isDisabled || 'fallback'
1595
+ if (typescript.isBinaryExpression(expr)) {
1596
+ classNames.push(...this.extractFromExpression(expr.left, context, attributeId));
1597
+ classNames.push(...this.extractFromExpression(expr.right, context, attributeId));
1598
+ return classNames;
1599
+ }
1600
+ // Handle parenthesized expressions: ('flex items-center')
1601
+ if (typescript.isParenthesizedExpression(expr)) {
1602
+ return this.extractFromExpression(expr.expression, context, attributeId);
1603
+ }
1604
+ // Handle type assertions: 'flex' as string, 'flex' as const
1605
+ if (typescript.isAsExpression(expr)) {
1606
+ return this.extractFromExpression(expr.expression, context, attributeId);
1607
+ }
1608
+ // Handle non-null assertions: someValue!
1609
+ if (typescript.isNonNullExpression(expr)) {
1610
+ return this.extractFromExpression(expr.expression, context, attributeId);
1611
+ }
1612
+ // Handle template literals
1613
+ if (typescript.isTemplateExpression(expr) || typescript.isNoSubstitutionTemplateLiteral(expr)) {
1614
+ const addAttrId = (classes) => attributeId ? classes.map(c => ({ ...c, attributeId })) : classes;
1615
+ return addAttrId(this.expressionExtractor.extract(expr, context));
1616
+ }
1617
+ return classNames;
1618
+ }
1619
+ /**
1620
+ * Extract classes from props.propertyName patterns with default values.
1621
+ * Vue generates __VLS_defaults for withDefaults() calls.
1622
+ *
1623
+ * Generated code pattern:
1624
+ * const __VLS_defaults = { buttonClass: 'flex items-center' };
1625
+ * ...{ class: (props.buttonClass) }
1626
+ */
1627
+ extractFromPropsWithDefaults(value, context, attributeId) {
1628
+ const { typescript, typeChecker } = context;
1629
+ if (!typeChecker) {
1630
+ return [];
1631
+ }
1632
+ // Unwrap parentheses: (props.buttonClass) -> props.buttonClass
1633
+ let expr = value;
1634
+ if (typescript.isParenthesizedExpression(expr)) {
1635
+ expr = expr.expression;
1636
+ }
1637
+ // Check for props.propertyName pattern
1638
+ if (!typescript.isPropertyAccessExpression(expr)) {
1639
+ return [];
1640
+ }
1641
+ const objectExpr = expr.expression;
1642
+ if (!typescript.isIdentifier(objectExpr) || objectExpr.text !== 'props') {
1643
+ return [];
1644
+ }
1645
+ const propertyName = expr.name;
1646
+ if (!typescript.isIdentifier(propertyName)) {
1647
+ return [];
1648
+ }
1649
+ // Look for __VLS_defaults in the source file
1650
+ const defaultsValue = this.findVlsDefaultsProperty(propertyName.text, context);
1651
+ if (defaultsValue) {
1652
+ return this.extractFromExpression(defaultsValue, context, attributeId);
1653
+ }
1654
+ return [];
1655
+ }
1656
+ /**
1657
+ * Find a property value in __VLS_defaults object.
1658
+ */
1659
+ findVlsDefaultsProperty(propertyName, context) {
1660
+ const { typescript, sourceFile } = context;
1661
+ // Walk through the source file to find __VLS_defaults
1662
+ let result;
1663
+ const visitor = (node) => {
1664
+ if (result)
1665
+ return;
1666
+ if (typescript.isVariableDeclaration(node)) {
1667
+ const name = node.name;
1668
+ if (typescript.isIdentifier(name) &&
1669
+ name.text === '__VLS_defaults' &&
1670
+ node.initializer &&
1671
+ typescript.isObjectLiteralExpression(node.initializer)) {
1672
+ // Found __VLS_defaults, look for the property
1673
+ for (const prop of node.initializer.properties) {
1674
+ if (typescript.isPropertyAssignment(prop)) {
1675
+ const propName = prop.name;
1676
+ if (typescript.isIdentifier(propName) && propName.text === propertyName) {
1677
+ result = prop.initializer;
1678
+ return;
1679
+ }
1680
+ }
1681
+ }
1682
+ }
1683
+ }
1684
+ typescript.forEachChild(node, visitor);
1685
+ };
1686
+ typescript.forEachChild(sourceFile, visitor);
1687
+ return result;
1688
+ }
1689
+ }
1690
+ exports.VueAttributeExtractor = VueAttributeExtractor;
1691
+ //# sourceMappingURL=VueAttributeExtractor.js.map