@formspec/build 0.1.0-alpha.26 → 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/analyzer/program.d.ts +12 -0
- package/dist/analyzer/program.d.ts.map +1 -1
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/build-alpha.d.ts +48 -22
- package/dist/build-beta.d.ts +48 -22
- package/dist/build-internal.d.ts +48 -22
- package/dist/build.d.ts +151 -21
- package/dist/cli.cjs +609 -41
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +605 -36
- package/dist/cli.js.map +1 -1
- package/dist/generators/class-schema.d.ts +27 -0
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts.map +1 -1
- package/dist/index.cjs +603 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +599 -36
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +552 -34
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.d.ts +1 -1
- package/dist/internals.d.ts.map +1 -1
- package/dist/internals.js +549 -31
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/schema.d.ts +2 -2
- package/dist/json-schema/types.d.ts +4 -4
- package/dist/ui-schema/schema.d.ts +2 -7
- package/dist/ui-schema/schema.d.ts.map +1 -1
- package/package.json +7 -6
package/dist/internals.cjs
CHANGED
|
@@ -32,12 +32,14 @@ var internals_exports = {};
|
|
|
32
32
|
__export(internals_exports, {
|
|
33
33
|
analyzeClassToIR: () => analyzeClassToIR,
|
|
34
34
|
analyzeInterfaceToIR: () => analyzeInterfaceToIR,
|
|
35
|
+
analyzeNamedTypeToIRFromProgramContext: () => analyzeNamedTypeToIRFromProgramContext,
|
|
35
36
|
analyzeTypeAliasToIR: () => analyzeTypeAliasToIR,
|
|
36
37
|
canonicalizeChainDSL: () => canonicalizeChainDSL,
|
|
37
38
|
canonicalizeTSDoc: () => canonicalizeTSDoc,
|
|
38
39
|
collectFormSpecReferences: () => collectFormSpecReferences,
|
|
39
40
|
createExtensionRegistry: () => createExtensionRegistry,
|
|
40
41
|
createProgramContext: () => createProgramContext,
|
|
42
|
+
createProgramContextFromProgram: () => createProgramContextFromProgram,
|
|
41
43
|
findClassByName: () => findClassByName,
|
|
42
44
|
findInterfaceByName: () => findInterfaceByName,
|
|
43
45
|
findTypeAliasByName: () => findTypeAliasByName,
|
|
@@ -443,6 +445,7 @@ var path = __toESM(require("path"), 1);
|
|
|
443
445
|
|
|
444
446
|
// src/analyzer/class-analyzer.ts
|
|
445
447
|
var ts3 = __toESM(require("typescript"), 1);
|
|
448
|
+
var import_internal2 = require("@formspec/analysis/internal");
|
|
446
449
|
|
|
447
450
|
// src/analyzer/jsdoc-constraints.ts
|
|
448
451
|
var ts2 = __toESM(require("typescript"), 1);
|
|
@@ -1365,9 +1368,17 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1365
1368
|
}
|
|
1366
1369
|
}
|
|
1367
1370
|
}
|
|
1371
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1372
|
+
fields,
|
|
1373
|
+
classDecl,
|
|
1374
|
+
classType,
|
|
1375
|
+
checker,
|
|
1376
|
+
file,
|
|
1377
|
+
diagnostics
|
|
1378
|
+
);
|
|
1368
1379
|
return {
|
|
1369
1380
|
name,
|
|
1370
|
-
fields,
|
|
1381
|
+
fields: specializedFields,
|
|
1371
1382
|
fieldLayouts,
|
|
1372
1383
|
typeRegistry,
|
|
1373
1384
|
...annotations.length > 0 && { annotations },
|
|
@@ -1407,10 +1418,18 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1407
1418
|
}
|
|
1408
1419
|
}
|
|
1409
1420
|
}
|
|
1410
|
-
const
|
|
1421
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1422
|
+
fields,
|
|
1423
|
+
interfaceDecl,
|
|
1424
|
+
interfaceType,
|
|
1425
|
+
checker,
|
|
1426
|
+
file,
|
|
1427
|
+
diagnostics
|
|
1428
|
+
);
|
|
1429
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
1411
1430
|
return {
|
|
1412
1431
|
name,
|
|
1413
|
-
fields,
|
|
1432
|
+
fields: specializedFields,
|
|
1414
1433
|
fieldLayouts,
|
|
1415
1434
|
typeRegistry,
|
|
1416
1435
|
...annotations.length > 0 && { annotations },
|
|
@@ -1459,12 +1478,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1459
1478
|
}
|
|
1460
1479
|
}
|
|
1461
1480
|
}
|
|
1481
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1482
|
+
fields,
|
|
1483
|
+
typeAlias,
|
|
1484
|
+
aliasType,
|
|
1485
|
+
checker,
|
|
1486
|
+
file,
|
|
1487
|
+
diagnostics
|
|
1488
|
+
);
|
|
1462
1489
|
return {
|
|
1463
1490
|
ok: true,
|
|
1464
1491
|
analysis: {
|
|
1465
1492
|
name,
|
|
1466
|
-
fields,
|
|
1467
|
-
fieldLayouts:
|
|
1493
|
+
fields: specializedFields,
|
|
1494
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
1468
1495
|
typeRegistry,
|
|
1469
1496
|
...annotations.length > 0 && { annotations },
|
|
1470
1497
|
...diagnostics.length > 0 && { diagnostics },
|
|
@@ -1473,6 +1500,396 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1473
1500
|
}
|
|
1474
1501
|
};
|
|
1475
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
|
+
}
|
|
1476
1893
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1477
1894
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1478
1895
|
return null;
|
|
@@ -1761,6 +2178,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1761
2178
|
file,
|
|
1762
2179
|
typeRegistry,
|
|
1763
2180
|
visiting,
|
|
2181
|
+
sourceNode,
|
|
1764
2182
|
extensionRegistry,
|
|
1765
2183
|
diagnostics
|
|
1766
2184
|
);
|
|
@@ -2024,35 +2442,60 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2024
2442
|
}
|
|
2025
2443
|
}
|
|
2026
2444
|
}
|
|
2027
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2445
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2446
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2028
2447
|
const typeName = getNamedTypeName(type);
|
|
2029
2448
|
const namedTypeName = typeName ?? void 0;
|
|
2030
2449
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2031
|
-
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);
|
|
2032
2467
|
const clearNamedTypeRegistration = () => {
|
|
2033
|
-
if (
|
|
2468
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2034
2469
|
return;
|
|
2035
2470
|
}
|
|
2036
|
-
Reflect.deleteProperty(typeRegistry,
|
|
2471
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2037
2472
|
};
|
|
2038
2473
|
if (visiting.has(type)) {
|
|
2039
|
-
if (
|
|
2040
|
-
return {
|
|
2474
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2475
|
+
return {
|
|
2476
|
+
kind: "reference",
|
|
2477
|
+
name: registryTypeName,
|
|
2478
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2479
|
+
};
|
|
2041
2480
|
}
|
|
2042
2481
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2043
2482
|
}
|
|
2044
|
-
if (
|
|
2045
|
-
typeRegistry[
|
|
2046
|
-
name:
|
|
2483
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
2484
|
+
typeRegistry[registryTypeName] = {
|
|
2485
|
+
name: registryTypeName,
|
|
2047
2486
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2048
2487
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2049
2488
|
};
|
|
2050
2489
|
}
|
|
2051
2490
|
visiting.add(type);
|
|
2052
|
-
if (
|
|
2053
|
-
if (typeRegistry[
|
|
2491
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
2492
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2054
2493
|
visiting.delete(type);
|
|
2055
|
-
return {
|
|
2494
|
+
return {
|
|
2495
|
+
kind: "reference",
|
|
2496
|
+
name: registryTypeName,
|
|
2497
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2498
|
+
};
|
|
2056
2499
|
}
|
|
2057
2500
|
}
|
|
2058
2501
|
const recordNode = tryResolveRecordType(
|
|
@@ -2062,24 +2505,28 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2062
2505
|
typeRegistry,
|
|
2063
2506
|
visiting,
|
|
2064
2507
|
extensionRegistry,
|
|
2065
|
-
|
|
2508
|
+
collectedDiagnostics
|
|
2066
2509
|
);
|
|
2067
2510
|
if (recordNode) {
|
|
2068
2511
|
visiting.delete(type);
|
|
2069
|
-
if (
|
|
2070
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
2512
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2513
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2071
2514
|
if (!isRecursiveRecord) {
|
|
2072
2515
|
clearNamedTypeRegistration();
|
|
2073
2516
|
return recordNode;
|
|
2074
2517
|
}
|
|
2075
2518
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2076
|
-
typeRegistry[
|
|
2077
|
-
name:
|
|
2519
|
+
typeRegistry[registryTypeName] = {
|
|
2520
|
+
name: registryTypeName,
|
|
2078
2521
|
type: recordNode,
|
|
2079
2522
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2080
2523
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2081
2524
|
};
|
|
2082
|
-
return {
|
|
2525
|
+
return {
|
|
2526
|
+
kind: "reference",
|
|
2527
|
+
name: registryTypeName,
|
|
2528
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2529
|
+
};
|
|
2083
2530
|
}
|
|
2084
2531
|
return recordNode;
|
|
2085
2532
|
}
|
|
@@ -2090,7 +2537,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2090
2537
|
file,
|
|
2091
2538
|
typeRegistry,
|
|
2092
2539
|
visiting,
|
|
2093
|
-
|
|
2540
|
+
collectedDiagnostics,
|
|
2094
2541
|
extensionRegistry
|
|
2095
2542
|
);
|
|
2096
2543
|
for (const prop of type.getProperties()) {
|
|
@@ -2106,7 +2553,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2106
2553
|
visiting,
|
|
2107
2554
|
declaration,
|
|
2108
2555
|
extensionRegistry,
|
|
2109
|
-
|
|
2556
|
+
collectedDiagnostics
|
|
2110
2557
|
);
|
|
2111
2558
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2112
2559
|
properties.push({
|
|
@@ -2121,18 +2568,29 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2121
2568
|
visiting.delete(type);
|
|
2122
2569
|
const objectNode = {
|
|
2123
2570
|
kind: "object",
|
|
2124
|
-
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,
|
|
2125
2579
|
additionalProperties: true
|
|
2126
2580
|
};
|
|
2127
|
-
if (
|
|
2581
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2128
2582
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2129
|
-
typeRegistry[
|
|
2130
|
-
name:
|
|
2583
|
+
typeRegistry[registryTypeName] = {
|
|
2584
|
+
name: registryTypeName,
|
|
2131
2585
|
type: objectNode,
|
|
2132
2586
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2133
2587
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2134
2588
|
};
|
|
2135
|
-
return {
|
|
2589
|
+
return {
|
|
2590
|
+
kind: "reference",
|
|
2591
|
+
name: registryTypeName,
|
|
2592
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2593
|
+
};
|
|
2136
2594
|
}
|
|
2137
2595
|
return objectNode;
|
|
2138
2596
|
}
|
|
@@ -2393,6 +2851,18 @@ function detectFormSpecReference(typeNode) {
|
|
|
2393
2851
|
}
|
|
2394
2852
|
|
|
2395
2853
|
// src/analyzer/program.ts
|
|
2854
|
+
function createProgramContextFromProgram(program, filePath) {
|
|
2855
|
+
const absolutePath = path.resolve(filePath);
|
|
2856
|
+
const sourceFile = program.getSourceFile(absolutePath) ?? program.getSourceFile(filePath);
|
|
2857
|
+
if (!sourceFile) {
|
|
2858
|
+
throw new Error(`Could not find source file in provided program: ${absolutePath}`);
|
|
2859
|
+
}
|
|
2860
|
+
return {
|
|
2861
|
+
program,
|
|
2862
|
+
checker: program.getTypeChecker(),
|
|
2863
|
+
sourceFile
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2396
2866
|
function createProgramContext(filePath) {
|
|
2397
2867
|
const absolutePath = path.resolve(filePath);
|
|
2398
2868
|
const fileDir = path.dirname(absolutePath);
|
|
@@ -2461,6 +2931,36 @@ function findInterfaceByName(sourceFile, interfaceName) {
|
|
|
2461
2931
|
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2462
2932
|
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2463
2933
|
}
|
|
2934
|
+
function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
|
|
2935
|
+
const analysisFilePath = path.resolve(filePath);
|
|
2936
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2937
|
+
if (classDecl !== null) {
|
|
2938
|
+
return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
|
|
2939
|
+
}
|
|
2940
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2941
|
+
if (interfaceDecl !== null) {
|
|
2942
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
|
|
2943
|
+
}
|
|
2944
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2945
|
+
if (typeAlias !== null) {
|
|
2946
|
+
const result = analyzeTypeAliasToIR(
|
|
2947
|
+
typeAlias,
|
|
2948
|
+
ctx.checker,
|
|
2949
|
+
analysisFilePath,
|
|
2950
|
+
extensionRegistry
|
|
2951
|
+
);
|
|
2952
|
+
if (result.ok) {
|
|
2953
|
+
return result.analysis;
|
|
2954
|
+
}
|
|
2955
|
+
throw new Error(result.error);
|
|
2956
|
+
}
|
|
2957
|
+
throw new Error(
|
|
2958
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${analysisFilePath}`
|
|
2959
|
+
);
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// src/generators/class-schema.ts
|
|
2963
|
+
var ts5 = require("typescript");
|
|
2464
2964
|
|
|
2465
2965
|
// src/json-schema/ir-generator.ts
|
|
2466
2966
|
function makeContext(options) {
|
|
@@ -3121,9 +3621,9 @@ function generateUiSchemaFromIR(ir) {
|
|
|
3121
3621
|
}
|
|
3122
3622
|
|
|
3123
3623
|
// src/validate/constraint-validator.ts
|
|
3124
|
-
var
|
|
3624
|
+
var import_internal3 = require("@formspec/analysis/internal");
|
|
3125
3625
|
function validateFieldNode(ctx, field) {
|
|
3126
|
-
const analysis = (0,
|
|
3626
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3127
3627
|
field.name,
|
|
3128
3628
|
field.type,
|
|
3129
3629
|
field.constraints,
|
|
@@ -3141,7 +3641,7 @@ function validateFieldNode(ctx, field) {
|
|
|
3141
3641
|
}
|
|
3142
3642
|
function validateObjectProperty(ctx, parentName, property) {
|
|
3143
3643
|
const qualifiedName = `${parentName}.${property.name}`;
|
|
3144
|
-
const analysis = (0,
|
|
3644
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3145
3645
|
qualifiedName,
|
|
3146
3646
|
property.type,
|
|
3147
3647
|
property.constraints,
|
|
@@ -3314,7 +3814,23 @@ var import_internals5 = require("@formspec/core/internals");
|
|
|
3314
3814
|
function typeToJsonSchema(type, checker) {
|
|
3315
3815
|
const typeRegistry = {};
|
|
3316
3816
|
const visiting = /* @__PURE__ */ new Set();
|
|
3317
|
-
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
|
+
}
|
|
3318
3834
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
3319
3835
|
const ir = {
|
|
3320
3836
|
kind: "form-ir",
|
|
@@ -3415,12 +3931,14 @@ function collectFormSpecReferences(methods) {
|
|
|
3415
3931
|
0 && (module.exports = {
|
|
3416
3932
|
analyzeClassToIR,
|
|
3417
3933
|
analyzeInterfaceToIR,
|
|
3934
|
+
analyzeNamedTypeToIRFromProgramContext,
|
|
3418
3935
|
analyzeTypeAliasToIR,
|
|
3419
3936
|
canonicalizeChainDSL,
|
|
3420
3937
|
canonicalizeTSDoc,
|
|
3421
3938
|
collectFormSpecReferences,
|
|
3422
3939
|
createExtensionRegistry,
|
|
3423
3940
|
createProgramContext,
|
|
3941
|
+
createProgramContextFromProgram,
|
|
3424
3942
|
findClassByName,
|
|
3425
3943
|
findInterfaceByName,
|
|
3426
3944
|
findTypeAliasByName,
|