@formspec/build 0.1.0-alpha.27 → 0.1.0-alpha.28
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/LICENSE +21 -0
- package/README.md +3 -2
- package/dist/analyzer/class-analyzer.d.ts +1 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/cli.cjs +491 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +488 -30
- package/dist/cli.js.map +1 -1
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/index.cjs +489 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +488 -30
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +506 -34
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +505 -31
- package/dist/internals.js.map +1 -1
- package/package.json +7 -6
package/dist/internals.cjs
CHANGED
|
@@ -445,6 +445,7 @@ var path = __toESM(require("path"), 1);
|
|
|
445
445
|
|
|
446
446
|
// src/analyzer/class-analyzer.ts
|
|
447
447
|
var ts3 = __toESM(require("typescript"), 1);
|
|
448
|
+
var import_internal2 = require("@formspec/analysis/internal");
|
|
448
449
|
|
|
449
450
|
// src/analyzer/jsdoc-constraints.ts
|
|
450
451
|
var ts2 = __toESM(require("typescript"), 1);
|
|
@@ -1367,9 +1368,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1367
1368
|
}
|
|
1368
1369
|
}
|
|
1369
1370
|
}
|
|
1371
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1372
|
+
fields,
|
|
1373
|
+
classDecl,
|
|
1374
|
+
classType,
|
|
1375
|
+
checker,
|
|
1376
|
+
file,
|
|
1377
|
+
diagnostics
|
|
1378
|
+
);
|
|
1370
1379
|
return {
|
|
1371
1380
|
name,
|
|
1372
|
-
fields,
|
|
1381
|
+
fields: specializedFields,
|
|
1373
1382
|
fieldLayouts,
|
|
1374
1383
|
typeRegistry,
|
|
1375
1384
|
...annotations.length > 0 && { annotations },
|
|
@@ -1409,10 +1418,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1409
1418
|
}
|
|
1410
1419
|
}
|
|
1411
1420
|
}
|
|
1412
|
-
const
|
|
1421
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1422
|
+
fields,
|
|
1423
|
+
interfaceDecl,
|
|
1424
|
+
interfaceType,
|
|
1425
|
+
checker,
|
|
1426
|
+
file,
|
|
1427
|
+
diagnostics
|
|
1428
|
+
);
|
|
1429
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
1413
1430
|
return {
|
|
1414
1431
|
name,
|
|
1415
|
-
fields,
|
|
1432
|
+
fields: specializedFields,
|
|
1416
1433
|
fieldLayouts,
|
|
1417
1434
|
typeRegistry,
|
|
1418
1435
|
...annotations.length > 0 && { annotations },
|
|
@@ -1461,12 +1478,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1461
1478
|
}
|
|
1462
1479
|
}
|
|
1463
1480
|
}
|
|
1481
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1482
|
+
fields,
|
|
1483
|
+
typeAlias,
|
|
1484
|
+
aliasType,
|
|
1485
|
+
checker,
|
|
1486
|
+
file,
|
|
1487
|
+
diagnostics
|
|
1488
|
+
);
|
|
1464
1489
|
return {
|
|
1465
1490
|
ok: true,
|
|
1466
1491
|
analysis: {
|
|
1467
1492
|
name,
|
|
1468
|
-
fields,
|
|
1469
|
-
fieldLayouts:
|
|
1493
|
+
fields: specializedFields,
|
|
1494
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
1470
1495
|
typeRegistry,
|
|
1471
1496
|
...annotations.length > 0 && { annotations },
|
|
1472
1497
|
...diagnostics.length > 0 && { diagnostics },
|
|
@@ -1475,6 +1500,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1475
1500
|
}
|
|
1476
1501
|
};
|
|
1477
1502
|
}
|
|
1503
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
1504
|
+
return {
|
|
1505
|
+
code,
|
|
1506
|
+
message,
|
|
1507
|
+
severity: "error",
|
|
1508
|
+
primaryLocation,
|
|
1509
|
+
relatedLocations
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
function getLeadingParsedTags(node) {
|
|
1513
|
+
const sourceFile = node.getSourceFile();
|
|
1514
|
+
const sourceText = sourceFile.getFullText();
|
|
1515
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1516
|
+
if (commentRanges === void 0) {
|
|
1517
|
+
return [];
|
|
1518
|
+
}
|
|
1519
|
+
const parsedTags = [];
|
|
1520
|
+
for (const range of commentRanges) {
|
|
1521
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
1525
|
+
if (!commentText.startsWith("/**")) {
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
parsedTags.push(...(0, import_internal2.parseCommentBlock)(commentText, { offset: range.pos }).tags);
|
|
1529
|
+
}
|
|
1530
|
+
return parsedTags;
|
|
1531
|
+
}
|
|
1532
|
+
function findDiscriminatorProperty(node, fieldName) {
|
|
1533
|
+
if (ts3.isClassDeclaration(node)) {
|
|
1534
|
+
for (const member of node.members) {
|
|
1535
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
1536
|
+
return member;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
if (ts3.isInterfaceDeclaration(node)) {
|
|
1542
|
+
for (const member of node.members) {
|
|
1543
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
1544
|
+
return member;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
if (ts3.isTypeLiteralNode(node.type)) {
|
|
1550
|
+
for (const member of node.type.members) {
|
|
1551
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
1552
|
+
return member;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
1559
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
1560
|
+
}
|
|
1561
|
+
function isNullishSemanticType(type) {
|
|
1562
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
1566
|
+
}
|
|
1567
|
+
function isStringLikeSemanticType(type) {
|
|
1568
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1571
|
+
if (type.isUnion()) {
|
|
1572
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
1573
|
+
}
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
1577
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
1578
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
1579
|
+
);
|
|
1580
|
+
if (discriminatorTags.length === 0) {
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
1584
|
+
for (const _duplicateTag of duplicateTags) {
|
|
1585
|
+
diagnostics.push(
|
|
1586
|
+
makeAnalysisDiagnostic(
|
|
1587
|
+
"DUPLICATE_TAG",
|
|
1588
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
1589
|
+
provenanceForNode(node, file)
|
|
1590
|
+
)
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
if (firstTag === void 0) {
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
const firstTarget = firstTag.target;
|
|
1597
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
1598
|
+
diagnostics.push(
|
|
1599
|
+
makeAnalysisDiagnostic(
|
|
1600
|
+
"INVALID_TAG_ARGUMENT",
|
|
1601
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
1602
|
+
provenanceForNode(node, file)
|
|
1603
|
+
)
|
|
1604
|
+
);
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
1608
|
+
diagnostics.push(
|
|
1609
|
+
makeAnalysisDiagnostic(
|
|
1610
|
+
"INVALID_TAG_ARGUMENT",
|
|
1611
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
1612
|
+
provenanceForNode(node, file)
|
|
1613
|
+
)
|
|
1614
|
+
);
|
|
1615
|
+
return null;
|
|
1616
|
+
}
|
|
1617
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
1618
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
1619
|
+
diagnostics.push(
|
|
1620
|
+
makeAnalysisDiagnostic(
|
|
1621
|
+
"INVALID_TAG_ARGUMENT",
|
|
1622
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
1623
|
+
provenanceForNode(node, file)
|
|
1624
|
+
)
|
|
1625
|
+
);
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
1630
|
+
typeParameterName,
|
|
1631
|
+
provenance: provenanceForNode(node, file)
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
1635
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
1636
|
+
if (directive === null) {
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1639
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
1640
|
+
diagnostics.push(
|
|
1641
|
+
makeAnalysisDiagnostic(
|
|
1642
|
+
"INVALID_TAG_ARGUMENT",
|
|
1643
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
1644
|
+
directive.provenance
|
|
1645
|
+
)
|
|
1646
|
+
);
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1649
|
+
const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
|
|
1650
|
+
if (propertyDecl === null) {
|
|
1651
|
+
diagnostics.push(
|
|
1652
|
+
makeAnalysisDiagnostic(
|
|
1653
|
+
"UNKNOWN_PATH_TARGET",
|
|
1654
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
1655
|
+
directive.provenance
|
|
1656
|
+
)
|
|
1657
|
+
);
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
if (propertyDecl.questionToken !== void 0) {
|
|
1661
|
+
diagnostics.push(
|
|
1662
|
+
makeAnalysisDiagnostic(
|
|
1663
|
+
"TYPE_MISMATCH",
|
|
1664
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
1665
|
+
directive.provenance,
|
|
1666
|
+
[provenanceForNode(propertyDecl, file)]
|
|
1667
|
+
)
|
|
1668
|
+
);
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
const propertyType = checker.getTypeAtLocation(propertyDecl);
|
|
1672
|
+
if (isNullishSemanticType(propertyType)) {
|
|
1673
|
+
diagnostics.push(
|
|
1674
|
+
makeAnalysisDiagnostic(
|
|
1675
|
+
"TYPE_MISMATCH",
|
|
1676
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
1677
|
+
directive.provenance,
|
|
1678
|
+
[provenanceForNode(propertyDecl, file)]
|
|
1679
|
+
)
|
|
1680
|
+
);
|
|
1681
|
+
return null;
|
|
1682
|
+
}
|
|
1683
|
+
if (!isStringLikeSemanticType(propertyType)) {
|
|
1684
|
+
diagnostics.push(
|
|
1685
|
+
makeAnalysisDiagnostic(
|
|
1686
|
+
"TYPE_MISMATCH",
|
|
1687
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
1688
|
+
directive.provenance,
|
|
1689
|
+
[provenanceForNode(propertyDecl, file)]
|
|
1690
|
+
)
|
|
1691
|
+
);
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
return directive;
|
|
1695
|
+
}
|
|
1696
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
1697
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
1698
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
1699
|
+
) ?? -1;
|
|
1700
|
+
if (typeParameterIndex < 0) {
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
1704
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
1705
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
1706
|
+
}
|
|
1707
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
1708
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
1709
|
+
}
|
|
1710
|
+
function extractDeclarationApiName(node) {
|
|
1711
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
1712
|
+
if (tag.normalizedTagName !== "apiName") {
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
if (tag.target === null && tag.argumentText.trim() !== "") {
|
|
1716
|
+
return tag.argumentText.trim();
|
|
1717
|
+
}
|
|
1718
|
+
if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
|
|
1719
|
+
const value = tag.argumentText.trim();
|
|
1720
|
+
if (value !== "") {
|
|
1721
|
+
return value;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
function inferJsonFacingName(name) {
|
|
1728
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
1729
|
+
}
|
|
1730
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
1731
|
+
if (seen.has(type)) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
seen.add(type);
|
|
1735
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
1736
|
+
if (symbol !== void 0) {
|
|
1737
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
1738
|
+
const targetSymbol = aliased ?? symbol;
|
|
1739
|
+
const declaration = targetSymbol.declarations?.find(
|
|
1740
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
1741
|
+
);
|
|
1742
|
+
if (declaration !== void 0) {
|
|
1743
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
1744
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
1745
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
1746
|
+
checker,
|
|
1747
|
+
seen
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
return declaration;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
|
|
1756
|
+
if (boundType === null) {
|
|
1757
|
+
diagnostics.push(
|
|
1758
|
+
makeAnalysisDiagnostic(
|
|
1759
|
+
"INVALID_TAG_ARGUMENT",
|
|
1760
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
1761
|
+
provenance
|
|
1762
|
+
)
|
|
1763
|
+
);
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
if (boundType.isStringLiteral()) {
|
|
1767
|
+
return boundType.value;
|
|
1768
|
+
}
|
|
1769
|
+
if (boundType.isUnion()) {
|
|
1770
|
+
const nonNullMembers = boundType.types.filter(
|
|
1771
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
1772
|
+
);
|
|
1773
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
1774
|
+
diagnostics.push(
|
|
1775
|
+
makeAnalysisDiagnostic(
|
|
1776
|
+
"INVALID_TAG_ARGUMENT",
|
|
1777
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
1778
|
+
provenance
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
1785
|
+
if (declaration !== null) {
|
|
1786
|
+
return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
|
|
1787
|
+
}
|
|
1788
|
+
diagnostics.push(
|
|
1789
|
+
makeAnalysisDiagnostic(
|
|
1790
|
+
"INVALID_TAG_ARGUMENT",
|
|
1791
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
1792
|
+
provenance
|
|
1793
|
+
)
|
|
1794
|
+
);
|
|
1795
|
+
return null;
|
|
1796
|
+
}
|
|
1797
|
+
function getDeclarationName(node) {
|
|
1798
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
1799
|
+
return node.name?.text ?? "anonymous";
|
|
1800
|
+
}
|
|
1801
|
+
return "anonymous";
|
|
1802
|
+
}
|
|
1803
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
|
|
1804
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
1805
|
+
if (directive === null) {
|
|
1806
|
+
return [...fields];
|
|
1807
|
+
}
|
|
1808
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
1809
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
1810
|
+
node,
|
|
1811
|
+
subjectType,
|
|
1812
|
+
checker,
|
|
1813
|
+
directive.typeParameterName
|
|
1814
|
+
),
|
|
1815
|
+
checker,
|
|
1816
|
+
directive.provenance,
|
|
1817
|
+
diagnostics
|
|
1818
|
+
);
|
|
1819
|
+
if (discriminatorValue === null) {
|
|
1820
|
+
return [...fields];
|
|
1821
|
+
}
|
|
1822
|
+
return fields.map(
|
|
1823
|
+
(field) => field.name === directive.fieldName ? {
|
|
1824
|
+
...field,
|
|
1825
|
+
type: {
|
|
1826
|
+
kind: "enum",
|
|
1827
|
+
members: [{ value: discriminatorValue }]
|
|
1828
|
+
}
|
|
1829
|
+
} : field
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
1833
|
+
const renderedArguments = typeArguments.map(
|
|
1834
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
1835
|
+
).filter((value) => value !== "");
|
|
1836
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
1837
|
+
}
|
|
1838
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1839
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1840
|
+
if (typeNode === void 0) {
|
|
1841
|
+
return [];
|
|
1842
|
+
}
|
|
1843
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1844
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
1845
|
+
return [];
|
|
1846
|
+
}
|
|
1847
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
1848
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
1849
|
+
return {
|
|
1850
|
+
tsType: argumentType,
|
|
1851
|
+
typeNode: resolveTypeNode(
|
|
1852
|
+
argumentType,
|
|
1853
|
+
checker,
|
|
1854
|
+
file,
|
|
1855
|
+
typeRegistry,
|
|
1856
|
+
visiting,
|
|
1857
|
+
argumentNode,
|
|
1858
|
+
extensionRegistry,
|
|
1859
|
+
diagnostics
|
|
1860
|
+
)
|
|
1861
|
+
};
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
|
|
1865
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
1866
|
+
if (directive === null) {
|
|
1867
|
+
return properties;
|
|
1868
|
+
}
|
|
1869
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
1870
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
1871
|
+
node,
|
|
1872
|
+
subjectType,
|
|
1873
|
+
checker,
|
|
1874
|
+
directive.typeParameterName
|
|
1875
|
+
),
|
|
1876
|
+
checker,
|
|
1877
|
+
directive.provenance,
|
|
1878
|
+
diagnostics
|
|
1879
|
+
);
|
|
1880
|
+
if (discriminatorValue === null) {
|
|
1881
|
+
return properties;
|
|
1882
|
+
}
|
|
1883
|
+
return properties.map(
|
|
1884
|
+
(property) => property.name === directive.fieldName ? {
|
|
1885
|
+
...property,
|
|
1886
|
+
type: {
|
|
1887
|
+
kind: "enum",
|
|
1888
|
+
members: [{ value: discriminatorValue }]
|
|
1889
|
+
}
|
|
1890
|
+
} : property
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1478
1893
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1479
1894
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1480
1895
|
return null;
|
|
@@ -1763,6 +2178,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1763
2178
|
file,
|
|
1764
2179
|
typeRegistry,
|
|
1765
2180
|
visiting,
|
|
2181
|
+
sourceNode,
|
|
1766
2182
|
extensionRegistry,
|
|
1767
2183
|
diagnostics
|
|
1768
2184
|
);
|
|
@@ -2026,35 +2442,60 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2026
2442
|
}
|
|
2027
2443
|
}
|
|
2028
2444
|
}
|
|
2029
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2445
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2446
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2030
2447
|
const typeName = getNamedTypeName(type);
|
|
2031
2448
|
const namedTypeName = typeName ?? void 0;
|
|
2032
2449
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2033
|
-
const
|
|
2450
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
2451
|
+
type,
|
|
2452
|
+
checker,
|
|
2453
|
+
file,
|
|
2454
|
+
typeRegistry,
|
|
2455
|
+
visiting,
|
|
2456
|
+
sourceNode,
|
|
2457
|
+
extensionRegistry,
|
|
2458
|
+
collectedDiagnostics
|
|
2459
|
+
);
|
|
2460
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
2461
|
+
namedTypeName,
|
|
2462
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
2463
|
+
checker
|
|
2464
|
+
) : void 0;
|
|
2465
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
2466
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2034
2467
|
const clearNamedTypeRegistration = () => {
|
|
2035
|
-
if (
|
|
2468
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2036
2469
|
return;
|
|
2037
2470
|
}
|
|
2038
|
-
Reflect.deleteProperty(typeRegistry,
|
|
2471
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2039
2472
|
};
|
|
2040
2473
|
if (visiting.has(type)) {
|
|
2041
|
-
if (
|
|
2042
|
-
return {
|
|
2474
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2475
|
+
return {
|
|
2476
|
+
kind: "reference",
|
|
2477
|
+
name: registryTypeName,
|
|
2478
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2479
|
+
};
|
|
2043
2480
|
}
|
|
2044
2481
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2045
2482
|
}
|
|
2046
|
-
if (
|
|
2047
|
-
typeRegistry[
|
|
2048
|
-
name:
|
|
2483
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
2484
|
+
typeRegistry[registryTypeName] = {
|
|
2485
|
+
name: registryTypeName,
|
|
2049
2486
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2050
2487
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2051
2488
|
};
|
|
2052
2489
|
}
|
|
2053
2490
|
visiting.add(type);
|
|
2054
|
-
if (
|
|
2055
|
-
if (typeRegistry[
|
|
2491
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
2492
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2056
2493
|
visiting.delete(type);
|
|
2057
|
-
return {
|
|
2494
|
+
return {
|
|
2495
|
+
kind: "reference",
|
|
2496
|
+
name: registryTypeName,
|
|
2497
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2498
|
+
};
|
|
2058
2499
|
}
|
|
2059
2500
|
}
|
|
2060
2501
|
const recordNode = tryResolveRecordType(
|
|
@@ -2064,24 +2505,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2064
2505
|
typeRegistry,
|
|
2065
2506
|
visiting,
|
|
2066
2507
|
extensionRegistry,
|
|
2067
|
-
|
|
2508
|
+
collectedDiagnostics
|
|
2068
2509
|
);
|
|
2069
2510
|
if (recordNode) {
|
|
2070
2511
|
visiting.delete(type);
|
|
2071
|
-
if (
|
|
2072
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
2512
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2513
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2073
2514
|
if (!isRecursiveRecord) {
|
|
2074
2515
|
clearNamedTypeRegistration();
|
|
2075
2516
|
return recordNode;
|
|
2076
2517
|
}
|
|
2077
2518
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2078
|
-
typeRegistry[
|
|
2079
|
-
name:
|
|
2519
|
+
typeRegistry[registryTypeName] = {
|
|
2520
|
+
name: registryTypeName,
|
|
2080
2521
|
type: recordNode,
|
|
2081
2522
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2082
2523
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2083
2524
|
};
|
|
2084
|
-
return {
|
|
2525
|
+
return {
|
|
2526
|
+
kind: "reference",
|
|
2527
|
+
name: registryTypeName,
|
|
2528
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2529
|
+
};
|
|
2085
2530
|
}
|
|
2086
2531
|
return recordNode;
|
|
2087
2532
|
}
|
|
@@ -2092,7 +2537,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2092
2537
|
file,
|
|
2093
2538
|
typeRegistry,
|
|
2094
2539
|
visiting,
|
|
2095
|
-
|
|
2540
|
+
collectedDiagnostics,
|
|
2096
2541
|
extensionRegistry
|
|
2097
2542
|
);
|
|
2098
2543
|
for (const prop of type.getProperties()) {
|
|
@@ -2108,7 +2553,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2108
2553
|
visiting,
|
|
2109
2554
|
declaration,
|
|
2110
2555
|
extensionRegistry,
|
|
2111
|
-
|
|
2556
|
+
collectedDiagnostics
|
|
2112
2557
|
);
|
|
2113
2558
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2114
2559
|
properties.push({
|
|
@@ -2123,18 +2568,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2123
2568
|
visiting.delete(type);
|
|
2124
2569
|
const objectNode = {
|
|
2125
2570
|
kind: "object",
|
|
2126
|
-
properties
|
|
2571
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
2572
|
+
properties,
|
|
2573
|
+
namedDecl,
|
|
2574
|
+
type,
|
|
2575
|
+
checker,
|
|
2576
|
+
file,
|
|
2577
|
+
collectedDiagnostics
|
|
2578
|
+
) : properties,
|
|
2127
2579
|
additionalProperties: true
|
|
2128
2580
|
};
|
|
2129
|
-
if (
|
|
2581
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2130
2582
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2131
|
-
typeRegistry[
|
|
2132
|
-
name:
|
|
2583
|
+
typeRegistry[registryTypeName] = {
|
|
2584
|
+
name: registryTypeName,
|
|
2133
2585
|
type: objectNode,
|
|
2134
2586
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2135
2587
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2136
2588
|
};
|
|
2137
|
-
return {
|
|
2589
|
+
return {
|
|
2590
|
+
kind: "reference",
|
|
2591
|
+
name: registryTypeName,
|
|
2592
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2593
|
+
};
|
|
2138
2594
|
}
|
|
2139
2595
|
return objectNode;
|
|
2140
2596
|
}
|
|
@@ -3165,9 +3621,9 @@ function generateUiSchemaFromIR(ir) {
|
|
|
3165
3621
|
}
|
|
3166
3622
|
|
|
3167
3623
|
// src/validate/constraint-validator.ts
|
|
3168
|
-
var
|
|
3624
|
+
var import_internal3 = require("@formspec/analysis/internal");
|
|
3169
3625
|
function validateFieldNode(ctx, field) {
|
|
3170
|
-
const analysis = (0,
|
|
3626
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3171
3627
|
field.name,
|
|
3172
3628
|
field.type,
|
|
3173
3629
|
field.constraints,
|
|
@@ -3185,7 +3641,7 @@ function validateFieldNode(ctx, field) {
|
|
|
3185
3641
|
}
|
|
3186
3642
|
function validateObjectProperty(ctx, parentName, property) {
|
|
3187
3643
|
const qualifiedName = `${parentName}.${property.name}`;
|
|
3188
|
-
const analysis = (0,
|
|
3644
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3189
3645
|
qualifiedName,
|
|
3190
3646
|
property.type,
|
|
3191
3647
|
property.constraints,
|
|
@@ -3358,7 +3814,23 @@ var import_internals5 = require("@formspec/core/internals");
|
|
|
3358
3814
|
function typeToJsonSchema(type, checker) {
|
|
3359
3815
|
const typeRegistry = {};
|
|
3360
3816
|
const visiting = /* @__PURE__ */ new Set();
|
|
3361
|
-
const
|
|
3817
|
+
const diagnostics = [];
|
|
3818
|
+
const typeNode = resolveTypeNode(
|
|
3819
|
+
type,
|
|
3820
|
+
checker,
|
|
3821
|
+
"",
|
|
3822
|
+
typeRegistry,
|
|
3823
|
+
visiting,
|
|
3824
|
+
void 0,
|
|
3825
|
+
void 0,
|
|
3826
|
+
diagnostics
|
|
3827
|
+
);
|
|
3828
|
+
if (diagnostics.length > 0) {
|
|
3829
|
+
const diagnosticDetails = diagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`).join("; ");
|
|
3830
|
+
throw new Error(
|
|
3831
|
+
`FormSpec validation failed while resolving method schema types. ${diagnosticDetails}`
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3362
3834
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
3363
3835
|
const ir = {
|
|
3364
3836
|
kind: "form-ir",
|