@formspec/build 0.1.0-alpha.26 → 0.1.0-alpha.28

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.
package/dist/cli.cjs CHANGED
@@ -1235,6 +1235,74 @@ var init_extensions = __esm({
1235
1235
  }
1236
1236
  });
1237
1237
 
1238
+ // src/json-schema/schema.ts
1239
+ var import_zod3, jsonSchemaTypeSchema, jsonSchema7Schema;
1240
+ var init_schema2 = __esm({
1241
+ "src/json-schema/schema.ts"() {
1242
+ "use strict";
1243
+ import_zod3 = require("zod");
1244
+ jsonSchemaTypeSchema = import_zod3.z.enum([
1245
+ "string",
1246
+ "number",
1247
+ "integer",
1248
+ "boolean",
1249
+ "object",
1250
+ "array",
1251
+ "null"
1252
+ ]);
1253
+ jsonSchema7Schema = import_zod3.z.lazy(
1254
+ () => import_zod3.z.object({
1255
+ $schema: import_zod3.z.string().optional(),
1256
+ $id: import_zod3.z.string().optional(),
1257
+ $ref: import_zod3.z.string().optional(),
1258
+ // Metadata
1259
+ title: import_zod3.z.string().optional(),
1260
+ description: import_zod3.z.string().optional(),
1261
+ deprecated: import_zod3.z.boolean().optional(),
1262
+ // Type
1263
+ type: import_zod3.z.union([jsonSchemaTypeSchema, import_zod3.z.array(jsonSchemaTypeSchema)]).optional(),
1264
+ // String validation
1265
+ minLength: import_zod3.z.number().optional(),
1266
+ maxLength: import_zod3.z.number().optional(),
1267
+ pattern: import_zod3.z.string().optional(),
1268
+ // Number validation
1269
+ minimum: import_zod3.z.number().optional(),
1270
+ maximum: import_zod3.z.number().optional(),
1271
+ exclusiveMinimum: import_zod3.z.number().optional(),
1272
+ exclusiveMaximum: import_zod3.z.number().optional(),
1273
+ // Enum
1274
+ enum: import_zod3.z.array(import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean(), import_zod3.z.null()])).readonly().optional(),
1275
+ const: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean(), import_zod3.z.null()]).optional(),
1276
+ // Object
1277
+ properties: import_zod3.z.record(import_zod3.z.string(), jsonSchema7Schema).optional(),
1278
+ required: import_zod3.z.array(import_zod3.z.string()).optional(),
1279
+ additionalProperties: import_zod3.z.union([import_zod3.z.boolean(), jsonSchema7Schema]).optional(),
1280
+ // Array
1281
+ items: import_zod3.z.union([jsonSchema7Schema, import_zod3.z.array(jsonSchema7Schema)]).optional(),
1282
+ minItems: import_zod3.z.number().optional(),
1283
+ maxItems: import_zod3.z.number().optional(),
1284
+ // Composition
1285
+ allOf: import_zod3.z.array(jsonSchema7Schema).optional(),
1286
+ anyOf: import_zod3.z.array(jsonSchema7Schema).optional(),
1287
+ oneOf: import_zod3.z.array(jsonSchema7Schema).optional(),
1288
+ not: jsonSchema7Schema.optional(),
1289
+ // Conditional
1290
+ if: jsonSchema7Schema.optional(),
1291
+ then: jsonSchema7Schema.optional(),
1292
+ else: jsonSchema7Schema.optional(),
1293
+ // Format
1294
+ format: import_zod3.z.string().optional(),
1295
+ // Default
1296
+ default: import_zod3.z.unknown().optional(),
1297
+ // FormSpec extensions
1298
+ "x-formspec-source": import_zod3.z.string().optional(),
1299
+ "x-formspec-params": import_zod3.z.array(import_zod3.z.string()).readonly().optional(),
1300
+ "x-formspec-schemaSource": import_zod3.z.string().optional()
1301
+ }).passthrough()
1302
+ );
1303
+ }
1304
+ });
1305
+
1238
1306
  // src/analyzer/tsdoc-parser.ts
