@formspec/build 0.1.0-alpha.32 → 0.1.0-alpha.34

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.
@@ -805,7 +805,7 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
805
805
  })
806
806
  );
807
807
  }
808
- for (const tagName of ["displayName", "format", "placeholder"]) {
808
+ for (const tagName of ["apiName", "displayName", "format", "placeholder"]) {
809
809
  config.addTagDefinition(
810
810
  new import_tsdoc.TSDocTagDefinition({
811
811
  tagName: "@" + tagName,
@@ -839,6 +839,16 @@ function sharedTagValueOptions(options) {
839
839
  };
840
840
  }
841
841
  var SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
842
+ function getExtensionTypeNames(registry) {
843
+ if (registry === void 0) {
844
+ return /* @__PURE__ */ new Set();
845
+ }
846
+ return new Set(
847
+ registry.extensions.flatMap(
848
+ (ext) => (ext.types ?? []).flatMap((t) => t.tsTypeNames ?? [t.typeName])
849
+ )
850
+ );
851
+ }
842
852
  function collectImportedNames(sourceFile) {
843
853
  const importedNames = /* @__PURE__ */ new Set();
844
854
  for (const statement of sourceFile.statements) {
@@ -878,6 +888,9 @@ function isNonReferenceIdentifier(node) {
878
888
  return false;
879
889
  }
880
890
  function statementReferencesImportedName(statement, importedNames) {
891
+ if (importedNames.size === 0) {
892
+ return false;
893
+ }
881
894
  let referencesImportedName = false;
882
895
  const visit = (node) => {
883
896
  if (referencesImportedName) {
@@ -892,14 +905,17 @@ function statementReferencesImportedName(statement, importedNames) {
892
905
  visit(statement);
893
906
  return referencesImportedName;
894
907
  }
895
- function buildSupportingDeclarations(sourceFile) {
908
+ function buildSupportingDeclarations(sourceFile, extensionTypeNames) {
896
909
  const importedNames = collectImportedNames(sourceFile);
910
+ const importedNamesToSkip = new Set(
911
+ [...importedNames].filter((name) => !extensionTypeNames.has(name))
912
+ );
897
913
  return sourceFile.statements.filter((statement) => {
898
914
  if (ts.isImportDeclaration(statement)) return false;
899
915
  if (ts.isImportEqualsDeclaration(statement)) return false;
900
916
  if (ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
901
917
  return false;
902
- if (importedNames.size > 0 && statementReferencesImportedName(statement, importedNames)) {
918
+ if (statementReferencesImportedName(statement, importedNamesToSkip)) {
903
919
  return false;
904
920
  }
905
921
  return true;
@@ -1156,6 +1172,14 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
1156
1172
  extensionId: extension.extensionId,
1157
1173
  ...extension.constraintTags !== void 0 ? {
1158
1174
  constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
1175
+ } : {},
1176
+ ...extension.metadataSlots !== void 0 ? {
1177
+ metadataSlots: extension.metadataSlots
1178
+ } : {},
1179
+ ...extension.types !== void 0 ? {
1180
+ customTypes: extension.types.map((t) => ({
1181
+ tsTypeNames: t.tsTypeNames ?? [t.typeName]
1182
+ }))
1159
1183
  } : {}
1160
1184
  }))
1161
1185
  } : {}
@@ -1177,7 +1201,10 @@ var parseResultCache = /* @__PURE__ */ new Map();
1177
1201
  function getParser(options) {
1178
1202
  const extensionTagNames = [
1179
1203
  ...options?.extensionRegistry?.extensions.flatMap(
1180
- (extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
1204
+ (extension) => (extension.constraintTags ?? []).map((tag) => (0, import_internal.normalizeFormSpecTagName)(tag.tagName))
1205
+ ) ?? [],
1206
+ ...options?.extensionRegistry?.extensions.flatMap(
1207
+ (extension) => (extension.metadataSlots ?? []).map((slot) => (0, import_internal.normalizeFormSpecTagName)(slot.tagName))
1181
1208
  ) ?? []
1182
1209
  ].sort();
1183
1210
  const cacheKey = extensionTagNames.join("|");
@@ -1197,7 +1224,16 @@ function getExtensionRegistryCacheKey(registry) {
1197
1224
  (extension) => JSON.stringify({
1198
1225
  extensionId: extension.extensionId,
1199
1226
  typeNames: extension.types?.map((type) => type.typeName) ?? [],
1200
- constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
1227
+ constraintTags: extension.constraintTags?.map((tag) => (0, import_internal.normalizeFormSpecTagName)(tag.tagName)) ?? [],
1228
+ metadataSlots: extension.metadataSlots?.map((slot) => ({
1229
+ tagName: (0, import_internal.normalizeFormSpecTagName)(slot.tagName),
1230
+ declarationKinds: [...slot.declarationKinds].sort(),
1231
+ allowBare: slot.allowBare !== false,
1232
+ qualifiers: (slot.qualifiers ?? []).map((qualifier) => ({
1233
+ qualifier: qualifier.qualifier,
1234
+ ...qualifier.sourceQualifier !== void 0 ? { sourceQualifier: qualifier.sourceQualifier } : {}
1235
+ })).sort((left, right) => left.qualifier.localeCompare(right.qualifier))
1236
+ })) ?? []
1201
1237
  })
1202
1238
  ).join("|");
1203
1239
  }
@@ -1232,7 +1268,8 @@ function parseTSDocTags(node, file = "", options) {
1232
1268
  const rawTextTags = [];
1233
1269
  const sourceFile = node.getSourceFile();
1234
1270
  const sourceText = sourceFile.getFullText();
1235
- const supportingDeclarations = buildSupportingDeclarations(sourceFile);
1271
+ const extensionTypeNames = getExtensionTypeNames(options?.extensionRegistry);
1272
+ const supportingDeclarations = buildSupportingDeclarations(sourceFile, extensionTypeNames);
1236
1273
  const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
1237
1274
  const rawTextFallbacks = collectRawTextFallbacks(node, file);
1238
1275
  if (commentRanges) {
@@ -1640,6 +1677,9 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1640
1677
  function isObjectType(type) {
1641
1678
  return !!(type.flags & ts3.TypeFlags.Object);
1642
1679
  }
1680
+ function isIntersectionType(type) {
1681
+ return !!(type.flags & ts3.TypeFlags.Intersection);
1682
+ }
1643
1683
  function isTypeReference(type) {
1644
1684
  return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
1645
1685
  }
@@ -1660,76 +1700,54 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
1660
1700
  ...hostType !== void 0 && { hostType }
1661
1701
  };
1662
1702
  }
1663
- function makeExplicitScalarMetadata(value) {
1664
- return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
1665
- }
1666
- function extractExplicitMetadata(node) {
1667
- let apiName;
1668
- let displayName;
1669
- let apiNamePlural;
1670
- let displayNamePlural;
1671
- for (const tag of getLeadingParsedTags(node)) {
1672
- const value = tag.argumentText.trim();
1673
- if (value === "") {
1674
- continue;
1675
- }
1676
- if (tag.normalizedTagName === "apiName") {
1677
- if (tag.target === null) {
1678
- apiName ??= value;
1679
- } else if (tag.target.kind === "variant") {
1680
- if (tag.target.rawText === "singular") {
1681
- apiName ??= value;
1682
- } else if (tag.target.rawText === "plural") {
1683
- apiNamePlural ??= value;
1684
- }
1685
- }
1686
- continue;
1687
- }
1688
- if (tag.normalizedTagName === "displayName") {
1689
- if (tag.target === null) {
1690
- displayName ??= value;
1691
- } else if (tag.target.kind === "variant") {
1692
- if (tag.target.rawText === "singular") {
1693
- displayName ??= value;
1694
- } else if (tag.target.rawText === "plural") {
1695
- displayNamePlural ??= value;
1696
- }
1697
- }
1698
- }
1699
- }
1700
- const resolvedApiName = makeExplicitScalarMetadata(apiName);
1701
- const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
1702
- const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
1703
- const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
1704
- const metadata = {
1705
- ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
1706
- ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
1707
- ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
1708
- ...resolvedDisplayNamePlural !== void 0 && {
1709
- displayNamePlural: resolvedDisplayNamePlural
1710
- }
1703
+ function createAnalyzerMetadataPolicy(input, discriminator) {
1704
+ return {
1705
+ raw: input,
1706
+ normalized: normalizeMetadataPolicy(input),
1707
+ discriminator
1711
1708
  };
1712
- return Object.keys(metadata).length === 0 ? void 0 : metadata;
1713
1709
  }
1714
- function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
1715
- const explicit = extractExplicitMetadata(node);
1716
- return resolveMetadata(
1717
- {
1718
- ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
1719
- ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
1720
- ...explicit?.apiNamePlural !== void 0 && {
1721
- apiNamePlural: explicit.apiNamePlural.value
1722
- },
1723
- ...explicit?.displayNamePlural !== void 0 && {
1724
- displayNamePlural: explicit.displayNamePlural.value
1725
- }
1726
- },
1727
- getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
1728
- makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
1710
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, checker, extensionRegistry, buildContext) {
1711
+ const analysis = (0, import_internal2.analyzeMetadataForNodeWithChecker)({
1712
+ checker,
1713
+ node,
1714
+ logicalName,
1715
+ metadata: metadataPolicy.raw,
1716
+ extensions: extensionRegistry?.extensions,
1717
+ ...buildContext !== void 0 && { buildContext }
1718
+ });
1719
+ const resolvedMetadata = analysis?.resolvedMetadata;
1720
+ const declarationPolicy = getDeclarationMetadataPolicy(
1721
+ metadataPolicy.normalized,
1722
+ declarationKind
1729
1723
  );
1724
+ if (resolvedMetadata?.apiName === void 0 && declarationPolicy.apiName.mode === "require-explicit") {
1725
+ throw new Error(
1726
+ `Metadata policy requires explicit apiName for ${declarationKind} "${logicalName}" on the tsdoc surface.`
1727
+ );
1728
+ }
1729
+ if (resolvedMetadata?.displayName === void 0 && declarationPolicy.displayName.mode === "require-explicit") {
1730
+ throw new Error(
1731
+ `Metadata policy requires explicit displayName for ${declarationKind} "${logicalName}" on the tsdoc surface.`
1732
+ );
1733
+ }
1734
+ if (resolvedMetadata?.apiNamePlural === void 0 && declarationPolicy.apiName.pluralization.mode === "require-explicit") {
1735
+ throw new Error(
1736
+ `Metadata policy requires explicit apiNamePlural for ${declarationKind} "${logicalName}" on the tsdoc surface.`
1737
+ );
1738
+ }
1739
+ if (resolvedMetadata?.displayNamePlural === void 0 && declarationPolicy.displayName.pluralization.mode === "require-explicit") {
1740
+ throw new Error(
1741
+ `Metadata policy requires explicit displayNamePlural for ${declarationKind} "${logicalName}" on the tsdoc surface.`
1742
+ );
1743
+ }
1744
+ return resolvedMetadata;
1730
1745
  }
1731
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
1732
- const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1746
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy, discriminatorOptions) {
1747
+ const normalizedMetadataPolicy = createAnalyzerMetadataPolicy(
1748
+ metadataPolicy,
1749
+ discriminatorOptions
1750
+ );
1733
1751
  const name = classDecl.name?.text ?? "AnonymousClass";
1734
1752
  const fields = [];
1735
1753
  const fieldLayouts = [];
@@ -1784,12 +1802,20 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, meta
1784
1802
  diagnostics,
1785
1803
  normalizedMetadataPolicy
1786
1804
  );
1787
- const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
1805
+ const metadata = resolveNodeMetadata(
1806
+ normalizedMetadataPolicy,
1807
+ "type",
1808
+ name,
1809
+ classDecl,
1788
1810
  checker,
1789
- declaration: classDecl,
1790
- subjectType: classType,
1791
- hostType: classType
1792
- });
1811
+ extensionRegistry,
1812
+ {
1813
+ checker,
1814
+ declaration: classDecl,
1815
+ subjectType: classType,
1816
+ hostType: classType
1817
+ }
1818
+ );
1793
1819
  return {
1794
1820
  name,
1795
1821
  ...metadata !== void 0 && { metadata },
@@ -1802,8 +1828,11 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, meta
1802
1828
  staticMethods
1803
1829
  };
1804
1830
  }
1805
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
1806
- const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1831
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy, discriminatorOptions) {
1832
+ const normalizedMetadataPolicy = createAnalyzerMetadataPolicy(
1833
+ metadataPolicy,
1834
+ discriminatorOptions
1835
+ );
1807
1836
  const name = interfaceDecl.name.text;
1808
1837
  const fields = [];
1809
1838
  const typeRegistry = {};
@@ -1845,12 +1874,20 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1845
1874
  normalizedMetadataPolicy
1846
1875
  );
1847
1876
  const fieldLayouts = specializedFields.map(() => ({}));
1848
- const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
1877
+ const metadata = resolveNodeMetadata(
1878
+ normalizedMetadataPolicy,
1879
+ "type",
1880
+ name,
1881
+ interfaceDecl,
1849
1882
  checker,
1850
- declaration: interfaceDecl,
1851
- subjectType: interfaceType,
1852
- hostType: interfaceType
1853
- });
1883
+ extensionRegistry,
1884
+ {
1885
+ checker,
1886
+ declaration: interfaceDecl,
1887
+ subjectType: interfaceType,
1888
+ hostType: interfaceType
1889
+ }
1890
+ );
1854
1891
  return {
1855
1892
  name,
1856
1893
  ...metadata !== void 0 && { metadata },
@@ -1863,19 +1900,31 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1863
1900
  staticMethods: []
1864
1901
  };
1865
1902
  }
1866
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
1867
- if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1903
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy, discriminatorOptions) {
1904
+ const members = getObjectLikeTypeAliasMembers(typeAlias.type);
1905
+ if (members === null) {
1868
1906
  const sourceFile = typeAlias.getSourceFile();
1869
1907
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1870
1908
  const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1871
1909
  return {
1872
1910
  ok: false,
1873
- error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
1911
+ error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object-like type alias (found ${kindDesc})`
1874
1912
  };
1875
1913
  }
1876
- const typeLiteral = typeAlias.type;
1877
- const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1914
+ const normalizedMetadataPolicy = createAnalyzerMetadataPolicy(
1915
+ metadataPolicy,
1916
+ discriminatorOptions
1917
+ );
1878
1918
  const name = typeAlias.name.text;
1919
+ const duplicatePropertyNames = findDuplicateObjectLikeTypeAliasPropertyNames(members);
1920
+ if (duplicatePropertyNames.length > 0) {
1921
+ const sourceFile = typeAlias.getSourceFile();
1922
+ const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1923
+ return {
1924
+ ok: false,
1925
+ error: `Type alias "${name}" at line ${String(line + 1)} contains duplicate property names across object-like members: ${duplicatePropertyNames.join(", ")}`
1926
+ };
1927
+ }
1879
1928
  const fields = [];
1880
1929
  const typeRegistry = {};
1881
1930
  const diagnostics = [];
@@ -1888,7 +1937,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry,
1888
1937
  const annotations = [...typeAliasDoc.annotations];
1889
1938
  diagnostics.push(...typeAliasDoc.diagnostics);
1890
1939
  const visiting = /* @__PURE__ */ new Set();
1891
- for (const member of typeLiteral.members) {
1940
+ for (const member of members) {
1892
1941
  if (ts3.isPropertySignature(member)) {
1893
1942
  const fieldNode = analyzeInterfacePropertyToIR(
1894
1943
  member,
@@ -1915,12 +1964,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry,
1915
1964
  diagnostics,
1916
1965
  normalizedMetadataPolicy
1917
1966
  );
1918
- const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
1967
+ const metadata = resolveNodeMetadata(
1968
+ normalizedMetadataPolicy,
1969
+ "type",
1970
+ name,
1971
+ typeAlias,
1919
1972
  checker,
1920
- declaration: typeAlias,
1921
- subjectType: aliasType,
1922
- hostType: aliasType
1923
- });
1973
+ extensionRegistry,
1974
+ {
1975
+ checker,
1976
+ declaration: typeAlias,
1977
+ subjectType: aliasType,
1978
+ hostType: aliasType
1979
+ }
1980
+ );
1924
1981
  return {
1925
1982
  ok: true,
1926
1983
  analysis: {
@@ -1989,15 +2046,43 @@ function isNullishSemanticType(type) {
1989
2046
  }
1990
2047
  return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
1991
2048
  }
1992
- function isStringLikeSemanticType(type) {
2049
+ function isStringLikeSemanticType(type, checker, seen = /* @__PURE__ */ new Set()) {
2050
+ if (seen.has(type)) {
2051
+ return false;
2052
+ }
2053
+ seen.add(type);
1993
2054
  if (type.flags & ts3.TypeFlags.StringLike) {
1994
2055
  return true;
1995
2056
  }
1996
2057
  if (type.isUnion()) {
1997
- return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
2058
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member, checker, seen));
2059
+ }
2060
+ const baseConstraint = checker.getBaseConstraintOfType(type);
2061
+ if (baseConstraint !== void 0 && baseConstraint !== type) {
2062
+ return isStringLikeSemanticType(baseConstraint, checker, seen);
1998
2063
  }
1999
2064
  return false;
2000
2065
  }
2066
+ function getObjectLikeTypeAliasMembers(typeNode) {
2067
+ if (ts3.isParenthesizedTypeNode(typeNode)) {
2068
+ return getObjectLikeTypeAliasMembers(typeNode.type);
2069
+ }
2070
+ if (ts3.isTypeLiteralNode(typeNode)) {
2071
+ return [...typeNode.members];
2072
+ }
2073
+ if (ts3.isIntersectionTypeNode(typeNode)) {
2074
+ const members = [];
2075
+ for (const intersectionMember of typeNode.types) {
2076
+ const resolvedMembers = getObjectLikeTypeAliasMembers(intersectionMember);
2077
+ if (resolvedMembers === null) {
2078
+ return null;
2079
+ }
2080
+ members.push(...resolvedMembers);
2081
+ }
2082
+ return members;
2083
+ }
2084
+ return null;
2085
+ }
2001
2086
  function extractDiscriminatorDirective(node, file, diagnostics) {
2002
2087
  const discriminatorTags = getLeadingParsedTags(node).filter(
2003
2088
  (tag) => tag.normalizedTagName === "discriminator"
@@ -2104,7 +2189,7 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2104
2189
  );
2105
2190
  return null;
2106
2191
  }
2107
- if (!isStringLikeSemanticType(property.type)) {
2192
+ if (!isStringLikeSemanticType(property.type, checker)) {
2108
2193
  diagnostics.push(
2109
2194
  makeAnalysisDiagnostic(
2110
2195
  "TYPE_MISMATCH",
@@ -2131,8 +2216,8 @@ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typ
2131
2216
  const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2132
2217
  return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2133
2218
  }
2134
- function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
2135
- const propertySymbol = boundType.getProperty(fieldName);
2219
+ function resolveLiteralDiscriminatorPropertyValue(boundType, propertyName, checker, provenance, diagnostics) {
2220
+ const propertySymbol = boundType.getProperty(propertyName);
2136
2221
  if (propertySymbol === void 0) {
2137
2222
  return void 0;
2138
2223
  }
@@ -2163,6 +2248,9 @@ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker,
2163
2248
  }
2164
2249
  return void 0;
2165
2250
  }
2251
+ function getDiscriminatorIdentityPropertyNames(fieldName) {
2252
+ return fieldName === "object" ? ["object"] : [fieldName, "object"];
2253
+ }
2166
2254
  function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
2167
2255
  const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2168
2256
  if (declaration === null) {
@@ -2173,6 +2261,8 @@ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
2173
2261
  "type",
2174
2262
  getDiscriminatorLogicalName(boundType, declaration, checker),
2175
2263
  declaration,
2264
+ checker,
2265
+ void 0,
2176
2266
  {
2177
2267
  checker,
2178
2268
  declaration,
@@ -2181,6 +2271,10 @@ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
2181
2271
  );
2182
2272
  return metadata?.apiName;
2183
2273
  }
2274
+ function applyDiscriminatorApiNamePrefix(value, discriminatorOptions) {
2275
+ const prefix = discriminatorOptions?.apiNamePrefix;
2276
+ return prefix === void 0 || prefix === "" ? value : `${prefix}${value}`;
2277
+ }
2184
2278
  function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2185
2279
  if (seen.has(type)) {
2186
2280
  return null;
@@ -2235,22 +2329,27 @@ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, di
2235
2329
  return null;
2236
2330
  }
2237
2331
  }
2238
- const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
2239
- boundType,
2240
- fieldName,
2241
- checker,
2242
- provenance,
2243
- diagnostics
2244
- );
2245
- if (literalIdentityValue !== void 0) {
2246
- return literalIdentityValue;
2332
+ for (const identityPropertyName of getDiscriminatorIdentityPropertyNames(fieldName)) {
2333
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
2334
+ boundType,
2335
+ identityPropertyName,
2336
+ checker,
2337
+ provenance,
2338
+ diagnostics
2339
+ );
2340
+ if (literalIdentityValue === null) {
2341
+ return null;
2342
+ }
2343
+ if (literalIdentityValue !== void 0) {
2344
+ return literalIdentityValue;
2345
+ }
2247
2346
  }
2248
2347
  const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
2249
2348
  if (apiName?.source === "explicit") {
2250
- return apiName.value;
2349
+ return applyDiscriminatorApiNamePrefix(apiName.value, metadataPolicy.discriminator);
2251
2350
  }
2252
2351
  if (apiName?.source === "inferred") {
2253
- return apiName.value;
2352
+ return applyDiscriminatorApiNamePrefix(apiName.value, metadataPolicy.discriminator);
2254
2353
  }
2255
2354
  diagnostics.push(
2256
2355
  makeAnalysisDiagnostic(
@@ -2313,15 +2412,20 @@ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2313
2412
  return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2314
2413
  }
2315
2414
  function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
2316
- const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2317
- if (typeNode === void 0) {
2415
+ const sourceTypeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2416
+ if (sourceTypeNode === void 0) {
2318
2417
  return [];
2319
2418
  }
2320
- const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
2321
- if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
2419
+ const unwrapParentheses = (typeNode) => ts3.isParenthesizedTypeNode(typeNode) ? unwrapParentheses(typeNode.type) : typeNode;
2420
+ const directTypeNode = unwrapParentheses(sourceTypeNode);
2421
+ const referenceTypeNode = ts3.isTypeReferenceNode(directTypeNode) ? directTypeNode : (() => {
2422
+ const resolvedTypeNode = resolveAliasedTypeNode(directTypeNode, checker);
2423
+ return ts3.isTypeReferenceNode(resolvedTypeNode) ? resolvedTypeNode : null;
2424
+ })();
2425
+ if (referenceTypeNode?.typeArguments === void 0) {
2322
2426
  return [];
2323
2427
  }
2324
- return resolvedTypeNode.typeArguments.map((argumentNode) => {
2428
+ return referenceTypeNode.typeArguments.map((argumentNode) => {
2325
2429
  const argumentType = checker.getTypeFromTypeNode(argumentNode);
2326
2430
  return {
2327
2431
  tsType: argumentType,
@@ -2409,12 +2513,20 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2409
2513
  annotations.push(defaultAnnotation);
2410
2514
  }
2411
2515
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2412
- const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
2516
+ const metadata = resolveNodeMetadata(
2517
+ metadataPolicy,
2518
+ "field",
2519
+ name,
2520
+ prop,
2413
2521
  checker,
2414
- declaration: prop,
2415
- subjectType: tsType,
2416
- hostType
2417
- });
2522
+ extensionRegistry,
2523
+ {
2524
+ checker,
2525
+ declaration: prop,
2526
+ subjectType: tsType,
2527
+ hostType
2528
+ }
2529
+ );
2418
2530
  return {
2419
2531
  kind: "field",
2420
2532
  name,
@@ -2427,10 +2539,10 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2427
2539
  };
2428
2540
  }
2429
2541
  function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2430
- if (!ts3.isIdentifier(prop.name)) {
2542
+ const name = getAnalyzableObjectLikePropertyName(prop.name);
2543
+ if (name === null) {
2431
2544
  return null;
2432
2545
  }
2433
- const name = prop.name.text;
2434
2546
  const tsType = checker.getTypeAtLocation(prop);
2435
2547
  const optional = prop.questionToken !== void 0;
2436
2548
  const provenance = provenanceForNode(prop, file);
@@ -2461,12 +2573,20 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2461
2573
  let annotations = [];
2462
2574
  annotations.push(...docResult.annotations);
2463
2575
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2464
- const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
2576
+ const metadata = resolveNodeMetadata(
2577
+ metadataPolicy,
2578
+ "field",
2579
+ name,
2580
+ prop,
2465
2581
  checker,
2466
- declaration: prop,
2467
- subjectType: tsType,
2468
- hostType
2469
- });
2582
+ extensionRegistry,
2583
+ {
2584
+ checker,
2585
+ declaration: prop,
2586
+ subjectType: tsType,
2587
+ hostType
2588
+ }
2589
+ );
2470
2590
  return {
2471
2591
  kind: "field",
2472
2592
  name,
@@ -2478,6 +2598,31 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2478
2598
  provenance
2479
2599
  };
2480
2600
  }
2601
+ function findDuplicateObjectLikeTypeAliasPropertyNames(members) {
2602
+ const seen = /* @__PURE__ */ new Set();
2603
+ const duplicates = /* @__PURE__ */ new Set();
2604
+ for (const member of members) {
2605
+ if (!ts3.isPropertySignature(member)) {
2606
+ continue;
2607
+ }
2608
+ const name = getAnalyzableObjectLikePropertyName(member.name);
2609
+ if (name === null) {
2610
+ continue;
2611
+ }
2612
+ if (seen.has(name)) {
2613
+ duplicates.add(name);
2614
+ continue;
2615
+ }
2616
+ seen.add(name);
2617
+ }
2618
+ return [...duplicates].sort();
2619
+ }
2620
+ function getAnalyzableObjectLikePropertyName(name) {
2621
+ if (!ts3.isIdentifier(name)) {
2622
+ return null;
2623
+ }
2624
+ return name.text;
2625
+ }
2481
2626
  function applyEnumMemberDisplayNames(type, annotations) {
2482
2627
  if (!annotations.some(
2483
2628
  (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
@@ -2595,7 +2740,7 @@ function getTypeNodeRegistrationName(typeNode) {
2595
2740
  }
2596
2741
  return null;
2597
2742
  }
2598
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2743
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2599
2744
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2600
2745
  if (customType) {
2601
2746
  return customType;
@@ -2670,6 +2815,23 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2670
2815
  diagnostics
2671
2816
  );
2672
2817
  }
2818
+ if (isIntersectionType(type)) {
2819
+ const sourceTypeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2820
+ const resolvedSourceTypeNode = sourceTypeNode === void 0 ? void 0 : resolveAliasedTypeNode(sourceTypeNode, checker);
2821
+ if (resolvedSourceTypeNode !== void 0 && getObjectLikeTypeAliasMembers(resolvedSourceTypeNode) !== null) {
2822
+ return resolveObjectType(
2823
+ type,
2824
+ checker,
2825
+ file,
2826
+ typeRegistry,
2827
+ visiting,
2828
+ sourceNode,
2829
+ metadataPolicy,
2830
+ extensionRegistry,
2831
+ diagnostics
2832
+ );
2833
+ }
2834
+ }
2673
2835
  if (isObjectType(type)) {
2674
2836
  return resolveObjectType(
2675
2837
  type,
@@ -2685,7 +2847,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2685
2847
  }
2686
2848
  return { kind: "primitive", primitiveKind: "string" };
2687
2849
  }
2688
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2850
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2689
2851
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2690
2852
  return null;
2691
2853
  }
@@ -2705,11 +2867,19 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2705
2867
  file,
2706
2868
  makeParseOptions(extensionRegistry)
2707
2869
  );
2708
- const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
2870
+ const metadata = resolveNodeMetadata(
2871
+ metadataPolicy,
2872
+ "type",
2873
+ aliasName,
2874
+ aliasDecl,
2709
2875
  checker,
2710
- declaration: aliasDecl,
2711
- subjectType: aliasType
2712
- });
2876
+ extensionRegistry,
2877
+ {
2878
+ checker,
2879
+ declaration: aliasDecl,
2880
+ subjectType: aliasType
2881
+ }
2882
+ );
2713
2883
  typeRegistry[aliasName] = {
2714
2884
  name: aliasName,
2715
2885
  ...metadata !== void 0 && { metadata },
@@ -2748,7 +2918,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2748
2918
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2749
2919
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2750
2920
  }
2751
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2921
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2752
2922
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2753
2923
  if (nestedAliasDecl !== void 0) {
2754
2924
  return resolveAliasedPrimitiveTarget(
@@ -2774,7 +2944,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2774
2944
  diagnostics
2775
2945
  );
2776
2946
  }
2777
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2947
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2778
2948
  const typeName = getNamedTypeName(type);
2779
2949
  const namedDecl = getNamedTypeDeclaration(type);
2780
2950
  if (typeName && typeName in typeRegistry) {
@@ -2809,11 +2979,19 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2809
2979
  return result;
2810
2980
  }
2811
2981
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2812
- const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
2982
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
2983
+ metadataPolicy,
2984
+ "type",
2985
+ typeName,
2986
+ namedDecl,
2813
2987
  checker,
2814
- declaration: namedDecl,
2815
- subjectType: type
2816
- }) : void 0;
2988
+ extensionRegistry,
2989
+ {
2990
+ checker,
2991
+ declaration: namedDecl,
2992
+ subjectType: type
2993
+ }
2994
+ ) : void 0;
2817
2995
  typeRegistry[typeName] = {
2818
2996
  name: typeName,
2819
2997
  ...metadata !== void 0 && { metadata },
@@ -2898,7 +3076,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2898
3076
  }
2899
3077
  return registerNamed({ kind: "union", members });
2900
3078
  }
2901
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3079
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2902
3080
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2903
3081
  const elementType = typeArgs?.[0];
2904
3082
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2915,7 +3093,7 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2915
3093
  ) : { kind: "primitive", primitiveKind: "string" };
2916
3094
  return { kind: "array", items };
2917
3095
  }
2918
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3096
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2919
3097
  if (type.getProperties().length > 0) {
2920
3098
  return null;
2921
3099
  }
@@ -2976,7 +3154,7 @@ function shouldEmitResolvedObjectProperty(property, declaration) {
2976
3154
  }
2977
3155
  return true;
2978
3156
  }
2979
- function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3157
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2980
3158
  const collectedDiagnostics = diagnostics ?? [];
2981
3159
  const typeName = getNamedTypeName(type);
2982
3160
  const namedTypeName = typeName ?? void 0;
@@ -3033,7 +3211,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3033
3211
  };
3034
3212
  }
3035
3213
  }
3036
- const recordNode = tryResolveRecordType(
3214
+ const recordNode = isObjectType(type) ? tryResolveRecordType(
3037
3215
  type,
3038
3216
  checker,
3039
3217
  file,
@@ -3042,7 +3220,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3042
3220
  metadataPolicy,
3043
3221
  extensionRegistry,
3044
3222
  collectedDiagnostics
3045
- );
3223
+ ) : null;
3046
3224
  if (recordNode) {
3047
3225
  visiting.delete(type);
3048
3226
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
@@ -3052,11 +3230,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3052
3230
  return recordNode;
3053
3231
  }
3054
3232
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3055
- const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
3233
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
3234
+ metadataPolicy,
3235
+ "type",
3236
+ registryTypeName,
3237
+ namedDecl,
3056
3238
  checker,
3057
- declaration: namedDecl,
3058
- subjectType: type
3059
- }) : void 0;
3239
+ extensionRegistry,
3240
+ {
3241
+ checker,
3242
+ declaration: namedDecl,
3243
+ subjectType: type
3244
+ }
3245
+ ) : void 0;
3060
3246
  typeRegistry[registryTypeName] = {
3061
3247
  name: registryTypeName,
3062
3248
  ...metadata !== void 0 && { metadata },
@@ -3152,11 +3338,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3152
3338
  };
3153
3339
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3154
3340
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3155
- const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
3341
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
3342
+ metadataPolicy,
3343
+ "type",
3344
+ registryTypeName,
3345
+ namedDecl,
3156
3346
  checker,
3157
- declaration: namedDecl,
3158
- subjectType: type
3159
- }) : void 0;
3347
+ extensionRegistry,
3348
+ {
3349
+ checker,
3350
+ declaration: namedDecl,
3351
+ subjectType: type
3352
+ }
3353
+ ) : void 0;
3160
3354
  typeRegistry[registryTypeName] = {
3161
3355
  name: registryTypeName,
3162
3356
  ...metadata !== void 0 && { metadata },
@@ -3223,9 +3417,10 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3223
3417
  );
3224
3418
  }
3225
3419
  const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
3226
- if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
3420
+ const typeAliasMembers = typeAliasDecl === void 0 ? null : getObjectLikeTypeAliasMembers(typeAliasDecl.type);
3421
+ if (typeAliasDecl && typeAliasMembers !== null) {
3227
3422
  return buildFieldNodeInfoMap(
3228
- typeAliasDecl.type.members,
3423
+ typeAliasMembers,
3229
3424
  checker,
3230
3425
  file,
3231
3426
  typeRegistry,
@@ -3516,7 +3711,7 @@ function findInterfaceByName(sourceFile, interfaceName) {
3516
3711
  function findTypeAliasByName(sourceFile, aliasName) {
3517
3712
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3518
3713
  }
3519
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3714
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy, discriminatorOptions) {
3520
3715
  const analysisFilePath = path.resolve(filePath);
3521
3716
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3522
3717
  if (classDecl !== null) {
@@ -3525,7 +3720,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3525
3720
  ctx.checker,
3526
3721
  analysisFilePath,
3527
3722
  extensionRegistry,
3528
- metadataPolicy
3723
+ metadataPolicy,
3724
+ discriminatorOptions
3529
3725
  );
3530
3726
  }
3531
3727
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
@@ -3535,7 +3731,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3535
3731
  ctx.checker,
3536
3732
  analysisFilePath,
3537
3733
  extensionRegistry,
3538
- metadataPolicy
3734
+ metadataPolicy,
3735
+ discriminatorOptions
3539
3736
  );
3540
3737
  }
3541
3738
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
@@ -3545,7 +3742,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3545
3742
  ctx.checker,
3546
3743
  analysisFilePath,
3547
3744
  extensionRegistry,
3548
- metadataPolicy
3745
+ metadataPolicy,
3746
+ discriminatorOptions
3549
3747
  );
3550
3748
  if (result.ok) {
3551
3749
  return result.analysis;
@@ -4659,13 +4857,29 @@ function formatLocation(location) {
4659
4857
  }
4660
4858
 
4661
4859
  // src/extensions/registry.ts
4860
+ var import_internals5 = require("@formspec/core/internals");
4861
+ var import_internal4 = require("@formspec/analysis/internal");
4862
+ var BUILTIN_METADATA_TAGS = /* @__PURE__ */ new Set(["apiName", "displayName"]);
4863
+ function buildConstraintTagSources(extensions) {
4864
+ return extensions.map((extension) => ({
4865
+ extensionId: extension.extensionId,
4866
+ ...extension.constraintTags !== void 0 ? {
4867
+ constraintTags: extension.constraintTags.map((tag) => ({
4868
+ tagName: (0, import_internal4.normalizeFormSpecTagName)(tag.tagName)
4869
+ }))
4870
+ } : {}
4871
+ }));
4872
+ }
4662
4873
  function createExtensionRegistry(extensions) {
4874
+ const reservedTagSources = buildConstraintTagSources(extensions);
4663
4875
  const typeMap = /* @__PURE__ */ new Map();
4664
4876
  const typeNameMap = /* @__PURE__ */ new Map();
4665
4877
  const constraintMap = /* @__PURE__ */ new Map();
4666
4878
  const constraintTagMap = /* @__PURE__ */ new Map();
4667
4879
  const builtinBroadeningMap = /* @__PURE__ */ new Map();
4668
4880
  const annotationMap = /* @__PURE__ */ new Map();
4881
+ const metadataSlotMap = /* @__PURE__ */ new Map();
4882
+ const metadataTagMap = /* @__PURE__ */ new Map();
4669
4883
  for (const ext of extensions) {
4670
4884
  if (ext.types !== void 0) {
4671
4885
  for (const type of ext.types) {
@@ -4708,10 +4922,11 @@ function createExtensionRegistry(extensions) {
4708
4922
  }
4709
4923
  if (ext.constraintTags !== void 0) {
4710
4924
  for (const tag of ext.constraintTags) {
4711
- if (constraintTagMap.has(tag.tagName)) {
4712
- throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
4925
+ const canonicalTagName = (0, import_internal4.normalizeFormSpecTagName)(tag.tagName);
4926
+ if (constraintTagMap.has(canonicalTagName)) {
4927
+ throw new Error(`Duplicate custom constraint tag: "@${canonicalTagName}"`);
4713
4928
  }
4714
- constraintTagMap.set(tag.tagName, {
4929
+ constraintTagMap.set(canonicalTagName, {
4715
4930
  extensionId: ext.extensionId,
4716
4931
  registration: tag
4717
4932
  });
@@ -4726,20 +4941,61 @@ function createExtensionRegistry(extensions) {
4726
4941
  annotationMap.set(qualifiedId, annotation);
4727
4942
  }
4728
4943
  }
4944
+ if (ext.metadataSlots !== void 0) {
4945
+ for (const slot of ext.metadataSlots) {
4946
+ if (metadataSlotMap.has(slot.slotId)) {
4947
+ throw new Error(`Duplicate metadata slot ID: "${slot.slotId}"`);
4948
+ }
4949
+ metadataSlotMap.set(slot.slotId, true);
4950
+ const canonicalTagName = (0, import_internal4.normalizeFormSpecTagName)(slot.tagName);
4951
+ if (slot.allowBare === false && (slot.qualifiers?.length ?? 0) === 0) {
4952
+ throw new Error(
4953
+ `Metadata tag "@${canonicalTagName}" must allow bare usage or declare at least one qualifier.`
4954
+ );
4955
+ }
4956
+ if (metadataTagMap.has(canonicalTagName)) {
4957
+ throw new Error(`Duplicate metadata tag: "@${canonicalTagName}"`);
4958
+ }
4959
+ if (BUILTIN_METADATA_TAGS.has(canonicalTagName)) {
4960
+ throw new Error(
4961
+ `Metadata tag "@${canonicalTagName}" conflicts with built-in metadata tags.`
4962
+ );
4963
+ }
4964
+ if (constraintTagMap.has(canonicalTagName)) {
4965
+ throw new Error(
4966
+ `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${canonicalTagName}".`
4967
+ );
4968
+ }
4969
+ if (Object.hasOwn(import_internals5.BUILTIN_CONSTRAINT_DEFINITIONS, (0, import_internals5.normalizeConstraintTagName)(canonicalTagName))) {
4970
+ throw new Error(
4971
+ `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${(0, import_internals5.normalizeConstraintTagName)(canonicalTagName)}".`
4972
+ );
4973
+ }
4974
+ const existingTag = (0, import_internal4.getTagDefinition)(canonicalTagName, reservedTagSources);
4975
+ if (existingTag !== null) {
4976
+ throw BUILTIN_METADATA_TAGS.has(existingTag.canonicalName) ? new Error(
4977
+ `Metadata tag "@${canonicalTagName}" conflicts with built-in metadata tags.`
4978
+ ) : new Error(
4979
+ `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${existingTag.canonicalName}".`
4980
+ );
4981
+ }
4982
+ metadataTagMap.set(canonicalTagName, true);
4983
+ }
4984
+ }
4729
4985
  }
4730
4986
  return {
4731
4987
  extensions,
4732
4988
  findType: (typeId) => typeMap.get(typeId),
4733
4989
  findTypeByName: (typeName) => typeNameMap.get(typeName),
4734
4990
  findConstraint: (constraintId) => constraintMap.get(constraintId),
4735
- findConstraintTag: (tagName) => constraintTagMap.get(tagName),
4991
+ findConstraintTag: (tagName) => constraintTagMap.get((0, import_internal4.normalizeFormSpecTagName)(tagName)),
4736
4992
  findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
4737
4993
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
4738
4994
  };
4739
4995
  }
4740
4996
 
4741
4997
  // src/generators/method-schema.ts
4742
- var import_internals5 = require("@formspec/core/internals");
4998
+ var import_internals6 = require("@formspec/core/internals");
4743
4999
  function typeToJsonSchema(type, checker) {
4744
5000
  const typeRegistry = {};
4745
5001
  const visiting = /* @__PURE__ */ new Set();
@@ -4764,7 +5020,7 @@ function typeToJsonSchema(type, checker) {
4764
5020
  const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
4765
5021
  const ir = {
4766
5022
  kind: "form-ir",
4767
- irVersion: import_internals5.IR_VERSION,
5023
+ irVersion: import_internals6.IR_VERSION,
4768
5024
  elements: [
4769
5025
  {
4770
5026
  kind: "field",