@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.cjs CHANGED
@@ -2230,9 +2230,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2230
2230
  }
2231
2231
  }
2232
2232
  }
2233
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2234
+ fields,
2235
+ classDecl,
2236
+ classType,
2237
+ checker,
2238
+ file,
2239
+ diagnostics
2240
+ );
2233
2241
  return {
2234
2242
  name,
2235
- fields,
2243
+ fields: specializedFields,
2236
2244
  fieldLayouts,
2237
2245
  typeRegistry,
2238
2246
  ...annotations.length > 0 && { annotations },
@@ -2272,10 +2280,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2272
2280
  }
2273
2281
  }
2274
2282
  }
2275
- 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(() => ({}));
2276
2292
  return {
2277
2293
  name,
2278
- fields,
2294
+ fields: specializedFields,
2279
2295
  fieldLayouts,
2280
2296
  typeRegistry,
2281
2297
  ...annotations.length > 0 && { annotations },
@@ -2324,12 +2340,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2324
2340
  }
2325
2341
  }
2326
2342
  }
2343
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2344
+ fields,
2345
+ typeAlias,
2346
+ aliasType,
2347
+ checker,
2348
+ file,
2349
+ diagnostics
2350
+ );
2327
2351
  return {
2328
2352
  ok: true,
2329
2353
  analysis: {
2330
2354
  name,
2331
- fields,
2332
- fieldLayouts: fields.map(() => ({})),
2355
+ fields: specializedFields,
2356
+ fieldLayouts: specializedFields.map(() => ({})),
2333
2357
  typeRegistry,
2334
2358
  ...annotations.length > 0 && { annotations },
2335
2359
  ...diagnostics.length > 0 && { diagnostics },
@@ -2338,6 +2362,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2338
2362
  }
2339
2363
  };
2340
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
+ }
2341
2755
  function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2342
2756
  if (!ts3.isIdentifier(prop.name)) {
2343
2757
  return null;
@@ -2626,6 +3040,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2626
3040
  file,
2627
3041
  typeRegistry,
2628
3042
  visiting,
3043
+ sourceNode,
2629
3044
  extensionRegistry,
2630
3045
  diagnostics
2631
3046
  );
@@ -2889,35 +3304,60 @@ function typeNodeContainsReference(type, targetName) {
2889
3304
  }
2890
3305
  }
2891
3306
  }
2892
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3307
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3308
+ const collectedDiagnostics = diagnostics ?? [];
2893
3309
  const typeName = getNamedTypeName(type);
2894
3310
  const namedTypeName = typeName ?? void 0;
2895
3311
  const namedDecl = getNamedTypeDeclaration(type);
2896
- 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);
2897
3329
  const clearNamedTypeRegistration = () => {
2898
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
3330
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2899
3331
  return;
2900
3332
  }
2901
- Reflect.deleteProperty(typeRegistry, namedTypeName);
3333
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2902
3334
  };
2903
3335
  if (visiting.has(type)) {
2904
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2905
- 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
+ };
2906
3342
  }
2907
3343
  return { kind: "object", properties: [], additionalProperties: false };
2908
3344
  }
2909
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2910
- typeRegistry[namedTypeName] = {
2911
- name: namedTypeName,
3345
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
3346
+ typeRegistry[registryTypeName] = {
3347
+ name: registryTypeName,
2912
3348
  type: RESOLVING_TYPE_PLACEHOLDER,
2913
3349
  provenance: provenanceForDeclaration(namedDecl, file)
2914
3350
  };
2915
3351
  }
2916
3352
  visiting.add(type);
2917
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2918
- 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) {
2919
3355
  visiting.delete(type);
2920
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3356
+ return {
3357
+ kind: "reference",
3358
+ name: registryTypeName,
3359
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3360
+ };
2921
3361
  }
2922
3362
  }
2923
3363
  const recordNode = tryResolveRecordType(
@@ -2927,24 +3367,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2927
3367
  typeRegistry,
2928
3368
  visiting,
2929
3369
  extensionRegistry,
2930
- diagnostics
3370
+ collectedDiagnostics
2931
3371
  );
