@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.
@@ -1 +1 @@
1
- {"version":3,"file":"method-schema.d.ts","sourceRoot":"","sources":["../../src/generators/method-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,+BAA+B,CAAC;AAG/E,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAG/F;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,yBAAyB;IACzB,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,UAAU,EAAE,cAAc,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gDAAgD;IAChD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AA6CD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAClD,aAAa,CAYf;AAmED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAY5E"}
1
+ {"version":3,"file":"method-schema.d.ts","sourceRoot":"","sources":["../../src/generators/method-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,+BAA+B,CAAC;AAG/E,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAG/F;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,yBAAyB;IACzB,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,UAAU,EAAE,cAAc,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gDAAgD;IAChD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AA+DD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,EAAE,CAAC,WAAW,EACvB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAClD,aAAa,CAYf;AAmED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAY5E"}
package/dist/index.cjs CHANGED
@@ -1258,6 +1258,7 @@ var path = __toESM(require("path"), 1);
1258
1258
 
1259
1259
  // src/analyzer/class-analyzer.ts
1260
1260
  var ts3 = __toESM(require("typescript"), 1);
1261
+ var import_internal2 = require("@formspec/analysis/internal");
1261
1262
 
1262
1263
  // src/analyzer/jsdoc-constraints.ts
1263
1264
  var ts2 = __toESM(require("typescript"), 1);
@@ -2180,9 +2181,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2180
2181
  }
2181
2182
  }
2182
2183
  }
2184
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2185
+ fields,
2186
+ classDecl,
2187
+ classType,
2188
+ checker,
2189
+ file,
2190
+ diagnostics
2191
+ );
2183
2192
  return {
2184
2193
  name,
2185
- fields,
2194
+ fields: specializedFields,
2186
2195
  fieldLayouts,
2187
2196
  typeRegistry,
2188
2197
  ...annotations.length > 0 && { annotations },
@@ -2222,10 +2231,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2222
2231
  }
2223
2232
  }
2224
2233
  }
2225
- const fieldLayouts = fields.map(() => ({}));
2234
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2235
+ fields,
2236
+ interfaceDecl,
2237
+ interfaceType,
2238
+ checker,
2239
+ file,
2240
+ diagnostics
2241
+ );
2242
+ const fieldLayouts = specializedFields.map(() => ({}));
2226
2243
  return {
2227
2244
  name,
2228
- fields,
2245
+ fields: specializedFields,
2229
2246
  fieldLayouts,
2230
2247
  typeRegistry,
2231
2248
  ...annotations.length > 0 && { annotations },
@@ -2274,12 +2291,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2274
2291
  }
2275
2292
  }
2276
2293
  }
2294
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2295
+ fields,
2296
+ typeAlias,
2297
+ aliasType,
2298
+ checker,
2299
+ file,
2300
+ diagnostics
2301
+ );
2277
2302
  return {
2278
2303
  ok: true,
2279
2304
  analysis: {
2280
2305
  name,
2281
- fields,
2282
- fieldLayouts: fields.map(() => ({})),
2306
+ fields: specializedFields,
2307
+ fieldLayouts: specializedFields.map(() => ({})),
2283
2308
  typeRegistry,
2284
2309
  ...annotations.length > 0 && { annotations },
2285
2310
  ...diagnostics.length > 0 && { diagnostics },
@@ -2288,6 +2313,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2288
2313
  }
2289
2314
  };
2290
2315
  }
