@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.js
CHANGED
|
@@ -392,6 +392,9 @@ import * as path from "path";
|
|
|
392
392
|
|
|
393
393
|
// src/analyzer/class-analyzer.ts
|
|
394
394
|
import * as ts3 from "typescript";
|
|
395
|
+
import {
|
|
396
|
+
parseCommentBlock as parseCommentBlock2
|
|
397
|
+
} from "@formspec/analysis/internal";
|
|
395
398
|
|
|
396
399
|
// src/analyzer/jsdoc-constraints.ts
|
|
397
400
|
import * as ts2 from "typescript";
|
|
@@ -1339,9 +1342,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1339
1342
|
}
|
|
1340
1343
|
}
|
|
1341
1344
|
}
|
|
1345
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1346
|
+
fields,
|
|
1347
|
+
classDecl,
|
|
1348
|
+
classType,
|
|
1349
|
+
checker,
|
|
1350
|
+
file,
|
|
1351
|
+
diagnostics
|
|
1352
|
+
);
|
|
1342
1353
|
return {
|
|
1343
1354
|
name,
|
|
1344
|
-
fields,
|
|
1355
|
+
fields: specializedFields,
|
|
1345
1356
|
fieldLayouts,
|
|
1346
1357
|
typeRegistry,
|
|
1347
1358
|
...annotations.length > 0 && { annotations },
|
|
@@ -1381,10 +1392,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1381
1392
|
}
|
|
1382
1393
|
}
|
|
1383
1394
|
}
|
|
1384
|
-
const
|
|
1395
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1396
|
+
fields,
|
|
1397
|
+
interfaceDecl,
|
|
1398
|
+
interfaceType,
|
|
1399
|
+
checker,
|
|
1400
|
+
file,
|
|
1401
|
+
diagnostics
|
|
1402
|
+
);
|
|
1403
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
1385
1404
|
return {
|
|
1386
1405
|
name,
|
|
1387
|
-
fields,
|
|
1406
|
+
fields: specializedFields,
|
|
1388
1407
|
fieldLayouts,
|
|
1389
1408
|
typeRegistry,
|
|
1390
1409
|
...annotations.length > 0 && { annotations },
|
|
@@ -1433,12 +1452,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1433
1452
|
}
|
|
1434
1453
|
}
|
|
1435
1454
|
}
|
|
1455
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1456
|
+
fields,
|
|
1457
|
+
typeAlias,
|
|
1458
|
+
aliasType,
|
|
1459
|
+
checker,
|
|
1460
|
+
file,
|
|
1461
|
+
diagnostics
|
|
1462
|
+
);
|
|
1436
1463
|
return {
|
|
1437
1464
|
ok: true,
|
|
1438
1465
|
analysis: {
|
|
1439
1466
|
name,
|
|
1440
|
-
fields,
|
|
1441
|
-
fieldLayouts:
|
|
1467
|
+
fields: specializedFields,
|
|
1468
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
1442
1469
|
typeRegistry,
|
|
1443
1470
|
...annotations.length > 0 && { annotations },
|
|
1444
1471
|
...diagnostics.length > 0 && { diagnostics },
|
|
@@ -1447,6 +1474,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1447
1474
|
}
|
|
1448
1475
|
};
|
|
1449
1476
|
}
|
|
1477
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
1478
|
+
return {
|
|
1479
|
+
code,
|
|
1480
|
+
message,
|
|
1481
|
+
severity: "error",
|
|
1482
|
+
primaryLocation,
|
|
1483
|
+
relatedLocations
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
function getLeadingParsedTags(node) {
|
|
1487
|
+
const sourceFile = node.getSourceFile();
|
|
1488
|
+
const sourceText = sourceFile.getFullText();
|
|
1489
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1490
|
+
if (commentRanges === void 0) {
|
|
1491
|
+
return [];
|
|
1492
|
+
}
|
|
1493
|
+
const parsedTags = [];
|
|
1494
|
+
for (const range of commentRanges) {
|
|
1495
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
1499
|
+
if (!commentText.startsWith("/**")) {
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
|
|
1503
|
+
}
|
|
1504
|
+
return parsedTags;
|
|
1505
|
+
}
|
|
1506
|
+
function findDiscriminatorProperty(node, fieldName) {
|
|
1507
|
+
if (ts3.isClassDeclaration(node)) {
|
|
1508
|
+
for (const member of node.members) {
|
|
1509
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
1510
|
+
return member;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1515
|
+
if (ts3.isInterfaceDeclaration(node)) {
|
|
1516
|
+
for (const member of node.members) {
|
|
1517
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
1518
|
+
return member;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
if (ts3.isTypeLiteralNode(node.type)) {
|
|
1524
|
+
for (const member of node.type.members) {
|
|
1525
|
+
if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
|
|
1526
|
+
return member;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
1533
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
1534
|
+
}
|
|
1535
|
+
function isNullishSemanticType(type) {
|
|
1536
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
1540
|
+
}
|
|
1541
|
+
function isStringLikeSemanticType(type) {
|
|
1542
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
1543
|
+
return true;
|
|
1544
|
+
}
|
|
1545
|
+
if (type.isUnion()) {
|
|
1546
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
1547
|
+
}
|
|
1548
|
+
return false;
|
|
1549
|
+
}
|
|
1550
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
1551
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
1552
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
1553
|
+
);
|
|
1554
|
+
if (discriminatorTags.length === 0) {
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
1558
|
+
for (const _duplicateTag of duplicateTags) {
|
|
1559
|
+
diagnostics.push(
|
|
1560
|
+
makeAnalysisDiagnostic(
|
|
1561
|
+
"DUPLICATE_TAG",
|
|
1562
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
1563
|
+
provenanceForNode(node, file)
|
|
1564
|
+
)
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
if (firstTag === void 0) {
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
const firstTarget = firstTag.target;
|
|
1571
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
1572
|
+
diagnostics.push(
|
|
1573
|
+
makeAnalysisDiagnostic(
|
|
1574
|
+
"INVALID_TAG_ARGUMENT",
|
|
1575
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
1576
|
+
provenanceForNode(node, file)
|
|
1577
|
+
)
|
|
1578
|
+
);
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
1582
|
+
diagnostics.push(
|
|
1583
|
+
makeAnalysisDiagnostic(
|
|
1584
|
+
"INVALID_TAG_ARGUMENT",
|
|
1585
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
1586
|
+
provenanceForNode(node, file)
|
|
1587
|
+
)
|
|
1588
|
+
);
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
1592
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
1593
|
+
diagnostics.push(
|
|
1594
|
+
makeAnalysisDiagnostic(
|
|
1595
|
+
"INVALID_TAG_ARGUMENT",
|
|
1596
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
1597
|
+
provenanceForNode(node, file)
|
|
1598
|
+
)
|
|
1599
|
+
);
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
1604
|
+
typeParameterName,
|
|
1605
|
+
provenance: provenanceForNode(node, file)
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
1609
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
1610
|
+
if (directive === null) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
1614
|
+
diagnostics.push(
|
|
1615
|
+
makeAnalysisDiagnostic(
|
|
1616
|
+
"INVALID_TAG_ARGUMENT",
|
|
1617
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
1618
|
+
directive.provenance
|
|
1619
|
+
)
|
|
1620
|
+
);
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
|
|
1624
|
+
if (propertyDecl === null) {
|
|
1625
|
+
diagnostics.push(
|
|
1626
|
+
makeAnalysisDiagnostic(
|
|
1627
|
+
"UNKNOWN_PATH_TARGET",
|
|
1628
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
1629
|
+
directive.provenance
|
|
1630
|
+
)
|
|
1631
|
+
);
|
|
1632
|
+
return null;
|
|
1633
|
+
}
|
|
1634
|
+
if (propertyDecl.questionToken !== void 0) {
|
|
1635
|
+
diagnostics.push(
|
|
1636
|
+
makeAnalysisDiagnostic(
|
|
1637
|
+
"TYPE_MISMATCH",
|
|
1638
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
1639
|
+
directive.provenance,
|
|
1640
|
+
[provenanceForNode(propertyDecl, file)]
|
|
1641
|
+
)
|
|
1642
|
+
);
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
const propertyType = checker.getTypeAtLocation(propertyDecl);
|
|
1646
|
+
if (isNullishSemanticType(propertyType)) {
|
|
1647
|
+
diagnostics.push(
|
|
1648
|
+
makeAnalysisDiagnostic(
|
|
1649
|
+
"TYPE_MISMATCH",
|
|
1650
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
1651
|
+
directive.provenance,
|
|
1652
|
+
[provenanceForNode(propertyDecl, file)]
|
|
1653
|
+
)
|
|
1654
|
+
);
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
if (!isStringLikeSemanticType(propertyType)) {
|
|
1658
|
+
diagnostics.push(
|
|
1659
|
+
makeAnalysisDiagnostic(
|
|
1660
|
+
"TYPE_MISMATCH",
|
|
1661
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
1662
|
+
directive.provenance,
|
|
1663
|
+
[provenanceForNode(propertyDecl, file)]
|
|
1664
|
+
)
|
|
1665
|
+
);
|
|
1666
|
+
return null;
|
|
1667
|
+
}
|
|
1668
|
+
return directive;
|
|
1669
|
+
}
|
|
1670
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
1671
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
1672
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
1673
|
+
) ?? -1;
|
|
1674
|
+
if (typeParameterIndex < 0) {
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
1678
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
1679
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
1680
|
+
}
|
|
1681
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
1682
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
1683
|
+
}
|
|
1684
|
+
function extractDeclarationApiName(node) {
|
|
1685
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
1686
|
+
if (tag.normalizedTagName !== "apiName") {
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
if (tag.target === null && tag.argumentText.trim() !== "") {
|
|
1690
|
+
return tag.argumentText.trim();
|
|
1691
|
+
}
|
|
1692
|
+
if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
|
|
1693
|
+
const value = tag.argumentText.trim();
|
|
1694
|
+
if (value !== "") {
|
|
1695
|
+
return value;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
function inferJsonFacingName(name) {
|
|
1702
|
+
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();
|
|
1703
|
+
}
|
|
1704
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
1705
|
+
if (seen.has(type)) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
seen.add(type);
|
|
1709
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
1710
|
+
if (symbol !== void 0) {
|
|
1711
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
1712
|
+
const targetSymbol = aliased ?? symbol;
|
|
1713
|
+
const declaration = targetSymbol.declarations?.find(
|
|
1714
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
1715
|
+
);
|
|
1716
|
+
if (declaration !== void 0) {
|
|
1717
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
1718
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
1719
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
1720
|
+
checker,
|
|
1721
|
+
seen
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
return declaration;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
|
|
1730
|
+
if (boundType === null) {
|
|
1731
|
+
diagnostics.push(
|
|
1732
|
+
makeAnalysisDiagnostic(
|
|
1733
|
+
"INVALID_TAG_ARGUMENT",
|
|
1734
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
1735
|
+
provenance
|
|
1736
|
+
)
|
|
1737
|
+
);
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
if (boundType.isStringLiteral()) {
|
|
1741
|
+
return boundType.value;
|
|
1742
|
+
}
|
|
1743
|
+
if (boundType.isUnion()) {
|
|
1744
|
+
const nonNullMembers = boundType.types.filter(
|
|
1745
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
1746
|
+
);
|
|
1747
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
1748
|
+
diagnostics.push(
|
|
1749
|
+
makeAnalysisDiagnostic(
|
|
1750
|
+
"INVALID_TAG_ARGUMENT",
|
|
1751
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
1752
|
+
provenance
|
|
1753
|
+
)
|
|
1754
|
+
);
|
|
1755
|
+
return null;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
1759
|
+
if (declaration !== null) {
|
|
1760
|
+
return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
|
|
1761
|
+
}
|
|
1762
|
+
diagnostics.push(
|
|
1763
|
+
makeAnalysisDiagnostic(
|
|
1764
|
+
"INVALID_TAG_ARGUMENT",
|
|
1765
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
1766
|
+
provenance
|
|
1767
|
+
)
|
|
1768
|
+
);
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
function getDeclarationName(node) {
|
|
1772
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
1773
|
+
return node.name?.text ?? "anonymous";
|
|
1774
|
+
}
|
|
1775
|
+
return "anonymous";
|
|
1776
|
+
}
|
|
1777
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
|
|
1778
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
1779
|
+
if (directive === null) {
|
|
1780
|
+
return [...fields];
|
|
1781
|
+
}
|
|
1782
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
1783
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
1784
|
+
node,
|
|
1785
|
+
subjectType,
|
|
1786
|
+
checker,
|
|
1787
|
+
directive.typeParameterName
|
|
1788
|
+
),
|
|
1789
|
+
checker,
|
|
1790
|
+
directive.provenance,
|
|
1791
|
+
diagnostics
|
|
1792
|
+
);
|
|
1793
|
+
if (discriminatorValue === null) {
|
|
1794
|
+
return [...fields];
|
|
1795
|
+
}
|
|
1796
|
+
return fields.map(
|
|
1797
|
+
(field) => field.name === directive.fieldName ? {
|
|
1798
|
+
...field,
|
|
1799
|
+
type: {
|
|
1800
|
+
kind: "enum",
|
|
1801
|
+
members: [{ value: discriminatorValue }]
|
|
1802
|
+
}
|
|
1803
|
+
} : field
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
1807
|
+
const renderedArguments = typeArguments.map(
|
|
1808
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
1809
|
+
).filter((value) => value !== "");
|
|
1810
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
1811
|
+
}
|
|
1812
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1813
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1814
|
+
if (typeNode === void 0) {
|
|
1815
|
+
return [];
|
|
1816
|
+
}
|
|
1817
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1818
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
1819
|
+
return [];
|
|
1820
|
+
}
|
|
1821
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
1822
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
1823
|
+
return {
|
|
1824
|
+
tsType: argumentType,
|
|
1825
|
+
typeNode: resolveTypeNode(
|
|
1826
|
+
argumentType,
|
|
1827
|
+
checker,
|
|
1828
|
+
file,
|
|
1829
|
+
typeRegistry,
|
|
1830
|
+
visiting,
|
|
1831
|
+
argumentNode,
|
|
1832
|
+
extensionRegistry,
|
|
1833
|
+
diagnostics
|
|
1834
|
+
)
|
|
1835
|
+
};
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
|
|
1839
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
1840
|
+
if (directive === null) {
|
|
1841
|
+
return properties;
|
|
1842
|
+
}
|
|
1843
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
1844
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
1845
|
+
node,
|
|
1846
|
+
subjectType,
|
|
1847
|
+
checker,
|
|
1848
|
+
directive.typeParameterName
|
|
1849
|
+
),
|
|
1850
|
+
checker,
|
|
1851
|
+
directive.provenance,
|
|
1852
|
+
diagnostics
|
|
1853
|
+
);
|
|
1854
|
+
if (discriminatorValue === null) {
|
|
1855
|
+
return properties;
|
|
1856
|
+
}
|
|
1857
|
+
return properties.map(
|
|
1858
|
+
(property) => property.name === directive.fieldName ? {
|
|
1859
|
+
...property,
|
|
1860
|
+
type: {
|
|
1861
|
+
kind: "enum",
|
|
1862
|
+
members: [{ value: discriminatorValue }]
|
|
1863
|
+
}
|
|
1864
|
+
} : property
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1450
1867
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1451
1868
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1452
1869
|
return null;
|
|
@@ -1735,6 +2152,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1735
2152
|
file,
|
|
1736
2153
|
typeRegistry,
|
|
1737
2154
|
visiting,
|
|
2155
|
+
sourceNode,
|
|
1738
2156
|
extensionRegistry,
|
|
1739
2157
|
diagnostics
|
|
1740
2158
|
);
|
|
@@ -1998,35 +2416,60 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1998
2416
|
}
|
|
1999
2417
|
}
|
|
2000
2418
|
}
|
|
2001
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2419
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2420
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2002
2421
|
const typeName = getNamedTypeName(type);
|
|
2003
2422
|
const namedTypeName = typeName ?? void 0;
|
|
2004
2423
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2005
|
-
const
|
|
2424
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
2425
|
+
type,
|
|
2426
|
+
checker,
|
|
2427
|
+
file,
|
|
2428
|
+
typeRegistry,
|
|
2429
|
+
visiting,
|
|
2430
|
+
sourceNode,
|
|
2431
|
+
extensionRegistry,
|
|
2432
|
+
collectedDiagnostics
|
|
2433
|
+
);
|
|
2434
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
2435
|
+
namedTypeName,
|
|
2436
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
2437
|
+
checker
|
|
2438
|
+
) : void 0;
|
|
2439
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
2440
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2006
2441
|
const clearNamedTypeRegistration = () => {
|
|
2007
|
-
if (
|
|
2442
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2008
2443
|
return;
|
|
2009
2444
|
}
|
|
2010
|
-
Reflect.deleteProperty(typeRegistry,
|
|
2445
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2011
2446
|
};
|
|
2012
2447
|
if (visiting.has(type)) {
|
|
2013
|
-
if (
|
|
2014
|
-
return {
|
|
2448
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2449
|
+
return {
|
|
2450
|
+
kind: "reference",
|
|
2451
|
+
name: registryTypeName,
|
|
2452
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2453
|
+
};
|
|
2015
2454
|
}
|
|
2016
2455
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2017
2456
|
}
|
|
2018
|
-
if (
|
|
2019
|
-
typeRegistry[
|
|
2020
|
-
name:
|
|
2457
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
2458
|
+
typeRegistry[registryTypeName] = {
|
|
2459
|
+
name: registryTypeName,
|
|
2021
2460
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2022
2461
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2023
2462
|
};
|
|
2024
2463
|
}
|
|
2025
2464
|
visiting.add(type);
|
|
2026
|
-
if (
|
|
2027
|
-
if (typeRegistry[
|
|
2465
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
2466
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2028
2467
|
visiting.delete(type);
|
|
2029
|
-
return {
|
|
2468
|
+
return {
|
|
2469
|
+
kind: "reference",
|
|
2470
|
+
name: registryTypeName,
|
|
2471
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2472
|
+
};
|
|
2030
2473
|
}
|
|
2031
2474
|
}
|
|
2032
2475
|
const recordNode = tryResolveRecordType(
|
|
@@ -2036,24 +2479,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2036
2479
|
typeRegistry,
|
|
2037
2480
|
visiting,
|
|
2038
2481
|
extensionRegistry,
|
|
2039
|
-
|
|
2482
|
+
collectedDiagnostics
|
|
2040
2483
|
);
|
|
2041
2484
|
if (recordNode) {
|
|
2042
2485
|
visiting.delete(type);
|
|
2043
|
-
if (
|
|
2044
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
2486
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2487
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2045
2488
|
if (!isRecursiveRecord) {
|
|
2046
2489
|
clearNamedTypeRegistration();
|
|
2047
2490
|
return recordNode;
|
|
2048
2491
|
}
|
|
2049
2492
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2050
|
-
typeRegistry[
|
|
2051
|
-
name:
|
|
2493
|
+
typeRegistry[registryTypeName] = {
|
|
2494
|
+
name: registryTypeName,
|
|
2052
2495
|
type: recordNode,
|
|
2053
2496
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2054
2497
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2055
2498
|
};
|
|
2056
|
-
return {
|
|
2499
|
+
return {
|
|
2500
|
+
kind: "reference",
|
|
2501
|
+
name: registryTypeName,
|
|
2502
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2503
|
+
};
|
|
2057
2504
|
}
|
|
2058
2505
|
return recordNode;
|
|
2059
2506
|
}
|
|
@@ -2064,7 +2511,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2064
2511
|
file,
|
|
2065
2512
|
typeRegistry,
|
|
2066
2513
|
visiting,
|
|
2067
|
-
|
|
2514
|
+
collectedDiagnostics,
|
|
2068
2515
|
extensionRegistry
|
|
2069
2516
|
);
|
|
2070
2517
|
for (const prop of type.getProperties()) {
|
|
@@ -2080,7 +2527,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2080
2527
|
visiting,
|
|
2081
2528
|
declaration,
|
|
2082
2529
|
extensionRegistry,
|
|
2083
|
-
|
|
2530
|
+
collectedDiagnostics
|
|
2084
2531
|
);
|
|
2085
2532
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2086
2533
|
properties.push({
|
|
@@ -2095,18 +2542,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2095
2542
|
visiting.delete(type);
|
|
2096
2543
|
const objectNode = {
|
|
2097
2544
|
kind: "object",
|
|
2098
|
-
properties
|
|
2545
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
2546
|
+
properties,
|
|
2547
|
+
namedDecl,
|
|
2548
|
+
type,
|
|
2549
|
+
checker,
|
|
2550
|
+
file,
|
|
2551
|
+
collectedDiagnostics
|
|
2552
|
+
) : properties,
|
|
2099
2553
|
additionalProperties: true
|
|
2100
2554
|
};
|
|
2101
|
-
if (
|
|
2555
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2102
2556
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2103
|
-
typeRegistry[
|
|
2104
|
-
name:
|
|
2557
|
+
typeRegistry[registryTypeName] = {
|
|
2558
|
+
name: registryTypeName,
|
|
2105
2559
|
type: objectNode,
|
|
2106
2560
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2107
2561
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2108
2562
|
};
|
|
2109
|
-
return {
|
|
2563
|
+
return {
|
|
2564
|
+
kind: "reference",
|
|
2565
|
+
name: registryTypeName,
|
|
2566
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2567
|
+
};
|
|
2110
2568
|
}
|
|
2111
2569
|
return objectNode;
|
|
2112
2570
|
}
|
|
@@ -3332,7 +3790,23 @@ import { IR_VERSION as IR_VERSION3 } from "@formspec/core/internals";
|
|
|
3332
3790
|
function typeToJsonSchema(type, checker) {
|
|
3333
3791
|
const typeRegistry = {};
|
|
3334
3792
|
const visiting = /* @__PURE__ */ new Set();
|
|
3335
|
-
const
|
|
3793
|
+
const diagnostics = [];
|
|
3794
|
+
const typeNode = resolveTypeNode(
|
|
3795
|
+
type,
|
|
3796
|
+
checker,
|
|
3797
|
+
"",
|
|
3798
|
+
typeRegistry,
|
|
3799
|
+
visiting,
|
|
3800
|
+
void 0,
|
|
3801
|
+
void 0,
|
|
3802
|
+
diagnostics
|
|
3803
|
+
);
|
|
3804
|
+
if (diagnostics.length > 0) {
|
|
3805
|
+
const diagnosticDetails = diagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`).join("; ");
|
|
3806
|
+
throw new Error(
|
|
3807
|
+
`FormSpec validation failed while resolving method schema types. ${diagnosticDetails}`
|
|
3808
|
+
);
|
|
3809
|
+
}
|
|
3336
3810
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
3337
3811
|
const ir = {
|
|
3338
3812
|
kind: "form-ir",
|