2932
3372
  if (recordNode) {
2933
3373
  visiting.delete(type);
2934
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2935
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
3374
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3375
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2936
3376
  if (!isRecursiveRecord) {
2937
3377
  clearNamedTypeRegistration();
2938
3378
  return recordNode;
2939
3379
  }
2940
3380
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2941
- typeRegistry[namedTypeName] = {
2942
- name: namedTypeName,
3381
+ typeRegistry[registryTypeName] = {
3382
+ name: registryTypeName,
2943
3383
  type: recordNode,
2944
3384
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2945
3385
  provenance: provenanceForDeclaration(namedDecl, file)
2946
3386
  };
2947
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3387
+ return {
3388
+ kind: "reference",
3389
+ name: registryTypeName,
3390
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3391
+ };
2948
3392
  }
2949
3393
  return recordNode;
2950
3394
  }
@@ -2955,7 +3399,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2955
3399
  file,
2956
3400
  typeRegistry,
2957
3401
  visiting,
2958
- diagnostics ?? [],
3402
+ collectedDiagnostics,
2959
3403
  extensionRegistry
2960
3404
  );
2961
3405
  for (const prop of type.getProperties()) {
@@ -2971,7 +3415,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2971
3415
  visiting,
2972
3416
  declaration,
2973
3417
  extensionRegistry,
2974
- diagnostics
3418
+ collectedDiagnostics
2975
3419
  );
2976
3420
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2977
3421
  properties.push({
@@ -2986,18 +3430,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2986
3430
  visiting.delete(type);
2987
3431
  const objectNode = {
2988
3432
  kind: "object",
2989
- 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,
2990
3441
  additionalProperties: true
2991
3442
  };
2992
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
3443
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2993
3444
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2994
- typeRegistry[namedTypeName] = {
2995
- name: namedTypeName,
3445
+ typeRegistry[registryTypeName] = {
3446
+ name: registryTypeName,
2996
3447
  type: objectNode,
2997
3448
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2998
3449
  provenance: provenanceForDeclaration(namedDecl, file)
2999
3450
  };
3000
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3451
+ return {
3452
+ kind: "reference",
3453
+ name: registryTypeName,
3454
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3455
+ };
3001
3456
  }
3002
3457
  return objectNode;
3003
3458
  }
@@ -3255,11 +3710,12 @@ function detectFormSpecReference(typeNode) {
3255
3710
  }
3256
3711
  return null;
3257
3712
  }
3258
- var ts3, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
3713
+ var ts3, import_internal2, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
3259
3714
  var init_class_analyzer = __esm({
3260
3715
  "src/analyzer/class-analyzer.ts"() {
3261
3716
  "use strict";
3262
3717
  ts3 = __toESM(require("typescript"), 1);
3718
+ import_internal2 = require("@formspec/analysis/internal");
3263
3719
  init_jsdoc_constraints();
3264
3720
  init_tsdoc_parser();
3265
3721
  RESOLVING_TYPE_PLACEHOLDER = {
@@ -3395,7 +3851,7 @@ var init_program = __esm({
3395
3851
 
3396
3852
  // src/validate/constraint-validator.ts
3397
3853
  function validateFieldNode(ctx, field) {
3398
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
3854
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3399
3855
  field.name,
3400
3856
  field.type,
3401
3857
  field.constraints,
@@ -3413,7 +3869,7 @@ function validateFieldNode(ctx, field) {
3413
3869
  }
3414
3870
  function validateObjectProperty(ctx, parentName, property) {
3415
3871
  const qualifiedName = `${parentName}.${property.name}`;
3416
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
3872
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3417
3873
  qualifiedName,
3418
3874
  property.type,
3419
3875
  property.constraints,
@@ -3464,11 +3920,11 @@ function validateIR(ir, options) {
3464
3920
  valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
3465
3921
  };
3466
3922
  }
3467
- var import_internal2;
3923
+ var import_internal3;
3468
3924
  var init_constraint_validator = __esm({
3469
3925
  "src/validate/constraint-validator.ts"() {
3470
3926
  "use strict";
3471
- import_internal2 = require("@formspec/analysis/internal");
3927
+ import_internal3 = require("@formspec/analysis/internal");
3472
3928
  }
3473
3929
  });
3474
3930