1239
1307
  function createFormSpecTSDocConfig(extensionTagNames = []) {
1240
1308
  const config = new import_tsdoc.TSDocConfiguration();
@@ -2162,9 +2230,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2162
2230
  }
2163
2231
  }
2164
2232
  }
2233
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2234
+ fields,
2235
+ classDecl,
2236
+ classType,
2237
+ checker,
2238
+ file,
2239
+ diagnostics
2240
+ );
2165
2241
  return {
2166
2242
  name,
2167
- fields,
2243
+ fields: specializedFields,
2168
2244
  fieldLayouts,
2169
2245
  typeRegistry,
2170
2246
  ...annotations.length > 0 && { annotations },
@@ -2204,10 +2280,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2204
2280
  }
2205
2281
  }
2206
2282
  }
2207
- const fieldLayouts = fields.map(() => ({}));
2283
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2284
+ fields,
2285
+ interfaceDecl,
2286
+ interfaceType,
2287
+ checker,
2288
+ file,
2289
+ diagnostics
2290
+ );
2291
+ const fieldLayouts = specializedFields.map(() => ({}));
2208
2292
  return {
2209
2293
  name,
2210
- fields,
2294
+ fields: specializedFields,
2211
2295
  fieldLayouts,
2212
2296
  typeRegistry,
2213
2297
  ...annotations.length > 0 && { annotations },
@@ -2256,12 +2340,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2256
2340
  }
2257
2341
  }
2258
2342
  }
2343
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2344
+ fields,
2345
+ typeAlias,
2346
+ aliasType,
2347
+ checker,
2348
+ file,
2349
+ diagnostics
2350
+ );
2259
2351
  return {
2260
2352
  ok: true,
2261
2353
  analysis: {
2262
2354
  name,
2263
- fields,
2264
- fieldLayouts: fields.map(() => ({})),
2355
+ fields: specializedFields,
2356
+ fieldLayouts: specializedFields.map(() => ({})),
2265
2357
  typeRegistry,
2266
2358
  ...annotations.length > 0 && { annotations },
2267
2359
  ...diagnostics.length > 0 && { diagnostics },
@@ -2270,6 +2362,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2270
2362
  }
2271
2363
  };
2272
2364
  }
