@formspec/build 0.1.0-alpha.35 → 0.1.0-alpha.37

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
@@ -2906,6 +2906,21 @@ function isObjectType(type) {
2906
2906
  function isIntersectionType(type) {
2907
2907
  return !!(type.flags & ts3.TypeFlags.Intersection);
2908
2908
  }
2909
+ function isResolvableObjectLikeAliasTypeNode(typeNode) {
2910
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2911
+ return isResolvableObjectLikeAliasTypeNode(typeNode.type);
2912
+ }
2913
+ if (ts3.isTypeLiteralNode(typeNode) || ts3.isTypeReferenceNode(typeNode)) {
2914
+ return true;
2915
+ }
2916
+ return ts3.isIntersectionTypeNode(typeNode) && typeNode.types.length > 0 && typeNode.types.every((member) => isResolvableObjectLikeAliasTypeNode(member));
2917
+ }
2918
+ function isSemanticallyPlainObjectLikeType(type, checker) {
2919
+ if (isIntersectionType(type)) {
2920
+ return type.types.length > 0 && type.types.every((member) => isSemanticallyPlainObjectLikeType(member, checker));
2921
+ }
2922
+ 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);
2923
+ }
2909
2924
  function isTypeReference(type) {
2910
2925
  return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
2911
2926
  }
@@ -3163,6 +3178,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry,
3163
3178
  const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
