@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/cli.js CHANGED
@@ -996,20 +996,29 @@ var init_collision_guards = __esm({
996
996
  });
997
997
 
998
998
  // src/json-schema/ir-generator.ts
999
+ function parseEnumSerialization(value) {
1000
+ switch (value) {
1001
+ case void 0:
1002
+ case "enum":
1003
+ return "enum";
1004
+ case "oneOf":
1005
+ return "oneOf";
1006
+ case "smart-size":
1007
+ return "smart-size";
1008
+ default:
1009
+ throw new Error(
1010
+ `Invalid enumSerialization "${String(value)}". Expected "enum", "oneOf", or "smart-size".`
1011
+ );
1012
+ }
1013
+ }
999
1014
  function makeContext(options) {
1000
1015
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
1001
- const rawEnumSerialization = options?.enumSerialization;
1016
+ const enumSerialization = parseEnumSerialization(options?.enumSerialization);
1002
1017
  if (!vendorPrefix.startsWith("x-")) {
1003
1018
  throw new Error(
1004
1019
  `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
1005
1020
  );
1006
1021
  }
1007
- if (rawEnumSerialization !== void 0 && rawEnumSerialization !== "enum" && rawEnumSerialization !== "oneOf") {
1008
- throw new Error(
1009
- `Invalid enumSerialization "${rawEnumSerialization}". Expected "enum" or "oneOf".`
1010
- );
1011
- }
1012
- const enumSerialization = rawEnumSerialization ?? "enum";
1013
1022
  return {
1014
1023
  defs: {},
1015
1024
  typeNameMap: {},
@@ -1217,21 +1226,31 @@ function generatePrimitiveType(type) {
1217
1226
  };
1218
1227
  }
1219
1228
  function generateEnumType(type, ctx) {
1220
- if (ctx.enumSerialization === "oneOf") {
1229
+ if (ctx.enumSerialization === "oneOf" || ctx.enumSerialization === "smart-size" && shouldSerializeEnumAsOneOf(type)) {
1221
1230
  return {
1222
- oneOf: type.members.map((m) => ({
1223
- const: m.value,
1224
- title: m.displayName ?? String(m.value)
1225
- }))
1231
+ oneOf: type.members.map((m) => {
1232
+ const stringValue = String(m.value);
1233
+ const title = m.displayName !== void 0 && m.displayName !== stringValue ? m.displayName : void 0;
1234
+ return title !== void 0 ? { const: m.value, title } : { const: m.value };
1235
+ })
1226
1236
  };
1227
1237
  }
1228
1238
  const schema = { enum: type.members.map((m) => m.value) };
1239
+ if (ctx.enumSerialization === "smart-size") {
1240
+ return schema;
1241
+ }
1229
1242
  const displayNames = buildEnumDisplayNameExtension(type);
1230
1243
  if (displayNames !== void 0) {
1231
1244
  schema[`${ctx.vendorPrefix}-display-names`] = displayNames;
1232
1245
  }
1233
1246
  return schema;
1234
1247
  }
1248
+ function shouldSerializeEnumAsOneOf(type) {
1249
+ return type.members.some((member) => {
1250
+ const title = member.displayName ?? String(member.value);
1251
+ return title !== String(member.value);
1252
+ });
1253
+ }
1235
1254
  function buildEnumDisplayNameExtension(type) {
1236
1255
  if (!type.members.some((member) => member.displayName !== void 0)) {
1237
1256
  return void 0;
@@ -2042,7 +2061,8 @@ import {
2042
2061
  } from "@formspec/core/internals";
2043
2062
  import {
2044
2063
  getTagDefinition,
2045
- normalizeFormSpecTagName
2064
+ normalizeFormSpecTagName,
2065
+ getSyntheticLogger
2046
2066
  } from "@formspec/analysis/internal";
2047
2067
  function buildConstraintTagSources(extensions) {
2048
2068
  return extensions.map((extension) => ({
@@ -2055,6 +2075,11 @@ function buildConstraintTagSources(extensions) {
2055
2075
  }));
2056
2076
  }
2057
2077
  function createExtensionRegistry(extensions) {
2078
+ const registryLog = getSyntheticLogger();
2079
+ registryLog.debug("createExtensionRegistry: constructing", {
2080
+ extensionCount: extensions.length,
2081
+ extensionIds: extensions.map((e) => e.extensionId)
2082
+ });
2058
2083
  const reservedTagSources = buildConstraintTagSources(extensions);
2059
2084
  let symbolMap = /* @__PURE__ */ new Map();
2060
2085
  const typeMap = /* @__PURE__ */ new Map();
@@ -2186,6 +2211,14 @@ function createExtensionRegistry(extensions) {
2186
2211
  }
2187
2212
  }
2188
2213
  }
2214
+ registryLog.debug("createExtensionRegistry: complete", {
2215
+ typeCount: typeMap.size,
2216
+ constraintCount: constraintMap.size,
2217
+ constraintTagCount: constraintTagMap.size,
2218
+ broadeningCount: builtinBroadeningMap.size,
2219
+ annotationCount: annotationMap.size,
2220
+ metadataSlotCount: metadataSlotMap.size
2221
+ });
2189
2222
  return {
2190
2223
  extensions,
2191
2224
  findType: (typeId) => typeMap.get(typeId),
@@ -2449,6 +2482,16 @@ import {
2449
2482
  isBuiltinConstraintName
2450
2483
  } from "@formspec/core/internals";
2451
2484
  import "@formspec/core/internals";
2485
+ import { noopLogger as noopLogger4 } from "@formspec/core";
2486
+ import {
2487
+ getBuildLogger,
2488
+ getBroadeningLogger,
2489
+ getSyntheticLogger as getSyntheticLogger2,
2490
+ describeTypeKind,
2491
+ elapsedMicros,
2492
+ nowMicros,
2493
+ logTagApplication
2494
+ } from "@formspec/analysis/internal";
2452
2495
  function sharedTagValueOptions(options) {
2453
2496
  return {
2454
2497
  ...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
@@ -2852,56 +2895,82 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2852
2895
  if (definition === null) {
2853
2896
  return [];
2854
2897
  }
2898
+ const nonNullPlacement = placement;
2899
+ const log2 = getBuildLogger();
2900
+ const broadeningLog = getBroadeningLogger();
2901
+ const syntheticLog = getSyntheticLogger2();
2902
+ const logsEnabled = log2 !== noopLogger4 || broadeningLog !== noopLogger4;
2903
+ const syntheticTraceEnabled = syntheticLog !== noopLogger4;
2904
+ const logStart = logsEnabled ? nowMicros() : 0;
2905
+ const subjectTypeKind = logsEnabled ? describeTypeKind(subjectType, checker) : "";
2906
+ function emit(outcome, result2) {
2907
+ if (!logsEnabled) {
2908
+ return result2;
2909
+ }
2910
+ const entry = {
2911
+ consumer: "build",
2912
+ tag: tagName,
2913
+ placement: nonNullPlacement,
2914
+ subjectTypeKind,
2915
+ roleOutcome: outcome,
2916
+ elapsedMicros: elapsedMicros(logStart)
2917
+ };
2918
+ logTagApplication(log2, entry);
2919
+ if (outcome === "bypass" || outcome === "D1" || outcome === "D2") {
2920
+ logTagApplication(broadeningLog, entry);
2921
+ }
2922
+ return result2;
2923
+ }
2855
2924
  if (!definition.placements.includes(placement)) {
2856
- return [
2925
+ return emit("A-reject", [
2857
2926
  makeDiagnostic(
2858
2927
  "INVALID_TAG_PLACEMENT",
2859
2928
  `Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
2860
2929
  provenance
2861
2930
  )
2862
- ];
2931
+ ]);
2863
2932
  }
