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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { noopLogger as noopLogger3 } from "@formspec/core";
2
+ import { noopLogger as noopLogger4 } from "@formspec/core";
3
3
 
4
4
  // src/json-schema/generator.ts
5
5
  import { noopLogger } from "@formspec/core";
@@ -944,20 +944,29 @@ function assertNoSerializedNameCollisions(ir) {
944
944
  }
945
945
 
946
946
  // src/json-schema/ir-generator.ts
947
+ function parseEnumSerialization(value) {
948
+ switch (value) {
949
+ case void 0:
950
+ case "enum":
951
+ return "enum";
952
+ case "oneOf":
953
+ return "oneOf";
954
+ case "smart-size":
955
+ return "smart-size";
956
+ default:
957
+ throw new Error(
958
+ `Invalid enumSerialization "${String(value)}". Expected "enum", "oneOf", or "smart-size".`
959
+ );
960
+ }
961
+ }
947
962
  function makeContext(options) {
948
963
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
949
- const rawEnumSerialization = options?.enumSerialization;
964
+ const enumSerialization = parseEnumSerialization(options?.enumSerialization);
950
965
  if (!vendorPrefix.startsWith("x-")) {
951
966
  throw new Error(
952
967
  `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
953
968
  );
954
969
  }
955
- if (rawEnumSerialization !== void 0 && rawEnumSerialization !== "enum" && rawEnumSerialization !== "oneOf") {
956
- throw new Error(
957
- `Invalid enumSerialization "${rawEnumSerialization}". Expected "enum" or "oneOf".`
958
- );
959
- }
960
- const enumSerialization = rawEnumSerialization ?? "enum";
961
970
  return {
962
971
  defs: {},
963
972
  typeNameMap: {},
@@ -1165,21 +1174,31 @@ function generatePrimitiveType(type) {
1165
1174
  };
1166
1175
  }
1167
1176
  function generateEnumType(type, ctx) {
1168
- if (ctx.enumSerialization === "oneOf") {
1177
+ if (ctx.enumSerialization === "oneOf" || ctx.enumSerialization === "smart-size" && shouldSerializeEnumAsOneOf(type)) {
1169
1178
  return {
1170
- oneOf: type.members.map((m) => ({
1171
- const: m.value,
1172
- title: m.displayName ?? String(m.value)
1173
- }))
1179
+ oneOf: type.members.map((m) => {
1180
+ const stringValue = String(m.value);
1181
+ const title = m.displayName !== void 0 && m.displayName !== stringValue ? m.displayName : void 0;
1182
+ return title !== void 0 ? { const: m.value, title } : { const: m.value };
1183
+ })
1174
1184
  };
1175
1185
  }
1176
1186
  const schema = { enum: type.members.map((m) => m.value) };
1187
+ if (ctx.enumSerialization === "smart-size") {
1188
+ return schema;
1189
+ }
1177
1190
  const displayNames = buildEnumDisplayNameExtension(type);
1178
1191
  if (displayNames !== void 0) {
1179
1192
  schema[`${ctx.vendorPrefix}-display-names`] = displayNames;
1180
1193
  }
1181
1194
  return schema;
1182
1195
  }
1196
+ function shouldSerializeEnumAsOneOf(type) {
1197
+ return type.members.some((member) => {
1198
+ const title = member.displayName ?? String(member.value);
1199
+ return title !== String(member.value);
1200
+ });
1201
+ }
1183
1202
  function buildEnumDisplayNameExtension(type) {
1184
1203
  if (!type.members.some((member) => member.displayName !== void 0)) {
1185
1204
  return void 0;
@@ -1959,7 +1978,8 @@ import {
1959
1978
  } from "@formspec/core/internals";
1960
1979
  import {
1961
1980
  getTagDefinition,
1962
- normalizeFormSpecTagName
1981
+ normalizeFormSpecTagName,
1982
+ getSyntheticLogger
1963
1983
  } from "@formspec/analysis/internal";
1964
1984
  var BUILTIN_METADATA_TAGS = /* @__PURE__ */ new Set(["apiName", "displayName"]);
1965
1985
  function buildConstraintTagSources(extensions) {
@@ -1973,6 +1993,11 @@ function buildConstraintTagSources(extensions) {
1973
1993
  }));
1974
1994
  }
1975
1995
  function createExtensionRegistry(extensions) {
1996
+ const registryLog = getSyntheticLogger();
1997
+ registryLog.debug("createExtensionRegistry: constructing", {
1998
+ extensionCount: extensions.length,
1999
+ extensionIds: extensions.map((e) => e.extensionId)
2000
+ });
1976
2001
  const reservedTagSources = buildConstraintTagSources(extensions);
1977
2002
  let symbolMap = /* @__PURE__ */ new Map();
1978
2003
  const typeMap = /* @__PURE__ */ new Map();
@@ -2104,6 +2129,14 @@ function createExtensionRegistry(extensions) {
2104
2129
  }
2105
2130
  }
2106
2131
  }
2132
+ registryLog.debug("createExtensionRegistry: complete", {
2133
+ typeCount: typeMap.size,
2134
+ constraintCount: constraintMap.size,
2135
+ constraintTagCount: constraintTagMap.size,
2136
+ broadeningCount: builtinBroadeningMap.size,
2137
+ annotationCount: annotationMap.size,
2138
+ metadataSlotCount: metadataSlotMap.size
2139
+ });
2107
2140
  return {
2108
2141
  extensions,
2109
2142
  findType: (typeId) => typeMap.get(typeId),
@@ -2223,6 +2256,7 @@ import {
2223
2256
  isBuiltinConstraintName
2224
2257
  } from "@formspec/core/internals";
2225
2258
  import "@formspec/core/internals";
2259
+ import { noopLogger as noopLogger3 } from "@formspec/core";
2226
2260
 
2227
2261
  // src/extensions/resolve-custom-type.ts
2228
2262
  import * as ts2 from "typescript";
@@ -2350,6 +2384,15 @@ function isIntegerBrandedType(type) {
2350
2384
  }
2351
2385
 
2352
2386
  // src/analyzer/tsdoc-parser.ts
2387
+ import {
2388
+ getBuildLogger,
2389
+ getBroadeningLogger,
2390
+ getSyntheticLogger as getSyntheticLogger2,
2391
+ describeTypeKind,
2392
+ elapsedMicros,
2393
+ nowMicros,
2394
+ logTagApplication
2395
+ } from "@formspec/analysis/internal";
2353
2396
  function sharedTagValueOptions(options) {
2354
2397
  return {
2355
2398
  ...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
@@ -2756,56 +2799,82 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2756
2799
  if (definition === null) {
2757
2800
  return [];
2758
2801
  }
2802
+ const nonNullPlacement = placement;
2803
+ const log = getBuildLogger();
2804
+ const broadeningLog = getBroadeningLogger();
2805
+ const syntheticLog = getSyntheticLogger2();
2806
+ const logsEnabled = log !== noopLogger3 || broadeningLog !== noopLogger3;
2807
+ const syntheticTraceEnabled = syntheticLog !== noopLogger3;
2808
+ const logStart = logsEnabled ? nowMicros() : 0;
2809
+ const subjectTypeKind = logsEnabled ? describeTypeKind(subjectType, checker) : "";
2810
+ function emit(outcome, result2) {
2811
+ if (!logsEnabled) {
2812
+ return result2;
2813
+ }
2814
+ const entry = {
2815
+ consumer: "build",
2816
+ tag: tagName,
2817
+ placement: nonNullPlacement,
2818
+ subjectTypeKind,
2819
+ roleOutcome: outcome,
2820
+ elapsedMicros: elapsedMicros(logStart)
2821
+ };
2822
+ logTagApplication(log, entry);
2823
+ if (outcome === "bypass" || outcome === "D1" || outcome === "D2") {
2824
+ logTagApplication(broadeningLog, entry);
2825
+ }
2826
+ return result2;
2827
+ }
2759
2828
  if (!definition.placements.includes(placement)) {
2760
- return [
2829
+ return emit("A-reject", [
2761
2830
  makeDiagnostic(
2762
2831
  "INVALID_TAG_PLACEMENT",
2763
2832
  `Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
2764
2833
  provenance
2765
2834
  )
2766
- ];
2835
+ ]);
2767
2836
  }