3164
3179
  return {
3165
3180
  ok: false,
3181
+ kind: "not-object-like",
3166
3182
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object-like type alias (found ${kindDesc})`
3167
3183
  };
3168
3184
  }
@@ -3177,6 +3193,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry,
3177
3193
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
3178
3194
  return {
3179
3195
  ok: false,
3196
+ kind: "duplicate-properties",
3180
3197
  error: `Type alias "${name}" at line ${String(line + 1)} contains duplicate property names across object-like members: ${duplicatePropertyNames.join(", ")}`
3181
3198
  };
3182
3199
  }
@@ -3873,10 +3890,10 @@ function findDuplicateObjectLikeTypeAliasPropertyNames(members) {
3873
3890
  return [...duplicates].sort();
3874
3891
  }
3875
3892
  function getAnalyzableObjectLikePropertyName(name) {
3876
- if (!ts3.isIdentifier(name)) {
3877
- return null;
3893
+ if (ts3.isIdentifier(name) || ts3.isStringLiteral(name) || ts3.isNumericLiteral(name)) {
3894
+ return name.text;
3878
3895
  }
3879
- return name.text;
3896
+ return null;
3880
3897
  }
3881
3898
  function applyEnumMemberDisplayNames(type, annotations) {
3882
3899
  if (!annotations.some(
@@ -3967,7 +3984,7 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
3967
3984
  };
3968
3985
  }
3969
3986
  if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
3970
- const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
3987
+ const aliasDecl = getTypeAliasDeclarationFromTypeReference(typeNode, checker);
3971
3988
  if (aliasDecl !== void 0) {
3972
3989
  return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
3973
3990
  }
@@ -4086,6 +4103,19 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4086
4103
  diagnostics
4087
4104
  );
4088
4105
  }
4106
+ if (resolvedSourceTypeNode !== void 0 && isResolvableObjectLikeAliasTypeNode(resolvedSourceTypeNode) && isSemanticallyPlainObjectLikeType(type, checker)) {
4107
+ return resolveObjectType(
4108
+ type,
4109
+ checker,
4110
+ file,
4111
+ typeRegistry,
4112
+ visiting,
4113
+ sourceNode,
4114
+ metadataPolicy,
4115
+ extensionRegistry,
4116
+ diagnostics
4117
+ );
4118
+ }
4089
4119
  }
4090
4120
  if (isObjectType(type)) {
4091
4121
  return resolveObjectType(
@@ -4160,13 +4190,13 @@ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
4160
4190
  if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
4161
4191
  return void 0;
4162
4192
  }
4163
- return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
4193
+ return getTypeAliasDeclarationFromTypeReference(typeNode, checker);
4164
4194
  }
4165
4195
  function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
4166
4196
  if (!ts3.isTypeReferenceNode(typeNode)) {
4167
4197
  return false;
4168
4198
  }
4169
- const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
4199
+ const aliasDecl = getTypeAliasDeclarationFromTypeReference(typeNode, checker);
4170
4200
  if (!aliasDecl) {
4171
4201
  return false;
4172
4202
  }
@@ -4735,8 +4765,7 @@ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new
4735
4765
  if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
4736
4766
  return typeNode;
4737
4767
  }
4738
- const symbol = checker.getSymbolAtLocation(typeNode.typeName);
4739
- const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
4768
+ const aliasDecl = getTypeAliasDeclarationFromTypeReference(typeNode, checker);
4740
4769
  if (aliasDecl === void 0 || visited.has(aliasDecl)) {
4741
4770
  return typeNode;
4742
4771
  }
@@ -4786,8 +4815,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
4786
4815
  );
4787
4816
  }
4788
4817
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
4789
- if (!symbol?.declarations) return [];
4790
- const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
4818
+ const aliasDecl = getAliasedTypeAliasDeclaration(symbol, checker);
4791
4819
  if (!aliasDecl) return [];
4792
4820
  if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
4793
4821
  const aliasFieldType = resolveTypeNode(
@@ -4810,6 +4838,18 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
4810
4838
  );
4811
4839
  return constraints;
4812
4840
  }
4841
+ function getAliasedSymbol(symbol, checker) {
4842
+ if (symbol === void 0) {
4843
+ return void 0;
4844
+ }
4845
+ return symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
4846
+ }
4847
+ function getAliasedTypeAliasDeclaration(symbol, checker) {
4848
+ return getAliasedSymbol(symbol, checker)?.declarations?.find(ts3.isTypeAliasDeclaration);
4849
+ }
4850
+ function getTypeAliasDeclarationFromTypeReference(typeNode, checker) {
4851
+ return getAliasedTypeAliasDeclaration(checker.getSymbolAtLocation(typeNode.typeName), checker);
4852
+ }
4813
4853
  function provenanceForNode(node, file) {
4814
4854
  const sourceFile = node.getSourceFile();
4815
4855
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
@@ -4983,6 +5023,95 @@ function findInterfaceByName(sourceFile, interfaceName) {
4983
5023
  function findTypeAliasByName(sourceFile, aliasName) {
4984
5024
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
4985
5025
  }
5026
+ function getResolvedObjectRootType(rootType, typeRegistry) {
5027
+ if (rootType.kind === "object") {
5028
+ return rootType;
5029
+ }
5030
+ if (rootType.kind !== "reference") {
5031
+ return null;
5032
+ }
5033
+ const definition = typeRegistry[rootType.name];
5034
+ return definition?.type.kind === "object" ? definition.type : null;
5035
+ }
5036
+ function createResolvedObjectAliasAnalysis(name, rootType, typeRegistry, rootInfo, diagnostics) {
5037
+ const resolvedRootType = getResolvedObjectRootType(rootType, typeRegistry);
5038
+ if (resolvedRootType === null) {
5039
+ return null;
5040
+ }
5041
+ const fields = resolvedRootType.properties.map((property) => ({
5042
+ kind: "field",
5043
+ name: property.name,
5044
+ ...property.metadata !== void 0 && { metadata: property.metadata },
5045
+ type: property.type,
5046
+ required: !property.optional,
5047
+ constraints: property.constraints,
5048
+ annotations: property.annotations,
5049
+ provenance: property.provenance
5050
+ }));
5051
+ return {
5052
+ name,
5053
+ ...rootInfo.metadata !== void 0 && { metadata: rootInfo.metadata },
5054
+ fields,
5055
+ fieldLayouts: fields.map(() => ({})),
5056
+ typeRegistry,
5057
+ ...rootInfo.annotations.length > 0 && { annotations: [...rootInfo.annotations] },
5058
+ ...diagnostics.length > 0 && { diagnostics: [...diagnostics] },
5059
+ instanceMethods: [],
5060
+ staticMethods: []
5061
+ };
5062
+ }
5063
+ function containsTypeReferenceInObjectLikeAlias(typeNode) {
5064
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
5065
+ return containsTypeReferenceInObjectLikeAlias(typeNode.type);
5066
+ }
5067
+ if (ts4.isTypeReferenceNode(typeNode)) {
5068
+ return true;
5069
+ }
5070
+ return ts4.isIntersectionTypeNode(typeNode) && typeNode.types.some((member) => containsTypeReferenceInObjectLikeAlias(member));
5071
+ }
5072
+ function collectFallbackAliasMemberPropertyNames(typeNode, checker) {
5073
+ if (ts4.isParenthesizedTypeNode(typeNode)) {
5074
+ return collectFallbackAliasMemberPropertyNames(typeNode.type, checker);
5075
+ }
5076
+ if (ts4.isTypeLiteralNode(typeNode)) {
5077
+ const propertyNames = [];
5078
+ for (const member of typeNode.members) {
5079
+ if (!ts4.isPropertySignature(member)) {
5080
+ continue;
5081
+ }
5082
+ const propertyName = getAnalyzableObjectLikePropertyName(member.name);
5083
+ if (propertyName !== null) {
5084
+ propertyNames.push(propertyName);
5085
+ }
5086
+ }
5087
+ return propertyNames;
5088
+ }
5089
+ if (ts4.isTypeReferenceNode(typeNode)) {
5090
+ return checker.getTypeFromTypeNode(typeNode).getProperties().map((property) => property.getName());
5091
+ }
5092
+ return null;
5093
+ }
5094
+ function findFallbackAliasDuplicatePropertyNames(typeNode, checker) {
5095
+ if (!ts4.isIntersectionTypeNode(typeNode)) {
5096
+ return [];
5097
+ }
5098
+ const seen = /* @__PURE__ */ new Set();
5099
+ const duplicates = /* @__PURE__ */ new Set();
5100
+ for (const member of typeNode.types) {
5101
+ const propertyNames = collectFallbackAliasMemberPropertyNames(member, checker);
5102
+ if (propertyNames === null) {
5103
+ continue;
5104
+ }
5105
+ for (const propertyName of propertyNames) {
5106
+ if (seen.has(propertyName)) {
5107
+ duplicates.add(propertyName);
5108
+ } else {
5109
+ seen.add(propertyName);
5110
+ }
5111
+ }
5112
+ }
5113
+ return [...duplicates].sort();
5114
+ }
4986
5115
  function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy, discriminatorOptions) {
4987
5116
  const ctx = createProgramContext(filePath);
4988
5117
  return analyzeNamedTypeToIRFromProgramContext(
@@ -5031,6 +5160,51 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
5031
5160
  if (result.ok) {
5032
5161
  return result.analysis;
5033
5162
  }
5163
+ const fallbackEligible = result.kind === "not-object-like" && isResolvableObjectLikeAliasTypeNode(typeAlias.type) && containsTypeReferenceInObjectLikeAlias(typeAlias.type);
5164
+ if (!fallbackEligible) {
5165
+ throw new Error(result.error);
5166
+ }
5167
+ const duplicatePropertyNames = findFallbackAliasDuplicatePropertyNames(
5168
+ typeAlias.type,
5169
+ ctx.checker
5170
+ );
5171
+ if (duplicatePropertyNames.length > 0) {
5172
+ const sourceFile = typeAlias.getSourceFile();
5173
+ const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
5174
+ throw new Error(
5175
+ `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} contains duplicate property names across object-like members: ${duplicatePropertyNames.join(", ")}`
5176
+ );
5177
+ }
5178
+ const rootInfo = analyzeDeclarationRootInfo(
5179
+ typeAlias,
5180
+ ctx.checker,
5181
+ analysisFilePath,
5182
+ extensionRegistry,
5183
+ metadataPolicy
5184
+ );
5185
+ const diagnostics = [...rootInfo.diagnostics];
5186
+ const typeRegistry = {};
5187
+ const rootType = resolveTypeNode(
5188
+ ctx.checker.getTypeAtLocation(typeAlias),
5189
+ ctx.checker,
5190
+ analysisFilePath,
5191
+ typeRegistry,
5192
+ /* @__PURE__ */ new Set(),
5193
+ typeAlias,
5194
+ createAnalyzerMetadataPolicy(metadataPolicy, discriminatorOptions),
5195
+ extensionRegistry,
5196
+ diagnostics
5197
+ );
5198
+ const fallbackAnalysis = createResolvedObjectAliasAnalysis(
5199
+ typeAlias.name.text,
5200
+ rootType,
5201
+ typeRegistry,
5202
+ rootInfo,
5203
+ diagnostics
5204
+ );
5205
+ if (fallbackAnalysis !== null) {
5206
+ return fallbackAnalysis;
5207
+ }
5034
5208
  throw new Error(result.error);
5035
5209
  }
5036
5210
  throw new Error(
@@ -5536,15 +5710,36 @@ function generateSchemasFromParameter(options) {
5536
5710
  }
5537
5711
  function generateSchemasFromReturnType(options) {
5538
5712
  const signature = options.context.checker.getSignatureFromDeclaration(options.declaration);
5539
- const type = signature !== void 0 ? options.context.checker.getReturnTypeOfSignature(signature) : options.context.checker.getTypeAtLocation(options.declaration);
5713
+ const returnType = signature !== void 0 ? options.context.checker.getReturnTypeOfSignature(signature) : options.context.checker.getTypeAtLocation(options.declaration);
5714
+ const type = unwrapPromiseType(options.context.checker, returnType);
5715
+ const sourceNode = type !== returnType ? unwrapPromiseTypeNode(options.declaration.type) ?? options.declaration.type ?? options.declaration : options.declaration.type ?? options.declaration;
5540
5716
  const fallbackName = options.declaration.name !== void 0 && ts7.isIdentifier(options.declaration.name) ? `${options.declaration.name.text}ReturnType` : "ReturnType";
5541
5717
  return generateSchemasFromResolvedType({
5542
5718
  ...options,
5543
5719
  type,
5544
- sourceNode: options.declaration.type ?? options.declaration,
5720
+ sourceNode,
5545
5721
  name: fallbackName
5546
5722
  });
5547
5723
  }
5724
+ function unwrapPromiseType(checker, type) {
5725
+ if (!("getAwaitedType" in checker) || typeof checker.getAwaitedType !== "function") {
5726
+ return type;
5727
+ }
5728
+ return checker.getAwaitedType(type) ?? type;
5729
+ }
5730
+ function unwrapPromiseTypeNode(typeNode) {
5731
+ if (typeNode === void 0) {
5732
+ return void 0;
5733
+ }
5734
+ if (ts7.isParenthesizedTypeNode(typeNode)) {
5735
+ const unwrapped = unwrapPromiseTypeNode(typeNode.type);
5736
+ return unwrapped ?? typeNode;
5737
+ }
5738
+ return isPromiseTypeReferenceNode(typeNode) ? typeNode.typeArguments[0] : typeNode;
5739
+ }
5740
+ function isPromiseTypeReferenceNode(typeNode) {
5741
+ return ts7.isTypeReferenceNode(typeNode) && ts7.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
5742
+ }
5548
5743
 
5549
5744
  // src/generators/mixed-authoring.ts
5550
5745
  function buildMixedAuthoringSchemas(options) {