@formspec/build 0.1.0-alpha.54 → 0.1.0-alpha.55

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.
@@ -469,46 +469,6 @@ declare interface CustomTypeRegistration_2 {
469
469
  * Note: `"__integerBrand"` is reserved for the builtin Integer type.
470
470
  */
471
471
  readonly brand?: string;
472
- /**
473
- * Optional callback to extract a payload from the TypeScript type at
474
- * analysis time. The returned value is stored on the custom type node
475
- * and later passed to `toJsonSchema`.
476
- *
477
- * Use this to carry type-level information (e.g., a generic argument's
478
- * resolved literal value) through the IR into schema generation.
479
- *
480
- * **Parameters:** typed as `unknown` because `@formspec/core` does not
481
- * depend on the TypeScript compiler API. Implementations should cast to
482
- * `ts.Type` and `ts.TypeChecker`. Both parameters originate from the
483
- * host program's type checker — extensions may rely on host-program
484
- * symbol identity.
485
- *
486
- * **Contract:**
487
- * - Must be a pure function of `(type, checker)` — no I/O or shared state.
488
- * - May be invoked multiple times for the same type (no memoization is provided).
489
- * - Must return a JSON-serializable `ExtensionPayloadValue`. Returning live
490
- * compiler objects (e.g., `ts.Type`) will corrupt any IR caching.
491
- * - Errors thrown by the callback are attributed to the extension and
492
- * reported as build diagnostics.
493
- * - Returning `undefined` is coerced to `null` at the call site.
494
- *
495
- * @param type - The resolved TypeScript type (cast to `ts.Type`).
496
- * @param checker - The TypeScript type checker (cast to `ts.TypeChecker`).
497
- * @returns A JSON-serializable payload, or `null` if no payload can be extracted.
498
- *
499
- * @example
500
- * ```typescript
501
- * extractPayload: (type: unknown, checker: unknown) => {
502
- * const tsType = type as ts.Type;
503
- * const tsChecker = checker as ts.TypeChecker;
504
- * const prop = tsType.getProperty('target');
505
- * if (!prop) return null;
506
- * const propType = tsChecker.getTypeOfSymbol(prop);
507
- * return propType.isStringLiteral() ? propType.value : null;
508
- * }
509
- * ```
510
- */
511
- readonly extractPayload?: (type: unknown, checker: unknown) => ExtensionPayloadValue;
512
472
  /**
513
473
  * Converts the custom type's payload into a JSON Schema fragment.
514
474
  *
@@ -469,46 +469,6 @@ declare interface CustomTypeRegistration_2 {
469
469
  * Note: `"__integerBrand"` is reserved for the builtin Integer type.
470
470
  */
471
471
  readonly brand?: string;
472
- /**
473
- * Optional callback to extract a payload from the TypeScript type at
474
- * analysis time. The returned value is stored on the custom type node
475
- * and later passed to `toJsonSchema`.
476
- *
477
- * Use this to carry type-level information (e.g., a generic argument's
478
- * resolved literal value) through the IR into schema generation.
479
- *
480
- * **Parameters:** typed as `unknown` because `@formspec/core` does not
481
- * depend on the TypeScript compiler API. Implementations should cast to
482
- * `ts.Type` and `ts.TypeChecker`. Both parameters originate from the
483
- * host program's type checker — extensions may rely on host-program
484
- * symbol identity.
485
- *
486
- * **Contract:**
487
- * - Must be a pure function of `(type, checker)` — no I/O or shared state.
488
- * - May be invoked multiple times for the same type (no memoization is provided).
489
- * - Must return a JSON-serializable `ExtensionPayloadValue`. Returning live
490
- * compiler objects (e.g., `ts.Type`) will corrupt any IR caching.
491
- * - Errors thrown by the callback are attributed to the extension and
492
- * reported as build diagnostics.
493
- * - Returning `undefined` is coerced to `null` at the call site.
494
- *
495
- * @param type - The resolved TypeScript type (cast to `ts.Type`).
496
- * @param checker - The TypeScript type checker (cast to `ts.TypeChecker`).
497
- * @returns A JSON-serializable payload, or `null` if no payload can be extracted.
498
- *
499
- * @example
500
- * ```typescript
501
- * extractPayload: (type: unknown, checker: unknown) => {
502
- * const tsType = type as ts.Type;
503
- * const tsChecker = checker as ts.TypeChecker;
504
- * const prop = tsType.getProperty('target');
505
- * if (!prop) return null;
506
- * const propType = tsChecker.getTypeOfSymbol(prop);
507
- * return propType.isStringLiteral() ? propType.value : null;
508
- * }
509
- * ```
510
- */
511
- readonly extractPayload?: (type: unknown, checker: unknown) => ExtensionPayloadValue;
512
472
  /**
513
473
  * Converts the custom type's payload into a JSON Schema fragment.
514
474
  *
@@ -469,46 +469,6 @@ declare interface CustomTypeRegistration_2 {
469
469
  * Note: `"__integerBrand"` is reserved for the builtin Integer type.
470
470
  */
471
471
  readonly brand?: string;
472
- /**
473
- * Optional callback to extract a payload from the TypeScript type at
474
- * analysis time. The returned value is stored on the custom type node
475
- * and later passed to `toJsonSchema`.
476
- *
477
- * Use this to carry type-level information (e.g., a generic argument's
478
- * resolved literal value) through the IR into schema generation.
479
- *
480
- * **Parameters:** typed as `unknown` because `@formspec/core` does not
481
- * depend on the TypeScript compiler API. Implementations should cast to
482
- * `ts.Type` and `ts.TypeChecker`. Both parameters originate from the
483
- * host program's type checker — extensions may rely on host-program
484
- * symbol identity.
485
- *
486
- * **Contract:**
487
- * - Must be a pure function of `(type, checker)` — no I/O or shared state.
488
- * - May be invoked multiple times for the same type (no memoization is provided).
489
- * - Must return a JSON-serializable `ExtensionPayloadValue`. Returning live
490
- * compiler objects (e.g., `ts.Type`) will corrupt any IR caching.
491
- * - Errors thrown by the callback are attributed to the extension and
492
- * reported as build diagnostics.
493
- * - Returning `undefined` is coerced to `null` at the call site.
494
- *
495
- * @param type - The resolved TypeScript type (cast to `ts.Type`).
496
- * @param checker - The TypeScript type checker (cast to `ts.TypeChecker`).
497
- * @returns A JSON-serializable payload, or `null` if no payload can be extracted.
498
- *
499
- * @example
500
- * ```typescript
501
- * extractPayload: (type: unknown, checker: unknown) => {
502
- * const tsType = type as ts.Type;
503
- * const tsChecker = checker as ts.TypeChecker;
504
- * const prop = tsType.getProperty('target');
505
- * if (!prop) return null;
506
- * const propType = tsChecker.getTypeOfSymbol(prop);
507
- * return propType.isStringLiteral() ? propType.value : null;
508
- * }
509
- * ```
510
- */
511
- readonly extractPayload?: (type: unknown, checker: unknown) => ExtensionPayloadValue;
512
472
  /**
513
473
  * Converts the custom type's payload into a JSON Schema fragment.
514
474
  *
package/dist/build.d.ts CHANGED
@@ -469,46 +469,6 @@ declare interface CustomTypeRegistration_2 {
469
469
  * Note: `"__integerBrand"` is reserved for the builtin Integer type.
470
470
  */
471
471
  readonly brand?: string;
472
- /**
473
- * Optional callback to extract a payload from the TypeScript type at
474
- * analysis time. The returned value is stored on the custom type node
475
- * and later passed to `toJsonSchema`.
476
- *
477
- * Use this to carry type-level information (e.g., a generic argument's
478
- * resolved literal value) through the IR into schema generation.
479
- *
480
- * **Parameters:** typed as `unknown` because `@formspec/core` does not
481
- * depend on the TypeScript compiler API. Implementations should cast to
482
- * `ts.Type` and `ts.TypeChecker`. Both parameters originate from the
483
- * host program's type checker — extensions may rely on host-program
484
- * symbol identity.
485
- *
486
- * **Contract:**
487
- * - Must be a pure function of `(type, checker)` — no I/O or shared state.
488
- * - May be invoked multiple times for the same type (no memoization is provided).
489
- * - Must return a JSON-serializable `ExtensionPayloadValue`. Returning live
490
- * compiler objects (e.g., `ts.Type`) will corrupt any IR caching.
491
- * - Errors thrown by the callback are attributed to the extension and
492
- * reported as build diagnostics.
493
- * - Returning `undefined` is coerced to `null` at the call site.
494
- *
495
- * @param type - The resolved TypeScript type (cast to `ts.Type`).
496
- * @param checker - The TypeScript type checker (cast to `ts.TypeChecker`).
497
- * @returns A JSON-serializable payload, or `null` if no payload can be extracted.
498
- *
499
- * @example
500
- * ```typescript
501
- * extractPayload: (type: unknown, checker: unknown) => {
502
- * const tsType = type as ts.Type;
503
- * const tsChecker = checker as ts.TypeChecker;
504
- * const prop = tsType.getProperty('target');
505
- * if (!prop) return null;
506
- * const propType = tsChecker.getTypeOfSymbol(prop);
507
- * return propType.isStringLiteral() ? propType.value : null;
508
- * }
509
- * ```
510
- */
511
- readonly extractPayload?: (type: unknown, checker: unknown) => ExtensionPayloadValue;
512
472
  /**
513
473
  * Converts the custom type's payload into a JSON Schema fragment.
514
474
  *
package/dist/cli.cjs CHANGED
@@ -1241,10 +1241,11 @@ function generatePrimitiveType(type) {
1241
1241
  function generateEnumType(type, ctx) {
1242
1242
  if (ctx.enumSerialization === "oneOf") {
1243
1243
  return {
1244
- oneOf: type.members.map((m) => ({
1245
- const: m.value,
1246
- title: m.displayName ?? String(m.value)
1247
- }))
1244
+ oneOf: type.members.map((m) => {
1245
+ const stringValue = String(m.value);
1246
+ const title = m.displayName !== void 0 && m.displayName !== stringValue ? m.displayName : void 0;
1247
+ return title !== void 0 ? { const: m.value, title } : { const: m.value };
1248
+ })
1248
1249
  };
1249
1250
  }
1250
1251
  const schema = { enum: type.members.map((m) => m.value) };
@@ -2072,6 +2073,11 @@ function buildConstraintTagSources(extensions) {
2072
2073
  }));
2073
2074
  }
2074
2075
  function createExtensionRegistry(extensions) {
2076
+ const registryLog = (0, import_internal.getSyntheticLogger)();
2077
+ registryLog.debug("createExtensionRegistry: constructing", {
2078
+ extensionCount: extensions.length,
2079
+ extensionIds: extensions.map((e) => e.extensionId)
2080
+ });
2075
2081
  const reservedTagSources = buildConstraintTagSources(extensions);
2076
2082
  let symbolMap = /* @__PURE__ */ new Map();
2077
2083
  const typeMap = /* @__PURE__ */ new Map();
@@ -2203,6 +2209,14 @@ function createExtensionRegistry(extensions) {
2203
2209
  }
2204
2210
  }