2365
+ function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
2366
+ return {
2367
+ code,
2368
+ message,
2369
+ severity: "error",
2370
+ primaryLocation,
2371
+ relatedLocations
2372
+ };
2373
+ }
2374
+ function getLeadingParsedTags(node) {
2375
+ const sourceFile = node.getSourceFile();
2376
+ const sourceText = sourceFile.getFullText();
2377
+ const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
2378
+ if (commentRanges === void 0) {
2379
+ return [];
2380
+ }
2381
+ const parsedTags = [];
2382
+ for (const range of commentRanges) {
2383
+ if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
2384
+ continue;
2385
+ }
2386
+ const commentText = sourceText.slice(range.pos, range.end);
2387
+ if (!commentText.startsWith("/**")) {
2388
+ continue;
2389
+ }
2390
+ parsedTags.push(...(0, import_internal2.parseCommentBlock)(commentText, { offset: range.pos }).tags);
2391
+ }
2392
+ return parsedTags;
2393
+ }
2394
+ function findDiscriminatorProperty(node, fieldName) {
2395
+ if (ts3.isClassDeclaration(node)) {
2396
+ for (const member of node.members) {
2397
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2398
+ return member;
2399
+ }
2400
+ }
2401
+ return null;
2402
+ }
2403
+ if (ts3.isInterfaceDeclaration(node)) {
2404
+ for (const member of node.members) {
2405
+ if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2406
+ return member;
2407
+ }
2408
+ }
2409
+ return null;
2410
+ }
2411
+ if (ts3.isTypeLiteralNode(node.type)) {
2412
+ for (const member of node.type.members) {
2413
+ if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2414
+ return member;
2415
+ }
2416
+ }
2417
+ }
2418
+ return null;
2419
+ }
2420
+ function isLocalTypeParameterName(node, typeParameterName) {
2421
+ return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
2422
+ }
2423
+ function isNullishSemanticType(type) {
2424
+ if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
2425
+ return true;
2426
+ }
2427
+ return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
2428
+ }
2429
+ function isStringLikeSemanticType(type) {
2430
+ if (type.flags & ts3.TypeFlags.StringLike) {
2431
+ return true;
2432
+ }
2433
+ if (type.isUnion()) {
2434
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
2435
+ }
2436
+ return false;
2437
+ }
2438
+ function extractDiscriminatorDirective(node, file, diagnostics) {
2439
+ const discriminatorTags = getLeadingParsedTags(node).filter(
2440
+ (tag) => tag.normalizedTagName === "discriminator"
2441
+ );
2442
+ if (discriminatorTags.length === 0) {
2443
+ return null;
2444
+ }
2445
+ const [firstTag, ...duplicateTags] = discriminatorTags;
2446
+ for (const _duplicateTag of duplicateTags) {
2447
+ diagnostics.push(
2448
+ makeAnalysisDiagnostic(
2449
+ "DUPLICATE_TAG",
2450
+ 'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
2451
+ provenanceForNode(node, file)
2452
+ )
2453
+ );
2454
+ }
2455
+ if (firstTag === void 0) {
2456
+ return null;
2457
+ }
2458
+ const firstTarget = firstTag.target;
2459
+ if (firstTarget?.path === null || firstTarget?.valid !== true) {
2460
+ diagnostics.push(
2461
+ makeAnalysisDiagnostic(
2462
+ "INVALID_TAG_ARGUMENT",
2463
+ 'Tag "@discriminator" requires a direct path target like ":kind".',
2464
+ provenanceForNode(node, file)
2465
+ )
2466
+ );
2467
+ return null;
2468
+ }
2469
+ if (firstTarget.path.segments.length !== 1) {
2470
+ diagnostics.push(
2471
+ makeAnalysisDiagnostic(
2472
+ "INVALID_TAG_ARGUMENT",
2473
+ 'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
2474
+ provenanceForNode(node, file)
2475
+ )
2476
+ );
2477
+ return null;
2478
+ }
2479
+ const typeParameterName = firstTag.argumentText.trim();
2480
+ if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
2481
+ diagnostics.push(
2482
+ makeAnalysisDiagnostic(
2483
+ "INVALID_TAG_ARGUMENT",
2484
+ 'Tag "@discriminator" requires a local type parameter name as its source operand.',
2485
+ provenanceForNode(node, file)
2486
+ )
2487
+ );
2488
+ return null;
2489
+ }
2490
+ return {
2491
+ fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
2492
+ typeParameterName,
2493
+ provenance: provenanceForNode(node, file)
2494
+ };
2495
+ }
2496
+ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2497
+ const directive = extractDiscriminatorDirective(node, file, diagnostics);
2498
+ if (directive === null) {
2499
+ return null;
2500
+ }
2501
+ if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
2502
+ diagnostics.push(
2503
+ makeAnalysisDiagnostic(
2504
+ "INVALID_TAG_ARGUMENT",
2505
+ `Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
2506
+ directive.provenance
2507
+ )
2508
+ );
2509
+ return null;
2510
+ }
2511
+ const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
2512
+ if (propertyDecl === null) {
2513
+ diagnostics.push(
2514
+ makeAnalysisDiagnostic(
2515
+ "UNKNOWN_PATH_TARGET",
2516
+ `Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
2517
+ directive.provenance
2518
+ )
2519
+ );
2520
+ return null;
2521
+ }
2522
+ if (propertyDecl.questionToken !== void 0) {
2523
+ diagnostics.push(
2524
+ makeAnalysisDiagnostic(
2525
+ "TYPE_MISMATCH",
2526
+ `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
2527
+ directive.provenance,
2528
+ [provenanceForNode(propertyDecl, file)]
2529
+ )
2530
+ );
2531
+ return null;
2532
+ }
2533
+ const propertyType = checker.getTypeAtLocation(propertyDecl);
2534
+ if (isNullishSemanticType(propertyType)) {
2535
+ diagnostics.push(
2536
+ makeAnalysisDiagnostic(
2537
+ "TYPE_MISMATCH",
2538
+ `Discriminator field "${directive.fieldName}" must not be nullable.`,
2539
+ directive.provenance,
2540
+ [provenanceForNode(propertyDecl, file)]
2541
+ )
2542
+ );
2543
+ return null;
2544
+ }
2545
+ if (!isStringLikeSemanticType(propertyType)) {
2546
+ diagnostics.push(
2547
+ makeAnalysisDiagnostic(
2548
+ "TYPE_MISMATCH",
2549
+ `Discriminator field "${directive.fieldName}" must be string-like.`,
2550
+ directive.provenance,
2551
+ [provenanceForNode(propertyDecl, file)]
2552
+ )
2553
+ );
2554
+ return null;
2555
+ }
2556
+ return directive;
2557
+ }
2558
+ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
2559
+ const typeParameterIndex = node.typeParameters?.findIndex(
2560
+ (typeParameter) => typeParameter.name.text === typeParameterName
2561
+ ) ?? -1;
2562
+ if (typeParameterIndex < 0) {
2563
+ return null;
2564
+ }
2565
+ const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
2566
+ if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
2567
+ return referenceTypeArguments[typeParameterIndex] ?? null;
2568
+ }
2569
+ const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2570
+ return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2571
+ }
2572
+ function extractDeclarationApiName(node) {
2573
+ for (const tag of getLeadingParsedTags(node)) {
2574
+ if (tag.normalizedTagName !== "apiName") {
2575
+ continue;
2576
+ }
2577
+ if (tag.target === null && tag.argumentText.trim() !== "") {
2578
+ return tag.argumentText.trim();
2579
+ }
2580
+ if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
2581
+ const value = tag.argumentText.trim();
2582
+ if (value !== "") {
2583
+ return value;
2584
+ }
2585
+ }
2586
+ }
2587
+ return null;
2588
+ }
2589
+ function inferJsonFacingName(name) {
2590
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
2591
+ }
2592
+ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2593
+ if (seen.has(type)) {
2594
+ return null;
2595
+ }
2596
+ seen.add(type);
2597
+ const symbol = type.aliasSymbol ?? type.getSymbol();
2598
+ if (symbol !== void 0) {
2599
+ const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
2600
+ const targetSymbol = aliased ?? symbol;
2601
+ const declaration = targetSymbol.declarations?.find(
2602
+ (candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
2603
+ );
2604
+ if (declaration !== void 0) {
2605
+ if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
2606
+ return resolveNamedDiscriminatorDeclaration(
2607
+ checker.getTypeFromTypeNode(declaration.type),
2608
+ checker,
2609
+ seen
2610
+ );
2611
+ }
2612
+ return declaration;
2613
+ }
2614
+ }
2615
+ return null;
2616
+ }
2617
+ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
2618
+ if (boundType === null) {
2619
+ diagnostics.push(
2620
+ makeAnalysisDiagnostic(
2621
+ "INVALID_TAG_ARGUMENT",
2622
+ "Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
2623
+ provenance
2624
+ )
2625
+ );
2626
+ return null;
2627
+ }
2628
+ if (boundType.isStringLiteral()) {
2629
+ return boundType.value;
2630
+ }
2631
+ if (boundType.isUnion()) {
2632
+ const nonNullMembers = boundType.types.filter(
2633
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2634
+ );
2635
+ if (nonNullMembers.every((member) => member.isStringLiteral())) {
2636
+ diagnostics.push(
2637
+ makeAnalysisDiagnostic(
2638
+ "INVALID_TAG_ARGUMENT",
2639
+ "Discriminator resolution for unions of string literals is out of scope for v1.",
2640
+ provenance
2641
+ )
2642
+ );
2643
+ return null;
2644
+ }
2645
+ }
2646
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2647
+ if (declaration !== null) {
2648
+ return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
2649
+ }
2650
+ diagnostics.push(
2651
+ makeAnalysisDiagnostic(
2652
+ "INVALID_TAG_ARGUMENT",
2653
+ "Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
2654
+ provenance
2655
+ )
2656
+ );
2657
+ return null;
2658
+ }
2659
+ function getDeclarationName(node) {
2660
+ if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
2661
+ return node.name?.text ?? "anonymous";
2662
+ }
2663
+ return "anonymous";
2664
+ }
2665
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
2666
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2667
+ if (directive === null) {
2668
+ return [...fields];
2669
+ }
2670
+ const discriminatorValue = resolveDiscriminatorValue(
2671
+ getConcreteTypeArgumentForDiscriminator(
2672
+ node,
2673
+ subjectType,
2674
+ checker,
2675
+ directive.typeParameterName
2676
+ ),
2677
+ checker,
2678
+ directive.provenance,
2679
+ diagnostics
2680
+ );
2681
+ if (discriminatorValue === null) {
2682
+ return [...fields];
2683
+ }
2684
+ return fields.map(
2685
+ (field) => field.name === directive.fieldName ? {
2686
+ ...field,
2687
+ type: {
2688
+ kind: "enum",
2689
+ members: [{ value: discriminatorValue }]
2690
+ }
2691
+ } : field
2692
+ );
2693
+ }
2694
+ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2695
+ const renderedArguments = typeArguments.map(
2696
+ (typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
2697
+ ).filter((value) => value !== "");
2698
+ return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2699
+ }
2700
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2701
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2702
+ if (typeNode === void 0) {
2703
+ return [];
2704
+ }
2705
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2706
+ if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
2707
+ return [];
2708
+ }
2709
+ return resolvedTypeNode.typeArguments.map((argumentNode) => {
2710
+ const argumentType = checker.getTypeFromTypeNode(argumentNode);
2711
+ return {
2712
+ tsType: argumentType,
2713
+ typeNode: resolveTypeNode(
2714
+ argumentType,
2715
+ checker,
2716
+ file,
2717
+ typeRegistry,
2718
+ visiting,
2719
+ argumentNode,
2720
+ extensionRegistry,
2721
+ diagnostics
2722
+ )
2723
+ };
2724
+ });
2725
+ }
2726
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
2727
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2728
+ if (directive === null) {
2729
+ return properties;
2730
+ }
2731
+ const discriminatorValue = resolveDiscriminatorValue(
2732
+ getConcreteTypeArgumentForDiscriminator(
2733
+ node,
2734
+ subjectType,
2735
+ checker,
2736
+ directive.typeParameterName
2737
+ ),
2738
+ checker,
2739
+ directive.provenance,
2740
+ diagnostics
2741
+ );
2742
+ if (discriminatorValue === null) {
2743
+ return properties;
2744
+ }
2745
+ return properties.map(
2746
+ (property) => property.name === directive.fieldName ? {
2747
+ ...property,
2748
+ type: {
2749
+ kind: "enum",
2750
+ members: [{ value: discriminatorValue }]
2751
+ }
2752
+ } : property
2753
+ );
2754
+ }
2273
2755
  function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2274
2756
  if (!ts3.isIdentifier(prop.name)) {
2275
2757
  return null;
@@ -2558,6 +3040,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2558
3040
  file,
2559
3041
  typeRegistry,
2560
3042
  visiting,
3043
+ sourceNode,
2561
3044
  extensionRegistry,
2562
3045
  diagnostics
2563
3046
  );
@@ -2821,35 +3304,60 @@ function typeNodeContainsReference(type, targetName) {
2821
3304
  }
2822
3305
  }
2823
3306
  }
2824
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3307
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3308
+ const collectedDiagnostics = diagnostics ?? [];
2825
3309
  const typeName = getNamedTypeName(type);
2826
3310
  const namedTypeName = typeName ?? void 0;
2827
3311
  const namedDecl = getNamedTypeDeclaration(type);
2828
- const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
3312
+ const referenceTypeArguments = extractReferenceTypeArguments(
3313
+ type,
3314
+ checker,
3315
+ file,
3316
+ typeRegistry,
3317
+ visiting,
3318
+ sourceNode,
3319
+ extensionRegistry,
3320
+ collectedDiagnostics
3321
+ );
3322
+ const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
3323
+ namedTypeName,
3324
+ referenceTypeArguments.map((argument) => argument.tsType),
3325
+ checker
3326
+ ) : void 0;
3327
+ const registryTypeName = instantiatedTypeName ?? namedTypeName;
3328
+ const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2829
3329
  const clearNamedTypeRegistration = () => {
2830
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
3330
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2831
3331
  return;
2832
3332
  }
2833
- Reflect.deleteProperty(typeRegistry, namedTypeName);
3333
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2834
3334
  };
2835
3335
  if (visiting.has(type)) {
2836
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2837
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3336
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3337
+ return {
3338
+ kind: "reference",
3339
+ name: registryTypeName,
3340
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3341
+ };
2838
3342
  }
2839
3343
  return { kind: "object", properties: [], additionalProperties: false };
2840
3344
  }
2841
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2842
- typeRegistry[namedTypeName] = {
2843
- name: namedTypeName,
3345
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
3346
+ typeRegistry[registryTypeName] = {
3347
+ name: registryTypeName,
2844
3348
  type: RESOLVING_TYPE_PLACEHOLDER,
2845
3349
  provenance: provenanceForDeclaration(namedDecl, file)
2846
3350
  };
2847
3351
  }
2848
3352
  visiting.add(type);
2849
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2850
- if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
3353
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
3354
+ if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2851
3355
  visiting.delete(type);
2852
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3356
+ return {
3357
+ kind: "reference",
3358
+ name: registryTypeName,
3359
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3360
+ };
2853
3361
  }
2854
3362
  }
2855
3363
  const recordNode = tryResolveRecordType(
@@ -2859,24 +3367,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2859
3367
  typeRegistry,
2860
3368
  visiting,
2861
3369
  extensionRegistry,
2862
- diagnostics
3370
+ collectedDiagnostics
2863
3371
  );
2864
3372
  if (recordNode) {
2865
3373
  visiting.delete(type);
2866
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2867
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
3374
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3375
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2868
3376
  if (!isRecursiveRecord) {
2869
3377
  clearNamedTypeRegistration();
2870
3378
  return recordNode;
2871
3379
  }
2872
3380
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2873
- typeRegistry[namedTypeName] = {
2874
- name: namedTypeName,
3381
+ typeRegistry[registryTypeName] = {
3382
+ name: registryTypeName,
2875
3383
  type: recordNode,
2876
3384
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2877
3385
  provenance: provenanceForDeclaration(namedDecl, file)
2878
3386
  };
2879
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3387
+ return {
3388
+ kind: "reference",
3389
+ name: registryTypeName,
3390
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3391
+ };
2880
3392
  }
2881
3393
  return recordNode;
2882
3394
  }
@@ -2887,7 +3399,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2887
3399
  file,
2888
3400
  typeRegistry,
2889
3401
  visiting,
2890
- diagnostics ?? [],
3402
+ collectedDiagnostics,
2891
3403
  extensionRegistry
2892
3404
  );
2893
3405
  for (const prop of type.getProperties()) {
@@ -2903,7 +3415,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2903
3415
  visiting,
2904
3416
  declaration,
2905
3417
  extensionRegistry,
2906
- diagnostics
3418
+ collectedDiagnostics
2907
3419
  );
2908
3420
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2909
3421
  properties.push({
@@ -2918,18 +3430,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2918
3430
  visiting.delete(type);
2919
3431
  const objectNode = {
2920
3432
  kind: "object",
2921
- properties,
3433
+ properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
3434
+ properties,
3435
+ namedDecl,
3436
+ type,
3437
+ checker,
3438
+ file,
3439
+ collectedDiagnostics
3440
+ ) : properties,
2922
3441
  additionalProperties: true
2923
3442
  };
2924
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
3443
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2925
3444
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2926
- typeRegistry[namedTypeName] = {
2927
- name: namedTypeName,
3445
+ typeRegistry[registryTypeName] = {
3446
+ name: registryTypeName,
2928
3447
  type: objectNode,
2929
3448
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2930
3449
  provenance: provenanceForDeclaration(namedDecl, file)
2931
3450
  };
2932
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3451
+ return {
3452
+ kind: "reference",
3453
+ name: registryTypeName,
3454
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3455
+ };
2933
3456
  }
2934
3457
  return objectNode;
2935
3458
  }
@@ -3187,11 +3710,12 @@ function detectFormSpecReference(typeNode) {
3187
3710
  }
3188
3711
  return null;
3189
3712
  }
3190
- var ts3, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
3713
+ var ts3, import_internal2, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
3191
3714
  var init_class_analyzer = __esm({
3192
3715
  "src/analyzer/class-analyzer.ts"() {
3193
3716
  "use strict";
3194
3717
  ts3 = __toESM(require("typescript"), 1);
3718
+ import_internal2 = require("@formspec/analysis/internal");
3195
3719
  init_jsdoc_constraints();
3196
3720
  init_tsdoc_parser();
3197
3721
  RESOLVING_TYPE_PLACEHOLDER = {
@@ -3204,6 +3728,18 @@ var init_class_analyzer = __esm({
3204
3728
  });
3205
3729
 
3206
3730
  // src/analyzer/program.ts
3731
+ function createProgramContextFromProgram(program, filePath) {
3732
+ const absolutePath = path.resolve(filePath);
3733
+ const sourceFile = program.getSourceFile(absolutePath) ?? program.getSourceFile(filePath);
3734
+ if (!sourceFile) {
3735
+ throw new Error(`Could not find source file in provided program: ${absolutePath}`);
3736
+ }
3737
+ return {
3738
+ program,
3739
+ checker: program.getTypeChecker(),
3740
+ sourceFile
3741
+ };
3742
+ }
3207
3743
  function createProgramContext(filePath) {
3208
3744
  const absolutePath = path.resolve(filePath);
3209
3745
  const fileDir = path.dirname(absolutePath);
@@ -3274,24 +3810,33 @@ function findTypeAliasByName(sourceFile, aliasName) {
3274
3810
  }
3275
3811
  function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
3276
3812
  const ctx = createProgramContext(filePath);
3813
+ return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
3814
+ }
3815
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
3816
+ const analysisFilePath = path.resolve(filePath);
3277
3817
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3278
3818
  if (classDecl !== null) {
3279
- return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
3819
+ return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
3280
3820
  }
3281
3821
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3282
3822
  if (interfaceDecl !== null) {
3283
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
3823
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
3284
3824
  }
3285
3825
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3286
3826
  if (typeAlias !== null) {
3287
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
3827
+ const result = analyzeTypeAliasToIR(
3828
+ typeAlias,
3829
+ ctx.checker,
3830
+ analysisFilePath,
3831
+ extensionRegistry
3832
+ );
3288
3833
  if (result.ok) {
3289
3834
  return result.analysis;
3290
3835
  }
3291
3836
  throw new Error(result.error);
3292
3837
  }
3293
3838
  throw new Error(
3294
- `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
3839
+ `Type "${typeName}" not found as a class, interface, or type alias in ${analysisFilePath}`
3295
3840
  );
3296
3841
  }
3297
3842
  var ts4, path;
@@ -3306,7 +3851,7 @@ var init_program = __esm({
3306
3851
 
3307
3852
  // src/validate/constraint-validator.ts
3308
3853
  function validateFieldNode(ctx, field) {
3309
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
3854
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3310
3855
  field.name,
3311
3856
  field.type,
3312
3857
  field.constraints,
@@ -3324,7 +3869,7 @@ function validateFieldNode(ctx, field) {
3324
3869
  }
3325
3870
  function validateObjectProperty(ctx, parentName, property) {
3326
3871
  const qualifiedName = `${parentName}.${property.name}`;
3327
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
3872
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3328
3873
  qualifiedName,
3329
3874
  property.type,
3330
3875
  property.constraints,
@@ -3375,11 +3920,11 @@ function validateIR(ir, options) {
3375
3920
  valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
3376
3921
  };
3377
3922
  }
3378
- var import_internal2;
3923
+ var import_internal3;
3379
3924
  var init_constraint_validator = __esm({
3380
3925
  "src/validate/constraint-validator.ts"() {
3381
3926
  "use strict";
3382
- import_internal2 = require("@formspec/analysis/internal");
3927
+ import_internal3 = require("@formspec/analysis/internal");
3383
3928
  }
3384
3929
  });
3385
3930
 
@@ -3448,16 +3993,34 @@ function generateSchemasFromClass(options) {
3448
3993
  );
3449
3994
  }
3450
3995
  function generateSchemas(options) {
3451
- const analysis = analyzeNamedTypeToIR(
3996
+ const ctx = createProgramContext(options.filePath);
3997
+ return generateSchemasFromProgram({
3998
+ ...options,
3999
+ program: ctx.program
4000
+ });
4001
+ }
4002
+ function generateSchemasFromProgram(options) {
4003
+ const ctx = createProgramContextFromProgram(options.program, options.filePath);
4004
+ const analysis = analyzeNamedTypeToIRFromProgramContext(
4005
+ ctx,
3452
4006
  options.filePath,
3453
4007
  options.typeName,
3454
4008
  options.extensionRegistry
3455
4009
  );
3456
- return generateClassSchemas(analysis, { file: options.filePath }, options);
4010
+ return generateClassSchemas(
4011
+ analysis,
4012
+ { file: options.filePath },
4013
+ {
4014
+ extensionRegistry: options.extensionRegistry,
4015
+ vendorPrefix: options.vendorPrefix
4016
+ }
4017
+ );
3457
4018
  }
4019
+ var ts5;
3458
4020
  var init_class_schema = __esm({
3459
4021
  "src/generators/class-schema.ts"() {
3460
4022
  "use strict";
4023
+ ts5 = require("typescript");
3461
4024
  init_program();
3462
4025
  init_class_analyzer();
3463
4026
  init_canonicalize();
@@ -3661,7 +4224,10 @@ __export(index_exports, {
3661
4224
  generateJsonSchema: () => generateJsonSchema,
3662
4225
  generateSchemas: () => generateSchemas,
3663
4226
  generateSchemasFromClass: () => generateSchemasFromClass,
4227
+ generateSchemasFromProgram: () => generateSchemasFromProgram,
3664
4228
  generateUiSchema: () => generateUiSchema,
4229
+ jsonSchema7Schema: () => jsonSchema7Schema,
4230
+ uiSchemaSchema: () => uiSchema,
3665
4231
  writeSchemas: () => writeSchemas
3666
4232
  });
3667
4233
  function buildFormSchemas(form, options) {
@@ -3693,6 +4259,8 @@ var init_index = __esm({
3693
4259
  fs = __toESM(require("fs"), 1);
3694
4260
  path2 = __toESM(require("path"), 1);
3695
4261
  init_extensions();
4262
+ init_schema2();
4263
+ init_schema();
3696
4264
  init_generator();
3697
4265
  init_generator2();
3698
4266
  init_class_schema();