@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/index.js CHANGED
@@ -1141,12 +1141,80 @@ function createExtensionRegistry(extensions) {
1141
1141
  };
1142
1142
  }
1143
1143
 
1144
+ // src/json-schema/schema.ts
1145
+ import { z as z3 } from "zod";
1146
+ var jsonSchemaTypeSchema = z3.enum([
1147
+ "string",
1148
+ "number",
1149
+ "integer",
1150
+ "boolean",
1151
+ "object",
1152
+ "array",
1153
+ "null"
1154
+ ]);
1155
+ var jsonSchema7Schema = z3.lazy(
1156
+ () => z3.object({
1157
+ $schema: z3.string().optional(),
1158
+ $id: z3.string().optional(),
1159
+ $ref: z3.string().optional(),
1160
+ // Metadata
1161
+ title: z3.string().optional(),
1162
+ description: z3.string().optional(),
1163
+ deprecated: z3.boolean().optional(),
1164
+ // Type
1165
+ type: z3.union([jsonSchemaTypeSchema, z3.array(jsonSchemaTypeSchema)]).optional(),
1166
+ // String validation
1167
+ minLength: z3.number().optional(),
1168
+ maxLength: z3.number().optional(),
1169
+ pattern: z3.string().optional(),
1170
+ // Number validation
1171
+ minimum: z3.number().optional(),
1172
+ maximum: z3.number().optional(),
1173
+ exclusiveMinimum: z3.number().optional(),
1174
+ exclusiveMaximum: z3.number().optional(),
1175
+ // Enum
1176
+ enum: z3.array(z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()])).readonly().optional(),
1177
+ const: z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()]).optional(),
1178
+ // Object
1179
+ properties: z3.record(z3.string(), jsonSchema7Schema).optional(),
1180
+ required: z3.array(z3.string()).optional(),
1181
+ additionalProperties: z3.union([z3.boolean(), jsonSchema7Schema]).optional(),
1182
+ // Array
1183
+ items: z3.union([jsonSchema7Schema, z3.array(jsonSchema7Schema)]).optional(),
1184
+ minItems: z3.number().optional(),
1185
+ maxItems: z3.number().optional(),
1186
+ // Composition
1187
+ allOf: z3.array(jsonSchema7Schema).optional(),
1188
+ anyOf: z3.array(jsonSchema7Schema).optional(),
1189
+ oneOf: z3.array(jsonSchema7Schema).optional(),
1190
+ not: jsonSchema7Schema.optional(),
1191
+ // Conditional
1192
+ if: jsonSchema7Schema.optional(),
1193
+ then: jsonSchema7Schema.optional(),
1194
+ else: jsonSchema7Schema.optional(),
1195
+ // Format
1196
+ format: z3.string().optional(),
1197
+ // Default
1198
+ default: z3.unknown().optional(),
1199
+ // FormSpec extensions
1200
+ "x-formspec-source": z3.string().optional(),
1201
+ "x-formspec-params": z3.array(z3.string()).readonly().optional(),
1202
+ "x-formspec-schemaSource": z3.string().optional()
1203
+ }).passthrough()
1204
+ );
1205
+
1206
+ // src/generators/class-schema.ts
1207
+ import "typescript";
1208
+
1144
1209
  // src/analyzer/program.ts
1145
1210
  import * as ts4 from "typescript";
1146
1211
  import * as path from "path";
1147
1212
 
1148
1213
  // src/analyzer/class-analyzer.ts
1149
1214
  import * as ts3 from "typescript";
1215
+ import {
1216
+ parseCommentBlock as parseCommentBlock2
1217
+ } from "@formspec/analysis/internal";
1150
1218
 
1151
1219
  // src/analyzer/jsdoc-constraints.ts
1152
1220
  import * as ts2 from "typescript";
@@ -2094,9 +2162,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2094
2162
  }
2095
2163
  }
2096
2164
  }