2316
+ function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
2317
+ return {
2318
+ code,
2319
+ message,
2320
+ severity: "error",
2321
+ primaryLocation,
2322
+ relatedLocations
2323
+ };
2324
+ }
2325
+ function getLeadingParsedTags(node) {
2326
+ const sourceFile = node.getSourceFile();
2327
+ const sourceText = sourceFile.getFullText();
2328
+ const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
2329
+ if (commentRanges === void 0) {
2330
+ return [];
2331
+ }
2332
+ const parsedTags = [];
2333
+ for (const range of commentRanges) {
2334
+ if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
2335
+ continue;
2336
+ }
2337
+ const commentText = sourceText.slice(range.pos, range.end);
2338
+ if (!commentText.startsWith("/**")) {
2339
+ continue;
2340
+ }
2341
+ parsedTags.push(...(0, import_internal2.parseCommentBlock)(commentText, { offset: range.pos }).tags);
2342
+ }
2343
+ return parsedTags;
2344
+ }
2345
+ function findDiscriminatorProperty(node, fieldName) {
2346
+ if (ts3.isClassDeclaration(node)) {
2347
+ for (const member of node.members) {
2348
+ if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2349
+ return member;
2350
+ }
2351
+ }
2352
+ return null;
2353
+ }
2354
+ if (ts3.isInterfaceDeclaration(node)) {
2355
+ for (const member of node.members) {
2356
+ if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2357
+ return member;
2358
+ }
2359
+ }
2360
+ return null;
2361
+ }
2362
+ if (ts3.isTypeLiteralNode(node.type)) {
2363
+ for (const member of node.type.members) {
2364
+ if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2365
+ return member;
2366
+ }
2367
+ }
2368
+ }
2369
+ return null;
2370
+ }
2371
+ function isLocalTypeParameterName(node, typeParameterName) {
2372
+ return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
2373
+ }
2374
+ function isNullishSemanticType(type) {
2375
+ if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
2376
+ return true;
2377
+ }
2378
+ return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
2379
+ }
2380
+ function isStringLikeSemanticType(type) {
2381
+ if (type.flags & ts3.TypeFlags.StringLike) {
2382
+ return true;
2383
+ }
2384
+ if (type.isUnion()) {
2385
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
2386
+ }
2387
+ return false;
2388
+ }
2389
+ function extractDiscriminatorDirective(node, file, diagnostics) {
2390
+ const discriminatorTags = getLeadingParsedTags(node).filter(
2391
+ (tag) => tag.normalizedTagName === "discriminator"
2392
+ );
2393
+ if (discriminatorTags.length === 0) {
2394
+ return null;
2395
+ }
2396
+ const [firstTag, ...duplicateTags] = discriminatorTags;
2397
+ for (const _duplicateTag of duplicateTags) {
2398
+ diagnostics.push(
2399
+ makeAnalysisDiagnostic(
2400
+ "DUPLICATE_TAG",
2401
+ 'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
2402
+ provenanceForNode(node, file)
2403
+ )
2404
+ );
2405
+ }
2406
+ if (firstTag === void 0) {
2407
+ return null;
2408
+ }
2409
+ const firstTarget = firstTag.target;
2410
+ if (firstTarget?.path === null || firstTarget?.valid !== true) {
2411
+ diagnostics.push(
2412
+ makeAnalysisDiagnostic(
2413
+ "INVALID_TAG_ARGUMENT",
2414
+ 'Tag "@discriminator" requires a direct path target like ":kind".',
2415
+ provenanceForNode(node, file)
2416
+ )
2417
+ );
2418
+ return null;
2419
+ }
2420
+ if (firstTarget.path.segments.length !== 1) {
2421
+ diagnostics.push(
2422
+ makeAnalysisDiagnostic(
2423
+ "INVALID_TAG_ARGUMENT",
2424
+ 'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
2425
+ provenanceForNode(node, file)
2426
+ )
2427
+ );
2428
+ return null;
2429
+ }
2430
+ const typeParameterName = firstTag.argumentText.trim();
2431
+ if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
2432
+ diagnostics.push(
2433
+ makeAnalysisDiagnostic(
2434
+ "INVALID_TAG_ARGUMENT",
2435
+ 'Tag "@discriminator" requires a local type parameter name as its source operand.',
2436
+ provenanceForNode(node, file)
2437
+ )
2438
+ );
2439
+ return null;
2440
+ }
2441
+ return {
2442
+ fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
2443
+ typeParameterName,
2444
+ provenance: provenanceForNode(node, file)
2445
+ };
2446
+ }
2447
+ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2448
+ const directive = extractDiscriminatorDirective(node, file, diagnostics);
2449
+ if (directive === null) {
2450
+ return null;
2451
+ }
2452
+ if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
2453
+ diagnostics.push(
2454
+ makeAnalysisDiagnostic(
2455
+ "INVALID_TAG_ARGUMENT",
2456
+ `Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
2457
+ directive.provenance
2458
+ )
2459
+ );
2460
+ return null;
2461
+ }
2462
+ const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
2463
+ if (propertyDecl === null) {
2464
+ diagnostics.push(
2465
+ makeAnalysisDiagnostic(
2466
+ "UNKNOWN_PATH_TARGET",
2467
+ `Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
2468
+ directive.provenance
2469
+ )
2470
+ );
2471
+ return null;
2472
+ }
2473
+ if (propertyDecl.questionToken !== void 0) {
2474
+ diagnostics.push(
2475
+ makeAnalysisDiagnostic(
2476
+ "TYPE_MISMATCH",
2477
+ `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
2478
+ directive.provenance,
2479
+ [provenanceForNode(propertyDecl, file)]
2480
+ )
2481
+ );
2482
+ return null;
2483
+ }
2484
+ const propertyType = checker.getTypeAtLocation(propertyDecl);
2485
+ if (isNullishSemanticType(propertyType)) {
2486
+ diagnostics.push(
2487
+ makeAnalysisDiagnostic(
2488
+ "TYPE_MISMATCH",
2489
+ `Discriminator field "${directive.fieldName}" must not be nullable.`,
2490
+ directive.provenance,
2491
+ [provenanceForNode(propertyDecl, file)]
2492
+ )
2493
+ );
2494
+ return null;
2495
+ }
2496
+ if (!isStringLikeSemanticType(propertyType)) {
2497
+ diagnostics.push(
2498
+ makeAnalysisDiagnostic(
2499
+ "TYPE_MISMATCH",
2500
+ `Discriminator field "${directive.fieldName}" must be string-like.`,
2501
+ directive.provenance,
2502
+ [provenanceForNode(propertyDecl, file)]
2503
+ )
2504
+ );
2505
+ return null;
2506
+ }
2507
+ return directive;
2508
+ }
2509
+ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
2510
+ const typeParameterIndex = node.typeParameters?.findIndex(
2511
+ (typeParameter) => typeParameter.name.text === typeParameterName
2512
+ ) ?? -1;
2513
+ if (typeParameterIndex < 0) {
2514
+ return null;
2515
+ }
2516
+ const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
2517
+ if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
2518
+ return referenceTypeArguments[typeParameterIndex] ?? null;
2519
+ }
2520
+ const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2521
+ return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2522
+ }
2523
+ function extractDeclarationApiName(node) {
2524
+ for (const tag of getLeadingParsedTags(node)) {
2525
+ if (tag.normalizedTagName !== "apiName") {
2526
+ continue;
2527
+ }
2528
+ if (tag.target === null && tag.argumentText.trim() !== "") {
2529
+ return tag.argumentText.trim();
2530
+ }
2531
+ if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
2532
+ const value = tag.argumentText.trim();
2533
+ if (value !== "") {
2534
+ return value;
2535
+ }
2536
+ }
2537
+ }
2538
+ return null;
2539
+ }
2540
+ function inferJsonFacingName(name) {
2541
+ 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();
2542
+ }
2543
+ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2544
+ if (seen.has(type)) {
2545
+ return null;
2546
+ }
2547
+ seen.add(type);
2548
+ const symbol = type.aliasSymbol ?? type.getSymbol();
2549
+ if (symbol !== void 0) {
2550
+ const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
2551
+ const targetSymbol = aliased ?? symbol;
2552
+ const declaration = targetSymbol.declarations?.find(
2553
+ (candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
2554
+ );
2555
+ if (declaration !== void 0) {
2556
+ if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
2557
+ return resolveNamedDiscriminatorDeclaration(
2558
+ checker.getTypeFromTypeNode(declaration.type),
2559
+ checker,
2560
+ seen
2561
+ );
2562
+ }
2563
+ return declaration;
2564
+ }
2565
+ }
2566
+ return null;
2567
+ }
2568
+ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
2569
+ if (boundType === null) {
2570
+ diagnostics.push(
2571
+ makeAnalysisDiagnostic(
2572
+ "INVALID_TAG_ARGUMENT",
2573
+ "Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
2574
+ provenance
2575
+ )
2576
+ );
2577
+ return null;
2578
+ }
2579
+ if (boundType.isStringLiteral()) {
2580
+ return boundType.value;
2581
+ }
2582
+ if (boundType.isUnion()) {
2583
+ const nonNullMembers = boundType.types.filter(
2584
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2585
+ );
2586
+ if (nonNullMembers.every((member) => member.isStringLiteral())) {
2587
+ diagnostics.push(
2588
+ makeAnalysisDiagnostic(
2589
+ "INVALID_TAG_ARGUMENT",
2590
+ "Discriminator resolution for unions of string literals is out of scope for v1.",
2591
+ provenance
2592
+ )
2593
+ );
2594
+ return null;
2595
+ }
2596
+ }
2597
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2598
+ if (declaration !== null) {
2599
+ return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
2600
+ }
2601
+ diagnostics.push(
2602
+ makeAnalysisDiagnostic(
2603
+ "INVALID_TAG_ARGUMENT",
2604
+ "Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
2605
+ provenance
2606
+ )
2607
+ );
2608
+ return null;
2609
+ }
2610
+ function getDeclarationName(node) {
2611
+ if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
2612
+ return node.name?.text ?? "anonymous";
2613
+ }
2614
+ return "anonymous";
2615
+ }
2616
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
2617
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2618
+ if (directive === null) {
2619
+ return [...fields];
2620
+ }
2621
+ const discriminatorValue = resolveDiscriminatorValue(
2622
+ getConcreteTypeArgumentForDiscriminator(
2623
+ node,
2624
+ subjectType,
2625
+ checker,
2626
+ directive.typeParameterName
2627
+ ),
2628
+ checker,
2629
+ directive.provenance,
2630
+ diagnostics
2631
+ );
2632
+ if (discriminatorValue === null) {
2633
+ return [...fields];
2634
+ }
2635
+ return fields.map(
2636
+ (field) => field.name === directive.fieldName ? {
2637
+ ...field,
2638
+ type: {
2639
+ kind: "enum",
2640
+ members: [{ value: discriminatorValue }]
2641
+ }
2642
+ } : field
2643
+ );
2644
+ }
2645
+ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2646
+ const renderedArguments = typeArguments.map(
2647
+ (typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
2648
+ ).filter((value) => value !== "");
2649
+ return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2650
+ }
2651
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2652
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2653
+ if (typeNode === void 0) {
2654
+ return [];
2655
+ }
2656
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2657
+ if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
2658
+ return [];
2659
+ }
2660
+ return resolvedTypeNode.typeArguments.map((argumentNode) => {
2661
+ const argumentType = checker.getTypeFromTypeNode(argumentNode);
2662
+ return {
2663
+ tsType: argumentType,
2664
+ typeNode: resolveTypeNode(
2665
+ argumentType,
2666
+ checker,
2667
+ file,
2668
+ typeRegistry,
2669
+ visiting,
2670
+ argumentNode,
2671
+ extensionRegistry,
2672
+ diagnostics
2673
+ )
2674
+ };
2675
+ });
2676
+ }
2677
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
2678
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2679
+ if (directive === null) {
2680
+ return properties;
2681
+ }
2682
+ const discriminatorValue = resolveDiscriminatorValue(
2683
+ getConcreteTypeArgumentForDiscriminator(
2684
+ node,
2685
+ subjectType,
2686
+ checker,
2687
+ directive.typeParameterName
2688
+ ),
2689
+ checker,
2690
+ directive.provenance,
2691
+ diagnostics
2692
+ );
2693
+ if (discriminatorValue === null) {
2694
+ return properties;
2695
+ }
2696
+ return properties.map(
2697
+ (property) => property.name === directive.fieldName ? {
2698
+ ...property,
2699
+ type: {
2700
+ kind: "enum",
2701
+ members: [{ value: discriminatorValue }]
2702
+ }
2703
+ } : property
2704
+ );
2705
+ }
2291
2706
  function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2292
2707
  if (!ts3.isIdentifier(prop.name)) {
2293
2708
  return null;
@@ -2576,6 +2991,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2576
2991
  file,
2577
2992
  typeRegistry,
2578
2993
  visiting,
2994
+ sourceNode,
2579
2995
  extensionRegistry,
2580
2996
  diagnostics
2581
2997
  );
@@ -2839,35 +3255,60 @@ function typeNodeContainsReference(type, targetName) {
2839
3255
  }
2840
3256
  }
2841
3257
  }
2842
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3258
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3259
+ const collectedDiagnostics = diagnostics ?? [];
2843
3260
  const typeName = getNamedTypeName(type);
2844
3261
  const namedTypeName = typeName ?? void 0;
2845
3262
  const namedDecl = getNamedTypeDeclaration(type);
2846
- const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
3263
+ const referenceTypeArguments = extractReferenceTypeArguments(
3264
+ type,
3265
+ checker,
3266
+ file,
3267
+ typeRegistry,
3268
+ visiting,
3269
+ sourceNode,
3270
+ extensionRegistry,
3271
+ collectedDiagnostics
3272
+ );
3273
+ const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
3274
+ namedTypeName,
3275
+ referenceTypeArguments.map((argument) => argument.tsType),
3276
+ checker
3277
+ ) : void 0;
3278
+ const registryTypeName = instantiatedTypeName ?? namedTypeName;
3279
+ const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2847
3280
  const clearNamedTypeRegistration = () => {
2848
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
3281
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2849
3282
  return;
2850
3283
  }
2851
- Reflect.deleteProperty(typeRegistry, namedTypeName);
3284
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2852
3285
  };
2853
3286
  if (visiting.has(type)) {
2854
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2855
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3287
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3288
+ return {
3289
+ kind: "reference",
3290
+ name: registryTypeName,
3291
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3292
+ };
2856
3293
  }
2857
3294
  return { kind: "object", properties: [], additionalProperties: false };
2858
3295
  }
2859
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2860
- typeRegistry[namedTypeName] = {
2861
- name: namedTypeName,
3296
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
3297
+ typeRegistry[registryTypeName] = {
3298
+ name: registryTypeName,
2862
3299
  type: RESOLVING_TYPE_PLACEHOLDER,
2863
3300
  provenance: provenanceForDeclaration(namedDecl, file)
2864
3301
  };
2865
3302
  }
2866
3303
  visiting.add(type);
2867
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2868
- if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
3304
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
3305
+ if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2869
3306
  visiting.delete(type);
2870
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3307
+ return {
3308
+ kind: "reference",
3309
+ name: registryTypeName,
3310
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3311
+ };
2871
3312
  }
2872
3313
  }
2873
3314
  const recordNode = tryResolveRecordType(
@@ -2877,24 +3318,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2877
3318
  typeRegistry,
2878
3319
  visiting,
2879
3320
  extensionRegistry,
2880
- diagnostics
3321
+ collectedDiagnostics
2881
3322
  );
2882
3323
  if (recordNode) {
2883
3324
  visiting.delete(type);
2884
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2885
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
3325
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3326
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2886
3327
  if (!isRecursiveRecord) {
2887
3328
  clearNamedTypeRegistration();
2888
3329
  return recordNode;
2889
3330
  }
2890
3331
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2891
- typeRegistry[namedTypeName] = {
2892
- name: namedTypeName,
3332
+ typeRegistry[registryTypeName] = {
3333
+ name: registryTypeName,
2893
3334
  type: recordNode,
2894
3335
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2895
3336
  provenance: provenanceForDeclaration(namedDecl, file)
2896
3337
  };
2897
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3338
+ return {
3339
+ kind: "reference",
3340
+ name: registryTypeName,
3341
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3342
+ };
2898
3343
  }
2899
3344
  return recordNode;
2900
3345
  }
@@ -2905,7 +3350,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2905
3350
  file,
2906
3351
  typeRegistry,
2907
3352
  visiting,
2908
- diagnostics ?? [],
3353
+ collectedDiagnostics,
2909
3354
  extensionRegistry
2910
3355
  );
2911
3356
  for (const prop of type.getProperties()) {
@@ -2921,7 +3366,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2921
3366
  visiting,
2922
3367
  declaration,
2923
3368
  extensionRegistry,
2924
- diagnostics
3369
+ collectedDiagnostics
2925
3370
  );
2926
3371
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2927
3372
  properties.push({
@@ -2936,18 +3381,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2936
3381
  visiting.delete(type);
2937
3382
  const objectNode = {
2938
3383
  kind: "object",
2939
- properties,
3384
+ properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
3385
+ properties,
3386
+ namedDecl,
3387
+ type,
3388
+ checker,
3389
+ file,
3390
+ collectedDiagnostics
3391
+ ) : properties,
2940
3392
  additionalProperties: true
2941
3393
  };
2942
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
3394
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2943
3395
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2944
- typeRegistry[namedTypeName] = {
2945
- name: namedTypeName,
3396
+ typeRegistry[registryTypeName] = {
3397
+ name: registryTypeName,
2946
3398
  type: objectNode,
2947
3399
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2948
3400
  provenance: provenanceForDeclaration(namedDecl, file)
2949
3401
  };
2950
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
3402
+ return {
3403
+ kind: "reference",
3404
+ name: registryTypeName,
3405
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
3406
+ };
2951
3407
  }
2952
3408
  return objectNode;
2953
3409
  }
@@ -3321,9 +3777,9 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3321
3777
  }
3322
3778
 
3323
3779
  // src/validate/constraint-validator.ts
3324
- var import_internal2 = require("@formspec/analysis/internal");
3780
+ var import_internal3 = require("@formspec/analysis/internal");
3325
3781
  function validateFieldNode(ctx, field) {
3326
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
3782
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3327
3783
  field.name,
3328
3784
  field.type,
3329
3785
  field.constraints,
@@ -3341,7 +3797,7 @@ function validateFieldNode(ctx, field) {
3341
3797
  }
3342
3798
  function validateObjectProperty(ctx, parentName, property) {
3343
3799
  const qualifiedName = `${parentName}.${property.name}`;
3344
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
3800
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3345
3801
  qualifiedName,
3346
3802
  property.type,
3347
3803
  property.constraints,