2864
2933
  const target = parsedTag?.target ?? null;
2865
2934
  let evaluatedType = subjectType;
2866
2935
  let targetLabel = node.getText(sourceFile);
2867
2936
  if (target !== null) {
2868
2937
  if (target.kind !== "path") {
2869
- return [
2938
+ return emit("B-reject", [
2870
2939
  makeDiagnostic(
2871
2940
  "UNSUPPORTED_TARGETING_SYNTAX",
2872
2941
  `Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
2873
2942
  provenance
2874
2943
  )
2875
- ];
2944
+ ]);
2876
2945
  }
2877
2946
  if (!target.valid || target.path === null) {
2878
- return [
2947
+ return emit("B-reject", [
2879
2948
  makeDiagnostic(
2880
2949
  "UNSUPPORTED_TARGETING_SYNTAX",
2881
2950
  `Tag "@${tagName}" has invalid path targeting syntax.`,
2882
2951
  provenance
2883
2952
  )
2884
- ];
2953
+ ]);
2885
2954
  }
2886
2955
  const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
2887
2956
  if (resolution.kind === "missing-property") {
2888
- return [
2957
+ return emit("B-reject", [
2889
2958
  makeDiagnostic(
2890
2959
  "UNKNOWN_PATH_TARGET",
2891
2960
  `Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
2892
2961
  provenance
2893
2962
  )
2894
- ];
2963
+ ]);
2895
2964
  }
2896
2965
  if (resolution.kind === "unresolvable") {
2897
2966
  const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2898
- return [
2967
+ return emit("B-reject", [
2899
2968
  makeDiagnostic(
2900
2969
  "TYPE_MISMATCH",
2901
2970
  `Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
2902
2971
  provenance
2903
2972
  )
2904
- ];
2973
+ ]);
2905
2974
  }
2906
2975
  evaluatedType = resolution.type;
2907
2976
  targetLabel = target.rawText;
@@ -2930,13 +2999,13 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2930
2999
  tagName,
2931
3000
  parsedTag?.argumentText
2932
3001
  ) : null;
2933
- return [
3002
+ return emit("B-reject", [
2934
3003
  makeDiagnostic(
2935
3004
  "TYPE_MISMATCH",
2936
3005
  hint === null ? baseMessage : `${baseMessage}. ${hint}`,
2937
3006
  provenance
2938
3007
  )
2939
- ];
3008
+ ]);
2940
3009
  }
2941
3010
  }
2942
3011
  const argumentExpression = renderSyntheticArgumentExpression(
@@ -2944,14 +3013,23 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2944
3013
  parsedTag?.argumentText ?? ""
2945
3014
  );
2946
3015
  if (definition.requiresArgument && argumentExpression === null) {
2947
- return [];
3016
+ return emit("A-pass", []);
2948
3017
  }
2949
3018
  if (hasBroadening) {
2950
- return [];
3019
+ return emit("bypass", []);
2951
3020
  }
2952
3021
  const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2953
3022
  const hostType = options?.hostType ?? subjectType;
2954
3023
  const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
3024
+ if (syntheticTraceEnabled) {
3025
+ syntheticLog.trace("invoking synthetic checker", {
3026
+ consumer: "build",
3027
+ tag: tagName,
3028
+ placement,
3029
+ subjectTypeKind,
3030
+ subjectTypeText
3031
+ });
3032
+ }
2955
3033
  const result = checkSyntheticTagApplication({
2956
3034
  tagName,
2957
3035
  placement,
@@ -2978,26 +3056,26 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2978
3056
  } : {}
2979
3057
  });
2980
3058
  if (result.diagnostics.length === 0) {
2981
- return [];
3059
+ return emit("C-pass", []);
2982
3060
  }
2983
3061
  const setupDiagnostic = result.diagnostics.find((diagnostic) => diagnostic.kind !== "typescript");
2984
3062
  if (setupDiagnostic !== void 0) {
2985
- return [
3063
+ return emit("C-reject", [
2986
3064
  makeDiagnostic(
2987
3065
  setupDiagnostic.kind === "unsupported-custom-type-override" ? "UNSUPPORTED_CUSTOM_TYPE_OVERRIDE" : "SYNTHETIC_SETUP_FAILURE",
2988
3066
  setupDiagnostic.message,
2989
3067
  provenance
2990
3068
  )
2991
- ];
3069
+ ]);
2992
3070
  }
2993
3071
  const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
2994
- return [
3072
+ return emit("C-reject", [
2995
3073
  makeDiagnostic(
2996
3074
  "TYPE_MISMATCH",
2997
3075
  `Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
2998
3076
  provenance
2999
3077
  )
3000
- ];
3078
+ ]);
3001
3079
  }
3002
3080
  function getExtensionTagNames(options) {
3003
3081
  return [
@@ -4175,6 +4253,22 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
4175
4253
  }
4176
4254
  return referenceTypeNode.typeArguments.map((argumentNode) => {
4177
4255
  const argumentType = checker.getTypeFromTypeNode(argumentNode);
4256
+ const baseSymbol = argumentType.aliasSymbol ?? argumentType.getSymbol();
4257
+ const argumentSymbol = baseSymbol !== void 0 && baseSymbol.flags & ts6.SymbolFlags.Alias ? checker.getAliasedSymbol(baseSymbol) : baseSymbol;
4258
+ const argumentDecl = argumentSymbol?.declarations?.[0];
4259
+ if (argumentDecl !== void 0 && argumentDecl.getSourceFile().fileName !== file) {
4260
+ const argumentName = argumentSymbol?.getName() ?? baseSymbol?.getName();
4261
+ if (argumentName !== void 0) {
4262
+ return {
4263
+ tsType: argumentType,
4264
+ typeNode: {
4265
+ kind: "reference",
4266
+ name: argumentName,
4267
+ typeArguments: []
4268
+ }
4269
+ };
4270
+ }
4271
+ }
4178
4272
  return {
4179
4273
  tsType: argumentType,
4180
4274
  typeNode: resolveTypeNode(
@@ -4441,22 +4535,10 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4441
4535
  sourceNode
4442
4536
  );
4443
4537
  if (customTypeLookup !== null) {
4444
- const typeId = customTypeIdFromLookup(customTypeLookup);
4445
- let payload = null;
4446
- if (customTypeLookup.registration.extractPayload !== void 0) {
4447
- try {
4448
- payload = customTypeLookup.registration.extractPayload(type, checker) ?? null;
4449
- } catch (cause) {
4450
- throw new Error(
4451
- `extractPayload for custom type "${customTypeLookup.registration.typeName}" in extension "${customTypeLookup.extensionId}" threw`,
4452
- { cause }
4453
- );
4454
- }
4455
- }
4456
4538
  return {
4457
4539
  kind: "custom",
4458
- typeId,
4459
- payload
4540
+ typeId: customTypeIdFromLookup(customTypeLookup),
4541
+ payload: null
4460
4542
  };
4461
4543
  }
4462
4544
  const primitiveAlias = tryResolveNamedPrimitiveAlias(
@@ -4640,6 +4722,21 @@ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
4640
4722
  }
4641
4723
  return getTypeAliasDeclarationFromTypeReference(typeNode, checker);
4642
4724
  }
4725
+ function resolveNamedTypeWithSourceRecovery(type, sourceNode, checker) {
4726
+ const typeName = getNamedTypeName(type);
4727
+ const namedDecl = getNamedTypeDeclaration(type);
4728
+ if (typeName !== null && namedDecl !== void 0) {
4729
+ return { typeName, namedDecl };
4730
+ }
4731
+ if (sourceNode === void 0) {
4732
+ return null;
4733
+ }
4734
+ const refAliasDecl = getReferencedTypeAliasDeclaration(sourceNode, checker);
4735
+ if (refAliasDecl === void 0) {
4736
+ return null;
4737
+ }
4738
+ return { typeName: refAliasDecl.name.text, namedDecl: refAliasDecl };
4739
+ }
4643
4740
  function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
4644
4741
  if (!ts6.isTypeReferenceNode(typeNode)) {
4645
4742
  return false;
@@ -4698,8 +4795,23 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
4698
4795
  );
4699
4796
  }
4700
4797
  function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
4701
- const typeName = getNamedTypeName(type);
4702
- const namedDecl = getNamedTypeDeclaration(type);
4798
+ const recovered = resolveNamedTypeWithSourceRecovery(type, sourceNode, checker);
4799
+ let typeName = null;
4800
+ let namedDecl;
4801
+ if (recovered !== null) {
4802
+ const recoveredAliasDecl = ts6.isTypeAliasDeclaration(recovered.namedDecl) ? recovered.namedDecl : void 0;
4803
+ if (recoveredAliasDecl !== void 0) {
4804
+ const aliasUnderlyingType = checker.getTypeFromTypeNode(recoveredAliasDecl.type);
4805
+ const isNonGeneric = recoveredAliasDecl.typeParameters === void 0 || recoveredAliasDecl.typeParameters.length === 0;
4806
+ if (isNonGeneric && (aliasUnderlyingType.isUnion() || isObjectType(aliasUnderlyingType))) {
4807
+ typeName = recovered.typeName;
4808
+ namedDecl = recovered.namedDecl;
4809
+ }
4810
+ } else {
4811
+ typeName = recovered.typeName;
4812
+ namedDecl = recovered.namedDecl;
4813
+ }
4814
+ }
4703
4815
  if (typeName && typeName in typeRegistry) {
4704
4816
  return { kind: "reference", name: typeName, typeArguments: [] };
4705
4817
  }
@@ -4731,6 +4843,10 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
4731
4843
  if (!typeName) {
4732
4844
  return result;
4733
4845
  }
4846
+ const existing = typeRegistry[typeName];
4847
+ if (existing !== void 0 && existing.type !== RESOLVING_TYPE_PLACEHOLDER) {
4848
+ return { kind: "reference", name: typeName, typeArguments: [] };
4849
+ }
4734
4850
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4735
4851
  const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
4736
4852
  metadataPolicy,
@@ -6995,11 +7111,11 @@ __export(index_exports, {
6995
7111
  uiSchemaSchema: () => uiSchema,
6996
7112
  writeSchemas: () => writeSchemas
6997
7113
  });
6998
- import { noopLogger as noopLogger4 } from "@formspec/core";
7114
+ import { noopLogger as noopLogger5 } from "@formspec/core";
6999
7115
  import * as fs from "fs";
7000
7116
  import * as path3 from "path";
7001
7117
  function buildFormSchemas(form, options) {
7002
- const logger = options?.logger ?? noopLogger4;
7118
+ const logger = options?.logger ?? noopLogger5;
7003
7119
  logger.debug("buildFormSchemas: starting schema generation");
7004
7120
  return {
7005
7121
  jsonSchema: generateJsonSchema(form, options),
@@ -7016,7 +7132,7 @@ function writeSchemas(form, options) {
7016
7132
  metadata,
7017
7133
  logger: rawLogger
7018
7134
  } = options;
7019
- const logger = (rawLogger ?? noopLogger4).child({ stage: "write" });
7135
+ const logger = (rawLogger ?? noopLogger5).child({ stage: "write" });
7020
7136
  const buildOptions = vendorPrefix === void 0 && enumSerialization === void 0 && metadata === void 0 ? { logger: rawLogger } : {
7021
7137
  ...vendorPrefix !== void 0 && { vendorPrefix },
7022
7138
  ...enumSerialization !== void 0 && { enumSerialization },
@@ -7096,7 +7212,7 @@ Usage:
7096
7212
  Options:
7097
7213
  -o, --out-dir <dir> Output directory (default: ./generated)
7098
7214
  -n, --name <name> Base name for output files (default: derived from input)
7099
- --enum-serialization <enum|oneOf>
7215
+ --enum-serialization <enum|oneOf|smart-size>
7100
7216
  Enum JSON Schema representation (default: enum)
7101
7217
  -h, --help Show this help message
7102
7218
 
@@ -7148,8 +7264,10 @@ function parseArgs(args) {
7148
7264
  console.error("Error: --enum-serialization requires a value");
7149
7265
  return null;
7150
7266
  }
7151
- if (nextArg !== "enum" && nextArg !== "oneOf") {
7152
- console.error('Error: --enum-serialization must be "enum" or "oneOf"');
7267
+ if (nextArg !== "enum" && nextArg !== "oneOf" && nextArg !== "smart-size") {
7268
+ console.error(
7269
+ 'Error: --enum-serialization must be "enum", "oneOf", or "smart-size"'
7270
+ );
7153
7271
  return null;
7154
7272
  }
7155
7273
  enumSerialization = nextArg;