2165
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2166
+ fields,
2167
+ classDecl,
2168
+ classType,
2169
+ checker,
2170
+ file,
2171
+ diagnostics
2172
+ );
2097
2173
  return {
2098
2174
  name,
2099
- fields,
2175
+ fields: specializedFields,
2100
2176
  fieldLayouts,
2101
2177
  typeRegistry,
2102
2178
  ...annotations.length > 0 && { annotations },
@@ -2136,10 +2212,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2136
2212
  }
2137
2213
  }
2138
2214
  }
2139
- const fieldLayouts = fields.map(() => ({}));
2215
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2216
+ fields,
2217
+ interfaceDecl,
2218
+ interfaceType,
2219
+ checker,
2220
+ file,
2221
+ diagnostics
2222
+ );
2223
+ const fieldLayouts = specializedFields.map(() => ({}));
2140
2224
  return {
2141
2225
  name,
2142
- fields,
2226
+ fields: specializedFields,
2143
2227
  fieldLayouts,
2144
2228
  typeRegistry,
2145
2229
  ...annotations.length > 0 && { annotations },
@@ -2188,12 +2272,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2188
2272
  }
2189
2273
  }
2190
2274
  }
2275
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2276
+ fields,
2277
+ typeAlias,
2278
+ aliasType,
2279
+ checker,
2280
+ file,
2281
+ diagnostics
2282
+ );
2191
2283
  return {
2192
2284
  ok: true,
2193
2285
  analysis: {
2194
2286
  name,
2195
- fields,
2196
- fieldLayouts: fields.map(() => ({})),
2287
+ fields: specializedFields,
2288
+ fieldLayouts: specializedFields.map(() => ({})),
2197
2289
  typeRegistry,
2198
2290
  ...annotations.length > 0 && { annotations },
2199
2291
  ...diagnostics.length > 0 && { diagnostics },
@@ -2202,6 +2294,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2202
2294
  }
2203
2295
  };
2204
2296
  }