2205
2211
  }
2212
+ registryLog.debug("createExtensionRegistry: complete", {
2213
+ typeCount: typeMap.size,
2214
+ constraintCount: constraintMap.size,
2215
+ constraintTagCount: constraintTagMap.size,
2216
+ broadeningCount: builtinBroadeningMap.size,
2217
+ annotationCount: annotationMap.size,
2218
+ metadataSlotCount: metadataSlotMap.size
2219
+ });
2206
2220
  return {
2207
2221
  extensions,
2208
2222
  findType: (typeId) => typeMap.get(typeId),
@@ -2851,56 +2865,82 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2851
2865
  if (definition === null) {
2852
2866
  return [];
2853
2867
  }
2868
+ const nonNullPlacement = placement;
2869
+ const log2 = (0, import_internal4.getBuildLogger)();
2870
+ const broadeningLog = (0, import_internal4.getBroadeningLogger)();
2871
+ const syntheticLog = (0, import_internal4.getSyntheticLogger)();
2872
+ const logsEnabled = log2 !== import_core4.noopLogger || broadeningLog !== import_core4.noopLogger;
2873
+ const syntheticTraceEnabled = syntheticLog !== import_core4.noopLogger;
2874
+ const logStart = logsEnabled ? (0, import_internal4.nowMicros)() : 0;
2875
+ const subjectTypeKind = logsEnabled ? (0, import_internal4.describeTypeKind)(subjectType, checker) : "";
2876
+ function emit(outcome, result2) {
2877
+ if (!logsEnabled) {
2878
+ return result2;
2879
+ }
2880
+ const entry = {
2881
+ consumer: "build",
2882
+ tag: tagName,
2883
+ placement: nonNullPlacement,
2884
+ subjectTypeKind,
2885
+ roleOutcome: outcome,
2886
+ elapsedMicros: (0, import_internal4.elapsedMicros)(logStart)
2887
+ };
2888
+ (0, import_internal4.logTagApplication)(log2, entry);
2889
+ if (outcome === "bypass" || outcome === "D1" || outcome === "D2") {
2890
+ (0, import_internal4.logTagApplication)(broadeningLog, entry);
2891
+ }
2892
+ return result2;
2893
+ }
2854
2894
  if (!definition.placements.includes(placement)) {
2855
- return [
2895
+ return emit("A-reject", [
2856
2896
  makeDiagnostic(
2857
2897
  "INVALID_TAG_PLACEMENT",
2858
2898
  `Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
2859
2899
  provenance
2860
2900
  )
2861
- ];
2901
+ ]);
2862
2902
  }
2863
2903
  const target = parsedTag?.target ?? null;
2864
2904
  let evaluatedType = subjectType;
2865
2905
  let targetLabel = node.getText(sourceFile);
2866
2906
  if (target !== null) {
2867
2907
  if (target.kind !== "path") {
2868
- return [
2908
+ return emit("B-reject", [
2869
2909
  makeDiagnostic(
2870
2910
  "UNSUPPORTED_TARGETING_SYNTAX",
2871
2911
  `Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
2872
2912
  provenance
2873
2913
  )
2874
- ];
2914
+ ]);
2875
2915
  }
2876
2916
  if (!target.valid || target.path === null) {
2877
- return [
2917
+ return emit("B-reject", [
2878
2918
  makeDiagnostic(
2879
2919
  "UNSUPPORTED_TARGETING_SYNTAX",
2880
2920
  `Tag "@${tagName}" has invalid path targeting syntax.`,
2881
2921
  provenance
2882
2922
  )
2883
- ];
2923
+ ]);
2884
2924
  }
2885
2925
  const resolution = (0, import_internal3.resolvePathTargetType)(subjectType, checker, target.path.segments);
2886
2926
  if (resolution.kind === "missing-property") {
2887
- return [
2927
+ return emit("B-reject", [
2888
2928
  makeDiagnostic(
2889
2929
  "UNKNOWN_PATH_TARGET",
2890
2930
  `Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
2891
2931
  provenance
2892
2932
  )
2893
- ];
2933
+ ]);
2894
2934
  }
2895
2935
  if (resolution.kind === "unresolvable") {
2896
2936
  const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2897
- return [
2937
+ return emit("B-reject", [
2898
2938
  makeDiagnostic(
2899
2939
  "TYPE_MISMATCH",
2900
2940
  `Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
2901
2941
  provenance
2902
2942
  )
2903
- ];
2943
+ ]);
2904
2944
  }
2905
2945
  evaluatedType = resolution.type;
2906
2946
  targetLabel = target.rawText;
@@ -2929,13 +2969,13 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2929
2969
  tagName,
2930
2970
  parsedTag?.argumentText
2931
2971
  ) : null;
