@vune-ui/compiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1316 @@
1
+ import * as ts from "typescript";
2
+ import { parseVuneBuilder, parseVuneStructs, lowerVuneBuilderAst } from "./ast.js";
3
+ import { findBuilder, findRawHtml, identifierAt, matching, regexCanStart, skipComment, skipRegex, skipString, skipTrivia, splitStatements, splitTopLevel, syntaxError, topLevelColon, } from "./scanner.js";
4
+ import * as Core from "@vune-ui/core";
5
+ import { resolveSemanticCall } from "@vune-ui/core";
6
+ import { lowerStaticImportedCalls, lowerStaticModifierChains, staticModifierNames } from "./specialization.js";
7
+ const nonBindingDollarNames = new Set([
8
+ "attrs", "data", "emit", "el", "forceUpdate", "nextTick", "options", "parent", "props", "refs", "root", "slots", "watch",
9
+ ]);
10
+ /** Compiler-facing view metadata is read from the same ViewType as runtime. */
11
+ const canonicalInitializerSymbols = new Map();
12
+ for (const [name, value] of Object.entries(Core)) {
13
+ if (typeof value !== "function")
14
+ continue;
15
+ const viewType = value.viewType;
16
+ if (viewType?.name && viewType.semanticSymbol)
17
+ canonicalInitializerSymbols.set(name, viewType.semanticSymbol.initializers);
18
+ }
19
+ function symbolsForCall(call, registry = canonicalInitializerSymbols) {
20
+ return registry.get(call.callee);
21
+ }
22
+ class VuneInitializerSyntaxError extends SyntaxError {
23
+ code = "VUNE_INITIALIZER";
24
+ offset;
25
+ constructor(message, offset) {
26
+ super(message);
27
+ this.name = "VuneInitializerSyntaxError";
28
+ this.offset = offset;
29
+ }
30
+ }
31
+ function buttonInitializerMessage(call) {
32
+ const labels = call.arguments.flatMap(argument => argument.label ? [argument.label] : []);
33
+ if (labels.includes("label") && labels.includes("action") && labels.indexOf("label") < labels.indexOf("action")) {
34
+ return "Button arguments must follow declaration order: action:, label:.";
35
+ }
36
+ if (call.trailing && call.arguments[0]?.label === "action") {
37
+ return "Button's custom-label initializer requires:\nButton(action: { ... }, label: { ... })";
38
+ }
39
+ return "Button requires a text label before its trailing action.\nUse:\nButton(\"Save\") { ... }";
40
+ }
41
+ function knownCallArguments(call) {
42
+ const arguments_ = call.arguments.flatMap((argument, argumentIndex) => {
43
+ if (argument.value.kind === "closure")
44
+ return [{ label: argument.label, type: "function" }];
45
+ if (call.callee === "ForEach" && argumentIndex === 1 && /^\{\s*(?:id|key)\s*:/.test(argument.value.source) && /=>/.test(argument.value.source)) {
46
+ return [{ label: "key", type: "function" }];
47
+ }
48
+ const named = /^namedArguments\s*\(\s*\{([\s\S]*)\}\s*\)$/.exec(argument.value.source.trim());
49
+ if (named)
50
+ return splitTopLevel(named[1]).map(value => compilerSemanticArgument(value));
51
+ return [compilerSemanticArgument(argument.label ? `${argument.label}: ${argument.value.source}` : argument.value.source)];
52
+ });
53
+ return call.trailing ? [...arguments_, { type: "function", trailing: true }] : arguments_;
54
+ }
55
+ function canDeferDynamicButton(call, arguments_) {
56
+ if (!arguments_.some(argument => argument.type === "unknown"))
57
+ return false;
58
+ if (call.trailing)
59
+ return call.arguments.length === 1 && !call.arguments[0]?.label;
60
+ const labels = call.arguments.map(argument => argument.label);
61
+ return labels.length === 2 && labels[0] === "action" && labels[1] === "label";
62
+ }
63
+ function resolveKnownCall(call, registry = canonicalInitializerSymbols) {
64
+ const symbols = symbolsForCall(call, registry);
65
+ if (!symbols)
66
+ return undefined;
67
+ const viewType = {
68
+ kind: "view",
69
+ name: call.callee,
70
+ qualifiedName: call.callee,
71
+ initializers: symbols,
72
+ fields: [],
73
+ };
74
+ const arguments_ = knownCallArguments(call);
75
+ const result = resolveSemanticCall(viewType, arguments_);
76
+ if (!result.resolvedInitializer) {
77
+ if (call.callee === "Button" && canDeferDynamicButton(call, arguments_))
78
+ return undefined;
79
+ if (call.callee === "Button")
80
+ throw new VuneInitializerSyntaxError(buttonInitializerMessage(call), call.range.start);
81
+ if (call.trailing && canonicalInitializerSymbols.get(call.callee) !== symbols) {
82
+ throw new VuneInitializerSyntaxError(result.diagnostics[0]?.message ?? `No matching initializer for ${call.callee}.`, call.range.start);
83
+ }
84
+ return undefined;
85
+ }
86
+ return { symbols, initializerIndex: result.resolvedInitializer.index, resolution: result };
87
+ }
88
+ function validateKnownCalls(program, registry = canonicalInitializerSymbols) {
89
+ const visit = (node) => {
90
+ if (node.kind === "call") {
91
+ if (node.callee === "Button" || node.trailing)
92
+ resolveKnownCall(node, registry);
93
+ for (const argument of node.arguments)
94
+ if (argument.value.kind === "closure")
95
+ validateKnownCalls(argument.value.body, registry);
96
+ if (node.trailing)
97
+ validateKnownCalls(node.trailing.body, registry);
98
+ return;
99
+ }
100
+ if (node.kind === "conditional") {
101
+ validateKnownCalls(node.then, registry);
102
+ if (node.otherwise) {
103
+ if (node.otherwise.kind === "conditional")
104
+ validateKnownCalls({ ...node.then, statements: [node.otherwise] }, registry);
105
+ else
106
+ validateKnownCalls(node.otherwise, registry);
107
+ }
108
+ }
109
+ };
110
+ for (const node of program.statements)
111
+ visit(node);
112
+ }
113
+ function validateKnownTypeScriptCalls(source, registry = canonicalInitializerSymbols) {
114
+ const file = ts.createSourceFile("vune-call-validation.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
115
+ const visit = (node) => {
116
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && registry.has(node.expression.text)) {
117
+ // The Vune scanner owns trailing closures. TypeScript sees the call
118
+ // prefix as a complete call, so leave that shape to validateKnownCalls.
119
+ const callText = source.slice(node.expression.end, node.end);
120
+ const hasVuneLabels = /(?:^|,)\s*[A-Za-z_$][A-Za-z0-9_$]*\s*:/.test(callText);
121
+ if (!hasVuneLabels && source[skipTrivia(source, node.end)] !== "{" && node.expression.text === "Button") {
122
+ const callSource = `${node.expression.text}(${node.arguments.map(argument => argument.getText(file)).join(", ")})`;
123
+ const parsed = parseVuneBuilder(callSource, node.expression.getStart(file)).statements[0];
124
+ if (parsed?.kind === "call")
125
+ resolveKnownCall(parsed, registry);
126
+ }
127
+ }
128
+ ts.forEachChild(node, visit);
129
+ };
130
+ visit(file);
131
+ }
132
+ function closureRoleForKnownCall(call, context, registry = canonicalInitializerSymbols) {
133
+ const resolved = resolveKnownCall(call, registry);
134
+ const resolvedArgumentIndex = context.position === "trailing"
135
+ ? call.arguments.length
136
+ : context.label
137
+ ? call.arguments.findIndex(argument => argument.label === context.label)
138
+ : context.argumentIndex ?? 0;
139
+ const sharedRole = resolved?.resolution.closureRoles[resolvedArgumentIndex];
140
+ if (sharedRole)
141
+ return sharedRole === "binding" ? undefined : sharedRole;
142
+ const symbols = resolved?.symbols ?? symbolsForCall(call, registry) ?? [];
143
+ const parameters = resolved?.symbols[resolved.initializerIndex]?.parameters
144
+ ?? (context.position === "trailing"
145
+ ? symbols.find(symbol => symbol.parameters.at(-1)?.trailing)?.parameters
146
+ : context.label
147
+ ? symbols.find(symbol => symbol.parameters.some(parameter => parameter.label === context.label))?.parameters
148
+ : undefined)
149
+ ?? [];
150
+ const role = (kind) => kind === "binding" ? undefined : kind;
151
+ if (context.position === "trailing")
152
+ return role(parameters.at(-1)?.kind);
153
+ if (context.label)
154
+ return role(parameters.find(parameter => parameter.label === context.label)?.kind);
155
+ const index = context.argumentIndex ?? 0;
156
+ let positional = 0;
157
+ for (const argument of call.arguments.slice(0, index))
158
+ if (!argument.label)
159
+ positional += 1;
160
+ return role(parameters[positional]?.kind);
161
+ }
162
+ function isIdentifierDeclaration(node) {
163
+ const parent = node.parent;
164
+ if (ts.isVariableDeclaration(parent) && parent.name === node)
165
+ return true;
166
+ if (ts.isParameter(parent) && parent.name === node)
167
+ return true;
168
+ if (ts.isBindingElement(parent) && parent.name === node)
169
+ return true;
170
+ if (ts.isFunctionDeclaration(parent) && parent.name === node)
171
+ return true;
172
+ if (ts.isClassDeclaration(parent) && parent.name === node)
173
+ return true;
174
+ if (ts.isImportClause(parent) && parent.name === node)
175
+ return true;
176
+ if (ts.isImportSpecifier(parent) && parent.name === node)
177
+ return true;
178
+ if (ts.isNamespaceImport(parent) && parent.name === node)
179
+ return true;
180
+ if (ts.isExportSpecifier(parent) && parent.name === node)
181
+ return true;
182
+ return false;
183
+ }
184
+ function isBindingShorthandIdentifier(node) {
185
+ if (!node.text.startsWith("$") || node.text.length === 1)
186
+ return false;
187
+ if (nonBindingDollarNames.has(node.text.slice(1)) || isIdentifierDeclaration(node))
188
+ return false;
189
+ const parent = node.parent;
190
+ if (ts.isPropertyAccessExpression(parent) && (parent.expression === node || parent.name === node))
191
+ return false;
192
+ if (ts.isElementAccessExpression(parent) && parent.expression === node)
193
+ return false;
194
+ if (ts.isPropertyAssignment(parent) && parent.name === node)
195
+ return false;
196
+ if (ts.isMethodDeclaration(parent) && parent.name === node)
197
+ return false;
198
+ if (ts.isPropertyDeclaration(parent) && parent.name === node)
199
+ return false;
200
+ if (ts.isMethodSignature(parent) && parent.name === node)
201
+ return false;
202
+ if (ts.isPropertySignature(parent) && parent.name === node)
203
+ return false;
204
+ if (ts.isShorthandPropertyAssignment(parent))
205
+ return false;
206
+ return true;
207
+ }
208
+ /**
209
+ * Lower only actual identifier nodes. The source is intentionally edited by
210
+ * span so the rest of Vune's syntax lowering keeps its original formatting.
211
+ * This prevents member properties, declarations, strings, comments, regexes,
212
+ * and identifiers containing `$` from being mistaken for projections.
213
+ */
214
+ function lowerShorthand(source) {
215
+ const file = ts.createSourceFile("vune-shorthand.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
216
+ const edits = [];
217
+ const visit = (node) => {
218
+ if (ts.isIdentifier(node) && isBindingShorthandIdentifier(node)) {
219
+ edits.push({ start: node.getStart(file), end: node.end, replacement: `Binding(${node.text.slice(1)})` });
220
+ }
221
+ ts.forEachChild(node, visit);
222
+ };
223
+ visit(file);
224
+ let result = source;
225
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
226
+ result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
227
+ }
228
+ return result;
229
+ }
230
+ function containsAwaitKeyword(source) {
231
+ for (let cursor = 0; cursor < source.length; cursor += 1) {
232
+ const character = source[cursor];
233
+ if (character === "\"" || character === "'" || character === "`") {
234
+ cursor = skipString(source, cursor) - 1;
235
+ continue;
236
+ }
237
+ if (character === "/" && (source[cursor + 1] === "/" || source[cursor + 1] === "*")) {
238
+ cursor = skipComment(source, cursor) - 1;
239
+ continue;
240
+ }
241
+ if (character === "/" && regexCanStart(source, cursor)) {
242
+ cursor = skipRegex(source, cursor) - 1;
243
+ continue;
244
+ }
245
+ const identifier = identifierAt(source, cursor);
246
+ if (!identifier)
247
+ continue;
248
+ if (identifier.name === "await")
249
+ return true;
250
+ cursor = identifier.end - 1;
251
+ }
252
+ return false;
253
+ }
254
+ function isViewBuilderExpression(expression) {
255
+ if (ts.isParenthesizedExpression(expression)
256
+ || ts.isAsExpression(expression)
257
+ || ts.isTypeAssertionExpression(expression)
258
+ || ts.isNonNullExpression(expression)
259
+ || ts.isSatisfiesExpression(expression)
260
+ || ts.isAwaitExpression(expression)) {
261
+ return isViewBuilderExpression(expression.expression);
262
+ }
263
+ if (ts.isConditionalExpression(expression)) {
264
+ return isViewBuilderExpression(expression.whenTrue) || isViewBuilderExpression(expression.whenFalse);
265
+ }
266
+ if (ts.isBinaryExpression(expression)) {
267
+ const operator = expression.operatorToken.kind;
268
+ if (operator === ts.SyntaxKind.AmpersandAmpersandToken
269
+ || operator === ts.SyntaxKind.BarBarToken
270
+ || operator === ts.SyntaxKind.QuestionQuestionToken) {
271
+ return isViewBuilderExpression(expression.left) || isViewBuilderExpression(expression.right);
272
+ }
273
+ return false;
274
+ }
275
+ if (ts.isArrayLiteralExpression(expression))
276
+ return expression.elements.some(item => ts.isExpression(item) && isViewBuilderExpression(item));
277
+ if (ts.isCallExpression(expression) || ts.isNewExpression(expression))
278
+ return true;
279
+ if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression))
280
+ return true;
281
+ return false;
282
+ }
283
+ function lowerViewBuilderAstStatements(source, registry, childrenName) {
284
+ // First lower nested Vune-only expressions (trailing closures, raw HTML,
285
+ // binding shorthand) so the statement tree is valid TypeScript. Then let
286
+ // TypeScript own control-flow parsing instead of maintaining a second,
287
+ // incomplete statement grammar in Vune.
288
+ const lowered = lowerRange(source, registry);
289
+ const wrapper = `function __vune_builder__() {\n${lowered}\n}`;
290
+ const file = ts.createSourceFile("vune-view-builder.ts", wrapper, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
291
+ const fn = file.statements.find(ts.isFunctionDeclaration);
292
+ if (!fn?.body)
293
+ return lowered;
294
+ const factory = ts.factory;
295
+ const pushExpression = (expression) => factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(childrenName), "push"), undefined, [expression]));
296
+ const transformStatement = (statement) => {
297
+ if (ts.isExpressionStatement(statement)) {
298
+ return isViewBuilderExpression(statement.expression) ? pushExpression(statement.expression) : statement;
299
+ }
300
+ if (ts.isBlock(statement))
301
+ return factory.updateBlock(statement, statement.statements.map(transformStatement));
302
+ if (ts.isIfStatement(statement)) {
303
+ return factory.updateIfStatement(statement, statement.expression, transformStatement(statement.thenStatement), statement.elseStatement ? transformStatement(statement.elseStatement) : undefined);
304
+ }
305
+ if (ts.isForStatement(statement)) {
306
+ return factory.updateForStatement(statement, statement.initializer, statement.condition, statement.incrementor, transformStatement(statement.statement));
307
+ }
308
+ if (ts.isForInStatement(statement)) {
309
+ return factory.updateForInStatement(statement, statement.initializer, statement.expression, transformStatement(statement.statement));
310
+ }
311
+ if (ts.isForOfStatement(statement)) {
312
+ return factory.updateForOfStatement(statement, statement.awaitModifier, statement.initializer, statement.expression, transformStatement(statement.statement));
313
+ }
314
+ if (ts.isWhileStatement(statement))
315
+ return factory.updateWhileStatement(statement, statement.expression, transformStatement(statement.statement));
316
+ if (ts.isDoStatement(statement))
317
+ return factory.updateDoStatement(statement, transformStatement(statement.statement), statement.expression);
318
+ if (ts.isSwitchStatement(statement)) {
319
+ const clauses = statement.caseBlock.clauses.map(clause => ts.isCaseClause(clause)
320
+ ? factory.updateCaseClause(clause, clause.expression, clause.statements.map(transformStatement))
321
+ : factory.updateDefaultClause(clause, clause.statements.map(transformStatement)));
322
+ return factory.updateSwitchStatement(statement, statement.expression, factory.updateCaseBlock(statement.caseBlock, clauses));
323
+ }
324
+ if (ts.isTryStatement(statement)) {
325
+ const tryBlock = factory.updateBlock(statement.tryBlock, statement.tryBlock.statements.map(transformStatement));
326
+ const catchClause = statement.catchClause
327
+ ? factory.updateCatchClause(statement.catchClause, statement.catchClause.variableDeclaration, factory.updateBlock(statement.catchClause.block, statement.catchClause.block.statements.map(transformStatement)))
328
+ : undefined;
329
+ const finallyBlock = statement.finallyBlock
330
+ ? factory.updateBlock(statement.finallyBlock, statement.finallyBlock.statements.map(transformStatement))
331
+ : undefined;
332
+ return factory.updateTryStatement(statement, tryBlock, catchClause, finallyBlock);
333
+ }
334
+ if (ts.isLabeledStatement(statement))
335
+ return factory.updateLabeledStatement(statement, statement.label, transformStatement(statement.statement));
336
+ if (ts.isWithStatement(statement))
337
+ return factory.updateWithStatement(statement, statement.expression, transformStatement(statement.statement));
338
+ // Function/class declarations intentionally remain opaque. A Text() call
339
+ // inside a helper function is not a child of the surrounding ViewBuilder.
340
+ return statement;
341
+ };
342
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, removeComments: false });
343
+ return fn.body.statements
344
+ .map(transformStatement)
345
+ .map(statement => printer.printNode(ts.EmitHint.Unspecified, statement, file))
346
+ .join("\n");
347
+ }
348
+ function needsStatementAwareViewBody(source) {
349
+ return /\b(?:const|let|var|return|throw|if|else|switch|case|for|while|do|try|catch|finally|break|continue|debugger)\b/.test(source);
350
+ }
351
+ function lowerViewBuilderClosure(body, parameter, registry) {
352
+ if (!needsStatementAwareViewBody(body)) {
353
+ const lowered = lowerVuneBuilderAst(parseVuneBuilder(body), {
354
+ transformRaw: value => lowerRange(value, registry),
355
+ transformArgument: (value, call, argumentIndex) => lowerForEachIdentityOptions(value, call, argumentIndex, registry),
356
+ closure: (nestedBody, nestedParameter, nestedRole) => lowerAstClosure(nestedBody, nestedParameter, nestedRole, registry),
357
+ closureRole: (nestedCall, context) => closureRoleForKnownCall(nestedCall, context, registry),
358
+ }).join(", ");
359
+ return `${parameter ? `(${parameter})` : "()"} => [${lowered}]`;
360
+ }
361
+ const prefix = parameter ? `(${parameter})` : "()";
362
+ let childrenName = "__vuneChildren";
363
+ let suffix = 0;
364
+ while (new RegExp(`\\b${childrenName}\\b`).test(body))
365
+ childrenName = `__vuneChildren${++suffix}`;
366
+ const statements = lowerViewBuilderAstStatements(body, registry, childrenName);
367
+ return `${prefix} => { const ${childrenName} = []; ${statements} return ${childrenName}; }`;
368
+ }
369
+ function lowerClosure(value, role, registry = canonicalInitializerSymbols) {
370
+ const source = value.trim();
371
+ if (!source.startsWith("{") || matching(source, 0, "{", "}") !== source.length - 1)
372
+ return lowerRange(source, registry);
373
+ const body = source.slice(1, -1).trim();
374
+ if (role === "viewBuilder")
375
+ return lowerViewBuilderClosure(body, undefined, registry);
376
+ const lowered = lowerStatements(body, registry);
377
+ const asynchronous = containsAwaitKeyword(body);
378
+ if (role === "action")
379
+ return `${asynchronous ? "async " : ""}() => {${lowerRange(body, registry)}}`;
380
+ const action = asynchronous || /\b(const|let|var|return|throw)\b/.test(body);
381
+ const builder = action ? "() => []" : `() => [${lowered}]`;
382
+ return `overloadClosure(${builder}, ${asynchronous ? "async " : ""}() => {${lowerRange(body, registry)}})`;
383
+ }
384
+ function lowerArguments(source, calleeName, registry = canonicalInitializerSymbols) {
385
+ const parsed = calleeName ? parseVuneBuilder(`${calleeName}(${source})`).statements[0] : undefined;
386
+ const call = parsed?.kind === "call" ? parsed : undefined;
387
+ if (call && (call.callee === "Button" || call.trailing))
388
+ resolveKnownCall(call, registry);
389
+ const positional = [];
390
+ const named = [];
391
+ for (const [argumentIndex, argument] of splitTopLevel(source).entries()) {
392
+ const colon = topLevelColon(argument);
393
+ const labelStart = skipTrivia(argument, 0);
394
+ if (colon >= 0 && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(argument.slice(labelStart, colon).trim())) {
395
+ const label = argument.slice(labelStart, colon).trim();
396
+ const role = call ? closureRoleForKnownCall(call, { position: "argument", argumentIndex, label }, registry) : undefined;
397
+ named.push(`${label}: ${lowerClosureOrExpression(argument.slice(colon + 1), role, registry)}`);
398
+ }
399
+ else {
400
+ const role = call ? closureRoleForKnownCall(call, { position: "argument", argumentIndex }, registry) : undefined;
401
+ positional.push(lowerClosureOrExpression(argument, role, registry));
402
+ }
403
+ }
404
+ if (named.length === 0)
405
+ return positional.join(", ");
406
+ return [...positional, `namedArguments({ ${named.join(", ")} })`].join(", ");
407
+ }
408
+ function lowerClosureOrExpression(source, role, registry = canonicalInitializerSymbols) {
409
+ const value = source.trim();
410
+ if (value.startsWith("{") && matching(value, 0, "{", "}") === value.length - 1 && !/^(?:\s*(?:[A-Za-z_$][A-Za-z0-9_$]*|["'][^"']*["']|-?\d+(?:\.\d+)?)\s*:)/.test(value.slice(1, -1)))
411
+ return lowerClosure(value, role, registry);
412
+ return lowerShorthand(lowerRange(value, registry));
413
+ }
414
+ function lowerForEachIdentityOptions(source, call, argumentIndex, registry) {
415
+ const value = source.trim();
416
+ if (call.callee !== "ForEach" || argumentIndex !== 1 || !value.startsWith("{") || matching(value, 0, "{", "}") !== value.length - 1) {
417
+ return lowerRange(source, registry);
418
+ }
419
+ const entries = splitTopLevel(value.slice(1, -1)).map(entry => {
420
+ const colon = topLevelColon(entry);
421
+ return colon < 0 ? undefined : { name: entry.slice(0, colon).trim(), value: entry.slice(colon + 1).trim() };
422
+ }).filter((entry) => entry !== undefined);
423
+ const identity = entries.find(entry => entry.name === "id" || entry.name === "key");
424
+ if (!identity)
425
+ return lowerRange(source, registry);
426
+ return `namedArguments({ key: ${lowerRange(identity.value, registry)} })`;
427
+ }
428
+ function lowerConditional(source, registry = canonicalInitializerSymbols) {
429
+ const match = /^if\s*\(/.exec(source);
430
+ if (!match)
431
+ return undefined;
432
+ const open = source.indexOf("(", match.index + match[0].length - 1);
433
+ const close = matching(source, open, "(", ")");
434
+ const thenOpen = skipTrivia(source, close + 1);
435
+ if (source[thenOpen] !== "{")
436
+ return undefined;
437
+ const thenClose = matching(source, thenOpen, "{", "}");
438
+ const afterThen = skipTrivia(source, thenClose + 1);
439
+ const condition = source.slice(open + 1, close).trim();
440
+ const thenValue = `[${lowerStatements(source.slice(thenOpen + 1, thenClose), registry)}]`;
441
+ if (source.slice(afterThen, afterThen + 4) !== "else")
442
+ return `(${lowerShorthand(condition)} ? ${thenValue} : [])`;
443
+ const elseOpen = skipTrivia(source, afterThen + 4);
444
+ if (source[elseOpen] !== "{")
445
+ return undefined;
446
+ const elseClose = matching(source, elseOpen, "{", "}");
447
+ return `(${lowerShorthand(condition)} ? ${thenValue} : [${lowerStatements(source.slice(elseOpen + 1, elseClose), registry)}])`;
448
+ }
449
+ function lowerStatements(source, registry = canonicalInitializerSymbols) {
450
+ const values = [];
451
+ for (const statement of splitStatements(source)) {
452
+ const conditional = lowerConditional(statement, registry);
453
+ if (conditional)
454
+ values.push(conditional);
455
+ else if (/^\s*(const|let|var|return|throw)\b/.test(statement))
456
+ continue;
457
+ else
458
+ values.push(lowerRange(statement, registry));
459
+ }
460
+ return values.join(", ");
461
+ }
462
+ function lowerAstClosure(body, parameter, role, registry = canonicalInitializerSymbols) {
463
+ if (role === "viewBuilder")
464
+ return lowerViewBuilderClosure(body, parameter, registry);
465
+ const parsed = parseVuneBuilder(body);
466
+ const lowered = lowerVuneBuilderAst(parsed, {
467
+ transformRaw: value => lowerRange(value, registry),
468
+ transformArgument: (value, call, argumentIndex) => lowerForEachIdentityOptions(value, call, argumentIndex, registry),
469
+ closure: (nestedBody, nestedParameter, nestedRole) => lowerAstClosure(nestedBody, nestedParameter, nestedRole, registry),
470
+ closureRole: (nestedCall, context) => closureRoleForKnownCall(nestedCall, context, registry),
471
+ }).join(", ");
472
+ if (parameter)
473
+ return `(${parameter}) => [${lowered}]`;
474
+ const asynchronous = containsAwaitKeyword(body);
475
+ if (role === "action")
476
+ return `${asynchronous ? "async " : ""}() => {${lowerRange(body, registry)}}`;
477
+ const action = asynchronous || /\b(const|let|var|return|throw)\b/.test(body);
478
+ const builder = action ? "() => []" : `() => [${lowered}]`;
479
+ return `overloadClosure(${builder}, ${asynchronous ? "async " : ""}() => {${lowerRange(body, registry)}})`;
480
+ }
481
+ function lowerBuilder(call, source, registry = canonicalInitializerSymbols) {
482
+ const parsed = parseVuneBuilder(source.slice(call.start, call.end), call.start);
483
+ const lowered = lowerVuneBuilderAst(parsed, {
484
+ transformRaw: value => lowerRange(value, registry),
485
+ transformArgument: (value, call, argumentIndex) => lowerForEachIdentityOptions(value, call, argumentIndex, registry),
486
+ closure: (body, parameter, role) => lowerAstClosure(body, parameter, role, registry),
487
+ closureRole: (nestedCall, context) => closureRoleForKnownCall(nestedCall, context, registry),
488
+ });
489
+ if (lowered.length === 1)
490
+ return lowered[0];
491
+ return `${call.name}(${lowerArguments(call.argumentSource, call.name, registry)})`;
492
+ }
493
+ function lowerRange(source, registry = canonicalInitializerSymbols) {
494
+ let output = "";
495
+ let cursor = 0;
496
+ let iterations = 0;
497
+ while (cursor < source.length) {
498
+ if (++iterations > source.length + 1)
499
+ throw syntaxError("Vune lowering did not advance past a builder expression", cursor);
500
+ const call = findBuilder(source, cursor);
501
+ const html = findRawHtml(source, cursor, value => lowerRange(value, registry));
502
+ if (!call && !html)
503
+ break;
504
+ if (html && (!call || html.start < call.start)) {
505
+ output += lowerShorthand(source.slice(cursor, html.start));
506
+ output += html.code;
507
+ cursor = html.end;
508
+ continue;
509
+ }
510
+ output += lowerShorthand(source.slice(cursor, call.start));
511
+ output += lowerBuilder(call, source, registry);
512
+ cursor = call.end;
513
+ }
514
+ output += lowerShorthand(source.slice(cursor));
515
+ return output;
516
+ }
517
+ function structParameter(source) {
518
+ const kind = source.includes("@ViewBuilder") ? "viewBuilder" : source.includes("@Action") ? "action" : source.includes("@Binding") ? "binding" : "value";
519
+ const clean = source.replace(/@(?:ViewBuilder|Action|Binding)\s*/g, "").trim();
520
+ const defaultIndex = topLevelEquals(clean);
521
+ const declaration = defaultIndex < 0 ? clean : clean.slice(0, defaultIndex).trim();
522
+ const defaultValue = defaultIndex < 0 ? undefined : clean.slice(defaultIndex + 1).trim();
523
+ const colon = topLevelColon(declaration);
524
+ const head = (colon < 0 ? declaration : declaration.slice(0, colon)).trim();
525
+ const words = head.split(/\s+/).filter(Boolean);
526
+ const name = words[words.length - 1]?.replace(/^_+/, "");
527
+ if (!name)
528
+ throw new SyntaxError(`Invalid struct initializer parameter: ${source}`);
529
+ return {
530
+ name,
531
+ label: words[0] === "_" ? undefined : words[0],
532
+ kind,
533
+ required: defaultIndex < 0,
534
+ defaultValue,
535
+ type: colon < 0 ? undefined : declaration.slice(colon + 1).trim(),
536
+ };
537
+ }
538
+ function topLevelEquals(source) {
539
+ let parens = 0;
540
+ let brackets = 0;
541
+ let braces = 0;
542
+ for (let cursor = 0; cursor < source.length; cursor += 1) {
543
+ const character = source[cursor];
544
+ if (character === "\"" || character === "'" || character === "`") {
545
+ cursor = skipString(source, cursor) - 1;
546
+ continue;
547
+ }
548
+ if (character === "(")
549
+ parens += 1;
550
+ else if (character === ")")
551
+ parens -= 1;
552
+ else if (character === "[")
553
+ brackets += 1;
554
+ else if (character === "]")
555
+ brackets -= 1;
556
+ else if (character === "{")
557
+ braces += 1;
558
+ else if (character === "}")
559
+ braces -= 1;
560
+ else if (character === "=" && parens === 0 && brackets === 0 && braces === 0 && source[cursor + 1] !== ">")
561
+ return cursor;
562
+ }
563
+ return -1;
564
+ }
565
+ function structInitializerPlans(declaration) {
566
+ const fields = declaration.fields.map(field => ({
567
+ name: field.name,
568
+ kind: field.kind === "state" ? "state" : field.kind === "binding" ? "binding" : "value",
569
+ type: field.type,
570
+ defaultValue: field.initializer,
571
+ }));
572
+ return declaration.initializers.length > 0
573
+ ? declaration.initializers.map(item => structInitializerPlan(item.parametersSource, item.bodySource))
574
+ : [structInitializerPlan(fields.filter(field => field.kind !== "state").map(field => `${field.name}: unknown${field.defaultValue === undefined ? "" : ` = ${field.defaultValue}`}`).join(", "), "")];
575
+ }
576
+ function structInitializerPlan(parameterSource, bodySource) {
577
+ const parsedParameters = splitTopLevel(parameterSource).filter(Boolean).map(structParameter);
578
+ const parameters = parsedParameters.map((parameter, index) => ({
579
+ ...parameter,
580
+ trailing: index === parsedParameters.length - 1 && (parameter.kind === "viewBuilder" || parameter.kind === "action"),
581
+ labelRequired: parameter.label !== undefined && !(index === parsedParameters.length - 1 && (parameter.kind === "viewBuilder" || parameter.kind === "action")),
582
+ }));
583
+ const assignments = new Map();
584
+ for (const match of bodySource.matchAll(/self\.([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*([^;\n]+)/g))
585
+ assignments.set(match[1], match[2].trim());
586
+ const delegationMatch = /\bself\.init\s*\(/.exec(bodySource);
587
+ if (!delegationMatch)
588
+ return { parameters, assignments };
589
+ const open = bodySource.indexOf("(", delegationMatch.index);
590
+ const close = matching(bodySource, open, "(", ")");
591
+ return { parameters, assignments, delegation: splitTopLevel(bodySource.slice(open + 1, close)).filter(Boolean) };
592
+ }
593
+ function structArgument(source) {
594
+ const colon = topLevelColon(source);
595
+ if (colon < 0)
596
+ return { value: source.trim() };
597
+ return { label: source.slice(0, colon).trim(), value: source.slice(colon + 1).trim() };
598
+ }
599
+ function delegatedParameterValues(parameters, arguments_) {
600
+ const values = new Map();
601
+ const used = new Set();
602
+ let nextPositional = 0;
603
+ for (const source of arguments_) {
604
+ const argument = structArgument(source);
605
+ let index = argument.label === undefined
606
+ ? (() => {
607
+ while (used.has(nextPositional))
608
+ nextPositional += 1;
609
+ return nextPositional;
610
+ })()
611
+ : parameters.findIndex(parameter => parameter.label === argument.label || parameter.name === argument.label);
612
+ if (index < 0 || index >= parameters.length || used.has(index))
613
+ return undefined;
614
+ used.add(index);
615
+ if (argument.label === undefined)
616
+ nextPositional = index + 1;
617
+ values.set(parameters[index].name, argument.value);
618
+ }
619
+ for (let index = 0; index < parameters.length; index += 1) {
620
+ const parameter = parameters[index];
621
+ if (!values.has(parameter.name)) {
622
+ if (parameter.required)
623
+ return undefined;
624
+ values.set(parameter.name, parameter.defaultValue ?? "undefined");
625
+ }
626
+ }
627
+ return values;
628
+ }
629
+ function compilerInitializerArguments(source) {
630
+ return splitTopLevel(source).flatMap(argument => {
631
+ const named = /^namedArguments\s*\(\s*\{([\s\S]*)\}\s*\)$/.exec(argument.trim());
632
+ return named ? splitTopLevel(named[1]) : [argument];
633
+ });
634
+ }
635
+ function compilerSemanticArgument(source) {
636
+ const argument = structArgument(source);
637
+ const value = argument.value.trim();
638
+ if (/^(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)$/.test(value))
639
+ return { label: argument.label, type: "string" };
640
+ if (/^-?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value))
641
+ return { label: argument.label, type: "number" };
642
+ if (/^(?:true|false)$/.test(value))
643
+ return { label: argument.label, type: "boolean" };
644
+ if (/^(?:overloadClosure|function)\s*\(/.test(value) || /=>/.test(value))
645
+ return { label: argument.label, type: "function" };
646
+ if (/^Binding\s*\(/.test(value))
647
+ return { label: argument.label, kind: "binding", type: "binding" };
648
+ if (/^State\s*\(/.test(value))
649
+ return { label: argument.label, type: "state" };
650
+ return { label: argument.label, type: "unknown" };
651
+ }
652
+ function semanticInitializerSymbol(name, plan, index) {
653
+ return {
654
+ kind: "initializer",
655
+ index,
656
+ signature: `${name}(${plan.parameters.map(parameter => `${parameter.kind === "viewBuilder" ? "@ViewBuilder " : parameter.kind === "action" ? "@Action " : parameter.kind === "binding" ? "@Binding " : ""}${parameter.label ?? parameter.name}`).join(", ")})`,
657
+ parameters: plan.parameters,
658
+ };
659
+ }
660
+ function staticInitializerIndex(declaration, argumentSource, offset = 0) {
661
+ const plans = structInitializerPlans(declaration);
662
+ const arguments_ = compilerInitializerArguments(argumentSource).map(compilerSemanticArgument);
663
+ const initializers = plans.map((plan, index) => semanticInitializerSymbol(declaration.name, plan, index));
664
+ const viewType = {
665
+ kind: "view",
666
+ name: declaration.name,
667
+ qualifiedName: declaration.name,
668
+ genericParameters: declaration.genericParameters,
669
+ fields: [],
670
+ initializers,
671
+ };
672
+ const result = resolveSemanticCall(viewType, arguments_);
673
+ if (result.resolvedInitializer)
674
+ return result.resolvedInitializer.index;
675
+ if (result.diagnostics[0]?.code === "VUNE_INITIALIZER" && arguments_.some(argument => argument.type === "unknown"))
676
+ return undefined;
677
+ throw new VuneInitializerSyntaxError(result.diagnostics[0]?.message ?? `No matching initializer for ${declaration.name}.`, offset);
678
+ }
679
+ function findDelegatedInitializer(plans, arguments_, excludedIndex) {
680
+ for (let index = 0; index < plans.length; index += 1) {
681
+ if (index === excludedIndex)
682
+ continue;
683
+ const plan = plans[index];
684
+ const values = delegatedParameterValues(plan.parameters, arguments_);
685
+ if (values)
686
+ return { plan, values };
687
+ }
688
+ return undefined;
689
+ }
690
+ function substituteStructParameters(expression, values) {
691
+ let result = expression;
692
+ for (const [name, value] of values)
693
+ result = result.replace(new RegExp(`\\b${name}\\b`, "g"), `(${value})`);
694
+ return result;
695
+ }
696
+ function resolvedStructFields(index, plans, fields, stack = new Set()) {
697
+ if (stack.has(index))
698
+ return new Map();
699
+ const nextStack = new Set(stack).add(index);
700
+ const plan = plans[index];
701
+ const values = new Map();
702
+ if (plan.delegation) {
703
+ const delegated = findDelegatedInitializer(plans, plan.delegation, index);
704
+ if (delegated) {
705
+ const targetIndex = plans.indexOf(delegated.plan);
706
+ const targetFields = resolvedStructFields(targetIndex, plans, fields, nextStack);
707
+ for (const [field, expression] of targetFields)
708
+ values.set(field, substituteStructParameters(expression, delegated.values));
709
+ }
710
+ }
711
+ for (const [field, expression] of plan.assignments)
712
+ values.set(field, expression);
713
+ for (const field of fields) {
714
+ if (values.has(field.name))
715
+ continue;
716
+ const parameter = plan.parameters.find(item => item.name === field.name);
717
+ if (parameter)
718
+ values.set(field.name, parameter.name);
719
+ else if (field.defaultValue !== undefined && field.kind !== "state")
720
+ values.set(field.name, `(${field.defaultValue})`);
721
+ else
722
+ values.set(field.name, "undefined");
723
+ }
724
+ return values;
725
+ }
726
+ function delegatedStructInitializer(name, plan, fields, plans, index) {
727
+ const parameters = plan.parameters;
728
+ const assignments = resolvedStructFields(index, plans, fields);
729
+ const checks = parameters.map((parameter, parameterIndex) => parameter.kind === "value"
730
+ ? "true"
731
+ : parameter.kind === "binding"
732
+ ? `(args[${parameterIndex}] && typeof args[${parameterIndex}] === "object" && (Object.getOwnPropertyDescriptor(args[${parameterIndex}], "value")?.get || Object.getOwnPropertyDescriptor(args[${parameterIndex}], "value")?.set))`
733
+ : parameter.required
734
+ ? `typeof args[${parameterIndex}] === "function"`
735
+ : `(args[${parameterIndex}] === undefined || typeof args[${parameterIndex}] === "function")`);
736
+ const values = fields.map(field => {
737
+ const expression = assignments.get(field.name) ?? "undefined";
738
+ const parameter = parameters.find(item => item.name === field.name);
739
+ const resolved = parameter?.kind === "viewBuilder" && (expression === `${parameter.name}()` || expression === `(${parameter.name})()`)
740
+ ? `resolveBuilderClosure(${parameter.name})`
741
+ : expression;
742
+ return `${field.name}: ${resolved}`;
743
+ });
744
+ const signature = `${name}(${parameters.map(parameter => `${parameter.kind === "viewBuilder" ? "@ViewBuilder " : parameter.kind === "action" ? "@Action " : parameter.kind === "binding" ? "@Binding " : ""}${parameter.label ?? parameter.name}${parameter.defaultValue === undefined ? "" : ` = ${parameter.defaultValue}`}`).join(", ")})`;
745
+ const metadata = `[${parameters.map(parameter => `{ name: ${JSON.stringify(parameter.name)}, kind: ${JSON.stringify(parameter.kind)}, label: ${parameter.label ? JSON.stringify(parameter.label) : "undefined"}, labelRequired: ${parameter.labelRequired === true}, required: ${parameter.required}, trailing: ${parameter.trailing === true}, type: ${parameter.type ? JSON.stringify(parameter.type) : "undefined"} }`).join(", ")}]`;
746
+ const required = parameters.filter(parameter => parameter.required).length;
747
+ const maximum = parameters.length;
748
+ return `initializer(${JSON.stringify(signature)}, args => args.length >= ${required} && args.length <= ${maximum}${checks.length ? ` && ${checks.join(" && ")}` : ""}, args => { ${parameters.map((parameter, parameterIndex) => `const ${parameter.name} = args[${parameterIndex}]${parameter.defaultValue ? ` === undefined ? (${parameter.defaultValue}) : args[${parameterIndex}]` : ""}`).join("; ")}; return { ${values.join(", ")} } }, ${metadata})`;
749
+ }
750
+ function lowerStructDefinition(declaration, registry = canonicalInitializerSymbols) {
751
+ const fields = declaration.fields.map(field => ({
752
+ name: field.name,
753
+ kind: field.kind === "state" ? "state" : field.kind === "binding" ? "binding" : "value",
754
+ type: field.type,
755
+ defaultValue: field.initializer,
756
+ }));
757
+ const plans = structInitializerPlans(declaration);
758
+ const initializers = plans.map((plan, index) => delegatedStructInitializer(declaration.name, plan, fields, plans, index));
759
+ const stateFields = fields.filter(field => field.kind === "state");
760
+ const state = stateFields.length === 0
761
+ ? ""
762
+ : `, state: () => ({ ${stateFields.map(field => `${field.name}: ${field.defaultValue !== undefined && /^State\s*\(/.test(field.defaultValue) ? field.defaultValue : `State(${field.defaultValue ?? "undefined"})`}`).join(", ")} })`;
763
+ const bodySource = declaration.bodyExpressionSource.trim().replace(/^return\s+/, "").replace(/;\s*$/, "");
764
+ const fieldMetadata = `fields: [${declaration.fields.map(field => `{ name: ${JSON.stringify(field.name)}, kind: ${JSON.stringify(field.kind)}, type: ${field.type === undefined ? "undefined" : JSON.stringify(field.type)}, defaultValue: ${field.initializer === undefined ? "undefined" : JSON.stringify(field.initializer)} }`).join(", ")}]`;
765
+ const definitionMetadata = [
766
+ declaration.genericParameters === undefined ? undefined : `genericParameters: ${JSON.stringify(declaration.genericParameters)}`,
767
+ fieldMetadata,
768
+ ].filter((item) => item !== undefined).join(", ");
769
+ return `defineView(${JSON.stringify(declaration.name)}, { ${definitionMetadata}, initializers: [${initializers.join(", ")}]${state}, body: (props: any) => { const { ${fields.map(field => field.name).join(", ")} } = props; return ${lowerRange(bodySource, registry)} } })`;
770
+ }
771
+ function lowerStructs(source, registry = canonicalInitializerSymbols) {
772
+ const declarations = parseVuneStructs(source);
773
+ if (declarations.length === 0)
774
+ return source;
775
+ let output = source;
776
+ for (const declaration of [...declarations].sort((left, right) => right.range.start - left.range.start)) {
777
+ const definition = lowerStructDefinition(declaration, registry);
778
+ const nested = declaration.nested ?? [];
779
+ const replacement = nested.length === 0
780
+ ? `const ${declaration.name} = ${definition}`
781
+ : `const ${declaration.name} = (() => { ${nested.map(item => `const ${item.name} = ${lowerStructDefinition(item)}`).join("; ")}; return Object.assign(${definition}, { ${nested.map(item => item.name).join(", ")} }); })()`;
782
+ output = output.slice(0, declaration.range.start) + replacement + output.slice(declaration.range.end);
783
+ }
784
+ return output;
785
+ }
786
+ function lowerStaticStructCalls(source, declarations) {
787
+ if (declarations.length === 0)
788
+ return source;
789
+ const known = new Map();
790
+ const add = (declaration) => {
791
+ known.set(declaration.name, declaration);
792
+ for (const nested of declaration.nested ?? [])
793
+ add(nested);
794
+ };
795
+ for (const declaration of declarations)
796
+ add(declaration);
797
+ const file = ts.createSourceFile("vune-specialization.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
798
+ const edits = [];
799
+ const visit = (node) => {
800
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
801
+ const declaration = known.get(node.expression.text);
802
+ const initializerIndex = declaration && staticInitializerIndex(declaration, node.arguments.map(argument => argument.getText(file)).join(", "), node.expression.getStart(file));
803
+ if (initializerIndex !== undefined) {
804
+ const argumentsSource = node.arguments.map(argument => argument.getText(file)).join(", ");
805
+ edits.push({
806
+ start: node.expression.getStart(file),
807
+ end: node.end,
808
+ replacement: `${node.expression.text}.viewType.createNodeSpecialized(${initializerIndex}, [${argumentsSource}])`,
809
+ });
810
+ return;
811
+ }
812
+ }
813
+ ts.forEachChild(node, visit);
814
+ };
815
+ visit(file);
816
+ let result = source;
817
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
818
+ result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
819
+ }
820
+ return result;
821
+ }
822
+ function isVunePackage(moduleName) {
823
+ return moduleName === "vune-ui" || moduleName.startsWith("@vune-ui/");
824
+ }
825
+ function bindingNames(name) {
826
+ if (ts.isIdentifier(name))
827
+ return [name.text];
828
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
829
+ return name.elements.flatMap(element => ts.isOmittedExpression(element) ? [] : bindingNames(element.name));
830
+ }
831
+ return [];
832
+ }
833
+ function vuneApiBindings(file) {
834
+ const state = new Set();
835
+ const view = new Set();
836
+ const namespaces = new Set();
837
+ let blockedState = false;
838
+ let blockedView = false;
839
+ for (const statement of file.statements) {
840
+ if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
841
+ const vune = isVunePackage(statement.moduleSpecifier.text);
842
+ const clause = statement.importClause;
843
+ if (clause?.name) {
844
+ if (!vune && clause.name.text === "State")
845
+ blockedState = true;
846
+ if (!vune && clause.name.text === "view")
847
+ blockedView = true;
848
+ }
849
+ const bindings = clause?.namedBindings;
850
+ if (bindings && ts.isNamespaceImport(bindings)) {
851
+ if (vune)
852
+ namespaces.add(bindings.name.text);
853
+ continue;
854
+ }
855
+ if (bindings && ts.isNamedImports(bindings)) {
856
+ for (const element of bindings.elements) {
857
+ const imported = element.propertyName?.text ?? element.name.text;
858
+ const local = element.name.text;
859
+ if (vune && imported === "State")
860
+ state.add(local);
861
+ else if (!vune && local === "State")
862
+ blockedState = true;
863
+ if (vune && imported === "view")
864
+ view.add(local);
865
+ else if (!vune && local === "view")
866
+ blockedView = true;
867
+ }
868
+ }
869
+ continue;
870
+ }
871
+ const names = [];
872
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
873
+ names.push(statement.name.text);
874
+ if (ts.isVariableStatement(statement)) {
875
+ for (const declaration of statement.declarationList.declarations)
876
+ names.push(...bindingNames(declaration.name));
877
+ }
878
+ if (names.includes("State"))
879
+ blockedState = true;
880
+ if (names.includes("view"))
881
+ blockedView = true;
882
+ }
883
+ return { state, view, namespaces, blockedState, blockedView };
884
+ }
885
+ function unwrapTsExpression(expression) {
886
+ let current = expression;
887
+ while (ts.isParenthesizedExpression(current)
888
+ || ts.isAsExpression(current)
889
+ || ts.isTypeAssertionExpression(current)
890
+ || ts.isNonNullExpression(current)
891
+ || ts.isSatisfiesExpression(current)) {
892
+ current = current.expression;
893
+ }
894
+ return current;
895
+ }
896
+ function isVuneApiCall(call, api, bindings) {
897
+ const expression = unwrapTsExpression(call.expression);
898
+ const named = api === "State" ? bindings.state : bindings.view;
899
+ const blocked = api === "State" ? bindings.blockedState : bindings.blockedView;
900
+ if (ts.isIdentifier(expression)) {
901
+ if (named.has(expression.text))
902
+ return true;
903
+ // `.vune.ts` supports the canonical names without an explicit import; do
904
+ // not claim them when the file has provided an unrelated binding.
905
+ return expression.text === api && !blocked && named.size === 0;
906
+ }
907
+ if (ts.isPropertyAccessExpression(expression)
908
+ && expression.name.text === api
909
+ && ts.isIdentifier(expression.expression)
910
+ && bindings.namespaces.has(expression.expression.text))
911
+ return true;
912
+ return false;
913
+ }
914
+ function collectTopLevelStates(file, bindings) {
915
+ const states = [];
916
+ for (const statement of file.statements) {
917
+ if (!ts.isVariableStatement(statement))
918
+ continue;
919
+ const isConst = (statement.declarationList.flags & ts.NodeFlags.Const) !== 0;
920
+ const exported = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
921
+ for (const declaration of statement.declarationList.declarations) {
922
+ if (!declaration.initializer)
923
+ continue;
924
+ const initializer = unwrapTsExpression(declaration.initializer);
925
+ if (!ts.isCallExpression(initializer) || !isVuneApiCall(initializer, "State", bindings))
926
+ continue;
927
+ states.push({
928
+ name: ts.isIdentifier(declaration.name) ? declaration.name.text : undefined,
929
+ statement,
930
+ declaration,
931
+ initializer: declaration.initializer,
932
+ eligible: isConst && !exported && ts.isIdentifier(declaration.name),
933
+ });
934
+ }
935
+ }
936
+ return states;
937
+ }
938
+ function isNonReferenceIdentifier(node) {
939
+ const parent = node.parent;
940
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node)
941
+ return true;
942
+ if (ts.isPropertyAssignment(parent) && parent.name === node)
943
+ return true;
944
+ if (ts.isMethodDeclaration(parent) && parent.name === node)
945
+ return true;
946
+ if (ts.isPropertyDeclaration(parent) && parent.name === node)
947
+ return true;
948
+ if (ts.isMethodSignature(parent) && parent.name === node)
949
+ return true;
950
+ if (ts.isPropertySignature(parent) && parent.name === node)
951
+ return true;
952
+ if (ts.isVariableDeclaration(parent) && parent.name === node)
953
+ return true;
954
+ if (ts.isParameter(parent) && parent.name === node)
955
+ return true;
956
+ if (ts.isBindingElement(parent) && parent.name === node)
957
+ return true;
958
+ if (ts.isFunctionDeclaration(parent) && parent.name === node)
959
+ return true;
960
+ if (ts.isClassDeclaration(parent) && parent.name === node)
961
+ return true;
962
+ if (ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent))
963
+ return true;
964
+ return false;
965
+ }
966
+ function directBlockBindings(node) {
967
+ const bindings = new Set();
968
+ for (const statement of node.statements) {
969
+ if (ts.isVariableStatement(statement)) {
970
+ for (const declaration of statement.declarationList.declarations) {
971
+ for (const name of bindingNames(declaration.name))
972
+ bindings.add(name);
973
+ }
974
+ }
975
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) {
976
+ bindings.add(statement.name.text);
977
+ }
978
+ }
979
+ return bindings;
980
+ }
981
+ function referencedStateNames(root, stateNames, skipped = new Set()) {
982
+ const result = new Set();
983
+ const visit = (node, shadowed) => {
984
+ if (skipped.has(node))
985
+ return;
986
+ if (ts.isIdentifier(node) && stateNames.has(node.text) && !shadowed.has(node.text) && !isNonReferenceIdentifier(node))
987
+ result.add(node.text);
988
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
989
+ const local = new Set(shadowed);
990
+ for (const parameter of node.parameters)
991
+ for (const name of bindingNames(parameter.name))
992
+ local.add(name);
993
+ if (ts.isFunctionDeclaration(node) && node.name)
994
+ local.add(node.name.text);
995
+ if (node.body)
996
+ visit(node.body, local);
997
+ return;
998
+ }
999
+ if (ts.isBlock(node)) {
1000
+ const local = new Set(shadowed);
1001
+ for (const name of directBlockBindings(node))
1002
+ local.add(name);
1003
+ ts.forEachChild(node, child => visit(child, local));
1004
+ return;
1005
+ }
1006
+ if (ts.isCatchClause(node)) {
1007
+ const local = new Set(shadowed);
1008
+ if (node.variableDeclaration)
1009
+ for (const name of bindingNames(node.variableDeclaration.name))
1010
+ local.add(name);
1011
+ visit(node.block, local);
1012
+ return;
1013
+ }
1014
+ ts.forEachChild(node, child => visit(child, shadowed));
1015
+ };
1016
+ visit(root, new Set());
1017
+ return result;
1018
+ }
1019
+ function collectVuneViewCalls(file, bindings) {
1020
+ const calls = [];
1021
+ const visit = (node) => {
1022
+ if (ts.isCallExpression(node) && isVuneApiCall(node, "view", bindings)) {
1023
+ calls.push(node);
1024
+ return;
1025
+ }
1026
+ ts.forEachChild(node, visit);
1027
+ };
1028
+ visit(file);
1029
+ return calls.sort((left, right) => left.getStart(file) - right.getStart(file));
1030
+ }
1031
+ function stateDependencies(state, stateNames) {
1032
+ return referencedStateNames(state.initializer, stateNames);
1033
+ }
1034
+ function transitiveStateClosure(initial, eligible, dependencies) {
1035
+ const result = new Set();
1036
+ const queue = [...initial];
1037
+ while (queue.length > 0) {
1038
+ const name = queue.pop();
1039
+ if (!eligible.has(name) || result.has(name))
1040
+ continue;
1041
+ result.add(name);
1042
+ for (const dependency of dependencies.get(name) ?? [])
1043
+ queue.push(dependency);
1044
+ }
1045
+ return result;
1046
+ }
1047
+ function lowerTopLevelState(source) {
1048
+ const file = ts.createSourceFile("vune-state.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1049
+ const bindings = vuneApiBindings(file);
1050
+ const states = collectTopLevelStates(file, bindings);
1051
+ const eligibleStates = states.filter((state) => state.eligible && state.name !== undefined);
1052
+ if (eligibleStates.length === 0)
1053
+ return source;
1054
+ const byName = new Map(eligibleStates.map(state => [state.name, state]));
1055
+ const eligibleNames = new Set(byName.keys());
1056
+ const allNames = new Set(states.flatMap(state => state.name ? [state.name] : []));
1057
+ const dependencies = new Map();
1058
+ for (const state of eligibleStates)
1059
+ dependencies.set(state.name, stateDependencies(state, allNames));
1060
+ const views = collectVuneViewCalls(file, bindings);
1061
+ if (views.length === 0)
1062
+ return source;
1063
+ const viewSet = new Set(views);
1064
+ // Any reference outside a Vune view keeps that State module-scoped. This
1065
+ // includes helper functions and ineligible/exported/mutable State factories.
1066
+ const directOutside = new Set();
1067
+ const stateDeclarations = new Set(states.map(state => state.declaration));
1068
+ for (const statement of file.statements) {
1069
+ if (ts.isVariableStatement(statement)) {
1070
+ for (const declaration of statement.declarationList.declarations) {
1071
+ if (stateDeclarations.has(declaration)) {
1072
+ const state = states.find(item => item.declaration === declaration);
1073
+ if (state && !state.eligible) {
1074
+ for (const dependency of referencedStateNames(state.initializer, allNames))
1075
+ directOutside.add(dependency);
1076
+ }
1077
+ continue;
1078
+ }
1079
+ if (declaration.initializer)
1080
+ for (const name of referencedStateNames(declaration.initializer, allNames, viewSet))
1081
+ directOutside.add(name);
1082
+ }
1083
+ continue;
1084
+ }
1085
+ for (const name of referencedStateNames(statement, allNames, viewSet))
1086
+ directOutside.add(name);
1087
+ }
1088
+ const outside = transitiveStateClosure(directOutside, eligibleNames, dependencies);
1089
+ const owners = new Map();
1090
+ const statesByView = new Map();
1091
+ views.forEach((view, index) => {
1092
+ const direct = referencedStateNames(view.arguments[0] ?? view, allNames);
1093
+ const closure = transitiveStateClosure(direct, eligibleNames, dependencies);
1094
+ for (const name of closure) {
1095
+ const set = owners.get(name) ?? new Set();
1096
+ set.add(index);
1097
+ owners.set(name, set);
1098
+ }
1099
+ });
1100
+ for (const state of eligibleStates) {
1101
+ const stateOwners = owners.get(state.name);
1102
+ if (outside.has(state.name) || stateOwners?.size !== 1)
1103
+ continue;
1104
+ const owner = [...stateOwners][0];
1105
+ const list = statesByView.get(owner) ?? [];
1106
+ list.push(state);
1107
+ statesByView.set(owner, list);
1108
+ }
1109
+ if (statesByView.size === 0)
1110
+ return source;
1111
+ const hoisted = new Set([...statesByView.values()].flat().map(state => state.declaration));
1112
+ const edits = [];
1113
+ // Remove only declarations that were actually assigned to one View. Mixed
1114
+ // declarations retain their non-State siblings.
1115
+ for (const statement of file.statements) {
1116
+ if (!ts.isVariableStatement(statement))
1117
+ continue;
1118
+ const removed = statement.declarationList.declarations.filter(declaration => hoisted.has(declaration));
1119
+ if (removed.length === 0)
1120
+ continue;
1121
+ const preserved = statement.declarationList.declarations.filter(declaration => !hoisted.has(declaration));
1122
+ if (preserved.length === 0) {
1123
+ edits.push({ start: statement.getStart(file), end: statement.end, replacement: "" });
1124
+ continue;
1125
+ }
1126
+ const keyword = (statement.declarationList.flags & ts.NodeFlags.Let) !== 0 ? "let" : (statement.declarationList.flags & ts.NodeFlags.Const) !== 0 ? "const" : "var";
1127
+ const prefix = source.slice(statement.getStart(file), statement.declarationList.getStart(file));
1128
+ const semicolon = source.slice(statement.getStart(file), statement.end).trimEnd().endsWith(";") ? ";" : "";
1129
+ edits.push({
1130
+ start: statement.getStart(file),
1131
+ end: statement.end,
1132
+ replacement: `${prefix}${keyword} ${preserved.map(declaration => declaration.getText(file)).join(", ")}${semicolon}`,
1133
+ });
1134
+ }
1135
+ for (const [viewIndex, ownedStates] of statesByView) {
1136
+ const call = views[viewIndex];
1137
+ const argument = call.arguments[0];
1138
+ if (!argument || call.arguments.length !== 1)
1139
+ continue;
1140
+ const callee = call.expression.getText(file);
1141
+ const body = argument.getText(file);
1142
+ const unwrapped = unwrapTsExpression(argument);
1143
+ const functionBody = ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped);
1144
+ const hasProps = functionBody && unwrapped.parameters.length > 0;
1145
+ const names = ownedStates.map(state => state.name);
1146
+ const declarations = ownedStates
1147
+ .sort((left, right) => left.declaration.getStart(file) - right.declaration.getStart(file))
1148
+ .map(state => `const ${state.name} = ${state.initializer.getText(file)};`)
1149
+ .join(" ");
1150
+ const renderedBody = functionBody ? `((${body})(${hasProps ? "props" : ""}))` : `(${body})`;
1151
+ const bodyParameters = hasProps ? `({ ${names.join(", ")} }, props)` : `({ ${names.join(", ")} })`;
1152
+ const replacement = `${callee}({ state: () => { ${declarations} return { ${names.join(", ")} } }, body: ${bodyParameters} => ${renderedBody} })`;
1153
+ edits.push({ start: call.getStart(file), end: call.end, replacement });
1154
+ }
1155
+ let result = source;
1156
+ for (const edit of edits.sort((left, right) => right.start - left.start))
1157
+ result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
1158
+ return result;
1159
+ }
1160
+ function ensureImports(source) {
1161
+ const required = ["defineView", "initializer", "resolveBuilderClosure", "namedArguments", "overloadClosure", "Binding", "State", "Element", "modifiedContent"]
1162
+ .filter(name => new RegExp(`\\b${name}(?:<[^()\\n]*>)?\\s*\\(`).test(source) || (name === "defineView" && /const\s+[A-Z]\w*\s*=\s*defineView/.test(source)));
1163
+ let result = source;
1164
+ if (required.length === 0)
1165
+ return result;
1166
+ const imports = [...result.matchAll(/import\s*\{([\s\S]*?)\}\s*from\s*(["'])(vune-ui|@vune-ui\/core)\2[\t ]*;?/g)];
1167
+ const imported = new Set(imports.flatMap(match => match[1].split(",").map(value => value.trim()).filter(Boolean)));
1168
+ const missing = required.filter(name => !imported.has(name));
1169
+ if (missing.length === 0)
1170
+ return result;
1171
+ const existingCore = imports.find(match => match[3] === "@vune-ui/core");
1172
+ if (!existingCore)
1173
+ return `import { ${missing.join(", ")} } from "@vune-ui/core"\n${result}`;
1174
+ const names = existingCore[1].split(",").map(value => value.trim()).filter(Boolean);
1175
+ for (const name of missing)
1176
+ if (!names.includes(name))
1177
+ names.push(name);
1178
+ const replacement = `import { ${names.join(", ")} } from ${existingCore[2]}@vune-ui/core${existingCore[2]}`;
1179
+ result = result.slice(0, existingCore.index) + replacement + result.slice(existingCore.index + existingCore[0].length);
1180
+ return result;
1181
+ }
1182
+ function lowerNamedVuneCalls(source, registry = canonicalInitializerSymbols) {
1183
+ let output = source;
1184
+ let iterations = 0;
1185
+ while (true) {
1186
+ if (++iterations > output.length + 1)
1187
+ throw syntaxError("Vune named-argument lowering did not advance", output.length);
1188
+ const calls = [...output.matchAll(/\b(?:[A-Z][A-Za-z0-9_$]*\.)*[A-Z][A-Za-z0-9_$]*\s*\(/g)];
1189
+ let replacement;
1190
+ for (const match of calls.reverse()) {
1191
+ const start = match.index ?? 0;
1192
+ const callee = /^(?:[A-Z][A-Za-z0-9_$]*\.)*[A-Z][A-Za-z0-9_$]*/.exec(match[0])?.[0];
1193
+ if (!callee)
1194
+ continue;
1195
+ const name = callee.split(".").at(-1);
1196
+ const preceding = output.slice(0, start).trimEnd();
1197
+ if (/\b(?:function|class|interface|type|new)$/.test(preceding) || preceding.endsWith("."))
1198
+ continue;
1199
+ const open = output.indexOf("(", start + callee.length);
1200
+ const close = matching(output, open, "(", ")");
1201
+ const argumentSource = output.slice(open + 1, close);
1202
+ if (!splitTopLevel(argumentSource).some(argument => topLevelColon(argument) >= 0))
1203
+ continue;
1204
+ replacement = { start, end: close + 1, value: `${callee}(${lowerArguments(argumentSource, name, registry)})` };
1205
+ break;
1206
+ }
1207
+ if (!replacement)
1208
+ return output;
1209
+ output = output.slice(0, replacement.start) + replacement.value + output.slice(replacement.end);
1210
+ }
1211
+ }
1212
+ function initializerRegistryFor(declarations) {
1213
+ const registry = new Map(canonicalInitializerSymbols);
1214
+ const add = (declaration) => {
1215
+ registry.set(declaration.name, structInitializerPlans(declaration).map((plan, index) => semanticInitializerSymbol(declaration.name, plan, index)));
1216
+ for (const nested of declaration.nested ?? [])
1217
+ add(nested);
1218
+ };
1219
+ for (const declaration of declarations)
1220
+ add(declaration);
1221
+ return registry;
1222
+ }
1223
+ function lowerVueComponentImports(source) {
1224
+ const file = ts.createSourceFile("vune-vue-imports.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1225
+ const existingNames = new Set();
1226
+ const collectNames = (node) => {
1227
+ if (ts.isIdentifier(node))
1228
+ existingNames.add(node.text);
1229
+ ts.forEachChild(node, collectNames);
1230
+ };
1231
+ collectNames(file);
1232
+ const replacements = [];
1233
+ let index = 0;
1234
+ for (const statement of file.statements) {
1235
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
1236
+ continue;
1237
+ if (!/\.vue$/i.test(statement.moduleSpecifier.text) || statement.importClause?.isTypeOnly)
1238
+ continue;
1239
+ const importedName = statement.importClause?.name?.text
1240
+ ?? (statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)
1241
+ ? statement.importClause.namedBindings.elements.find(element => element.propertyName?.text === "default")?.name.text
1242
+ : undefined);
1243
+ if (!importedName)
1244
+ continue;
1245
+ let adapterName = `__vuneForeignComponent${index++}`;
1246
+ while (existingNames.has(adapterName))
1247
+ adapterName = `__vuneForeignComponent${index++}`;
1248
+ existingNames.add(adapterName);
1249
+ const quote = source[statement.moduleSpecifier.getStart(file)];
1250
+ const module = statement.moduleSpecifier.text;
1251
+ const lineStart = source.lastIndexOf("\n", statement.getStart(file) - 1) + 1;
1252
+ const indent = source.slice(lineStart, statement.getStart(file)).match(/^[ \t]*/)?.[0] ?? "";
1253
+ replacements.push({
1254
+ start: statement.getStart(file),
1255
+ end: statement.end,
1256
+ value: `${indent}import ${adapterName} from ${quote}${module}${quote}\n${indent}const ${importedName} = __vuneForeignComponent(${adapterName})`,
1257
+ });
1258
+ }
1259
+ if (replacements.length === 0)
1260
+ return source;
1261
+ let result = source;
1262
+ for (const replacement of replacements.reverse())
1263
+ result = result.slice(0, replacement.start) + replacement.value + result.slice(replacement.end);
1264
+ return `import { foreignComponent as __vuneForeignComponent } from "@vune-ui/vue"\n${result}`;
1265
+ }
1266
+ export function transformVuneSource(source, fileName = "vune-source.ts") {
1267
+ const withVueImports = lowerVueComponentImports(source);
1268
+ const declarations = parseVuneStructs(withVueImports);
1269
+ const registry = initializerRegistryFor(declarations);
1270
+ validateKnownCalls(parseVuneBuilder(withVueImports), registry);
1271
+ validateKnownTypeScriptCalls(withVueImports, registry);
1272
+ for (const declaration of declarations) {
1273
+ validateKnownCalls(parseVuneBuilder(declaration.bodyExpressionSource, declaration.bodyExpressionRange.start), registry);
1274
+ }
1275
+ const withStructs = lowerStructs(withVueImports, registry);
1276
+ const withNamedArguments = lowerNamedVuneCalls(withStructs, registry);
1277
+ const withBuilderSyntax = lowerRange(withNamedArguments, registry);
1278
+ // State ownership is resolved only after Vune-only syntax has become valid
1279
+ // TypeScript. This lets the TypeScript AST see complete view() arguments
1280
+ // instead of truncating them at trailing builder blocks.
1281
+ const lowered = lowerTopLevelState(withBuilderSyntax);
1282
+ const withStaticStructCalls = lowerStaticStructCalls(lowered, declarations);
1283
+ const withStaticModifiers = lowerStaticModifierChains(withStaticStructCalls, fileName);
1284
+ return ensureImports(lowerStaticImportedCalls(withStaticModifiers, fileName));
1285
+ }
1286
+ function hasNamedVuneArguments(source) {
1287
+ const calls = /\b[A-Z][A-Za-z0-9_$]*\s*\(/g;
1288
+ let match;
1289
+ while ((match = calls.exec(source))) {
1290
+ const open = source.indexOf("(", match.index);
1291
+ const close = matching(source, open, "(", ")");
1292
+ if (/\bfunction$/.test(source.slice(0, match.index).trimEnd())) {
1293
+ calls.lastIndex = close + 1;
1294
+ continue;
1295
+ }
1296
+ if (splitTopLevel(source.slice(open + 1, close)).some(argument => topLevelColon(argument) >= 0))
1297
+ return true;
1298
+ calls.lastIndex = close + 1;
1299
+ }
1300
+ return false;
1301
+ }
1302
+ function hasBindingShorthand(source) {
1303
+ return lowerShorthand(source) !== source;
1304
+ }
1305
+ function hasStaticModifierSyntax(source) {
1306
+ return Array.from(staticModifierNames).some(name => new RegExp(`\\.${name}\\s*\\(`).test(source));
1307
+ }
1308
+ export function hasVuneSyntax(source, allowRawHtml = true) {
1309
+ return /\bstruct\s+[A-Z][A-Za-z0-9_$]*(?:\s*<[^>{}]*>)?\s*:\s*View/.test(source)
1310
+ || (allowRawHtml && findRawHtml(source) !== undefined)
1311
+ || findBuilder(source, 0, true) !== undefined
1312
+ || hasBindingShorthand(source)
1313
+ || hasNamedVuneArguments(source)
1314
+ || hasStaticModifierSyntax(source);
1315
+ }
1316
+ //# sourceMappingURL=pipeline.js.map