2768
2837
  const target = parsedTag?.target ?? null;
2769
2838
  let evaluatedType = subjectType;
2770
2839
  let targetLabel = node.getText(sourceFile);
2771
2840
  if (target !== null) {
2772
2841
  if (target.kind !== "path") {
2773
- return [
2842
+ return emit("B-reject", [
2774
2843
  makeDiagnostic(
2775
2844
  "UNSUPPORTED_TARGETING_SYNTAX",
2776
2845
  `Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
2777
2846
  provenance
2778
2847
  )
2779
- ];
2848
+ ]);
2780
2849
  }
2781
2850
  if (!target.valid || target.path === null) {
2782
- return [
2851
+ return emit("B-reject", [
2783
2852
  makeDiagnostic(
2784
2853
  "UNSUPPORTED_TARGETING_SYNTAX",
2785
2854
  `Tag "@${tagName}" has invalid path targeting syntax.`,
2786
2855
  provenance
2787
2856
  )
2788
- ];
2857
+ ]);
2789
2858
  }
2790
2859
  const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
2791
2860
  if (resolution.kind === "missing-property") {
2792
- return [
2861
+ return emit("B-reject", [
2793
2862
  makeDiagnostic(
2794
2863
  "UNKNOWN_PATH_TARGET",
2795
2864
  `Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
2796
2865
  provenance
2797
2866
  )
2798
- ];
2867
+ ]);
2799
2868
  }
2800
2869
  if (resolution.kind === "unresolvable") {
2801
2870
  const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2802
- return [
2871
+ return emit("B-reject", [
2803
2872
  makeDiagnostic(
2804
2873
  "TYPE_MISMATCH",
2805
2874
  `Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
2806
2875
  provenance
2807
2876
  )
2808
- ];
2877
+ ]);
2809
2878
  }
2810
2879
  evaluatedType = resolution.type;
2811
2880
  targetLabel = target.rawText;
@@ -2834,13 +2903,13 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2834
2903
  tagName,
2835
2904
  parsedTag?.argumentText
2836
2905
  ) : null;
2837
- return [
2906
+ return emit("B-reject", [
2838
2907
  makeDiagnostic(
2839
2908
  "TYPE_MISMATCH",
2840
2909
  hint === null ? baseMessage : `${baseMessage}. ${hint}`,
2841
2910
  provenance
2842
2911
  )
2843
- ];
2912
+ ]);
2844
2913
  }
2845
2914
  }
2846
2915
  const argumentExpression = renderSyntheticArgumentExpression(
@@ -2848,14 +2917,23 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2848
2917
  parsedTag?.argumentText ?? ""
2849
2918
  );
2850
2919
  if (definition.requiresArgument && argumentExpression === null) {
2851
- return [];
2920
+ return emit("A-pass", []);
2852
2921
  }
2853
2922
  if (hasBroadening) {
2854
- return [];
2923
+ return emit("bypass", []);
2855
2924
  }
2856
2925
  const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2857
2926
  const hostType = options?.hostType ?? subjectType;
2858
2927
  const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2928
+ if (syntheticTraceEnabled) {
2929
+ syntheticLog.trace("invoking synthetic checker", {
2930
+ consumer: "build",
2931
+ tag: tagName,
2932
+ placement,
2933
+ subjectTypeKind,
2934
+ subjectTypeText
2935
+ });
2936
+ }
2859
2937
  const result = checkSyntheticTagApplication({
2860
2938
  tagName,
2861
2939
  placement,
@@ -2882,26 +2960,26 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2882
2960
  } : {}
2883
2961
  });
2884
2962
  if (result.diagnostics.length === 0) {
2885
- return [];
2963
+ return emit("C-pass", []);
2886
2964
  }
2887
2965
  const setupDiagnostic = result.diagnostics.find((diagnostic) => diagnostic.kind !== "typescript");
2888
2966
  if (setupDiagnostic !== void 0) {
2889
- return [
2967
+ return emit("C-reject", [
2890
2968
  makeDiagnostic(
2891
2969
  setupDiagnostic.kind === "unsupported-custom-type-override" ? "UNSUPPORTED_CUSTOM_TYPE_OVERRIDE" : "SYNTHETIC_SETUP_FAILURE",
2892
2970
  setupDiagnostic.message,
2893
2971
  provenance
2894
2972
  )
2895
- ];
2973
+ ]);
2896
2974
  }
2897
2975
  const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
2898
- return [
2976
+ return emit("C-reject", [
2899
2977
  makeDiagnostic(
2900
2978
  "TYPE_MISMATCH",
2901
2979
  `Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
2902
2980
  provenance
2903
2981
  )
2904
- ];
2982
+ ]);
2905
2983
  }
2906
2984
  var parseResultCache = /* @__PURE__ */ new Map();
2907
2985
  function getExtensionTagNames(options) {
@@ -4061,6 +4139,22 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
4061
4139
  }
4062
4140
  return referenceTypeNode.typeArguments.map((argumentNode) => {
4063
4141
  const argumentType = checker.getTypeFromTypeNode(argumentNode);
4142
+ const baseSymbol = argumentType.aliasSymbol ?? argumentType.getSymbol();
4143
+ const argumentSymbol = baseSymbol !== void 0 && baseSymbol.flags & ts6.SymbolFlags.Alias ? checker.getAliasedSymbol(baseSymbol) : baseSymbol;
4144
+ const argumentDecl = argumentSymbol?.declarations?.[0];
4145
+ if (argumentDecl !== void 0 && argumentDecl.getSourceFile().fileName !== file) {
4146
+ const argumentName = argumentSymbol?.getName() ?? baseSymbol?.getName();
4147
+ if (argumentName !== void 0) {
4148
+ return {
4149
+ tsType: argumentType,
4150
+ typeNode: {
4151
+ kind: "reference",
4152
+ name: argumentName,
4153
+ typeArguments: []
4154
+ }
4155
+ };
4156
+ }
4157
+ }
4064
4158
  return {
4065
4159
  tsType: argumentType,
4066
4160
  typeNode: resolveTypeNode(
@@ -4327,22 +4421,10 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4327
4421
  sourceNode
4328
4422
  );
4329
4423
  if (customTypeLookup !== null) {
4330
- const typeId = customTypeIdFromLookup(customTypeLookup);
4331
- let payload = null;
4332
- if (customTypeLookup.registration.extractPayload !== void 0) {
4333
- try {
4334
- payload = customTypeLookup.registration.extractPayload(type, checker) ?? null;
4335
- } catch (cause) {
4336
- throw new Error(
4337
- `extractPayload for custom type "${customTypeLookup.registration.typeName}" in extension "${customTypeLookup.extensionId}" threw`,
4338
- { cause }
4339
- );
4340
- }
4341
- }
4342
4424
  return {
4343
4425
  kind: "custom",
4344
- typeId,
4345
- payload
4426
+ typeId: customTypeIdFromLookup(customTypeLookup),
4427
+ payload: null
4346
4428
  };
4347
4429
  }
4348
4430
  const primitiveAlias = tryResolveNamedPrimitiveAlias(
@@ -4526,6 +4608,21 @@ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
4526
4608
  }
4527
4609
  return getTypeAliasDeclarationFromTypeReference(typeNode, checker);
4528
4610
  }
4611
+ function resolveNamedTypeWithSourceRecovery(type, sourceNode, checker) {
4612
+ const typeName = getNamedTypeName(type);
4613
+ const namedDecl = getNamedTypeDeclaration(type);
4614
+ if (typeName !== null && namedDecl !== void 0) {
4615
+ return { typeName, namedDecl };
4616
+ }
4617
+ if (sourceNode === void 0) {
4618
+ return null;
4619
+ }
4620
+ const refAliasDecl = getReferencedTypeAliasDeclaration(sourceNode, checker);
4621
+ if (refAliasDecl === void 0) {
4622
+ return null;
4623
+ }
4624
+ return { typeName: refAliasDecl.name.text, namedDecl: refAliasDecl };
4625
+ }
4529
4626
  function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
4530
4627
  if (!ts6.isTypeReferenceNode(typeNode)) {
4531
4628
  return false;
@@ -4584,8 +4681,23 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
4584
4681
  );
4585
4682
  }
4586
4683
  function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
4587
- const typeName = getNamedTypeName(type);
4588
- const namedDecl = getNamedTypeDeclaration(type);
4684
+ const recovered = resolveNamedTypeWithSourceRecovery(type, sourceNode, checker);
4685
+ let typeName = null;
4686
+ let namedDecl;
4687
+ if (recovered !== null) {
4688
+ const recoveredAliasDecl = ts6.isTypeAliasDeclaration(recovered.namedDecl) ? recovered.namedDecl : void 0;
4689
+ if (recoveredAliasDecl !== void 0) {
4690
+ const aliasUnderlyingType = checker.getTypeFromTypeNode(recoveredAliasDecl.type);
4691
+ const isNonGeneric = recoveredAliasDecl.typeParameters === void 0 || recoveredAliasDecl.typeParameters.length === 0;
4692
+ if (isNonGeneric && (aliasUnderlyingType.isUnion() || isObjectType(aliasUnderlyingType))) {
4693
+ typeName = recovered.typeName;
4694
+ namedDecl = recovered.namedDecl;
4695
+ }
4696
+ } else {
4697
+ typeName = recovered.typeName;
4698
+ namedDecl = recovered.namedDecl;
4699
+ }
4700
+ }
4589
4701
  if (typeName && typeName in typeRegistry) {
4590
4702
  return { kind: "reference", name: typeName, typeArguments: [] };
4591
4703
  }
@@ -4617,6 +4729,10 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
4617
4729
  if (!typeName) {
4618
4730
  return result;
4619
4731
  }
4732
+ const existing = typeRegistry[typeName];
4733
+ if (existing !== void 0 && existing.type !== RESOLVING_TYPE_PLACEHOLDER) {
4734
+ return { kind: "reference", name: typeName, typeArguments: [] };
4735
+ }
4620
4736
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4621
4737
  const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
4622
4738
  metadataPolicy,
@@ -6772,7 +6888,7 @@ function annotationKey(annotation) {
6772
6888
 
6773
6889
  // src/index.ts
6774
6890
  function buildFormSchemas(form, options) {
6775
- const logger = options?.logger ?? noopLogger3;
6891
+ const logger = options?.logger ?? noopLogger4;
6776
6892
  logger.debug("buildFormSchemas: starting schema generation");
6777
6893
  return {
6778
6894
  jsonSchema: generateJsonSchema(form, options),
@@ -6789,7 +6905,7 @@ function writeSchemas(form, options) {
6789
6905
  metadata,
6790
6906
  logger: rawLogger
6791
6907
  } = options;
6792
- const logger = (rawLogger ?? noopLogger3).child({ stage: "write" });
6908
+ const logger = (rawLogger ?? noopLogger4).child({ stage: "write" });
6793
6909
  const buildOptions = vendorPrefix === void 0 && enumSerialization === void 0 && metadata === void 0 ? { logger: rawLogger } : {
6794
6910
  ...vendorPrefix !== void 0 && { vendorPrefix },
6795
6911
  ...enumSerialization !== void 0 && { enumSerialization },