2932
- return [
2972
+ return emit("B-reject", [
2933
2973
  makeDiagnostic(
2934
2974
  "TYPE_MISMATCH",
2935
2975
  hint === null ? baseMessage : `${baseMessage}. ${hint}`,
2936
2976
  provenance
2937
2977
  )
2938
- ];
2978
+ ]);
2939
2979
  }
2940
2980
  }
2941
2981
  const argumentExpression = renderSyntheticArgumentExpression(
@@ -2943,14 +2983,23 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2943
2983
  parsedTag?.argumentText ?? ""
2944
2984
  );
2945
2985
  if (definition.requiresArgument && argumentExpression === null) {
2946
- return [];
2986
+ return emit("A-pass", []);
2947
2987
  }
2948
2988
  if (hasBroadening) {
2949
- return [];
2989
+ return emit("bypass", []);
2950
2990
  }
2951
2991
  const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2952
2992
  const hostType = options?.hostType ?? subjectType;
2953
2993
  const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2994
+ if (syntheticTraceEnabled) {
2995
+ syntheticLog.trace("invoking synthetic checker", {
2996
+ consumer: "build",
2997
+ tag: tagName,
2998
+ placement,
2999
+ subjectTypeKind,
3000
+ subjectTypeText
3001
+ });
3002
+ }
2954
3003
  const result = (0, import_internal3.checkSyntheticTagApplication)({
2955
3004
  tagName,
2956
3005
  placement,
@@ -2977,26 +3026,26 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2977
3026
  } : {}
2978
3027
  });
