@formspec/build 0.1.0-alpha.27 → 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
@@ -2169,6 +2169,9 @@ var init_jsdoc_constraints = __esm({
2169
2169
 
2170
2170
  // src/analyzer/class-analyzer.ts
2171
2171
  import * as ts3 from "typescript";
2172
+ import {
2173
+ parseCommentBlock as parseCommentBlock2
2174
+ } from "@formspec/analysis/internal";
2172
2175
  function isObjectType(type) {
2173
2176
  return !!(type.flags & ts3.TypeFlags.Object);
2174
2177
  }
@@ -2232,9 +2235,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2232
2235
  }
2233
2236
  }
2234
2237
  }
2238
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2239
+ fields,
2240
+ classDecl,
2241
+ classType,
2242
+ checker,
2243
+ file,
2244
+ diagnostics
2245
+ );
2235
2246
  return {
2236
2247
  name,
2237
- fields,
2248
+ fields: specializedFields,
2238
2249
  fieldLayouts,
2239
2250
  typeRegistry,
2240
2251
  ...annotations.length > 0 && { annotations },
@@ -2274,10 +2285,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2274
2285
  }
2275
2286
  }
2276
2287
  }
2277
- 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(() => ({}));
2278
2297
  return {
2279
2298
  name,
2280
- fields,
2299
+ fields: specializedFields,
2281
2300
  fieldLayouts,
2282
2301
  typeRegistry,
2283
2302
  ...annotations.length > 0 && { annotations },
@@ -2326,12 +2345,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2326
2345
  }
2327
2346
  }
2328
2347
  }
2348
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2349
+ fields,
2350
+ typeAlias,
2351
+ aliasType,
2352
+ checker,
2353
+ file,
2354
+ diagnostics
2355
+ );
2329
2356
  return {
2330
2357
  ok: true,
2331
2358
  analysis: {
2332
2359
  name,
2333
- fields,
2334
- fieldLayouts: fields.map(() => ({})),
2360
+ fields: specializedFields,
2361
+ fieldLayouts: specializedFields.map(() => ({})),
2335
2362
  typeRegistry,
2336
2363
  ...annotations.length > 0 && { annotations },
2337
2364
  ...diagnostics.length > 0 && { diagnostics },
@@ -2340,6 +2367,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2340
2367
  }
2341
2368
  };
2342
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
+ }
2343
2760
  function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2344
2761
  if (!ts3.isIdentifier(prop.name)) {
2345
2762
  return null;
@@ -2628,6 +3045,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2628
3045
  file,
2629
3046
  typeRegistry,
2630
3047
  visiting,
3048
+ sourceNode,
2631
3049
  extensionRegistry,
2632
3050
  diagnostics
2633
3051
  );
@@ -2891,35 +3309,60 @@ function typeNodeContainsReference(type, targetName) {
2891
3309
  }
2892
3310
  }
2893
3311
  }
2894
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3312
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3313
+ const collectedDiagnostics = diagnostics ?? [];
2895
3314
  const typeName = getNamedTypeName(type);
2896
3315
  const namedTypeName = typeName ?? void 0;
2897
3316
  const namedDecl = getNamedTypeDeclaration(type);
2898
- 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);
2899
3334
  const clearNamedTypeRegistration = () => {
2900
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
3335
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2901
3336
  return;
2902
3337
  }
2903
- Reflect.deleteProperty(typeRegistry, namedTypeName);
3338
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2904
3339
  };
2905
3340
  if (visiting.has(type)) {
2906
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2907
- 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
+ };
2908
3347
  }
2909
3348
  return { kind: "object", properties: [], additionalProperties: false };
2910
3349
  }
2911
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2912
- typeRegistry[namedTypeName] = {
2913
- name: namedTypeName,
3350
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
3351
+ typeRegistry[registryTypeName] = {
3352
+ name: registryTypeName,
2914
3353
  type: RESOLVING_TYPE_PLACEHOLDER,
2915
3354
  provenance: provenanceForDeclaration(namedDecl, file)
2916
3355
  };
2917
3356
  }
2918
3357
  visiting.add(type);
2919
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2920
- 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) {
2921
3360
  visiting.delete(type);
2922
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3361
+ return {
3362
+ kind: "reference",
3363
+ name: registryTypeName,
3364
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3365
+ };
2923
3366
  }
2924
3367
  }
2925
3368
  const recordNode = tryResolveRecordType(
@@ -2929,24 +3372,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2929
3372
  typeRegistry,
2930
3373
  visiting,
2931
3374
  extensionRegistry,
2932
- diagnostics
3375
+ collectedDiagnostics
2933
3376
  );
2934
3377
  if (recordNode) {
2935
3378
  visiting.delete(type);
2936
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2937
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
3379
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3380
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2938
3381
  if (!isRecursiveRecord) {
2939
3382
  clearNamedTypeRegistration();
2940
3383
  return recordNode;
2941
3384
  }
2942
3385
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2943
- typeRegistry[namedTypeName] = {
2944
- name: namedTypeName,
3386
+ typeRegistry[registryTypeName] = {
3387
+ name: registryTypeName,
2945
3388
  type: recordNode,
2946
3389
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2947
3390
  provenance: provenanceForDeclaration(namedDecl, file)
2948
3391
  };
2949
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3392
+ return {
3393
+ kind: "reference",
3394
+ name: registryTypeName,
3395
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3396
+ };
2950
3397
  }
2951
3398
  return recordNode;
2952
3399
  }
@@ -2957,7 +3404,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2957
3404
  file,
2958
3405
  typeRegistry,
2959
3406
  visiting,
2960
- diagnostics ?? [],
3407
+ collectedDiagnostics,
2961
3408
  extensionRegistry
2962
3409
  );
2963
3410
  for (const prop of type.getProperties()) {
@@ -2973,7 +3420,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2973
3420
  visiting,
2974
3421
  declaration,
2975
3422
  extensionRegistry,
2976
- diagnostics
3423
+ collectedDiagnostics
2977
3424
  );
2978
3425
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2979
3426
  properties.push({
@@ -2988,18 +3435,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2988
3435
  visiting.delete(type);
2989
3436
  const objectNode = {
2990
3437
  kind: "object",
2991
- 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,
2992
3446
  additionalProperties: true
2993
3447
  };
2994
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
3448
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2995
3449
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2996
- typeRegistry[namedTypeName] = {
2997
- name: namedTypeName,
3450
+ typeRegistry[registryTypeName] = {
3451
+ name: registryTypeName,
2998
3452
  type: objectNode,
2999
3453
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3000
3454
  provenance: provenanceForDeclaration(namedDecl, file)
3001
3455
  };
3002
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3456
+ return {
3457
+ kind: "reference",
3458
+ name: registryTypeName,
3459
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3460
+ };
3003
3461
  }
3004
3462
  return objectNode;
3005
3463
  }