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