@formspec/build 0.1.0-alpha.36 → 0.1.0-alpha.38

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
@@ -2147,6 +2147,16 @@ function buildSupportingDeclarations(sourceFile, extensionTypeNames) {
2147
2147
  return true;
2148
2148
  }).map((statement) => statement.getText(sourceFile));
2149
2149
  }
2150
+ function pushUniqueCompilerDiagnostics(target, additions) {
2151
+ for (const diagnostic of additions) {
2152
+ if ((diagnostic.code === "UNSUPPORTED_CUSTOM_TYPE_OVERRIDE" || diagnostic.code === "SYNTHETIC_SETUP_FAILURE") && target.some(
2153
+ (existing) => existing.code === diagnostic.code && existing.message === diagnostic.message
2154
+ )) {
2155
+ continue;
2156
+ }
2157
+ target.push(diagnostic);
2158
+ }
2159
+ }
2150
2160
  function renderSyntheticArgumentExpression(valueKind, argumentText) {
2151
2161
  const trimmed = argumentText.trim();
2152
2162
  if (trimmed === "") {
@@ -2413,6 +2423,16 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2413
2423
  if (result.diagnostics.length === 0) {
2414
2424
  return [];
2415
2425
  }
2426
+ const setupDiagnostic = result.diagnostics.find((diagnostic) => diagnostic.kind !== "typescript");
2427
+ if (setupDiagnostic !== void 0) {
2428
+ return [
2429
+ makeDiagnostic(
2430
+ setupDiagnostic.kind === "unsupported-custom-type-override" ? "UNSUPPORTED_CUSTOM_TYPE_OVERRIDE" : "SYNTHETIC_SETUP_FAILURE",
2431
+ setupDiagnostic.message,
2432
+ provenance
2433
+ )
2434
+ ];
2435
+ }
2416
2436
  const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
2417
2437
  return [
2418
2438
  makeDiagnostic(
@@ -2578,7 +2598,7 @@ function parseTSDocTags(node, file = "", options) {
2578
2598
  options
2579
2599
  );
2580
2600
  if (compilerDiagnostics.length > 0) {
2581
- diagnostics.push(...compilerDiagnostics);
2601
+ pushUniqueCompilerDiagnostics(diagnostics, compilerDiagnostics);
2582
2602
  continue;
2583
2603
  }
2584
2604
  const constraintNode = parseConstraintTagValue(
@@ -2665,7 +2685,7 @@ function parseTSDocTags(node, file = "", options) {
2665
2685
  options
2666
2686
  );
2667
2687
  if (compilerDiagnostics.length > 0) {
2668
- diagnostics.push(...compilerDiagnostics);
2688
+ pushUniqueCompilerDiagnostics(diagnostics, compilerDiagnostics);
2669
2689
  continue;
2670
2690
  }
2671
2691
  const constraintNode = parseConstraintTagValue(
@@ -2699,7 +2719,7 @@ function parseTSDocTags(node, file = "", options) {
2699
2719
  options
2700
2720
  );
2701
2721
  if (compilerDiagnostics.length > 0) {
2702
- diagnostics.push(...compilerDiagnostics);
2722
+ pushUniqueCompilerDiagnostics(diagnostics, compilerDiagnostics);
2703
2723
  continue;
2704
2724
  }
2705
2725
  const constraintNode = parseConstraintTagValue(
@@ -2906,6 +2926,21 @@ function isObjectType(type) {
2906
2926
  function isIntersectionType(type) {
2907
2927
  return !!(type.flags & ts3.TypeFlags.Intersection);
2908
2928
  }
2929
+ function isResolvableObjectLikeAliasTypeNode(typeNode) {
2930
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2931
+ return isResolvableObjectLikeAliasTypeNode(typeNode.type);
2932
+ }
2933
+ if (ts3.isTypeLiteralNode(typeNode) || ts3.isTypeReferenceNode(typeNode)) {
2934
+ return true;
2935
+ }
2936
+ return ts3.isIntersectionTypeNode(typeNode) && typeNode.types.length > 0 && typeNode.types.every((member) => isResolvableObjectLikeAliasTypeNode(member));
2937
+ }
2938
+ function isSemanticallyPlainObjectLikeType(type, checker) {
2939
+ if (isIntersectionType(type)) {
2940
+ return type.types.length > 0 && type.types.every((member) => isSemanticallyPlainObjectLikeType(member, checker));
2941
+ }
2942
+ return isObjectType(type) && checker.getSignaturesOfType(type, ts3.SignatureKind.Call).length === 0 && checker.getSignaturesOfType(type, ts3.SignatureKind.Construct).length === 0 && !checker.isArrayType(type) && !checker.isTupleType(type);
2943
+ }
2909
2944
  function isTypeReference(type) {
2910
2945
  return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
2911
2946
  }
@@ -3163,6 +3198,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry,
3163
3198
  const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
3164
3199
  return {
3165
3200
  ok: false,
3201
+ kind: "not-object-like",
3166
3202
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object-like type alias (found ${kindDesc})`
3167
3203
  };
3168
3204
  }
@@ -3177,6 +3213,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry,
3177
3213
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
3178
3214
  return {
3179
3215
  ok: false,
3216
+ kind: "duplicate-properties",
3180
3217
  error: `Type alias "${name}" at line ${String(line + 1)} contains duplicate property names across object-like members: ${duplicatePropertyNames.join(", ")}`
3181
3218
  };
3182
3219
  }
@@ -3873,10 +3910,10 @@ function findDuplicateObjectLikeTypeAliasPropertyNames(members) {
3873
3910
  return [...duplicates].sort();
3874
3911
  }
3875
3912
  function getAnalyzableObjectLikePropertyName(name) {
3876
- if (!ts3.isIdentifier(name)) {
3877
- return null;
3913
+ if (ts3.isIdentifier(name) || ts3.isStringLiteral(name) || ts3.isNumericLiteral(name)) {
3914
+ return name.text;
3878
3915
  }
3879
- return name.text;
3916
+ return null;
3880
3917
  }
3881
3918
  function applyEnumMemberDisplayNames(type, annotations) {
3882
3919
  if (!annotations.some(
@@ -4086,6 +4123,19 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4086
4123
  diagnostics
4087
4124
  );
4088
4125
  }
4126
+ if (resolvedSourceTypeNode !== void 0 && isResolvableObjectLikeAliasTypeNode(resolvedSourceTypeNode) && isSemanticallyPlainObjectLikeType(type, checker)) {
4127
+ return resolveObjectType(
4128
+ type,
4129
+ checker,
4130
+ file,
4131
+ typeRegistry,
4132
+ visiting,
4133
+ sourceNode,
4134
+ metadataPolicy,
4135
+ extensionRegistry,
4136
+ diagnostics
4137
+ );
4138
+ }
4089
4139
  }
4090
4140
  if (isObjectType(type)) {
4091
4141
  return resolveObjectType(
@@ -4993,6 +5043,95 @@ function findInterfaceByName(sourceFile, interfaceName) {
4993
5043
  function findTypeAliasByName(sourceFile, aliasName) {
4994
5044
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
4995
5045
  }
5046
+ function getResolvedObjectRootType(rootType, typeRegistry) {
5047
+ if (rootType.kind === "object") {
5048
+ return rootType;
5049
+ }
5050
+ if (rootType.kind !== "reference") {
5051
+ return null;
5052
+ }
5053
+ const definition = typeRegistry[rootType.name];
5054
+ return definition?.type.kind === "object" ? definition.type : null;
5055
+ }
5056
+ function createResolvedObjectAliasAnalysis(name, rootType, typeRegistry, rootInfo, diagnostics) {
5057
+ const resolvedRootType = getResolvedObjectRootType(rootType, typeRegistry);
5058
+ if (resolvedRootType === null) {
5059
+ return null;
5060
+ }
5061
+ const fields = resolvedRootType.properties.map((property) => ({
5062
+ kind: "field",
5063
+ name: property.name,
5064
+ ...property.metadata !== void 0 && { metadata: property.metadata },
5065
+ type: property.type,
5066
+ required: !property.optional,
5067
+ constraints: property.constraints,
5068
+ annotations: property.annotations,
5069
+ provenance: property.provenance
5070
+ }));
5071
+ return {
5072
+ name,
5073
+ ...rootInfo.metadata !== void 0 && { metadata: rootInfo.metadata },
5074
+ fields,
5075
+ fieldLayouts: fields.map(() => ({})),
5076
+ typeRegistry,
5077
+ ...rootInfo.annotations.length > 0 && { annotations: [...rootInfo.annotations] },
5078
+ ...diagnostics.length > 0 && { diagnostics: [...diagnostics] },
5079
+ instanceMethods: [],
5080
+ staticMethods: []
5081
+ };
5082
+ }
5083
+ function containsTypeReferenceInObjectLikeAlias(typeNode) {
5084
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
5085
+ return containsTypeReferenceInObjectLikeAlias(typeNode.type);
5086
+ }
5087
+ if (ts4.isTypeReferenceNode(typeNode)) {
5088
+ return true;
5089
+ }
5090
+ return ts4.isIntersectionTypeNode(typeNode) && typeNode.types.some((member) => containsTypeReferenceInObjectLikeAlias(member));
5091
+ }
5092
+ function collectFallbackAliasMemberPropertyNames(typeNode, checker) {
5093
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
5094
+ return collectFallbackAliasMemberPropertyNames(typeNode.type, checker);
5095
+ }
5096
+ if (ts4.isTypeLiteralNode(typeNode)) {
5097
+ const propertyNames = [];
5098
+ for (const member of typeNode.members) {
5099
+ if (!ts4.isPropertySignature(member)) {
5100
+ continue;
5101
+ }
5102
+ const propertyName = getAnalyzableObjectLikePropertyName(member.name);
5103
+ if (propertyName !== null) {
5104
+ propertyNames.push(propertyName);
5105
+ }
5106
+ }
5107
+ return propertyNames;
5108
+ }
5109
+ if (ts4.isTypeReferenceNode(typeNode)) {
5110
+ return checker.getTypeFromTypeNode(typeNode).getProperties().map((property) => property.getName());
5111
+ }
5112
+ return null;
5113
+ }
5114
+ function findFallbackAliasDuplicatePropertyNames(typeNode, checker) {
5115
+ if (!ts4.isIntersectionTypeNode(typeNode)) {
5116
+ return [];
5117
+ }
5118
+ const seen = /* @__PURE__ */ new Set();
5119
+ const duplicates = /* @__PURE__ */ new Set();
5120
+ for (const member of typeNode.types) {
5121
+ const propertyNames = collectFallbackAliasMemberPropertyNames(member, checker);
5122
+ if (propertyNames === null) {
5123
+ continue;
5124
+ }
5125
+ for (const propertyName of propertyNames) {
5126
+ if (seen.has(propertyName)) {
5127
+ duplicates.add(propertyName);
5128
+ } else {
5129
+ seen.add(propertyName);
5130
+ }
5131
+ }
5132
+ }
5133
+ return [...duplicates].sort();
5134
+ }
4996
5135
  function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy, discriminatorOptions) {
4997
5136
  const ctx = createProgramContext(filePath);
4998
5137
  return analyzeNamedTypeToIRFromProgramContext(
@@ -5041,6 +5180,51 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
5041
5180
  if (result.ok) {
5042
5181
  return result.analysis;
5043
5182
  }
5183
+ const fallbackEligible = result.kind === "not-object-like" && isResolvableObjectLikeAliasTypeNode(typeAlias.type) && containsTypeReferenceInObjectLikeAlias(typeAlias.type);
5184
+ if (!fallbackEligible) {
5185
+ throw new Error(result.error);
5186
+ }
5187
+ const duplicatePropertyNames = findFallbackAliasDuplicatePropertyNames(
5188
+ typeAlias.type,
5189
+ ctx.checker
5190
+ );
5191
+ if (duplicatePropertyNames.length > 0) {
5192
+ const sourceFile = typeAlias.getSourceFile();
5193
+ const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
5194
+ throw new Error(
5195
+ `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} contains duplicate property names across object-like members: ${duplicatePropertyNames.join(", ")}`
5196
+ );
5197
+ }
5198
+ const rootInfo = analyzeDeclarationRootInfo(
5199
+ typeAlias,
5200
+ ctx.checker,
5201
+ analysisFilePath,
5202
+ extensionRegistry,
5203
+ metadataPolicy
5204
+ );
5205
+ const diagnostics = [...rootInfo.diagnostics];
5206
+ const typeRegistry = {};
5207
+ const rootType = resolveTypeNode(
5208
+ ctx.checker.getTypeAtLocation(typeAlias),
5209
+ ctx.checker,
5210
+ analysisFilePath,
5211
+ typeRegistry,
5212
+ /* @__PURE__ */ new Set(),
5213
+ typeAlias,
5214
+ createAnalyzerMetadataPolicy(metadataPolicy, discriminatorOptions),
5215
+ extensionRegistry,
5216
+ diagnostics
5217
+ );
5218
+ const fallbackAnalysis = createResolvedObjectAliasAnalysis(
5219
+ typeAlias.name.text,
5220
+ rootType,
5221
+ typeRegistry,
5222
+ rootInfo,
5223
+ diagnostics
5224
+ );
5225
+ if (fallbackAnalysis !== null) {
5226
+ return fallbackAnalysis;
5227
+ }
5044
5228
  throw new Error(result.error);
5045
5229
  }
5046
5230
  throw new Error(
@@ -5546,15 +5730,36 @@ function generateSchemasFromParameter(options) {
5546
5730
  }
5547
5731
  function generateSchemasFromReturnType(options) {
5548
5732
  const signature = options.context.checker.getSignatureFromDeclaration(options.declaration);
5549
- const type = signature !== void 0 ? options.context.checker.getReturnTypeOfSignature(signature) : options.context.checker.getTypeAtLocation(options.declaration);
5733
+ const returnType = signature !== void 0 ? options.context.checker.getReturnTypeOfSignature(signature) : options.context.checker.getTypeAtLocation(options.declaration);
5734
+ const type = unwrapPromiseType(options.context.checker, returnType);
5735
+ const sourceNode = type !== returnType ? unwrapPromiseTypeNode(options.declaration.type) ?? options.declaration.type ?? options.declaration : options.declaration.type ?? options.declaration;
5550
5736
  const fallbackName = options.declaration.name !== void 0 && ts7.isIdentifier(options.declaration.name) ? `${options.declaration.name.text}ReturnType` : "ReturnType";
5551
5737
  return generateSchemasFromResolvedType({
5552
5738
  ...options,
5553
5739
  type,
5554
- sourceNode: options.declaration.type ?? options.declaration,
5740
+ sourceNode,
5555
5741
  name: fallbackName
5556
5742
  });
5557
5743
  }
5744
+ function unwrapPromiseType(checker, type) {
5745
+ if (!("getAwaitedType" in checker) || typeof checker.getAwaitedType !== "function") {
5746
+ return type;
5747
+ }
5748
+ return checker.getAwaitedType(type) ?? type;
5749
+ }
5750
+ function unwrapPromiseTypeNode(typeNode) {
5751
+ if (typeNode === void 0) {
5752
+ return void 0;
5753
+ }
5754
+ if (ts7.isParenthesizedTypeNode(typeNode)) {
5755
+ const unwrapped = unwrapPromiseTypeNode(typeNode.type);
5756
+ return unwrapped ?? typeNode;
5757
+ }
5758
+ return isPromiseTypeReferenceNode(typeNode) ? typeNode.typeArguments[0] : typeNode;
5759
+ }
5760
+ function isPromiseTypeReferenceNode(typeNode) {
5761
+ return ts7.isTypeReferenceNode(typeNode) && ts7.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
5762
+ }
5558
5763
 
5559
5764
  // src/generators/mixed-authoring.ts
5560
5765
  function buildMixedAuthoringSchemas(options) {