2297
+ function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
2298
+ return {
2299
+ code,
2300
+ message,
2301
+ severity: "error",
2302
+ primaryLocation,
2303
+ relatedLocations
2304
+ };
2305
+ }
2306
+ function getLeadingParsedTags(node) {
2307
+ const sourceFile = node.getSourceFile();
2308
+ const sourceText = sourceFile.getFullText();
2309
+ const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
2310
+ if (commentRanges === void 0) {
2311
+ return [];
2312
+ }
2313
+ const parsedTags = [];
2314
+ for (const range of commentRanges) {
2315
+ if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
2316
+ continue;
2317
+ }
2318
+ const commentText = sourceText.slice(range.pos, range.end);
2319
+ if (!commentText.startsWith("/**")) {
2320
+ continue;
2321
+ }
2322
+ parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
2323
+ }
2324
+ return parsedTags;
2325
+ }
2326
+ function findDiscriminatorProperty(node, fieldName) {
2327
+ if (ts3.isClassDeclaration(node)) {
2328
+ for (const member of node.members) {
2329
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2330
+ return member;
2331
+ }
2332
+ }
2333
+ return null;
2334
+ }
2335
+ if (ts3.isInterfaceDeclaration(node)) {
2336
+ for (const member of node.members) {
2337
+ if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2338
+ return member;
2339
+ }
2340
+ }
2341
+ return null;
2342
+ }
2343
+ if (ts3.isTypeLiteralNode(node.type)) {
2344
+ for (const member of node.type.members) {
2345
+ if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2346
+ return member;
2347
+ }
2348
+ }
2349
+ }
2350
+ return null;
2351
+ }
2352
+ function isLocalTypeParameterName(node, typeParameterName) {
2353
+ return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
2354
+ }
2355
+ function isNullishSemanticType(type) {
2356
+ if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
2357
+ return true;
2358
+ }
2359
+ return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
2360
+ }
2361
+ function isStringLikeSemanticType(type) {
2362
+ if (type.flags & ts3.TypeFlags.StringLike) {
2363
+ return true;
2364
+ }
2365
+ if (type.isUnion()) {
2366
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
2367
+ }
2368
+ return false;
2369
+ }
2370
+ function extractDiscriminatorDirective(node, file, diagnostics) {
2371
+ const discriminatorTags = getLeadingParsedTags(node).filter(
2372
+ (tag) => tag.normalizedTagName === "discriminator"
2373
+ );
2374
+ if (discriminatorTags.length === 0) {
2375
+ return null;
2376
+ }
2377
+ const [firstTag, ...duplicateTags] = discriminatorTags;
2378
+ for (const _duplicateTag of duplicateTags) {
2379
+ diagnostics.push(
2380
+ makeAnalysisDiagnostic(
2381
+ "DUPLICATE_TAG",
2382
+ 'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
2383
+ provenanceForNode(node, file)
2384
+ )
2385
+ );
2386
+ }
2387
+ if (firstTag === void 0) {
2388
+ return null;
2389
+ }
2390
+ const firstTarget = firstTag.target;
2391
+ if (firstTarget?.path === null || firstTarget?.valid !== true) {
2392
+ diagnostics.push(
2393
+ makeAnalysisDiagnostic(
2394
+ "INVALID_TAG_ARGUMENT",
2395
+ 'Tag "@discriminator" requires a direct path target like ":kind".',
2396
+ provenanceForNode(node, file)
2397
+ )
2398
+ );
2399
+ return null;
2400
+ }
2401
+ if (firstTarget.path.segments.length !== 1) {
2402
+ diagnostics.push(
2403
+ makeAnalysisDiagnostic(
2404
+ "INVALID_TAG_ARGUMENT",
2405
+ 'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
2406
+ provenanceForNode(node, file)
2407
+ )
2408
+ );
2409
+ return null;
2410
+ }
2411
+ const typeParameterName = firstTag.argumentText.trim();
2412
+ if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
2413
+ diagnostics.push(
2414
+ makeAnalysisDiagnostic(
2415
+ "INVALID_TAG_ARGUMENT",
2416
+ 'Tag "@discriminator" requires a local type parameter name as its source operand.',
2417
+ provenanceForNode(node, file)
2418
+ )
2419
+ );
2420
+ return null;
2421
+ }
2422
+ return {
2423
+ fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
2424
+ typeParameterName,
2425
+ provenance: provenanceForNode(node, file)
2426
+ };
2427
+ }
2428
+ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2429
+ const directive = extractDiscriminatorDirective(node, file, diagnostics);
2430
+ if (directive === null) {
2431
+ return null;
2432
+ }
2433
+ if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
2434
+ diagnostics.push(
2435
+ makeAnalysisDiagnostic(
2436
+ "INVALID_TAG_ARGUMENT",
2437
+ `Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
2438
+ directive.provenance
2439
+ )
2440
+ );
2441
+ return null;
2442
+ }
2443
+ const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
2444
+ if (propertyDecl === null) {
2445
+ diagnostics.push(
2446
+ makeAnalysisDiagnostic(
2447
+ "UNKNOWN_PATH_TARGET",
2448
+ `Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
2449
+ directive.provenance
2450
+ )
2451
+ );
2452
+ return null;
2453
+ }
2454
+ if (propertyDecl.questionToken !== void 0) {
2455
+ diagnostics.push(
2456
+ makeAnalysisDiagnostic(
2457
+ "TYPE_MISMATCH",
2458
+ `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
2459
+ directive.provenance,
2460
+ [provenanceForNode(propertyDecl, file)]
2461
+ )
2462
+ );
2463
+ return null;
2464
+ }
2465
+ const propertyType = checker.getTypeAtLocation(propertyDecl);
2466
+ if (isNullishSemanticType(propertyType)) {
2467
+ diagnostics.push(
2468
+ makeAnalysisDiagnostic(
2469
+ "TYPE_MISMATCH",
2470
+ `Discriminator field "${directive.fieldName}" must not be nullable.`,
2471
+ directive.provenance,
2472
+ [provenanceForNode(propertyDecl, file)]
2473
+ )
2474
+ );
2475
+ return null;
2476
+ }
2477
+ if (!isStringLikeSemanticType(propertyType)) {
2478
+ diagnostics.push(
2479
+ makeAnalysisDiagnostic(
2480
+ "TYPE_MISMATCH",
2481
+ `Discriminator field "${directive.fieldName}" must be string-like.`,
2482
+ directive.provenance,
2483
+ [provenanceForNode(propertyDecl, file)]
2484
+ )
2485
+ );
2486
+ return null;
2487
+ }
2488
+ return directive;
2489
+ }
2490
+ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
2491
+ const typeParameterIndex = node.typeParameters?.findIndex(
2492
+ (typeParameter) => typeParameter.name.text === typeParameterName
2493
+ ) ?? -1;
2494
+ if (typeParameterIndex < 0) {
2495
+ return null;
2496
+ }
2497
+ const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
2498
+ if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
2499
+ return referenceTypeArguments[typeParameterIndex] ?? null;
2500
+ }
2501
+ const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2502
+ return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2503
+ }
2504
+ function extractDeclarationApiName(node) {
2505
+ for (const tag of getLeadingParsedTags(node)) {
2506
+ if (tag.normalizedTagName !== "apiName") {
2507
+ continue;
2508
+ }
2509
+ if (tag.target === null && tag.argumentText.trim() !== "") {
2510
+ return tag.argumentText.trim();
2511
+ }
2512
+ if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
2513
+ const value = tag.argumentText.trim();
2514
+ if (value !== "") {
2515
+ return value;
2516
+ }
2517
+ }
2518
+ }
2519
+ return null;
2520
+ }
2521
+ function inferJsonFacingName(name) {
2522
+ 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();
2523
+ }
2524
+ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2525
+ if (seen.has(type)) {
2526
+ return null;
2527
+ }
2528
+ seen.add(type);
2529
+ const symbol = type.aliasSymbol ?? type.getSymbol();
2530
+ if (symbol !== void 0) {
2531
+ const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
2532
+ const targetSymbol = aliased ?? symbol;
2533
+ const declaration = targetSymbol.declarations?.find(
2534
+ (candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
2535
+ );
2536
+ if (declaration !== void 0) {
2537
+ if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
2538
+ return resolveNamedDiscriminatorDeclaration(
2539
+ checker.getTypeFromTypeNode(declaration.type),
2540
+ checker,
2541
+ seen
2542
+ );
2543
+ }
2544
+ return declaration;
2545
+ }
2546
+ }
2547
+ return null;
2548
+ }
2549
+ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
2550
+ if (boundType === null) {
2551
+ diagnostics.push(
2552
+ makeAnalysisDiagnostic(
2553
+ "INVALID_TAG_ARGUMENT",
2554
+ "Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
2555
+ provenance
2556
+ )
2557
+ );
2558
+ return null;
2559
+ }
2560
+ if (boundType.isStringLiteral()) {
2561
+ return boundType.value;
2562
+ }
2563
+ if (boundType.isUnion()) {
2564
+ const nonNullMembers = boundType.types.filter(
2565
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2566
+ );
2567
+ if (nonNullMembers.every((member) => member.isStringLiteral())) {
2568
+ diagnostics.push(
2569
+ makeAnalysisDiagnostic(
2570
+ "INVALID_TAG_ARGUMENT",
2571
+ "Discriminator resolution for unions of string literals is out of scope for v1.",
2572
+ provenance
2573
+ )
2574
+ );
2575
+ return null;
2576
+ }
2577
+ }
2578
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2579
+ if (declaration !== null) {
2580
+ return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
2581
+ }
2582
+ diagnostics.push(
2583
+ makeAnalysisDiagnostic(
2584
+ "INVALID_TAG_ARGUMENT",
2585
+ "Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
2586
+ provenance
2587
+ )
2588
+ );
2589
+ return null;
2590
+ }
2591
+ function getDeclarationName(node) {
2592
+ if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
2593
+ return node.name?.text ?? "anonymous";
2594
+ }
2595
+ return "anonymous";
2596
+ }
2597
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
2598
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2599
+ if (directive === null) {
2600
+ return [...fields];
2601
+ }
2602
+ const discriminatorValue = resolveDiscriminatorValue(
2603
+ getConcreteTypeArgumentForDiscriminator(
2604
+ node,
2605
+ subjectType,
2606
+ checker,
2607
+ directive.typeParameterName
2608
+ ),
2609
+ checker,
2610
+ directive.provenance,
2611
+ diagnostics
2612
+ );
2613
+ if (discriminatorValue === null) {
2614
+ return [...fields];
2615
+ }
2616
+ return fields.map(
2617
+ (field) => field.name === directive.fieldName ? {
2618
+ ...field,
2619
+ type: {
2620
+ kind: "enum",
2621
+ members: [{ value: discriminatorValue }]
2622
+ }
2623
+ } : field
2624
+ );
2625
+ }
2626
+ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2627
+ const renderedArguments = typeArguments.map(
2628
+ (typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
2629
+ ).filter((value) => value !== "");
2630
+ return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2631
+ }
2632
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2633
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2634
+ if (typeNode === void 0) {
2635
+ return [];
2636
+ }
2637
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2638
+ if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
2639
+ return [];
2640
+ }
2641
+ return resolvedTypeNode.typeArguments.map((argumentNode) => {
2642
+ const argumentType = checker.getTypeFromTypeNode(argumentNode);
2643
+ return {
2644
+ tsType: argumentType,
2645
+ typeNode: resolveTypeNode(
2646
+ argumentType,
2647
+ checker,
2648
+ file,
2649
+ typeRegistry,
2650
+ visiting,
2651
+ argumentNode,
2652
+ extensionRegistry,
2653
+ diagnostics
2654
+ )
2655
+ };
2656
+ });
2657
+ }
2658
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
2659
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2660
+ if (directive === null) {
2661
+ return properties;
2662
+ }
2663
+ const discriminatorValue = resolveDiscriminatorValue(
2664
+ getConcreteTypeArgumentForDiscriminator(
2665
+ node,
2666
+ subjectType,
2667
+ checker,
2668
+ directive.typeParameterName
2669
+ ),
2670
+ checker,
2671
+ directive.provenance,
2672
+ diagnostics
2673
+ );
2674
+ if (discriminatorValue === null) {
2675
+ return properties;
2676
+ }
2677
+ return properties.map(
2678
+ (property) => property.name === directive.fieldName ? {
2679
+ ...property,
2680
+ type: {
2681
+ kind: "enum",
2682
+ members: [{ value: discriminatorValue }]
2683
+ }
2684
+ } : property
2685
+ );
2686
+ }
2205
2687
  function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2206
2688
  if (!ts3.isIdentifier(prop.name)) {
2207
2689
  return null;
@@ -2490,6 +2972,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2490
2972
  file,
2491
2973
  typeRegistry,
2492
2974
  visiting,
2975
+ sourceNode,
2493
2976
  extensionRegistry,
2494
2977
  diagnostics
2495
2978
  );
@@ -2753,35 +3236,60 @@ function typeNodeContainsReference(type, targetName) {
2753
3236
  }
2754
3237
  }
2755
3238
  }
2756
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3239
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3240
+ const collectedDiagnostics = diagnostics ?? [];
2757
3241
  const typeName = getNamedTypeName(type);
2758
3242
  const namedTypeName = typeName ?? void 0;
2759
3243
  const namedDecl = getNamedTypeDeclaration(type);
2760
- const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
3244
+ const referenceTypeArguments = extractReferenceTypeArguments(
3245
+ type,
3246
+ checker,
3247
+ file,
3248
+ typeRegistry,
3249
+ visiting,
3250
+ sourceNode,
3251
+ extensionRegistry,
3252
+ collectedDiagnostics
3253
+ );
3254
+ const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
3255
+ namedTypeName,
3256
+ referenceTypeArguments.map((argument) => argument.tsType),
3257
+ checker
3258
+ ) : void 0;
3259
+ const registryTypeName = instantiatedTypeName ?? namedTypeName;
3260
+ const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2761
3261
  const clearNamedTypeRegistration = () => {
2762
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
3262
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2763
3263
  return;
2764
3264
  }
2765
- Reflect.deleteProperty(typeRegistry, namedTypeName);
3265
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2766
3266
  };
2767
3267
  if (visiting.has(type)) {
2768
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2769
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3268
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3269
+ return {
3270
+ kind: "reference",
3271
+ name: registryTypeName,
3272
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3273
+ };
2770
3274
  }
2771
3275
  return { kind: "object", properties: [], additionalProperties: false };
2772
3276
  }
2773
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2774
- typeRegistry[namedTypeName] = {
2775
- name: namedTypeName,
3277
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
3278
+ typeRegistry[registryTypeName] = {
3279
+ name: registryTypeName,
2776
3280
  type: RESOLVING_TYPE_PLACEHOLDER,
2777
3281
  provenance: provenanceForDeclaration(namedDecl, file)
2778
3282
  };
2779
3283
  }
2780
3284
  visiting.add(type);
2781
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2782
- if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
3285
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
3286
+ if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2783
3287
  visiting.delete(type);
2784
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3288
+ return {
3289
+ kind: "reference",
3290
+ name: registryTypeName,
3291
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3292
+ };
2785
3293
  }
2786
3294
  }
2787
3295
  const recordNode = tryResolveRecordType(
@@ -2791,24 +3299,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2791
3299
  typeRegistry,
2792
3300
  visiting,
2793
3301
  extensionRegistry,
2794
- diagnostics
3302
+ collectedDiagnostics
2795
3303
  );
2796
3304
  if (recordNode) {
2797
3305
  visiting.delete(type);
2798
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2799
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
3306
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3307
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2800
3308
  if (!isRecursiveRecord) {
2801
3309
  clearNamedTypeRegistration();
2802
3310
  return recordNode;
2803
3311
  }
2804
3312
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2805
- typeRegistry[namedTypeName] = {
2806
- name: namedTypeName,
3313
+ typeRegistry[registryTypeName] = {
3314
+ name: registryTypeName,
2807
3315
  type: recordNode,
2808
3316
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2809
3317
  provenance: provenanceForDeclaration(namedDecl, file)
2810
3318
  };
2811
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3319
+ return {
3320
+ kind: "reference",
3321
+ name: registryTypeName,
3322
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3323
+ };
2812
3324
  }
2813
3325
  return recordNode;
2814
3326
  }
@@ -2819,7 +3331,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2819
3331
  file,
2820
3332
  typeRegistry,
2821
3333
  visiting,
2822
- diagnostics ?? [],
3334
+ collectedDiagnostics,
2823
3335
  extensionRegistry
2824
3336
  );
2825
3337
  for (const prop of type.getProperties()) {
@@ -2835,7 +3347,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2835
3347
  visiting,
2836
3348
  declaration,
2837
3349
  extensionRegistry,
2838
- diagnostics
3350
+ collectedDiagnostics
2839
3351
  );
2840
3352
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2841
3353
  properties.push({
@@ -2850,18 +3362,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2850
3362
  visiting.delete(type);
2851
3363
  const objectNode = {
2852
3364
  kind: "object",
2853
- properties,
3365
+ properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
3366
+ properties,
3367
+ namedDecl,
3368
+ type,
3369
+ checker,
3370
+ file,
3371
+ collectedDiagnostics
3372
+ ) : properties,
2854
3373
  additionalProperties: true
2855
3374
  };
2856
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
3375
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2857
3376
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2858
- typeRegistry[namedTypeName] = {
2859
- name: namedTypeName,
3377
+ typeRegistry[registryTypeName] = {
3378
+ name: registryTypeName,
2860
3379
  type: objectNode,
2861
3380
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2862
3381
  provenance: provenanceForDeclaration(namedDecl, file)
2863
3382
  };
2864
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3383
+ return {
3384
+ kind: "reference",
3385
+ name: registryTypeName,
3386
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3387
+ };
2865
3388
  }
2866
3389
  return objectNode;
2867
3390
  }
@@ -3122,6 +3645,18 @@ function detectFormSpecReference(typeNode) {
3122
3645
  }
3123
3646
 
3124
3647
  // src/analyzer/program.ts
3648
+ function createProgramContextFromProgram(program, filePath) {
3649
+ const absolutePath = path.resolve(filePath);
3650
+ const sourceFile = program.getSourceFile(absolutePath) ?? program.getSourceFile(filePath);
3651
+ if (!sourceFile) {
3652
+ throw new Error(`Could not find source file in provided program: ${absolutePath}`);
3653
+ }
3654
+ return {
3655
+ program,
3656
+ checker: program.getTypeChecker(),
3657
+ sourceFile
3658
+ };
3659
+ }
3125
3660
  function createProgramContext(filePath) {
3126
3661
  const absolutePath = path.resolve(filePath);
3127
3662
  const fileDir = path.dirname(absolutePath);
@@ -3192,24 +3727,33 @@ function findTypeAliasByName(sourceFile, aliasName) {
3192
3727
  }
3193
3728
  function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
3194
3729
  const ctx = createProgramContext(filePath);
3730
+ return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
3731
+ }
3732
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
3733
+ const analysisFilePath = path.resolve(filePath);
3195
3734
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3196
3735
  if (classDecl !== null) {
3197
- return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
3736
+ return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
3198
3737
  }
3199
3738
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3200
3739
  if (interfaceDecl !== null) {
3201
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
3740
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
3202
3741
  }
3203
3742
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3204
3743
  if (typeAlias !== null) {
3205
- const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
3744
+ const result = analyzeTypeAliasToIR(
3745
+ typeAlias,
3746
+ ctx.checker,
3747
+ analysisFilePath,
3748
+ extensionRegistry
3749
+ );
3206
3750
  if (result.ok) {
3207
3751
  return result.analysis;
3208
3752
  }
3209
3753
  throw new Error(result.error);
3210
3754
  }
3211
3755
  throw new Error(
3212
- `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
3756
+ `Type "${typeName}" not found as a class, interface, or type alias in ${analysisFilePath}`
3213
3757
  );
3214
3758
  }
3215
3759
 
@@ -3345,12 +3889,28 @@ function generateSchemasFromClass(options) {
3345
3889
  );
3346
3890
  }
