@runelight/vue 0.0.9

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,1358 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { baseParse, NodeTypes } from "@vue/compiler-dom";
4
+ import ts from "typescript";
5
+ export function createRunelightVueAnalysisCache() {
6
+ return createRunelightVueAnalysisCacheData();
7
+ }
8
+ export function createRunelightVueAnalysisCacheData(sourceFilesByPath = new Map()) {
9
+ return {
10
+ importedStaticSourcePathByKey: new Map(),
11
+ sourceFilesByPath,
12
+ };
13
+ }
14
+ export function readRunelightVueAnalysisCacheData(cache) {
15
+ return cache;
16
+ }
17
+ export const RUNELIGHT_VUE_COMPONENT_FILE_EXTENSION = ".g.vue";
18
+ export function isRunelightVueComponentFile(filePath) {
19
+ return filePath.split("?", 1)[0]?.endsWith(RUNELIGHT_VUE_COMPONENT_FILE_EXTENSION) ?? false;
20
+ }
21
+ export function analyzeRunelightVueEntry(options) {
22
+ const entryCoordinate = parseEntryCoordinate(options.entry);
23
+ const entryPath = resolve(options.cwd, entryCoordinate.file);
24
+ const cache = readRunelightVueAnalysisCacheData(options.cache);
25
+ if (!existsSync(entryPath)) {
26
+ return {
27
+ entry: options.entry,
28
+ mode: "unknown",
29
+ defaultExport: false,
30
+ frames: [],
31
+ providers: {},
32
+ diagnostics: [
33
+ {
34
+ stage: "contract-extraction",
35
+ severity: "error",
36
+ code: "entry-not-found",
37
+ message: `Runelight entry does not exist: ${options.entry}`,
38
+ file: options.entry,
39
+ },
40
+ ],
41
+ };
42
+ }
43
+ if (entryCoordinate.explicitExportName && entryCoordinate.exportName !== "default") {
44
+ return {
45
+ entry: options.entry,
46
+ mode: "unknown",
47
+ defaultExport: false,
48
+ frames: [],
49
+ providers: {},
50
+ diagnostics: [
51
+ {
52
+ stage: "contract-extraction",
53
+ severity: "error",
54
+ code: "missing-component-export",
55
+ message: `.g.vue entries expose a single default Vue component export; "${entryCoordinate.exportName}" is not available.`,
56
+ file: options.entry,
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ const source = readFileSync(entryPath, "utf8");
62
+ const extraction = extractVueFrames(source, options.entry);
63
+ const frames = extraction.frames;
64
+ const providerFrames = Object.fromEntries(extraction.frameSourceFile ? getGVueProviderSummariesForFile(extraction.frameSourceFile, entryPath, options.cwd, cache) : []);
65
+ const importedNames = extraction.frameSourceFile ? getImportedNames(extraction.frameSourceFile) : new Set();
66
+ const mode = frames.length === 0
67
+ ? "unknown"
68
+ : frames.some((frame) => frame.kind === "scope")
69
+ ? "scope"
70
+ : "pure";
71
+ const diagnostics = [...extraction.diagnostics];
72
+ if (frames.length === 0 && !diagnostics.some((diagnostic) => diagnostic.code === "malformed-frames")) {
73
+ diagnostics.push({
74
+ stage: "contract-extraction",
75
+ severity: "error",
76
+ code: "missing-frames",
77
+ message: "A .g.vue entry must declare a <g:frames> block with a statically enumerable default export.",
78
+ file: options.entry,
79
+ });
80
+ }
81
+ for (const frame of frames) {
82
+ for (const providerName of frame.providers ?? []) {
83
+ if (!providerFrames[providerName] && importedNames.has(providerName)) {
84
+ providerFrames[providerName] = { name: providerName, frames: [] };
85
+ }
86
+ }
87
+ }
88
+ for (const frame of frames) {
89
+ validateVueProviderSelections(frame, providerFrames, diagnostics, options.entry);
90
+ validateVueProviderVariantSelections(frame, providerFrames, diagnostics, options.entry);
91
+ }
92
+ validateVueProviderVariantCoverage(readVueInjectedProviderNames(source), providerFrames, frames, diagnostics, options.entry);
93
+ validateVueTemplateReachability(source, extraction.staticFrames, diagnostics, options.entry);
94
+ return {
95
+ entry: options.entry,
96
+ mode,
97
+ defaultExport: true,
98
+ frames: mode === "scope" ? frames.map((frame) => ({ ...frame, kind: "scope" })) : frames,
99
+ providers: providerFrames,
100
+ diagnostics,
101
+ };
102
+ }
103
+ export function extractVueFrames(source, file) {
104
+ const block = extractVueFramesBlock(source);
105
+ if (!block) {
106
+ return {
107
+ frameImportCode: "",
108
+ frameObjectCode: "{}",
109
+ frames: [],
110
+ staticFrames: [],
111
+ diagnostics: [
112
+ {
113
+ stage: "contract-extraction",
114
+ severity: "error",
115
+ code: "missing-frames",
116
+ message: "A .g.vue entry must include a <g:frames> block.",
117
+ file,
118
+ },
119
+ ],
120
+ };
121
+ }
122
+ const parsed = parseVueFramesExport(block.content, file);
123
+ if (!parsed) {
124
+ return {
125
+ frameImportCode: "",
126
+ frameObjectCode: "{}",
127
+ frames: [],
128
+ staticFrames: [],
129
+ diagnostics: [
130
+ {
131
+ stage: "contract-extraction",
132
+ severity: "error",
133
+ code: "malformed-frames",
134
+ message: "<g:frames> must contain `export default { ... }`.",
135
+ file,
136
+ },
137
+ ],
138
+ };
139
+ }
140
+ const diagnostics = [];
141
+ const frameSummary = readVueFramesObject(parsed.objectLiteral, parsed.sourceFile, diagnostics, file);
142
+ return {
143
+ frameImportCode: parsed.frameImportCode,
144
+ frameObjectCode: parsed.frameObjectCode,
145
+ frameSourceFile: parsed.sourceFile,
146
+ frames: frameSummary.frames,
147
+ staticFrames: frameSummary.staticFrames,
148
+ diagnostics,
149
+ };
150
+ }
151
+ export function extractVueFrameNames(source, file) {
152
+ return extractVueFrames(source, file).frames.map((frame) => frame.name);
153
+ }
154
+ export function vueComponentNameFromFilePath(filePath) {
155
+ const rawName = basename(filePath).replace(/\.g\.vue$/i, "");
156
+ const words = rawName.split(/[^A-Za-z0-9]+/).filter(Boolean);
157
+ const name = words.map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`).join("");
158
+ return name || "RunelightVueComponent";
159
+ }
160
+ function extractVueFramesBlock(source) {
161
+ const match = /<g:frames\b[^>]*>([\s\S]*?)<\/g:frames>/i.exec(source);
162
+ return match ? { content: match[1] ?? "" } : undefined;
163
+ }
164
+ function parseVueFramesExport(code, file) {
165
+ const sourceFile = ts.createSourceFile(`${file}.frames.ts`, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
166
+ const frameImportCode = sourceFile.statements
167
+ .filter(ts.isImportDeclaration)
168
+ .map((statement) => code.slice(statement.getStart(sourceFile), statement.end))
169
+ .join("\n");
170
+ for (const statement of sourceFile.statements) {
171
+ if (!ts.isExportAssignment(statement))
172
+ continue;
173
+ const expression = unwrapExpression(statement.expression);
174
+ if (!ts.isObjectLiteralExpression(expression))
175
+ return undefined;
176
+ return {
177
+ frameImportCode,
178
+ frameObjectCode: code.slice(statement.expression.getStart(sourceFile), statement.expression.end),
179
+ objectLiteral: expression,
180
+ sourceFile,
181
+ };
182
+ }
183
+ return undefined;
184
+ }
185
+ function readVueFramesObject(objectLiteral, sourceFile, diagnostics, file) {
186
+ const frames = [];
187
+ const staticFrames = [];
188
+ for (const property of objectLiteral.properties) {
189
+ if (ts.isSpreadAssignment(property)) {
190
+ diagnostics.push({
191
+ stage: "contract-extraction",
192
+ severity: "error",
193
+ code: "malformed-frames",
194
+ message: "Vue <g:frames> does not support top-level spread composition yet.",
195
+ file,
196
+ });
197
+ continue;
198
+ }
199
+ if (!ts.isPropertyAssignment(property))
200
+ continue;
201
+ const frameName = getStaticPropertyName(property.name);
202
+ if (!frameName) {
203
+ diagnostics.push({
204
+ stage: "contract-extraction",
205
+ severity: "error",
206
+ code: "non-static-frame-key",
207
+ message: "Vue frame keys must be statically enumerable object literal keys.",
208
+ file,
209
+ });
210
+ continue;
211
+ }
212
+ const frameValue = unwrapExpression(property.initializer);
213
+ if (!ts.isObjectLiteralExpression(frameValue)) {
214
+ diagnostics.push({
215
+ stage: "contract-extraction",
216
+ severity: "error",
217
+ code: "malformed-frames",
218
+ message: `Vue frame "${frameName}" must be an object literal.`,
219
+ file,
220
+ frameName,
221
+ });
222
+ continue;
223
+ }
224
+ const providers = readProviderSelections(frameValue);
225
+ const providerVariants = readProviderVariantMarkers(property.initializer);
226
+ const kind = hasStaticProperty(frameValue, "scope") ? "scope" : "pure";
227
+ frames.push({
228
+ kind,
229
+ name: frameName,
230
+ ...(providerVariants && Object.keys(providerVariants).length > 0 ? { providerVariants } : {}),
231
+ ...(providers && providers.length > 0 ? { providers } : {}),
232
+ });
233
+ staticFrames.push(readVueFrameStaticFacts(frameName, frameValue, providerVariants));
234
+ }
235
+ return { frames, staticFrames };
236
+ }
237
+ function readProviderSelections(frameValue) {
238
+ const providersProperty = frameValue.properties.find((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === "providers");
239
+ if (!providersProperty)
240
+ return undefined;
241
+ const providersValue = unwrapExpression(providersProperty.initializer);
242
+ if (!ts.isArrayLiteralExpression(providersValue))
243
+ return undefined;
244
+ const providers = [];
245
+ for (const element of providersValue.elements) {
246
+ const entry = unwrapExpression(element);
247
+ if (!ts.isArrayLiteralExpression(entry))
248
+ continue;
249
+ const providerExpression = entry.elements[0] ? unwrapExpression(entry.elements[0]) : undefined;
250
+ if (providerExpression && ts.isIdentifier(providerExpression)) {
251
+ providers.push(providerExpression.text);
252
+ }
253
+ }
254
+ return providers;
255
+ }
256
+ function readVueFrameStaticFacts(frameName, frameValue, providerVariants) {
257
+ const values = new Map();
258
+ if (ts.isObjectLiteralExpression(frameValue)) {
259
+ const props = objectLiteralPropertyExpression(frameValue, "props");
260
+ if (props)
261
+ flattenVueStaticObjectExpression(props, "props", values);
262
+ const scope = objectLiteralPropertyExpression(frameValue, "scope");
263
+ if (scope)
264
+ flattenVueStaticObjectExpression(scope, "scope", values);
265
+ const providers = objectLiteralPropertyExpression(frameValue, "providers");
266
+ if (providers)
267
+ flattenVueProviderStaticValues(providers, values);
268
+ }
269
+ for (const [providerName, selection] of Object.entries(providerVariants ?? {})) {
270
+ const variants = providerVariantSelectionValues(selection);
271
+ values.set(`context.${providerName}.variant`, variants.length === 1
272
+ ? { kind: "string", value: variants[0] }
273
+ : { kind: "oneOf", values: variants.map((variant) => ({ kind: "string", value: variant })) });
274
+ }
275
+ return {
276
+ name: frameName,
277
+ ...(providerVariants && Object.keys(providerVariants).length > 0 ? { providerVariants } : {}),
278
+ values,
279
+ };
280
+ }
281
+ function objectLiteralPropertyExpression(objectLiteral, propertyName) {
282
+ const property = objectLiteral.properties.find((candidate) => ts.isPropertyAssignment(candidate) && getStaticPropertyName(candidate.name) === propertyName);
283
+ return property ? unwrapExpression(property.initializer) : undefined;
284
+ }
285
+ function flattenVueProviderStaticValues(expression, values) {
286
+ const providersValue = unwrapExpression(expression);
287
+ if (!ts.isArrayLiteralExpression(providersValue))
288
+ return;
289
+ for (const element of providersValue.elements) {
290
+ const entry = unwrapExpression(element);
291
+ if (!ts.isArrayLiteralExpression(entry))
292
+ continue;
293
+ const providerExpression = entry.elements[0] ? unwrapExpression(entry.elements[0]) : undefined;
294
+ const valueExpression = entry.elements[1] ? unwrapExpression(entry.elements[1]) : undefined;
295
+ if (!providerExpression || !ts.isIdentifier(providerExpression) || !valueExpression)
296
+ continue;
297
+ flattenVueStaticObjectExpression(valueExpression, `context.${providerExpression.text}`, values);
298
+ }
299
+ }
300
+ function flattenVueStaticObjectExpression(expression, prefix, values) {
301
+ const value = unwrapExpression(expression);
302
+ const staticValue = readVueStaticBranchValue(value);
303
+ if (staticValue) {
304
+ values.set(prefix, staticValue);
305
+ if (staticValue.kind === "array")
306
+ values.set(`${prefix}.length`, { kind: "number", value: staticValue.length });
307
+ if (staticValue.kind === "string")
308
+ values.set(`${prefix}.length`, { kind: "number", value: staticValue.value.length });
309
+ }
310
+ if (ts.isArrayLiteralExpression(value)) {
311
+ for (const element of value.elements) {
312
+ if (ts.isSpreadElement(element)) {
313
+ values.set(`${prefix}.number`, { kind: "unknown" });
314
+ continue;
315
+ }
316
+ flattenVueStaticObjectExpression(element, `${prefix}.number`, values);
317
+ }
318
+ return;
319
+ }
320
+ if (!ts.isObjectLiteralExpression(value)) {
321
+ if (!staticValue)
322
+ values.set(prefix, { kind: "unknown" });
323
+ return;
324
+ }
325
+ for (const property of value.properties) {
326
+ if (ts.isSpreadAssignment(property)) {
327
+ values.set(prefix, { kind: "unknown" });
328
+ continue;
329
+ }
330
+ if (!ts.isPropertyAssignment(property))
331
+ continue;
332
+ const propertyName = getStaticPropertyName(property.name);
333
+ if (!propertyName)
334
+ continue;
335
+ flattenVueStaticObjectExpression(property.initializer, `${prefix}.${propertyName}`, values);
336
+ }
337
+ }
338
+ function readVueStaticBranchValue(expression) {
339
+ const value = unwrapExpression(expression);
340
+ if (value.kind === ts.SyntaxKind.TrueKeyword)
341
+ return { kind: "boolean", value: true };
342
+ if (value.kind === ts.SyntaxKind.FalseKeyword)
343
+ return { kind: "boolean", value: false };
344
+ if (value.kind === ts.SyntaxKind.NullKeyword)
345
+ return { kind: "null" };
346
+ if (ts.isIdentifier(value) && value.text === "undefined")
347
+ return { kind: "undefined" };
348
+ if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value))
349
+ return { kind: "string", value: value.text };
350
+ if (ts.isNumericLiteral(value))
351
+ return { kind: "number", value: Number(value.text) };
352
+ if (ts.isArrayLiteralExpression(value)) {
353
+ if (value.elements.some((element) => ts.isSpreadElement(element)))
354
+ return undefined;
355
+ return { kind: "array", length: value.elements.length };
356
+ }
357
+ if (ts.isObjectLiteralExpression(value))
358
+ return { kind: "object" };
359
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value))
360
+ return { kind: "truthy" };
361
+ if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.ExclamationToken) {
362
+ const inner = readVueStaticBranchValue(value.operand);
363
+ const truthy = inner ? vueStaticBranchValueTruthy(inner) : undefined;
364
+ return truthy === undefined ? undefined : { kind: "boolean", value: !truthy };
365
+ }
366
+ return undefined;
367
+ }
368
+ function readProviderVariantMarkers(expression) {
369
+ if (ts.isSatisfiesExpression(expression))
370
+ return readProviderVariantMarkersFromType(expression.type);
371
+ if (ts.isAsExpression(expression) || ts.isParenthesizedExpression(expression))
372
+ return readProviderVariantMarkers(expression.expression);
373
+ return undefined;
374
+ }
375
+ function readProviderVariantMarkersFromType(typeNode) {
376
+ const variants = new Map();
377
+ visit(typeNode);
378
+ return Object.fromEntries([...variants.entries()].map(([providerName, providerVariants]) => {
379
+ const values = [...providerVariants];
380
+ return [providerName, values.length === 1 ? values[0] : values];
381
+ }));
382
+ function visit(node) {
383
+ if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
384
+ for (const child of node.types)
385
+ visit(child);
386
+ return;
387
+ }
388
+ if (ts.isParenthesizedTypeNode(node)) {
389
+ visit(node.type);
390
+ return;
391
+ }
392
+ if (!ts.isTypeReferenceNode(node))
393
+ return;
394
+ if (!ts.isIdentifier(node.typeName))
395
+ return;
396
+ if (node.typeName.text !== "GVueProviderFrame")
397
+ return;
398
+ const providerType = node.typeArguments?.[0];
399
+ const variantType = node.typeArguments?.[1];
400
+ if (!providerType || !variantType || !ts.isTypeQueryNode(providerType))
401
+ return;
402
+ if (!ts.isIdentifier(providerType.exprName))
403
+ return;
404
+ const providerVariants = readProviderVariantTypeValues(variantType);
405
+ if (providerVariants.length === 0)
406
+ return;
407
+ const providerName = providerType.exprName.text;
408
+ const current = variants.get(providerName) ?? new Set();
409
+ for (const variant of providerVariants)
410
+ current.add(variant);
411
+ variants.set(providerName, current);
412
+ }
413
+ }
414
+ function readProviderVariantTypeValues(typeNode) {
415
+ if (ts.isParenthesizedTypeNode(typeNode))
416
+ return readProviderVariantTypeValues(typeNode.type);
417
+ if (ts.isUnionTypeNode(typeNode))
418
+ return typeNode.types.flatMap(readProviderVariantTypeValues);
419
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal))
420
+ return [typeNode.literal.text];
421
+ return [];
422
+ }
423
+ function getGVueProviderSummaries(sourceFile) {
424
+ const providers = new Map();
425
+ for (const statement of sourceFile.statements) {
426
+ if (ts.isVariableStatement(statement)) {
427
+ for (const declaration of statement.declarationList.declarations) {
428
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
429
+ continue;
430
+ const initializer = unwrapExpression(declaration.initializer);
431
+ if (!isDefineGInjectionKeyCall(initializer))
432
+ continue;
433
+ const variants = readDefineGInjectionKeyVariants(initializer);
434
+ providers.set(declaration.name.text, {
435
+ name: declaration.name.text,
436
+ frames: [],
437
+ ...(variants && variants.length > 0 ? { variants } : {}),
438
+ });
439
+ }
440
+ continue;
441
+ }
442
+ if (ts.isExportAssignment(statement)) {
443
+ const expression = unwrapExpression(statement.expression);
444
+ if (!isDefineGInjectionKeyCall(expression))
445
+ continue;
446
+ const variants = readDefineGInjectionKeyVariants(expression);
447
+ providers.set("default", {
448
+ name: "default",
449
+ frames: [],
450
+ ...(variants && variants.length > 0 ? { variants } : {}),
451
+ });
452
+ }
453
+ }
454
+ return providers;
455
+ }
456
+ function getGVueProviderSummariesForFile(sourceFile, filePath, cwd, cache, visited = new Set()) {
457
+ if (visited.has(filePath))
458
+ return new Map();
459
+ visited.add(filePath);
460
+ const providers = new Map(getGVueProviderSummaries(sourceFile));
461
+ for (const statement of sourceFile.statements) {
462
+ if (!ts.isImportDeclaration(statement) || !statement.importClause || !ts.isStringLiteral(statement.moduleSpecifier))
463
+ continue;
464
+ const targetPath = resolveImportedVueSourcePath(filePath, cwd, statement.moduleSpecifier.text, cache);
465
+ if (!targetPath)
466
+ continue;
467
+ const targetSource = sourceFileForAbsolutePath(targetPath, cache);
468
+ if (!targetSource)
469
+ continue;
470
+ const targetProviders = getGVueProviderSummariesForFile(targetSource, targetPath, cwd, cache, visited);
471
+ const importClause = statement.importClause;
472
+ if (importClause.name) {
473
+ const importedDefault = targetProviders.get("default");
474
+ if (importedDefault) {
475
+ providers.set(importClause.name.text, { ...importedDefault, name: importClause.name.text });
476
+ }
477
+ }
478
+ const namedBindings = importClause.namedBindings;
479
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
480
+ continue;
481
+ for (const element of namedBindings.elements) {
482
+ const importedName = element.propertyName?.text ?? element.name.text;
483
+ const importedProvider = targetProviders.get(importedName);
484
+ if (importedProvider) {
485
+ providers.set(element.name.text, { ...importedProvider, name: element.name.text });
486
+ }
487
+ }
488
+ }
489
+ return providers;
490
+ }
491
+ function readDefineGInjectionKeyVariants(expression) {
492
+ const options = expression.arguments[0] ? unwrapExpression(expression.arguments[0]) : undefined;
493
+ if (!options || !ts.isObjectLiteralExpression(options))
494
+ return undefined;
495
+ const variantsProperty = options.properties.find((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === "variants");
496
+ if (!variantsProperty)
497
+ return undefined;
498
+ const variantsValue = unwrapExpression(variantsProperty.initializer);
499
+ if (!ts.isArrayLiteralExpression(variantsValue))
500
+ return undefined;
501
+ return variantsValue.elements.flatMap((element) => {
502
+ const variant = unwrapExpression(element);
503
+ return ts.isStringLiteral(variant) ? [variant.text] : [];
504
+ });
505
+ }
506
+ function isDefineGInjectionKeyCall(expression) {
507
+ return ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "defineGInjectionKey";
508
+ }
509
+ function resolveImportedVueSourcePath(entryPath, cwd, specifier, cache) {
510
+ const cacheKey = `${entryPath}\0vue-context\0${specifier}`;
511
+ const cached = cache?.importedStaticSourcePathByKey.get(cacheKey);
512
+ if (cached !== undefined)
513
+ return cached ?? undefined;
514
+ const basePath = resolveImportBasePath(entryPath, cwd, specifier);
515
+ const resolvedPath = basePath ? importedVueSourcePathCandidates(basePath).find((candidate) => isFile(candidate)) : undefined;
516
+ cache?.importedStaticSourcePathByKey.set(cacheKey, resolvedPath ?? null);
517
+ return resolvedPath;
518
+ }
519
+ function resolveImportBasePath(entryPath, cwd, specifier) {
520
+ if (specifier.startsWith("@/"))
521
+ return resolve(cwd, "src", specifier.slice(2));
522
+ if (specifier.startsWith("."))
523
+ return resolve(dirname(entryPath), specifier);
524
+ return undefined;
525
+ }
526
+ function importedVueSourcePathCandidates(basePath) {
527
+ const hasKnownExtension = /\.(?:tsx|ts|jsx|js)$/.test(basePath);
528
+ const candidates = [
529
+ hasKnownExtension ? basePath : undefined,
530
+ basePath.endsWith(".g") ? `${basePath}.ts` : undefined,
531
+ basePath.endsWith(".g") ? `${basePath}.tsx` : undefined,
532
+ !hasKnownExtension ? `${basePath}.g.ts` : undefined,
533
+ !hasKnownExtension ? `${basePath}.g.tsx` : undefined,
534
+ !hasKnownExtension ? `${basePath}.ts` : undefined,
535
+ !hasKnownExtension ? `${basePath}.tsx` : undefined,
536
+ !hasKnownExtension ? `${basePath}.js` : undefined,
537
+ !hasKnownExtension ? `${basePath}.jsx` : undefined,
538
+ join(basePath, "index.g.ts"),
539
+ join(basePath, "index.g.tsx"),
540
+ join(basePath, "index.ts"),
541
+ join(basePath, "index.tsx"),
542
+ join(basePath, "index.js"),
543
+ join(basePath, "index.jsx"),
544
+ ];
545
+ return [...new Set(candidates.filter((candidate) => Boolean(candidate)))];
546
+ }
547
+ function sourceFileForAbsolutePath(filePath, cache) {
548
+ const cached = cache?.sourceFilesByPath.get(filePath);
549
+ if (cached)
550
+ return cached;
551
+ if (!isFile(filePath))
552
+ return undefined;
553
+ const sourceFile = ts.createSourceFile(filePath, readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
554
+ cache?.sourceFilesByPath.set(filePath, sourceFile);
555
+ return sourceFile;
556
+ }
557
+ function isFile(filePath) {
558
+ try {
559
+ return statSync(filePath).isFile();
560
+ }
561
+ catch {
562
+ return false;
563
+ }
564
+ }
565
+ function getImportedNames(sourceFile) {
566
+ const names = new Set();
567
+ for (const statement of sourceFile.statements) {
568
+ if (!ts.isImportDeclaration(statement))
569
+ continue;
570
+ const clause = statement.importClause;
571
+ if (!clause)
572
+ continue;
573
+ if (clause.name) {
574
+ names.add(clause.name.text);
575
+ }
576
+ const namedBindings = clause.namedBindings;
577
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
578
+ continue;
579
+ for (const element of namedBindings.elements) {
580
+ names.add(element.name.text);
581
+ }
582
+ }
583
+ return names;
584
+ }
585
+ function readVueInjectedProviderNames(source) {
586
+ const providers = new Set();
587
+ for (const script of extractVueScriptBlocks(source)) {
588
+ const sourceFile = ts.createSourceFile("component.vue-script.ts", script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
589
+ visit(sourceFile);
590
+ }
591
+ return providers;
592
+ function visit(node) {
593
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "inject") {
594
+ const providerExpression = node.arguments[0] ? unwrapExpression(node.arguments[0]) : undefined;
595
+ if (providerExpression && ts.isIdentifier(providerExpression)) {
596
+ providers.add(providerExpression.text);
597
+ }
598
+ }
599
+ ts.forEachChild(node, visit);
600
+ }
601
+ }
602
+ function extractVueScriptBlocks(source) {
603
+ const pattern = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
604
+ const scripts = [];
605
+ let match;
606
+ while ((match = pattern.exec(source))) {
607
+ scripts.push(match[1] ?? "");
608
+ }
609
+ return scripts;
610
+ }
611
+ function validateVueProviderSelections(frame, providerFrames, diagnostics, file) {
612
+ if (!frame.providers)
613
+ return;
614
+ for (const providerName of frame.providers) {
615
+ if (!providerFrames[providerName]) {
616
+ diagnostics.push({
617
+ stage: "contract-extraction",
618
+ severity: "error",
619
+ code: "missing-provider",
620
+ message: `Vue frame "${frame.name}" provides unknown injection key "${providerName}".`,
621
+ file,
622
+ frameName: frame.name,
623
+ });
624
+ }
625
+ }
626
+ }
627
+ function validateVueProviderVariantSelections(frame, providerFrames, diagnostics, file) {
628
+ if (!frame.providerVariants)
629
+ return;
630
+ for (const [providerName, selection] of Object.entries(frame.providerVariants)) {
631
+ const variants = providerVariantSelectionValues(selection);
632
+ const provider = providerFrames[providerName];
633
+ if (!provider) {
634
+ diagnostics.push({
635
+ stage: "contract-extraction",
636
+ severity: "error",
637
+ code: "missing-provider",
638
+ message: `Vue frame "${frame.name}" marks unknown injection key "${providerName}" variants "${variants.join(", ")}".`,
639
+ file,
640
+ frameName: frame.name,
641
+ });
642
+ continue;
643
+ }
644
+ if (!provider.variants || provider.variants.length === 0) {
645
+ diagnostics.push({
646
+ stage: "contract-extraction",
647
+ severity: "error",
648
+ code: "missing-provider-variants",
649
+ message: `Vue frame "${frame.name}" marks injection key "${providerName}" variants "${variants.join(", ")}", but "${providerName}" does not declare variants.`,
650
+ file,
651
+ frameName: frame.name,
652
+ });
653
+ continue;
654
+ }
655
+ const unknownVariants = variants.filter((variant) => !provider.variants?.includes(variant));
656
+ if (unknownVariants.length > 0) {
657
+ diagnostics.push({
658
+ stage: "contract-extraction",
659
+ severity: "error",
660
+ code: "unknown-provider-variant",
661
+ message: `Vue frame "${frame.name}" marks unknown "${providerName}" variants "${unknownVariants.join(", ")}". Expected one of: ${provider.variants.join(", ")}.`,
662
+ file,
663
+ frameName: frame.name,
664
+ });
665
+ }
666
+ }
667
+ }
668
+ function validateVueProviderVariantCoverage(consumedProviderNames, providerFrames, selectedFrames, diagnostics, file) {
669
+ for (const providerName of consumedProviderNames) {
670
+ const provider = providerFrames[providerName];
671
+ if (!provider?.variants || provider.variants.length === 0)
672
+ continue;
673
+ const coveredVariants = new Set(selectedFrames.flatMap((frame) => {
674
+ const selection = frame.providerVariants?.[providerName];
675
+ return selection ? providerVariantSelectionValues(selection) : [];
676
+ }));
677
+ const missingVariants = provider.variants.filter((variant) => !coveredVariants.has(variant));
678
+ if (missingVariants.length === 0)
679
+ continue;
680
+ diagnostics.push({
681
+ stage: "contract-extraction",
682
+ severity: "error",
683
+ code: "missing-provider-variant-frames",
684
+ message: `Runelight Vue entry injects "${providerName}" but its frames do not cover variants: ${missingVariants.join(", ")}.`,
685
+ file,
686
+ });
687
+ }
688
+ }
689
+ function validateVueTemplateReachability(source, staticFrames, diagnostics, file) {
690
+ if (staticFrames.length === 0)
691
+ return;
692
+ const template = extractVueTemplateBlock(source);
693
+ if (!template)
694
+ return;
695
+ const ast = baseParse(template.content);
696
+ const context = {
697
+ aliases: new Map(),
698
+ injectedBindings: readVueInjectedProviderBindings(source),
699
+ };
700
+ const branches = reachableVueTemplateBranches(ast, context);
701
+ const reported = new Set();
702
+ for (const branch of branches) {
703
+ const opaque = firstOpaqueVueTemplatePredicate(branch.condition);
704
+ if (opaque) {
705
+ const key = `opaque:${branch.tagName}:${opaque.text}`;
706
+ if (reported.has(key))
707
+ continue;
708
+ reported.add(key);
709
+ diagnostics.push({
710
+ stage: "contract-extraction",
711
+ severity: "error",
712
+ code: "opaque-vue-template-control-flow",
713
+ message: `Vue template branch <${branch.tagName}> is controlled by an opaque expression "${opaque.text}". Use props, frame scope, or injected context values directly so frames can cover the template structure.`,
714
+ file,
715
+ });
716
+ continue;
717
+ }
718
+ const evaluations = staticFrames.map((frame) => evaluateVueTemplatePredicate(branch.condition, frame.values));
719
+ const coveredFrameNames = staticFrames.filter((_frame, index) => evaluations[index] === true).map((frame) => frame.name);
720
+ if (coveredFrameNames.length > 0)
721
+ continue;
722
+ const unknownFrameNames = staticFrames.filter((_frame, index) => evaluations[index] === "unknown").map((frame) => frame.name);
723
+ if (unknownFrameNames.length > 0) {
724
+ const key = `unknown:${branch.tagName}:${formatVueTemplatePredicate(branch.condition)}`;
725
+ if (reported.has(key))
726
+ continue;
727
+ reported.add(key);
728
+ diagnostics.push({
729
+ stage: "contract-extraction",
730
+ severity: "error",
731
+ code: "unknown-vue-branch-coverage",
732
+ message: `Vue template branch <${branch.tagName}> is controlled by "${formatVueTemplatePredicate(branch.condition)}", but frame values are not static enough to prove coverage. Unknown frames: ${unknownFrameNames.join(", ")}.`,
733
+ file,
734
+ });
735
+ continue;
736
+ }
737
+ const key = `uncovered:${branch.tagName}:${formatVueTemplatePredicate(branch.condition)}`;
738
+ if (reported.has(key))
739
+ continue;
740
+ reported.add(key);
741
+ diagnostics.push({
742
+ stage: "contract-extraction",
743
+ severity: "error",
744
+ code: "uncovered-vue-template-branch",
745
+ message: `No frame renders Vue template branch <${branch.tagName}> behind "${formatVueTemplatePredicate(branch.condition)}". Add a frame whose props, frame scope, or injected context values make that branch reachable.`,
746
+ file,
747
+ });
748
+ }
749
+ }
750
+ function reachableVueTemplateBranches(root, context) {
751
+ return reachableVueTemplateChildBranches(root.children, { kind: "always" }, context);
752
+ }
753
+ function reachableVueTemplateChildBranches(children, condition, context) {
754
+ const branches = [];
755
+ const consumed = new Set();
756
+ for (let index = 0; index < children.length; index += 1) {
757
+ if (consumed.has(index))
758
+ continue;
759
+ const child = children[index];
760
+ if (child.type !== NodeTypes.ELEMENT)
761
+ continue;
762
+ const ifDirective = vueDirective(child, "if");
763
+ if (ifDirective) {
764
+ const chain = collectVueIfChain(children, index);
765
+ for (const chainIndex of chain.indices)
766
+ consumed.add(chainIndex);
767
+ branches.push(...reachableVueIfChainBranches(chain.elements, condition, context));
768
+ continue;
769
+ }
770
+ if (vueDirective(child, "else-if") || vueDirective(child, "else"))
771
+ continue;
772
+ branches.push(...reachableVueElementBranches(child, condition, context));
773
+ }
774
+ return branches;
775
+ }
776
+ function collectVueIfChain(children, startIndex) {
777
+ const elements = [];
778
+ const indices = [];
779
+ for (let index = startIndex; index < children.length; index += 1) {
780
+ const child = children[index];
781
+ if (isWhitespaceVueTextNode(child))
782
+ continue;
783
+ if (child.type !== NodeTypes.ELEMENT)
784
+ break;
785
+ if (index === startIndex) {
786
+ if (!vueDirective(child, "if"))
787
+ break;
788
+ }
789
+ else if (!vueDirective(child, "else-if") && !vueDirective(child, "else")) {
790
+ break;
791
+ }
792
+ elements.push(child);
793
+ indices.push(index);
794
+ if (vueDirective(child, "else"))
795
+ break;
796
+ }
797
+ return { elements, indices };
798
+ }
799
+ function reachableVueIfChainBranches(elements, condition, context) {
800
+ const branches = [];
801
+ let previousBranches = { kind: "static", value: false };
802
+ for (const element of elements) {
803
+ const ifDirective = vueDirective(element, "if") ?? vueDirective(element, "else-if");
804
+ const elseDirective = vueDirective(element, "else");
805
+ const ownPredicate = elseDirective ? { kind: "always" } : vuePredicateFromDirective(ifDirective, context);
806
+ const branchCondition = andVueTemplatePredicates(condition, andVueTemplatePredicates(notVueTemplatePredicate(previousBranches), ownPredicate));
807
+ branches.push({ condition: branchCondition, expression: ifDirective?.exp?.loc.source, tagName: element.tag });
808
+ branches.push(...reachableVueElementBranches(element, branchCondition, context, { skipIfDirective: true }));
809
+ previousBranches = orVueTemplatePredicates(previousBranches, ownPredicate);
810
+ }
811
+ return branches;
812
+ }
813
+ function reachableVueElementBranches(element, condition, context, options = {}) {
814
+ const branches = [];
815
+ let elementCondition = condition;
816
+ if (!options.skipIfDirective) {
817
+ const ifDirective = vueDirective(element, "if");
818
+ if (ifDirective) {
819
+ elementCondition = andVueTemplatePredicates(elementCondition, vuePredicateFromDirective(ifDirective, context));
820
+ branches.push({ condition: elementCondition, expression: ifDirective.exp?.loc.source, tagName: element.tag });
821
+ }
822
+ }
823
+ const showDirective = vueDirective(element, "show");
824
+ if (showDirective) {
825
+ const showCondition = andVueTemplatePredicates(elementCondition, vuePredicateFromDirective(showDirective, context));
826
+ branches.push({ condition: showCondition, expression: showDirective.exp?.loc.source, tagName: element.tag });
827
+ }
828
+ const forDirective = vueDirective(element, "for");
829
+ let childContext = context;
830
+ let childCondition = elementCondition;
831
+ const forExpression = forDirective?.exp?.loc.source.trim();
832
+ const parsedForExpression = forExpression ? parseVueForExpression(forExpression) : undefined;
833
+ if (forDirective && parsedForExpression?.source) {
834
+ const sourcePath = vueExpressionPath(parsedForExpression.source);
835
+ if (sourcePath) {
836
+ const sourceRef = vueTemplateReferenceForPath(sourcePath, context);
837
+ const nonEmptyCondition = andVueTemplatePredicates(elementCondition, nonEmptyVueCollectionPredicate(sourceRef));
838
+ branches.push({ condition: nonEmptyCondition, expression: forDirective.exp?.loc.source, tagName: element.tag });
839
+ childCondition = nonEmptyCondition;
840
+ if (parsedForExpression.value) {
841
+ const aliases = new Map(context.aliases);
842
+ aliases.set(parsedForExpression.value, `${sourceRef.candidates[0]}.number`);
843
+ childContext = { ...context, aliases };
844
+ }
845
+ }
846
+ else {
847
+ const opaque = opaqueVueTemplatePredicate(forDirective.exp?.loc.source ?? "v-for", "v-for");
848
+ branches.push({ condition: andVueTemplatePredicates(elementCondition, opaque), expression: forDirective.exp?.loc.source, tagName: element.tag });
849
+ }
850
+ }
851
+ const dynamicIsExpression = vueDynamicIsExpression(element);
852
+ if (dynamicIsExpression) {
853
+ const dynamicIsPredicate = parseVueTemplatePredicateExpression(dynamicIsExpression, childContext);
854
+ branches.push({ condition: andVueTemplatePredicates(childCondition, dynamicIsPredicate), expression: dynamicIsExpression, tagName: element.tag });
855
+ }
856
+ branches.push(...reachableVueTemplateChildBranches(element.children, childCondition, childContext));
857
+ return branches;
858
+ }
859
+ function parseVueForExpression(expression) {
860
+ const match = /^(.*?)\s+(?:in|of)\s+(.+)$/.exec(expression.trim());
861
+ if (!match)
862
+ return undefined;
863
+ const left = (match[1] ?? "").trim().replace(/^\(/, "").replace(/\)$/, "");
864
+ const source = (match[2] ?? "").trim();
865
+ const value = left.split(",", 1)[0]?.trim();
866
+ return source ? { source, ...(value ? { value } : {}) } : undefined;
867
+ }
868
+ function vueDirective(element, name) {
869
+ return element.props.find((property) => property.type === NodeTypes.DIRECTIVE && property.name === name);
870
+ }
871
+ function vuePredicateFromDirective(directive, context) {
872
+ const expression = directive?.exp?.loc.source.trim();
873
+ if (!expression)
874
+ return { kind: "static", value: true };
875
+ return parseVueTemplatePredicateExpression(expression, context);
876
+ }
877
+ function parseVueTemplatePredicateExpression(expression, context) {
878
+ const parsed = parseVueTemplateExpression(expression);
879
+ if (!parsed)
880
+ return opaqueVueTemplatePredicate(expression, "parse");
881
+ return parseVueTemplatePredicate(parsed, context);
882
+ }
883
+ function parseVueTemplateExpression(expression) {
884
+ const sourceFile = ts.createSourceFile("vue-template-expression.ts", `const __runelightVueExpression = (${expression})`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
885
+ const statement = sourceFile.statements[0];
886
+ if (!statement || !ts.isVariableStatement(statement))
887
+ return undefined;
888
+ const declaration = statement.declarationList.declarations[0];
889
+ if (!declaration?.initializer)
890
+ return undefined;
891
+ return declaration.initializer;
892
+ }
893
+ function parseVueTemplatePredicate(expression, context) {
894
+ const value = unwrapExpression(expression);
895
+ if (ts.isPrefixUnaryExpression(value) && value.operator === ts.SyntaxKind.ExclamationToken) {
896
+ return notVueTemplatePredicate(parseVueTemplatePredicate(value.operand, context));
897
+ }
898
+ if (ts.isBinaryExpression(value)) {
899
+ if (value.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
900
+ return andVueTemplatePredicates(parseVueTemplatePredicate(value.left, context), parseVueTemplatePredicate(value.right, context));
901
+ }
902
+ if (value.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
903
+ return orVueTemplatePredicates(parseVueTemplatePredicate(value.left, context), parseVueTemplatePredicate(value.right, context));
904
+ }
905
+ const binaryPredicate = parseVueBinaryTemplatePredicate(value, context);
906
+ if (binaryPredicate)
907
+ return binaryPredicate;
908
+ }
909
+ const staticValue = readVueStaticBranchValue(value);
910
+ if (staticValue) {
911
+ const truthy = evaluateVueStaticBranchValueTruthy(staticValue);
912
+ return truthy === "unknown" ? opaqueVueTemplatePredicate(value.getText(), "static") : { kind: "static", value: truthy };
913
+ }
914
+ const reference = vueTemplateReferenceForExpression(value, context);
915
+ if (reference)
916
+ return { kind: "truthy", ref: reference };
917
+ return opaqueVueTemplatePredicate(value.getText(), "expression");
918
+ }
919
+ function parseVueBinaryTemplatePredicate(expression, context) {
920
+ const leftReference = vueTemplateReferenceForExpression(expression.left, context);
921
+ const rightReference = vueTemplateReferenceForExpression(expression.right, context);
922
+ const leftValue = readVueStaticBranchValue(expression.left);
923
+ const rightValue = readVueStaticBranchValue(expression.right);
924
+ if (leftReference && rightReference) {
925
+ const predicate = { kind: "equals-ref", left: leftReference, right: rightReference };
926
+ if (expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken || expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken) {
927
+ return predicate;
928
+ }
929
+ if (expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken || expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
930
+ return notVueTemplatePredicate(predicate);
931
+ }
932
+ return undefined;
933
+ }
934
+ if (leftReference && rightValue) {
935
+ return vueComparisonPredicate(leftReference, expression.operatorToken.kind, rightValue);
936
+ }
937
+ if (rightReference && leftValue) {
938
+ return vueComparisonPredicate(rightReference, flipVueComparisonOperator(expression.operatorToken.kind), leftValue);
939
+ }
940
+ return undefined;
941
+ }
942
+ function vueComparisonPredicate(reference, operator, value) {
943
+ if (operator === ts.SyntaxKind.EqualsEqualsEqualsToken || operator === ts.SyntaxKind.EqualsEqualsToken) {
944
+ return { kind: "equals", ref: reference, value };
945
+ }
946
+ if (operator === ts.SyntaxKind.ExclamationEqualsEqualsToken || operator === ts.SyntaxKind.ExclamationEqualsToken) {
947
+ return notVueTemplatePredicate({ kind: "equals", ref: reference, value });
948
+ }
949
+ if (operator === ts.SyntaxKind.LessThanToken)
950
+ return { kind: "relation", ref: reference, operator: "<", value };
951
+ if (operator === ts.SyntaxKind.LessThanEqualsToken)
952
+ return { kind: "relation", ref: reference, operator: "<=", value };
953
+ if (operator === ts.SyntaxKind.GreaterThanToken)
954
+ return { kind: "relation", ref: reference, operator: ">", value };
955
+ if (operator === ts.SyntaxKind.GreaterThanEqualsToken)
956
+ return { kind: "relation", ref: reference, operator: ">=", value };
957
+ return undefined;
958
+ }
959
+ function flipVueComparisonOperator(operator) {
960
+ if (operator === ts.SyntaxKind.LessThanToken)
961
+ return ts.SyntaxKind.GreaterThanToken;
962
+ if (operator === ts.SyntaxKind.LessThanEqualsToken)
963
+ return ts.SyntaxKind.GreaterThanEqualsToken;
964
+ if (operator === ts.SyntaxKind.GreaterThanToken)
965
+ return ts.SyntaxKind.LessThanToken;
966
+ if (operator === ts.SyntaxKind.GreaterThanEqualsToken)
967
+ return ts.SyntaxKind.LessThanEqualsToken;
968
+ return operator;
969
+ }
970
+ function vueTemplateReferenceForExpression(expression, context) {
971
+ const path = vueExpressionPath(expression);
972
+ return path ? vueTemplateReferenceForPath(path, context) : undefined;
973
+ }
974
+ function vueExpressionPath(expression) {
975
+ const parsed = typeof expression === "string" ? parseVueTemplateExpression(expression) : expression;
976
+ const value = parsed ? unwrapExpression(parsed) : undefined;
977
+ if (!value)
978
+ return undefined;
979
+ if (ts.isIdentifier(value))
980
+ return [value.text];
981
+ if (ts.isPropertyAccessExpression(value)) {
982
+ const parent = vueExpressionPath(value.expression);
983
+ return parent ? [...parent, value.name.text] : undefined;
984
+ }
985
+ if (ts.isElementAccessExpression(value)) {
986
+ const parent = vueExpressionPath(value.expression);
987
+ const argument = value.argumentExpression ? unwrapExpression(value.argumentExpression) : undefined;
988
+ if (!parent || !argument)
989
+ return undefined;
990
+ if (ts.isStringLiteral(argument) || ts.isNumericLiteral(argument))
991
+ return [...parent, argument.text];
992
+ }
993
+ return undefined;
994
+ }
995
+ function vueTemplateReferenceForPath(path, context) {
996
+ const [root, ...rest] = path;
997
+ if (!root)
998
+ return { candidates: [], label: "" };
999
+ const alias = context.aliases.get(root);
1000
+ if (alias)
1001
+ return { candidates: [[alias, ...rest].join(".")], label: path.join(".") };
1002
+ const providerName = context.injectedBindings.get(root);
1003
+ if (providerName)
1004
+ return { candidates: [[`context.${providerName}`, ...rest].join(".")], label: path.join(".") };
1005
+ if (root === "props")
1006
+ return { candidates: [`props.${rest.join(".")}`.replace(/\.$/, "")], label: path.join(".") };
1007
+ if (root === "scope")
1008
+ return { candidates: [`scope.${rest.join(".")}`.replace(/\.$/, "")], label: path.join(".") };
1009
+ return { candidates: [`scope.${path.join(".")}`, `props.${path.join(".")}`], label: path.join(".") };
1010
+ }
1011
+ function nonEmptyVueCollectionPredicate(reference) {
1012
+ return { kind: "relation", ref: { candidates: reference.candidates.map((candidate) => `${candidate}.length`), label: `${reference.label}.length` }, operator: ">", value: { kind: "number", value: 0 } };
1013
+ }
1014
+ function vueDynamicIsExpression(element) {
1015
+ if (element.tag !== "component")
1016
+ return undefined;
1017
+ const bind = element.props.find((property) => {
1018
+ return (property.type === NodeTypes.DIRECTIVE &&
1019
+ property.name === "bind" &&
1020
+ property.arg?.type === NodeTypes.SIMPLE_EXPRESSION &&
1021
+ property.arg.content === "is");
1022
+ });
1023
+ return bind?.exp?.loc.source.trim();
1024
+ }
1025
+ function isWhitespaceVueTextNode(node) {
1026
+ return node.type === NodeTypes.TEXT && node.content.trim() === "";
1027
+ }
1028
+ function extractVueTemplateBlock(source) {
1029
+ const match = /<template\b[^>]*>([\s\S]*?)<\/template>/i.exec(source);
1030
+ return match ? { content: match[1] ?? "" } : undefined;
1031
+ }
1032
+ function readVueInjectedProviderBindings(source) {
1033
+ const bindings = new Map();
1034
+ for (const script of extractVueScriptBlocks(source)) {
1035
+ const sourceFile = ts.createSourceFile("component.vue-script.ts", script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
1036
+ for (const statement of sourceFile.statements) {
1037
+ if (!ts.isVariableStatement(statement))
1038
+ continue;
1039
+ for (const declaration of statement.declarationList.declarations) {
1040
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
1041
+ continue;
1042
+ const initializer = unwrapExpression(declaration.initializer);
1043
+ if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression) || initializer.expression.text !== "inject")
1044
+ continue;
1045
+ const providerExpression = initializer.arguments[0] ? unwrapExpression(initializer.arguments[0]) : undefined;
1046
+ if (providerExpression && ts.isIdentifier(providerExpression)) {
1047
+ bindings.set(declaration.name.text, providerExpression.text);
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ return bindings;
1053
+ }
1054
+ function firstOpaqueVueTemplatePredicate(predicate) {
1055
+ if (predicate.kind === "opaque")
1056
+ return predicate;
1057
+ if (predicate.kind === "not")
1058
+ return firstOpaqueVueTemplatePredicate(predicate.predicate);
1059
+ if (predicate.kind === "and" || predicate.kind === "or") {
1060
+ for (const child of predicate.predicates) {
1061
+ const opaque = firstOpaqueVueTemplatePredicate(child);
1062
+ if (opaque)
1063
+ return opaque;
1064
+ }
1065
+ }
1066
+ return undefined;
1067
+ }
1068
+ function evaluateVueTemplatePredicate(predicate, values) {
1069
+ if (predicate.kind === "always")
1070
+ return true;
1071
+ if (predicate.kind === "static")
1072
+ return predicate.value;
1073
+ if (predicate.kind === "opaque")
1074
+ return "unknown";
1075
+ if (predicate.kind === "truthy")
1076
+ return evaluateVueStaticBranchValueTruthy(vueStaticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" });
1077
+ if (predicate.kind === "equals") {
1078
+ return evaluateSameVueStaticBranchValue(vueStaticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" }, predicate.value);
1079
+ }
1080
+ if (predicate.kind === "equals-ref") {
1081
+ return evaluateSameVueStaticBranchValue(vueStaticBranchValueForReference(values, predicate.left) ?? { kind: "undefined" }, vueStaticBranchValueForReference(values, predicate.right) ?? { kind: "undefined" });
1082
+ }
1083
+ if (predicate.kind === "relation") {
1084
+ return evaluateVueStaticBranchRelation(vueStaticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" }, predicate.operator, predicate.value);
1085
+ }
1086
+ if (predicate.kind === "not")
1087
+ return evaluateNegatedVueTemplatePredicate(predicate.predicate, values);
1088
+ if (predicate.kind === "and") {
1089
+ let unknown = false;
1090
+ for (const child of predicate.predicates) {
1091
+ const value = evaluateVueTemplatePredicate(child, values);
1092
+ if (value === false)
1093
+ return false;
1094
+ if (value === "unknown")
1095
+ unknown = true;
1096
+ }
1097
+ return unknown ? "unknown" : true;
1098
+ }
1099
+ if (predicate.kind === "or") {
1100
+ let unknown = false;
1101
+ for (const child of predicate.predicates) {
1102
+ const value = evaluateVueTemplatePredicate(child, values);
1103
+ if (value === true)
1104
+ return true;
1105
+ if (value === "unknown")
1106
+ unknown = true;
1107
+ }
1108
+ return unknown ? "unknown" : false;
1109
+ }
1110
+ return "unknown";
1111
+ }
1112
+ function evaluateNegatedVueTemplatePredicate(predicate, values) {
1113
+ if (predicate.kind === "truthy") {
1114
+ const truthy = evaluateVueStaticBranchValueTruthy(vueStaticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" });
1115
+ return truthy === "unknown" ? "unknown" : !truthy;
1116
+ }
1117
+ if (predicate.kind === "equals") {
1118
+ const result = evaluateSameVueStaticBranchValue(vueStaticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" }, predicate.value);
1119
+ return result === "unknown" ? "unknown" : !result;
1120
+ }
1121
+ if (predicate.kind === "equals-ref") {
1122
+ const result = evaluateSameVueStaticBranchValue(vueStaticBranchValueForReference(values, predicate.left) ?? { kind: "undefined" }, vueStaticBranchValueForReference(values, predicate.right) ?? { kind: "undefined" });
1123
+ return result === "unknown" ? "unknown" : !result;
1124
+ }
1125
+ if (predicate.kind === "relation") {
1126
+ const result = evaluateVueStaticBranchRelation(vueStaticBranchValueForReference(values, predicate.ref) ?? { kind: "undefined" }, predicate.operator, predicate.value);
1127
+ return result === "unknown" ? "unknown" : !result;
1128
+ }
1129
+ if (predicate.kind === "and")
1130
+ return evaluateVueTemplatePredicate({ kind: "or", predicates: predicate.predicates.map(notVueTemplatePredicate) }, values);
1131
+ if (predicate.kind === "or")
1132
+ return evaluateVueTemplatePredicate({ kind: "and", predicates: predicate.predicates.map(notVueTemplatePredicate) }, values);
1133
+ const value = evaluateVueTemplatePredicate(predicate, values);
1134
+ return value === "unknown" ? "unknown" : !value;
1135
+ }
1136
+ function vueStaticBranchValueForReference(values, reference) {
1137
+ for (const candidate of reference.candidates) {
1138
+ const value = vueStaticBranchValueForKey(values, candidate);
1139
+ if (value)
1140
+ return value;
1141
+ }
1142
+ return undefined;
1143
+ }
1144
+ function vueStaticBranchValueForKey(values, key) {
1145
+ const exact = values.get(key);
1146
+ if (exact)
1147
+ return exact;
1148
+ const parts = key.split(".");
1149
+ while (parts.length > 1) {
1150
+ parts.pop();
1151
+ const ancestor = values.get(parts.join("."));
1152
+ if (ancestor && vueStaticBranchValueIncludesUnknown(ancestor))
1153
+ return { kind: "unknown" };
1154
+ }
1155
+ return undefined;
1156
+ }
1157
+ function vueStaticBranchValueIncludesUnknown(value) {
1158
+ return value.kind === "unknown" || (value.kind === "oneOf" && value.values.some(vueStaticBranchValueIncludesUnknown));
1159
+ }
1160
+ function evaluateVueStaticBranchValueTruthy(value) {
1161
+ if (value.kind === "unknown")
1162
+ return "unknown";
1163
+ if (value.kind === "oneOf") {
1164
+ let unknown = false;
1165
+ for (const option of value.values) {
1166
+ const result = evaluateVueStaticBranchValueTruthy(option);
1167
+ if (result === true)
1168
+ return true;
1169
+ if (result === "unknown")
1170
+ unknown = true;
1171
+ }
1172
+ return unknown ? "unknown" : false;
1173
+ }
1174
+ return vueStaticBranchValueTruthy(value);
1175
+ }
1176
+ function evaluateSameVueStaticBranchValue(left, right) {
1177
+ if (left.kind === "unknown" || right.kind === "unknown")
1178
+ return "unknown";
1179
+ if (left.kind === "oneOf") {
1180
+ let unknown = false;
1181
+ for (const option of left.values) {
1182
+ const result = evaluateSameVueStaticBranchValue(option, right);
1183
+ if (result === true)
1184
+ return true;
1185
+ if (result === "unknown")
1186
+ unknown = true;
1187
+ }
1188
+ return unknown ? "unknown" : false;
1189
+ }
1190
+ if (right.kind === "oneOf") {
1191
+ let unknown = false;
1192
+ for (const option of right.values) {
1193
+ const result = evaluateSameVueStaticBranchValue(left, option);
1194
+ if (result === true)
1195
+ return true;
1196
+ if (result === "unknown")
1197
+ unknown = true;
1198
+ }
1199
+ return unknown ? "unknown" : false;
1200
+ }
1201
+ return sameVueStaticBranchValue(left, right);
1202
+ }
1203
+ function evaluateVueStaticBranchRelation(left, operator, right) {
1204
+ if (left.kind === "unknown" || right.kind === "unknown")
1205
+ return "unknown";
1206
+ const leftNumber = vueStaticBranchValueNumber(left);
1207
+ const rightNumber = vueStaticBranchValueNumber(right);
1208
+ if (leftNumber === undefined || rightNumber === undefined)
1209
+ return "unknown";
1210
+ if (operator === "<")
1211
+ return leftNumber < rightNumber;
1212
+ if (operator === "<=")
1213
+ return leftNumber <= rightNumber;
1214
+ if (operator === ">")
1215
+ return leftNumber > rightNumber;
1216
+ return leftNumber >= rightNumber;
1217
+ }
1218
+ function vueStaticBranchValueTruthy(value) {
1219
+ if (value.kind === "unknown")
1220
+ return false;
1221
+ if (value.kind === "oneOf")
1222
+ return value.values.some(vueStaticBranchValueTruthy);
1223
+ if (value.kind === "undefined" || value.kind === "null")
1224
+ return false;
1225
+ if (value.kind === "boolean")
1226
+ return value.value;
1227
+ if (value.kind === "number")
1228
+ return value.value !== 0 && !Number.isNaN(value.value);
1229
+ if (value.kind === "string")
1230
+ return value.value.length > 0;
1231
+ if (value.kind === "array" || value.kind === "object" || value.kind === "truthy")
1232
+ return true;
1233
+ return false;
1234
+ }
1235
+ function sameVueStaticBranchValue(left, right) {
1236
+ if (left.kind === "undefined" && right.kind === "undefined")
1237
+ return true;
1238
+ if (left.kind === "null" && right.kind === "null")
1239
+ return true;
1240
+ if (left.kind === "boolean" && right.kind === "boolean")
1241
+ return left.value === right.value;
1242
+ if (left.kind === "number" && right.kind === "number")
1243
+ return left.value === right.value;
1244
+ if (left.kind === "string" && right.kind === "string")
1245
+ return left.value === right.value;
1246
+ if (left.kind === "array" && right.kind === "array")
1247
+ return left.length === right.length;
1248
+ return false;
1249
+ }
1250
+ function vueStaticBranchValueNumber(value) {
1251
+ if (value.kind === "number")
1252
+ return value.value;
1253
+ if (value.kind === "array")
1254
+ return value.length;
1255
+ return undefined;
1256
+ }
1257
+ function andVueTemplatePredicates(left, right) {
1258
+ if (left.kind === "always")
1259
+ return right;
1260
+ if (right.kind === "always")
1261
+ return left;
1262
+ if (left.kind === "static" && left.value === false)
1263
+ return left;
1264
+ if (right.kind === "static" && right.value === false)
1265
+ return right;
1266
+ const predicates = [
1267
+ ...(left.kind === "and" ? left.predicates : [left]),
1268
+ ...(right.kind === "and" ? right.predicates : [right]),
1269
+ ].filter((predicate) => predicate.kind !== "always");
1270
+ return predicates.length === 1 ? predicates[0] : { kind: "and", predicates };
1271
+ }
1272
+ function orVueTemplatePredicates(left, right) {
1273
+ if (left.kind === "always" || right.kind === "always")
1274
+ return { kind: "always" };
1275
+ if (left.kind === "static" && left.value === false)
1276
+ return right;
1277
+ if (right.kind === "static" && right.value === false)
1278
+ return left;
1279
+ const predicates = [
1280
+ ...(left.kind === "or" ? left.predicates : [left]),
1281
+ ...(right.kind === "or" ? right.predicates : [right]),
1282
+ ];
1283
+ return predicates.length === 1 ? predicates[0] : { kind: "or", predicates };
1284
+ }
1285
+ function notVueTemplatePredicate(predicate) {
1286
+ if (predicate.kind === "static")
1287
+ return { kind: "static", value: !predicate.value };
1288
+ if (predicate.kind === "not")
1289
+ return predicate.predicate;
1290
+ return { kind: "not", predicate };
1291
+ }
1292
+ function opaqueVueTemplatePredicate(text, reason) {
1293
+ return { kind: "opaque", reason, text };
1294
+ }
1295
+ function formatVueTemplatePredicate(predicate) {
1296
+ if (predicate.kind === "always")
1297
+ return "always";
1298
+ if (predicate.kind === "static")
1299
+ return String(predicate.value);
1300
+ if (predicate.kind === "truthy")
1301
+ return predicate.ref.label;
1302
+ if (predicate.kind === "equals")
1303
+ return `${predicate.ref.label} === ${formatVueStaticBranchValue(predicate.value)}`;
1304
+ if (predicate.kind === "equals-ref")
1305
+ return `${predicate.left.label} === ${predicate.right.label}`;
1306
+ if (predicate.kind === "relation")
1307
+ return `${predicate.ref.label} ${predicate.operator} ${formatVueStaticBranchValue(predicate.value)}`;
1308
+ if (predicate.kind === "not")
1309
+ return `!(${formatVueTemplatePredicate(predicate.predicate)})`;
1310
+ if (predicate.kind === "and")
1311
+ return predicate.predicates.map(formatVueTemplatePredicate).join(" && ");
1312
+ if (predicate.kind === "or")
1313
+ return predicate.predicates.map(formatVueTemplatePredicate).join(" || ");
1314
+ return predicate.text;
1315
+ }
1316
+ function formatVueStaticBranchValue(value) {
1317
+ if (value.kind === "boolean")
1318
+ return String(value.value);
1319
+ if (value.kind === "number")
1320
+ return String(value.value);
1321
+ if (value.kind === "string")
1322
+ return JSON.stringify(value.value);
1323
+ if (value.kind === "array")
1324
+ return `array(length=${value.length})`;
1325
+ if (value.kind === "oneOf")
1326
+ return `oneOf(${value.values.map(formatVueStaticBranchValue).join(", ")})`;
1327
+ return value.kind;
1328
+ }
1329
+ function providerVariantSelectionValues(selection) {
1330
+ return Array.isArray(selection) ? selection : [selection];
1331
+ }
1332
+ function hasStaticProperty(objectLiteral, propertyName) {
1333
+ return objectLiteral.properties.some((property) => ts.isPropertyAssignment(property) && getStaticPropertyName(property.name) === propertyName);
1334
+ }
1335
+ function getStaticPropertyName(name) {
1336
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
1337
+ return name.text;
1338
+ }
1339
+ return undefined;
1340
+ }
1341
+ function unwrapExpression(expression) {
1342
+ if (ts.isSatisfiesExpression(expression) ||
1343
+ ts.isAsExpression(expression) ||
1344
+ ts.isNonNullExpression(expression) ||
1345
+ ts.isParenthesizedExpression(expression)) {
1346
+ return unwrapExpression(expression.expression);
1347
+ }
1348
+ return expression;
1349
+ }
1350
+ function parseEntryCoordinate(entry) {
1351
+ const [file, exportName] = entry.split("#", 2);
1352
+ return {
1353
+ file: file ?? entry,
1354
+ exportName,
1355
+ explicitExportName: entry.includes("#"),
1356
+ };
1357
+ }
1358
+ //# sourceMappingURL=contract-analyzer.js.map