2979
3028
  if (result.diagnostics.length === 0) {
2980
- return [];
3029
+ return emit("C-pass", []);
2981
3030
  }
2982
3031
  const setupDiagnostic = result.diagnostics.find((diagnostic) => diagnostic.kind !== "typescript");
2983
3032
  if (setupDiagnostic !== void 0) {
2984
- return [
3033
+ return emit("C-reject", [
2985
3034
  makeDiagnostic(
2986
3035
  setupDiagnostic.kind === "unsupported-custom-type-override" ? "UNSUPPORTED_CUSTOM_TYPE_OVERRIDE" : "SYNTHETIC_SETUP_FAILURE",
2987
3036
  setupDiagnostic.message,
2988
3037
  provenance
2989
3038
  )
2990
- ];
3039
+ ]);
2991
3040
  }
2992
3041
  const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
2993
- return [
3042
+ return emit("C-reject", [
2994
3043
  makeDiagnostic(
2995
3044
  "TYPE_MISMATCH",
2996
3045
  `Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
2997
3046
  provenance
2998
3047
  )
2999
- ];
3048
+ ]);
3000
3049
  }
3001
3050
  function getExtensionTagNames(options) {
3002
3051
  return [
@@ -3308,7 +3357,7 @@ function getTagCommentText(tag) {
3308
3357
  }
3309
3358
  return ts4.getTextOfJSDocComment(tag.comment);
3310
3359
  }
3311
- var ts4, import_internal3, import_internals4, import_internals5, SYNTHETIC_TYPE_FORMAT_FLAGS, MAX_HINT_CANDIDATES, MAX_HINT_DEPTH, parseResultCache;
3360
+ var ts4, import_internal3, import_internals4, import_internals5, import_core4, import_internal4, SYNTHETIC_TYPE_FORMAT_FLAGS, MAX_HINT_CANDIDATES, MAX_HINT_DEPTH, parseResultCache;
3312
3361
  var init_tsdoc_parser = __esm({
3313
3362
  "src/analyzer/tsdoc-parser.ts"() {
3314
3363
  "use strict";
@@ -3316,8 +3365,10 @@ var init_tsdoc_parser = __esm({
3316
3365
  import_internal3 = require("@formspec/analysis/internal");
3317
3366
  import_internals4 = require("@formspec/core/internals");
3318
3367
  import_internals5 = require("@formspec/core/internals");
3368
+ import_core4 = require("@formspec/core");
3319
3369
  init_resolve_custom_type();
3320
3370
  init_builtin_brands();
3371
+ import_internal4 = require("@formspec/analysis/internal");
3321
3372
  SYNTHETIC_TYPE_FORMAT_FLAGS = ts4.TypeFormatFlags.NoTruncation | ts4.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
3322
3373
  MAX_HINT_CANDIDATES = 5;
3323
3374
  MAX_HINT_DEPTH = 3;
@@ -3424,7 +3475,7 @@ function createAnalyzerMetadataPolicy(input, discriminator) {
3424
3475
  };
3425
3476
  }
3426
3477
  function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, checker, extensionRegistry, buildContext) {
3427
- const analysis = (0, import_internal4.analyzeMetadataForNodeWithChecker)({
3478
+ const analysis = (0, import_internal5.analyzeMetadataForNodeWithChecker)({
3428
3479
  checker,
3429
3480
  node,
3430
3481
  logicalName,
@@ -3765,7 +3816,7 @@ function getLeadingParsedTags(node) {
3765
3816
  if (!commentText.startsWith("/**")) {
3766
3817
  continue;
3767
3818
  }
3768
- parsedTags.push(...(0, import_internal4.parseCommentBlock)(commentText, { offset: range.pos }).tags);
3819
+ parsedTags.push(...(0, import_internal5.parseCommentBlock)(commentText, { offset: range.pos }).tags);
3769
3820
  }
3770
3821
  return parsedTags;
3771
3822
  }
@@ -4174,6 +4225,22 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
4174
4225
  }
4175
4226
  return referenceTypeNode.typeArguments.map((argumentNode) => {
4176
4227
  const argumentType = checker.getTypeFromTypeNode(argumentNode);
4228
+ const baseSymbol = argumentType.aliasSymbol ?? argumentType.getSymbol();
4229
+ const argumentSymbol = baseSymbol !== void 0 && baseSymbol.flags & ts6.SymbolFlags.Alias ? checker.getAliasedSymbol(baseSymbol) : baseSymbol;
4230
+ const argumentDecl = argumentSymbol?.declarations?.[0];
4231
+ if (argumentDecl !== void 0 && argumentDecl.getSourceFile().fileName !== file) {
4232
+ const argumentName = argumentSymbol?.getName() ?? baseSymbol?.getName();
4233
+ if (argumentName !== void 0) {
4234
+ return {
4235
+ tsType: argumentType,
4236
+ typeNode: {
4237
+ kind: "reference",
4238
+ name: argumentName,
4239
+ typeArguments: []
4240
+ }
4241
+ };
4242
+ }
4243
+ }
4177
4244
  return {
4178
4245
  tsType: argumentType,
4179
4246
  typeNode: resolveTypeNode(
@@ -4440,22 +4507,10 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4440
4507
  sourceNode
4441
4508
  );
4442
4509
  if (customTypeLookup !== null) {
4443
- const typeId = customTypeIdFromLookup(customTypeLookup);
4444
- let payload = null;
4445
- if (customTypeLookup.registration.extractPayload !== void 0) {
4446
- try {
4447
- payload = customTypeLookup.registration.extractPayload(type, checker) ?? null;
4448
- } catch (cause) {
4449
- throw new Error(
4450
- `extractPayload for custom type "${customTypeLookup.registration.typeName}" in extension "${customTypeLookup.extensionId}" threw`,
4451
- { cause }
4452
- );
4453
- }
4454
- }
4455
4510
  return {
4456
4511
  kind: "custom",
4457
- typeId,
4458
- payload
4512
+ typeId: customTypeIdFromLookup(customTypeLookup),
4513
+ payload: null
4459
4514
  };
4460
4515
  }
4461
4516
  const primitiveAlias = tryResolveNamedPrimitiveAlias(
@@ -4639,6 +4694,21 @@ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
4639
4694
  }
4640
4695
  return getTypeAliasDeclarationFromTypeReference(typeNode, checker);
4641
4696
  }
4697
+ function resolveNamedTypeWithSourceRecovery(type, sourceNode, checker) {
4698
+ const typeName = getNamedTypeName(type);
4699
+ const namedDecl = getNamedTypeDeclaration(type);
4700
+ if (typeName !== null && namedDecl !== void 0) {
4701
+ return { typeName, namedDecl };
4702
+ }
4703
+ if (sourceNode === void 0) {
4704
+ return null;
4705
+ }
4706
+ const refAliasDecl = getReferencedTypeAliasDeclaration(sourceNode, checker);
4707
+ if (refAliasDecl === void 0) {
4708
+ return null;
4709
+ }
4710
+ return { typeName: refAliasDecl.name.text, namedDecl: refAliasDecl };
4711
+ }
4642
4712
  function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
4643
4713
  if (!ts6.isTypeReferenceNode(typeNode)) {
4644
4714
  return false;
@@ -4697,8 +4767,23 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
4697
4767
  );
4698
4768
  }
4699
4769
  function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
4700
- const typeName = getNamedTypeName(type);
4701
- const namedDecl = getNamedTypeDeclaration(type);
4770
+ const recovered = resolveNamedTypeWithSourceRecovery(type, sourceNode, checker);
4771
+ let typeName = null;
4772
+ let namedDecl;
4773
+ if (recovered !== null) {
4774
+ const recoveredAliasDecl = ts6.isTypeAliasDeclaration(recovered.namedDecl) ? recovered.namedDecl : void 0;
4775
+ if (recoveredAliasDecl !== void 0) {
4776
+ const aliasUnderlyingType = checker.getTypeFromTypeNode(recoveredAliasDecl.type);
4777
+ const isNonGeneric = recoveredAliasDecl.typeParameters === void 0 || recoveredAliasDecl.typeParameters.length === 0;
4778
+ if (isNonGeneric && (aliasUnderlyingType.isUnion() || isObjectType(aliasUnderlyingType))) {
4779
+ typeName = recovered.typeName;
4780
+ namedDecl = recovered.namedDecl;
4781
+ }
4782
+ } else {
4783
+ typeName = recovered.typeName;
4784
+ namedDecl = recovered.namedDecl;
4785
+ }
4786
+ }
4702
4787
  if (typeName && typeName in typeRegistry) {
4703
4788
  return { kind: "reference", name: typeName, typeArguments: [] };
4704
4789
  }
@@ -4730,6 +4815,10 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
4730
4815
  if (!typeName) {
4731
4816
  return result;
4732
4817
  }
4818
+ const existing = typeRegistry[typeName];
4819
+ if (existing !== void 0 && existing.type !== RESOLVING_TYPE_PLACEHOLDER) {
4820
+ return { kind: "reference", name: typeName, typeArguments: [] };
4821
+ }
4733
4822
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4734
4823
  const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
4735
4824
  metadataPolicy,
@@ -5377,12 +5466,12 @@ function detectFormSpecReference(typeNode) {
5377
5466
  }
5378
5467
  return null;
5379
5468
  }
5380
- var ts6, import_internal4, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
5469
+ var ts6, import_internal5, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
5381
5470
  var init_class_analyzer = __esm({
5382
5471
  "src/analyzer/class-analyzer.ts"() {
5383
5472
  "use strict";
5384
5473
  ts6 = __toESM(require("typescript"), 1);
5385
- import_internal4 = require("@formspec/analysis/internal");
5474
+ import_internal5 = require("@formspec/analysis/internal");
5386
5475
  init_jsdoc_constraints();
5387
5476
  init_tsdoc_parser();
5388
5477
  init_resolve_custom_type();
@@ -5889,7 +5978,7 @@ var init_symbol_registry = __esm({
5889
5978
 
5890
5979
  // src/validate/constraint-validator.ts
5891
5980
  function validateFieldNode(ctx, field) {
5892
- const analysis = (0, import_internal5.analyzeConstraintTargets)(
5981
+ const analysis = (0, import_internal6.analyzeConstraintTargets)(
5893
5982
  field.name,
5894
5983
  field.type,
5895
5984
  field.constraints,
@@ -5907,7 +5996,7 @@ function validateFieldNode(ctx, field) {
5907
5996
  }
5908
5997
  function validateObjectProperty(ctx, parentName, property) {
5909
5998
  const qualifiedName = `${parentName}.${property.name}`;
5910
- const analysis = (0, import_internal5.analyzeConstraintTargets)(
5999
+ const analysis = (0, import_internal6.analyzeConstraintTargets)(
5911
6000
  qualifiedName,
5912
6001
  property.type,
5913
6002
  property.constraints,
@@ -5958,11 +6047,11 @@ function validateIR(ir, options) {
5958
6047
  valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
5959
6048
  };
5960
6049
  }
5961
- var import_internal5;
6050
+ var import_internal6;
5962
6051
  var init_constraint_validator = __esm({
5963
6052
  "src/validate/constraint-validator.ts"() {
5964
6053
  "use strict";
5965
- import_internal5 = require("@formspec/analysis/internal");
6054
+ import_internal6 = require("@formspec/analysis/internal");
5966
6055
  }
5967
6056
  });
5968
6057
 
@@ -6710,7 +6799,7 @@ function generateSchemasFromReturnType(options) {
6710
6799
  }
6711
6800
  function resolveDeclarationMetadata(options) {
6712
6801
  const resolved = resolveStaticOptions(options);
6713
- const analysis = (0, import_internal6.analyzeMetadataForNodeWithChecker)({
6802
+ const analysis = (0, import_internal7.analyzeMetadataForNodeWithChecker)({
6714
6803
  checker: options.context.checker,
6715
6804
  node: options.declaration,
6716
6805
  metadata: resolved.metadata,
@@ -6756,12 +6845,12 @@ function unwrapPromiseTypeNode(typeNode) {
6756
6845
  function isPromiseTypeReferenceNode(typeNode) {
6757
6846
  return ts11.isTypeReferenceNode(typeNode) && ts11.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
6758
6847
  }
6759
- var ts11, import_internal6, import_internals6;
6848
+ var ts11, import_internal7, import_internals6;
6760
6849
  var init_discovered_schema = __esm({
6761
6850
  "src/generators/discovered-schema.ts"() {
6762
6851
  "use strict";
6763
6852
  ts11 = __toESM(require("typescript"), 1);
6764
- import_internal6 = require("@formspec/analysis/internal");
6853
+ import_internal7 = require("@formspec/analysis/internal");
6765
6854
  init_class_analyzer();
6766
6855
  init_class_schema();
6767
6856
  init_ir_generator();
@@ -7003,7 +7092,7 @@ __export(index_exports, {
7003
7092
  writeSchemas: () => writeSchemas
7004
7093
  });
7005
7094
  function buildFormSchemas(form, options) {
7006
- const logger = options?.logger ?? import_core4.noopLogger;
7095
+ const logger = options?.logger ?? import_core5.noopLogger;
7007
7096
  logger.debug("buildFormSchemas: starting schema generation");
7008
7097
  return {
7009
7098
  jsonSchema: generateJsonSchema(form, options),
@@ -7020,7 +7109,7 @@ function writeSchemas(form, options) {
7020
7109
  metadata,
7021
7110
  logger: rawLogger
7022
7111
  } = options;
7023
- const logger = (rawLogger ?? import_core4.noopLogger).child({ stage: "write" });
7112
+ const logger = (rawLogger ?? import_core5.noopLogger).child({ stage: "write" });
7024
7113
  const buildOptions = vendorPrefix === void 0 && enumSerialization === void 0 && metadata === void 0 ? { logger: rawLogger } : {
7025
7114
  ...vendorPrefix !== void 0 && { vendorPrefix },
7026
7115
  ...enumSerialization !== void 0 && { enumSerialization },
@@ -7039,11 +7128,11 @@ function writeSchemas(form, options) {
7039
7128
  fs.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
7040
7129
  return { jsonSchemaPath, uiSchemaPath };
7041
7130
  }
7042
- var import_core4, fs, path3;
7131
+ var import_core5, fs, path3;
7043
7132
  var init_index = __esm({
7044
7133
  "src/index.ts"() {
7045
7134
  "use strict";
7046
- import_core4 = require("@formspec/core");
7135
+ import_core5 = require("@formspec/core");
7047
7136
  init_generator();
7048
7137
  init_generator2();
7049
7138
  init_ir_generator();