3347
3891
  function generateSchemas(options) {
3348
- const analysis = analyzeNamedTypeToIR(
3892
+ const ctx = createProgramContext(options.filePath);
3893
+ return generateSchemasFromProgram({
3894
+ ...options,
3895
+ program: ctx.program
3896
+ });
3897
+ }
3898
+ function generateSchemasFromProgram(options) {
3899
+ const ctx = createProgramContextFromProgram(options.program, options.filePath);
3900
+ const analysis = analyzeNamedTypeToIRFromProgramContext(
3901
+ ctx,
3349
3902
  options.filePath,
3350
3903
  options.typeName,
3351
3904
  options.extensionRegistry
3352
3905
  );
3353
- return generateClassSchemas(analysis, { file: options.filePath }, options);
3906
+ return generateClassSchemas(
3907
+ analysis,
3908
+ { file: options.filePath },
3909
+ {
3910
+ extensionRegistry: options.extensionRegistry,
3911
+ vendorPrefix: options.vendorPrefix
3912
+ }
3913
+ );
3354
3914
  }
3355
3915
 
3356
3916
  // src/generators/mixed-authoring.ts
@@ -3556,7 +4116,10 @@ export {
3556
4116
  generateJsonSchema,
3557
4117
  generateSchemas,
3558
4118
  generateSchemasFromClass,
4119
+ generateSchemasFromProgram,
3559
4120
  generateUiSchema,
4121
+ jsonSchema7Schema,
4122
+ uiSchema as uiSchemaSchema,
3560
4123
  writeSchemas
3561
4124
  };
3562
4125
  //# sourceMappingURL=index.js.map