@twin.org/tools-core 0.0.3-next.16 → 0.0.3-next.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +1 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/utils/enum.js +3 -3
- package/dist/es/utils/enum.js.map +1 -1
- package/dist/es/utils/{utility.js → jsDoc.js} +21 -35
- package/dist/es/utils/jsDoc.js.map +1 -0
- package/dist/es/utils/jsonSchemaBuilder.js +141 -39
- package/dist/es/utils/jsonSchemaBuilder.js.map +1 -1
- package/dist/es/utils/typeScriptToSchema.js +13 -3
- package/dist/es/utils/typeScriptToSchema.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/{utility.d.ts → jsDoc.d.ts} +9 -21
- package/dist/types/utils/jsonSchemaBuilder.d.ts +16 -0
- package/docs/changelog.md +28 -0
- package/docs/reference/classes/{Utility.md → JsDoc.md} +32 -55
- package/docs/reference/classes/JsonSchemaBuilder.md +58 -0
- package/docs/reference/index.md +1 -1
- package/package.json +2 -2
- package/dist/es/utils/utility.js.map +0 -1
package/dist/es/index.js
CHANGED
|
@@ -18,6 +18,6 @@ export * from "./utils/regEx.js";
|
|
|
18
18
|
export * from "./utils/resolver.js";
|
|
19
19
|
export * from "./utils/templateLiteralPatternBuilder.js";
|
|
20
20
|
export * from "./utils/typeScriptToSchema.js";
|
|
21
|
-
export * from "./utils/
|
|
21
|
+
export * from "./utils/jsDoc.js";
|
|
22
22
|
export * from "./utils/utilityTypeSchemaMapper.js";
|
|
23
23
|
//# sourceMappingURL=index.js.map
|
package/dist/es/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0CAA0C,CAAC;AACzD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0CAA0C,CAAC;AACzD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,wCAAwC,CAAC;AACvD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0CAA0C,CAAC;AACzD,cAAc,0CAA0C,CAAC;AACzD,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,qCAAqC,CAAC;AACpD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0CAA0C,CAAC;AACzD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,oCAAoC,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./models/ITypeScriptToSchemaContext.js\";\nexport * from \"./models/ITypeScriptToSchemaDiagnostics.js\";\nexport * from \"./models/ITypeScriptToSchemaOptions.js\";\nexport * from \"./utils/diagnosticReporter.js\";\nexport * from \"./utils/disallowedTypeGuard.js\";\nexport * from \"./utils/enum.js\";\nexport * from \"./utils/fileUtils.js\";\nexport * from \"./utils/importTypeQuerySchemaResolver.js\";\nexport * from \"./utils/indexSignaturePatternResolver.js\";\nexport * from \"./utils/intersectionSchemaMerger.js\";\nexport * from \"./utils/jsonSchemaBuilder.js\";\nexport * from \"./utils/constants.js\";\nexport * from \"./utils/mappedTypeSchemaResolver.js\";\nexport * from \"./utils/objectTransformer.js\";\nexport * from \"./utils/regEx.js\";\nexport * from \"./utils/resolver.js\";\nexport * from \"./utils/templateLiteralPatternBuilder.js\";\nexport * from \"./utils/typeScriptToSchema.js\";\nexport * from \"./utils/jsDoc.js\";\nexport * from \"./utils/utilityTypeSchemaMapper.js\";\n"]}
|
package/dist/es/utils/enum.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
3
|
import { Is } from "@twin.org/core";
|
|
4
4
|
import * as ts from "typescript";
|
|
5
|
-
import {
|
|
5
|
+
import { JsDoc } from "./jsDoc.js";
|
|
6
6
|
/**
|
|
7
7
|
* Utility methods for extracting enum values from TypeScript AST nodes.
|
|
8
8
|
*
|
|
@@ -67,7 +67,7 @@ export class Enum {
|
|
|
67
67
|
.map(prop => {
|
|
68
68
|
const assignment = prop;
|
|
69
69
|
const initializer = assignment.initializer;
|
|
70
|
-
const description =
|
|
70
|
+
const description = JsDoc.getNodeJsDocDescription(assignment);
|
|
71
71
|
// "a" (string literal initializer)
|
|
72
72
|
if (ts.isStringLiteral(initializer)) {
|
|
73
73
|
return {
|
|
@@ -101,7 +101,7 @@ export class Enum {
|
|
|
101
101
|
if (resolvedValue !== undefined) {
|
|
102
102
|
entries.push({
|
|
103
103
|
value: resolvedValue,
|
|
104
|
-
description:
|
|
104
|
+
description: JsDoc.getNodeJsDocDescription(member)
|
|
105
105
|
});
|
|
106
106
|
if (Is.number(resolvedValue)) {
|
|
107
107
|
nextNumericValue = resolvedValue + 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enum.js","sourceRoot":"","sources":["../../../src/utils/enum.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,OAAO,IAAI;IAChB;;;;;;;OAOG;IACI,MAAM,CAAC,iCAAiC,CAC9C,IAAY,EACZ,UAAyB;QAEzB,0CAA0C;QAC1C,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACjD,kBAAkB;YAClB,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,OAAO,KAAK,CAAC;YACd,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAClD,4CAA4C;YAC5C,OAAO,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAC5E,CAAC,CAAqC,CAAC;QAEvC,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;YACxB,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,IAAI,UAAkD,CAAC;QAEvD,qBAAqB;QACrB,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YACpD,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC;QAC/B,CAAC;aAAM;QACN,8BAA8B;QAC9B,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC;YACnC,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EACxD,CAAC;YACF,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC;QAC1C,CAAC;QAED,IAAI,CAAC,UAAU,EAAE,CAAC;YACjB,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,iCAAiC,CAAC,UAAU,CAAC,CAAC;QACnE,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,iCAAiC,CAC9C,UAAsC;QAEtC,OAAO,CACN,UAAU,CAAC,UAAU;YACpB,oDAAoD;aACnD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC;aACjE,GAAG,CAA0D,IAAI,CAAC,EAAE;YACpE,MAAM,UAAU,GAAG,IAA6B,CAAC;YACjD,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC;YAC3C,MAAM,WAAW,GAAG,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;YAEhE,oCAAoC;YACpC,IAAI,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC;gBACrC,OAAO;oBACN,KAAK,EAAE,WAAW,CAAC,IAAuB;oBAC1C,WAAW,EAAE,WAAW,IAAI,SAAS;iBACrC,CAAC;YACH,CAAC;YAED,oCAAoC;YACpC,IAAI,EAAE,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtC,OAAO;oBACN,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;oBAC/B,WAAW,EAAE,WAAW,IAAI,SAAS;iBACrC,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACb,CAAC,CAAC;aACD,MAAM,CACN,CAAC,KAAK,EAA6D,EAAE,CAAC,KAAK,KAAK,IAAI,CACpF,CACF,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,oCAAoC,CACjD,WAA+B;QAE/B,MAAM,OAAO,GAAuD,EAAE,CAAC;QACvE,IAAI,gBAAgB,GAAG,CAAC,CAAC;QAEzB,wCAAwC;QACxC,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAC5E,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC;oBACZ,KAAK,EAAE,aAAa;oBACpB,WAAW,EAAE,OAAO,CAAC,uBAAuB,CAAC,MAAM,CAAC;iBACpD,CAAC,CAAC;gBAEH,IAAI,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;oBAC9B,gBAAgB,GAAG,aAAa,GAAG,CAAC,CAAC;gBACtC,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO,OAAO,CAAC;IAChB,CAAC;IAED;;;;;;;;OAQG;IACK,MAAM,CAAC,sBAAsB,CACpC,MAAqB,EACrB,mBAA2B;QAE3B,iFAAiF;QACjF,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACzB,OAAO,mBAAmB,CAAC;QAC5B,CAAC;QAED,4CAA4C;QAC5C,IAAI,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5C,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;QAChC,CAAC;QAED,qDAAqD;QACrD,IAAI,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QAED;QACC,mEAAmE;QACnE,EAAE,CAAC,uBAAuB,CAAC,MAAM,CAAC,WAAW,CAAC;YAC9C,MAAM,CAAC,WAAW,CAAC,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,UAAU;YACxD,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAC9C,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC;QAED;QACC,kEAAkE;QAClE,EAAE,CAAC,uBAAuB,CAAC,MAAM,CAAC,WAAW,CAAC;YAC9C,MAAM,CAAC,WAAW,CAAC,QAAQ,KAAK,EAAE,CAAC,UAAU,CAAC,SAAS;YACvD,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAC9C,CAAC;YACF,OAAO,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QAED,OAAO,SAAS,CAAC;IAClB,CAAC;CACD","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"@twin.org/core\";\nimport * as ts from \"typescript\";\nimport { Utility } from \"./utility.js\";\n\n/**\n * Utility methods for extracting enum values from TypeScript AST nodes.\n *\n * Handles three distinct patterns used to define enumerable sets of values:\n * - Native enum declarations: `enum Color { Red = \"red\" }`\n * - Const-object-with-matching-type patterns: `const Foo = { A: \"a\" } as const` paired with a type alias named identically to the const\n */\nexport class Enum {\n\t/**\n\t * Extract enum values from a const-and-type pair where a const object and a type alias share the\n\t * same name. The const object is located first because its members may carry JSDoc descriptions\n\t * that would be lost if the type alias were processed in isolation.\n\t * @param name The name to search for.\n\t * @param sourceFile The source file to search.\n\t * @returns The extracted enum entries or undefined when the pattern is not present.\n\t */\n\tpublic static extractEnumValuesFromConstAndType(\n\t\tname: string,\n\t\tsourceFile: ts.SourceFile\n\t): { value: string | number; description?: string }[] | undefined {\n\t\t// const Foo = { A: \"a\", B: \"b\" } as const\n\t\tconst varStmt = sourceFile.statements.find(stmt => {\n\t\t\t// const Foo = ...\n\t\t\tif (!ts.isVariableStatement(stmt)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst decl = stmt.declarationList.declarations[0];\n\t\t\t// Foo (the identifier that names the const)\n\t\t\treturn decl?.name && ts.isIdentifier(decl.name) && decl.name.text === name;\n\t\t}) as ts.VariableStatement | undefined;\n\n\t\tif (!varStmt) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst decl = varStmt.declarationList.declarations[0];\n\t\tif (!decl?.initializer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet objLiteral: ts.ObjectLiteralExpression | undefined;\n\n\t\t// { A: \"a\", B: \"b\" }\n\t\tif (ts.isObjectLiteralExpression(decl.initializer)) {\n\t\t\tobjLiteral = decl.initializer;\n\t\t} else if (\n\t\t\t// { A: \"a\", B: \"b\" } as const\n\t\t\tts.isAsExpression(decl.initializer) &&\n\t\t\tts.isObjectLiteralExpression(decl.initializer.expression)\n\t\t) {\n\t\t\tobjLiteral = decl.initializer.expression;\n\t\t}\n\n\t\tif (!objLiteral) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst entries = Enum.extractEnumEntriesFromConstObject(objLiteral);\n\t\treturn entries.length > 0 ? entries : undefined;\n\t}\n\n\t/**\n\t * Extract enum entries from a const object literal.\n\t * Only properties whose initializer is a string or numeric literal are included.\n\t * @param objLiteral The object literal expression.\n\t * @returns The extracted enum entries.\n\t */\n\tpublic static extractEnumEntriesFromConstObject(\n\t\tobjLiteral: ts.ObjectLiteralExpression\n\t): { value: string | number; description?: string }[] {\n\t\treturn (\n\t\t\tobjLiteral.properties\n\t\t\t\t// A: \"a\" (property assignment with an initializer)\n\t\t\t\t.filter(prop => ts.isPropertyAssignment(prop) && prop.initializer)\n\t\t\t\t.map<{ value: string | number; description?: string } | null>(prop => {\n\t\t\t\t\tconst assignment = prop as ts.PropertyAssignment;\n\t\t\t\t\tconst initializer = assignment.initializer;\n\t\t\t\t\tconst description = Utility.getNodeJsDocDescription(assignment);\n\n\t\t\t\t\t// \"a\" (string literal initializer)\n\t\t\t\t\tif (ts.isStringLiteral(initializer)) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tvalue: initializer.text as string | number,\n\t\t\t\t\t\t\tdescription: description ?? undefined\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// 42 (numeric literal initializer)\n\t\t\t\t\tif (ts.isNumericLiteral(initializer)) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tvalue: Number(initializer.text),\n\t\t\t\t\t\t\tdescription: description ?? undefined\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null;\n\t\t\t\t})\n\t\t\t\t.filter(\n\t\t\t\t\t(entry): entry is { value: string | number; description?: string } => entry !== null\n\t\t\t\t)\n\t\t);\n\t}\n\n\t/**\n\t * Extract enum values from a native TypeScript enum declaration.\n\t * Numeric members without an explicit initializer are auto-incremented from the previous value.\n\t * @param declaration The enum declaration.\n\t * @returns The extracted enum entries.\n\t */\n\tpublic static extractEnumValuesFromEnumDeclaration(\n\t\tdeclaration: ts.EnumDeclaration\n\t): { value: string | number; description?: string }[] {\n\t\tconst entries: { value: string | number; description?: string }[] = [];\n\t\tlet nextNumericValue = 0;\n\n\t\t// enum Color { Red = \"red\", Count = 1 }\n\t\tfor (const member of declaration.members) {\n\t\t\tconst resolvedValue = Enum.resolveEnumMemberValue(member, nextNumericValue);\n\t\t\tif (resolvedValue !== undefined) {\n\t\t\t\tentries.push({\n\t\t\t\t\tvalue: resolvedValue,\n\t\t\t\t\tdescription: Utility.getNodeJsDocDescription(member)\n\t\t\t\t});\n\n\t\t\t\tif (Is.number(resolvedValue)) {\n\t\t\t\t\tnextNumericValue = resolvedValue + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/**\n\t * Resolve the literal value of an enum member when possible.\n\t * Members without an initializer inherit the current auto-increment counter.\n\t * Negative numeric members expressed as unary minus (e.g. -1) are also resolved.\n\t * @param member The enum member node.\n\t * @param defaultNumericValue The default numeric value for auto-incremented members.\n\t * @returns The resolved enum value, or undefined when the initializer cannot be evaluated statically.\n\t * @internal\n\t */\n\tprivate static resolveEnumMemberValue(\n\t\tmember: ts.EnumMember,\n\t\tdefaultNumericValue: number\n\t): string | number | undefined {\n\t\t// Red (member with no explicit initializer inherits the auto-increment counter)\n\t\tif (!member.initializer) {\n\t\t\treturn defaultNumericValue;\n\t\t}\n\n\t\t// Red = \"red\" (string literal enum member)\n\t\tif (ts.isStringLiteral(member.initializer)) {\n\t\t\treturn member.initializer.text;\n\t\t}\n\n\t\t// Count = 42 (positive numeric literal enum member)\n\t\tif (ts.isNumericLiteral(member.initializer)) {\n\t\t\treturn Number(member.initializer.text);\n\t\t}\n\n\t\tif (\n\t\t\t// Negative = -1 (prefix unary minus applied to a numeric literal)\n\t\t\tts.isPrefixUnaryExpression(member.initializer) &&\n\t\t\tmember.initializer.operator === ts.SyntaxKind.MinusToken &&\n\t\t\tts.isNumericLiteral(member.initializer.operand)\n\t\t) {\n\t\t\treturn -Number(member.initializer.operand.text);\n\t\t}\n\n\t\tif (\n\t\t\t// Explicit = +1 (prefix unary plus applied to a numeric literal)\n\t\t\tts.isPrefixUnaryExpression(member.initializer) &&\n\t\t\tmember.initializer.operator === ts.SyntaxKind.PlusToken &&\n\t\t\tts.isNumericLiteral(member.initializer.operand)\n\t\t) {\n\t\t\treturn Number(member.initializer.operand.text);\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"enum.js","sourceRoot":"","sources":["../../../src/utils/enum.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"@twin.org/core\";\nimport * as ts from \"typescript\";\nimport { JsDoc } from \"./jsDoc.js\";\n\n/**\n * Utility methods for extracting enum values from TypeScript AST nodes.\n *\n * Handles three distinct patterns used to define enumerable sets of values:\n * - Native enum declarations: `enum Color { Red = \"red\" }`\n * - Const-object-with-matching-type patterns: `const Foo = { A: \"a\" } as const` paired with a type alias named identically to the const\n */\nexport class Enum {\n\t/**\n\t * Extract enum values from a const-and-type pair where a const object and a type alias share the\n\t * same name. The const object is located first because its members may carry JSDoc descriptions\n\t * that would be lost if the type alias were processed in isolation.\n\t * @param name The name to search for.\n\t * @param sourceFile The source file to search.\n\t * @returns The extracted enum entries or undefined when the pattern is not present.\n\t */\n\tpublic static extractEnumValuesFromConstAndType(\n\t\tname: string,\n\t\tsourceFile: ts.SourceFile\n\t): { value: string | number; description?: string }[] | undefined {\n\t\t// const Foo = { A: \"a\", B: \"b\" } as const\n\t\tconst varStmt = sourceFile.statements.find(stmt => {\n\t\t\t// const Foo = ...\n\t\t\tif (!ts.isVariableStatement(stmt)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst decl = stmt.declarationList.declarations[0];\n\t\t\t// Foo (the identifier that names the const)\n\t\t\treturn decl?.name && ts.isIdentifier(decl.name) && decl.name.text === name;\n\t\t}) as ts.VariableStatement | undefined;\n\n\t\tif (!varStmt) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst decl = varStmt.declarationList.declarations[0];\n\t\tif (!decl?.initializer) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet objLiteral: ts.ObjectLiteralExpression | undefined;\n\n\t\t// { A: \"a\", B: \"b\" }\n\t\tif (ts.isObjectLiteralExpression(decl.initializer)) {\n\t\t\tobjLiteral = decl.initializer;\n\t\t} else if (\n\t\t\t// { A: \"a\", B: \"b\" } as const\n\t\t\tts.isAsExpression(decl.initializer) &&\n\t\t\tts.isObjectLiteralExpression(decl.initializer.expression)\n\t\t) {\n\t\t\tobjLiteral = decl.initializer.expression;\n\t\t}\n\n\t\tif (!objLiteral) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst entries = Enum.extractEnumEntriesFromConstObject(objLiteral);\n\t\treturn entries.length > 0 ? entries : undefined;\n\t}\n\n\t/**\n\t * Extract enum entries from a const object literal.\n\t * Only properties whose initializer is a string or numeric literal are included.\n\t * @param objLiteral The object literal expression.\n\t * @returns The extracted enum entries.\n\t */\n\tpublic static extractEnumEntriesFromConstObject(\n\t\tobjLiteral: ts.ObjectLiteralExpression\n\t): { value: string | number; description?: string }[] {\n\t\treturn (\n\t\t\tobjLiteral.properties\n\t\t\t\t// A: \"a\" (property assignment with an initializer)\n\t\t\t\t.filter(prop => ts.isPropertyAssignment(prop) && prop.initializer)\n\t\t\t\t.map<{ value: string | number; description?: string } | null>(prop => {\n\t\t\t\t\tconst assignment = prop as ts.PropertyAssignment;\n\t\t\t\t\tconst initializer = assignment.initializer;\n\t\t\t\t\tconst description = JsDoc.getNodeJsDocDescription(assignment);\n\n\t\t\t\t\t// \"a\" (string literal initializer)\n\t\t\t\t\tif (ts.isStringLiteral(initializer)) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tvalue: initializer.text as string | number,\n\t\t\t\t\t\t\tdescription: description ?? undefined\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\t// 42 (numeric literal initializer)\n\t\t\t\t\tif (ts.isNumericLiteral(initializer)) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tvalue: Number(initializer.text),\n\t\t\t\t\t\t\tdescription: description ?? undefined\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null;\n\t\t\t\t})\n\t\t\t\t.filter(\n\t\t\t\t\t(entry): entry is { value: string | number; description?: string } => entry !== null\n\t\t\t\t)\n\t\t);\n\t}\n\n\t/**\n\t * Extract enum values from a native TypeScript enum declaration.\n\t * Numeric members without an explicit initializer are auto-incremented from the previous value.\n\t * @param declaration The enum declaration.\n\t * @returns The extracted enum entries.\n\t */\n\tpublic static extractEnumValuesFromEnumDeclaration(\n\t\tdeclaration: ts.EnumDeclaration\n\t): { value: string | number; description?: string }[] {\n\t\tconst entries: { value: string | number; description?: string }[] = [];\n\t\tlet nextNumericValue = 0;\n\n\t\t// enum Color { Red = \"red\", Count = 1 }\n\t\tfor (const member of declaration.members) {\n\t\t\tconst resolvedValue = Enum.resolveEnumMemberValue(member, nextNumericValue);\n\t\t\tif (resolvedValue !== undefined) {\n\t\t\t\tentries.push({\n\t\t\t\t\tvalue: resolvedValue,\n\t\t\t\t\tdescription: JsDoc.getNodeJsDocDescription(member)\n\t\t\t\t});\n\n\t\t\t\tif (Is.number(resolvedValue)) {\n\t\t\t\t\tnextNumericValue = resolvedValue + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn entries;\n\t}\n\n\t/**\n\t * Resolve the literal value of an enum member when possible.\n\t * Members without an initializer inherit the current auto-increment counter.\n\t * Negative numeric members expressed as unary minus (e.g. -1) are also resolved.\n\t * @param member The enum member node.\n\t * @param defaultNumericValue The default numeric value for auto-incremented members.\n\t * @returns The resolved enum value, or undefined when the initializer cannot be evaluated statically.\n\t * @internal\n\t */\n\tprivate static resolveEnumMemberValue(\n\t\tmember: ts.EnumMember,\n\t\tdefaultNumericValue: number\n\t): string | number | undefined {\n\t\t// Red (member with no explicit initializer inherits the auto-increment counter)\n\t\tif (!member.initializer) {\n\t\t\treturn defaultNumericValue;\n\t\t}\n\n\t\t// Red = \"red\" (string literal enum member)\n\t\tif (ts.isStringLiteral(member.initializer)) {\n\t\t\treturn member.initializer.text;\n\t\t}\n\n\t\t// Count = 42 (positive numeric literal enum member)\n\t\tif (ts.isNumericLiteral(member.initializer)) {\n\t\t\treturn Number(member.initializer.text);\n\t\t}\n\n\t\tif (\n\t\t\t// Negative = -1 (prefix unary minus applied to a numeric literal)\n\t\t\tts.isPrefixUnaryExpression(member.initializer) &&\n\t\t\tmember.initializer.operator === ts.SyntaxKind.MinusToken &&\n\t\t\tts.isNumericLiteral(member.initializer.operand)\n\t\t) {\n\t\t\treturn -Number(member.initializer.operand.text);\n\t\t}\n\n\t\tif (\n\t\t\t// Explicit = +1 (prefix unary plus applied to a numeric literal)\n\t\t\tts.isPrefixUnaryExpression(member.initializer) &&\n\t\t\tmember.initializer.operator === ts.SyntaxKind.PlusToken &&\n\t\t\tts.isNumericLiteral(member.initializer.operand)\n\t\t) {\n\t\t\treturn Number(member.initializer.operand.text);\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -3,40 +3,9 @@
|
|
|
3
3
|
import { Is } from "@twin.org/core";
|
|
4
4
|
import * as ts from "typescript";
|
|
5
5
|
/**
|
|
6
|
-
* General-purpose utility methods for working with
|
|
7
|
-
*
|
|
8
|
-
* Enum-related helpers live in TypeScriptEnum.
|
|
9
|
-
* Reference-mapping regex helpers live in TypeScriptRegEx.
|
|
6
|
+
* General-purpose utility methods for working with JsDoc.
|
|
10
7
|
*/
|
|
11
|
-
export class
|
|
12
|
-
/**
|
|
13
|
-
* Determine whether an input value is a valid TypeScript type identifier.
|
|
14
|
-
* An identifier must start with a letter, underscore, or dollar sign and contain only
|
|
15
|
-
* alphanumerics, underscores, or dollar signs thereafter.
|
|
16
|
-
* @param value The value to inspect.
|
|
17
|
-
* @returns True if the value looks like a type name.
|
|
18
|
-
*/
|
|
19
|
-
static isTypeNameInput(value) {
|
|
20
|
-
return /^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(value);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Extract the inner type node from a named or rest tuple element.
|
|
24
|
-
* Named tuple members and rest elements both wrap an inner type node; this unwraps them.
|
|
25
|
-
* Plain type nodes are returned as-is.
|
|
26
|
-
* @param element The tuple element.
|
|
27
|
-
* @returns The inner type node.
|
|
28
|
-
*/
|
|
29
|
-
static extractTupleElementType(element) {
|
|
30
|
-
// label: string (named tuple member, e.g. [label: string, count: number])
|
|
31
|
-
if (ts.isNamedTupleMember(element)) {
|
|
32
|
-
return element.type;
|
|
33
|
-
}
|
|
34
|
-
// ...string[] (rest element in a tuple, e.g. [first: string, ...rest: string[]])
|
|
35
|
-
if (ts.isRestTypeNode(element)) {
|
|
36
|
-
return element.type;
|
|
37
|
-
}
|
|
38
|
-
return element;
|
|
39
|
-
}
|
|
8
|
+
export class JsDoc {
|
|
40
9
|
/**
|
|
41
10
|
* Extract the JSDoc description comment for an AST node.
|
|
42
11
|
* Only top-level JSDoc block comments are considered; inline tags are ignored.
|
|
@@ -86,7 +55,7 @@ export class Utility {
|
|
|
86
55
|
const output = {};
|
|
87
56
|
for (const jsDocTag of ts.getJSDocTags(node)) {
|
|
88
57
|
if (jsDocTag.tagName.text === tagName) {
|
|
89
|
-
const commentText =
|
|
58
|
+
const commentText = JsDoc.getJSDocTagCommentText(jsDocTag);
|
|
90
59
|
if (commentText) {
|
|
91
60
|
const separatorIndex = commentText.indexOf(":");
|
|
92
61
|
if (separatorIndex > 0) {
|
|
@@ -101,6 +70,23 @@ export class Utility {
|
|
|
101
70
|
}
|
|
102
71
|
return output;
|
|
103
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Read the plain comment text for the first matching JSDoc tag on a node.
|
|
75
|
+
* @param node The node to inspect.
|
|
76
|
+
* @param tagName The tag name to filter by (e.g., 'default').
|
|
77
|
+
* @returns The trimmed comment text, or undefined when absent.
|
|
78
|
+
*/
|
|
79
|
+
static getNodeTagComment(node, tagName) {
|
|
80
|
+
for (const jsDocTag of ts.getJSDocTags(node)) {
|
|
81
|
+
if (jsDocTag.tagName.text === tagName) {
|
|
82
|
+
const commentText = JsDoc.getJSDocTagCommentText(jsDocTag)?.trim();
|
|
83
|
+
if (commentText) {
|
|
84
|
+
return commentText;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
104
90
|
/**
|
|
105
91
|
* Parse a custom JSDoc tag value into JSON-compatible data.
|
|
106
92
|
* Values that begin with a JSON token character ({, [, ", true, false, null) or look like a
|
|
@@ -131,4 +117,4 @@ export class Utility {
|
|
|
131
117
|
}
|
|
132
118
|
}
|
|
133
119
|
}
|
|
134
|
-
//# sourceMappingURL=
|
|
120
|
+
//# sourceMappingURL=jsDoc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsDoc.js","sourceRoot":"","sources":["../../../src/utils/jsDoc.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"@twin.org/core\";\nimport * as ts from \"typescript\";\n\n/**\n * General-purpose utility methods for working with JsDoc.\n */\nexport class JsDoc {\n\t/**\n\t * Extract the JSDoc description comment for an AST node.\n\t * Only top-level JSDoc block comments are considered; inline tags are ignored.\n\t * @param node The node to inspect.\n\t * @returns The trimmed description text, or undefined when absent.\n\t */\n\tpublic static getNodeJsDocDescription(node: ts.Node): string | undefined {\n\t\t// /** Description text */ (JSDoc block attached to a declaration)\n\t\tconst jsDocNodes = ts\n\t\t\t.getJSDocCommentsAndTags(node)\n\t\t\t.filter(commentOrTag => ts.isJSDoc(commentOrTag));\n\t\tconst description = jsDocNodes\n\t\t\t.map(jsDocNode => ts.getTextOfJSDocComment(jsDocNode.comment)?.trim())\n\t\t\t.find((value): value is string => Boolean(value));\n\n\t\tif (description) {\n\t\t\treturn description;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Convert a JSDoc tag's comment portion to a plain text string.\n\t * JSDoc tag comments may be plain strings or arrays of link/text parts.\n\t * @param jsDocTag The JSDoc tag.\n\t * @returns The comment text, or undefined when absent.\n\t */\n\tpublic static getJSDocTagCommentText(jsDocTag: ts.JSDocTag): string | undefined {\n\t\tif (Is.string(jsDocTag.comment)) {\n\t\t\treturn jsDocTag.comment;\n\t\t}\n\n\t\tif (Array.isArray(jsDocTag.comment)) {\n\t\t\treturn jsDocTag.comment\n\t\t\t\t.map(part => part.text)\n\t\t\t\t.join(\"\")\n\t\t\t\t.trim();\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Read all custom JSDoc tags matching a tag name from a node and convert them to key/value pairs.\n\t * Each matching tag's comment must be in the form \"key: value\"; entries that do not follow this\n\t * convention are silently skipped.\n\t * @param node The node to inspect.\n\t * @param tagName The tag name to filter by (e.g., 'json-schema').\n\t * @returns The extracted key/value pairs.\n\t */\n\tpublic static getNodeTags(node: ts.Node, tagName: string): { [id: string]: string } {\n\t\tconst output: { [id: string]: string } = {};\n\t\tfor (const jsDocTag of ts.getJSDocTags(node)) {\n\t\t\tif (jsDocTag.tagName.text === tagName) {\n\t\t\t\tconst commentText = JsDoc.getJSDocTagCommentText(jsDocTag);\n\t\t\t\tif (commentText) {\n\t\t\t\t\tconst separatorIndex = commentText.indexOf(\":\");\n\t\t\t\t\tif (separatorIndex > 0) {\n\t\t\t\t\t\tconst key = commentText.slice(0, separatorIndex).trim();\n\t\t\t\t\t\tconst value = commentText.slice(separatorIndex + 1).trim();\n\t\t\t\t\t\tif (key.length > 0) {\n\t\t\t\t\t\t\toutput[key] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn output;\n\t}\n\n\t/**\n\t * Read the plain comment text for the first matching JSDoc tag on a node.\n\t * @param node The node to inspect.\n\t * @param tagName The tag name to filter by (e.g., 'default').\n\t * @returns The trimmed comment text, or undefined when absent.\n\t */\n\tpublic static getNodeTagComment(node: ts.Node, tagName: string): string | undefined {\n\t\tfor (const jsDocTag of ts.getJSDocTags(node)) {\n\t\t\tif (jsDocTag.tagName.text === tagName) {\n\t\t\t\tconst commentText = JsDoc.getJSDocTagCommentText(jsDocTag)?.trim();\n\t\t\t\tif (commentText) {\n\t\t\t\t\treturn commentText;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Parse a custom JSDoc tag value into JSON-compatible data.\n\t * Values that begin with a JSON token character ({, [, \", true, false, null) or look like a\n\t * number are parsed with JSON.parse. All other values are returned as plain strings.\n\t * @param value The raw value text.\n\t * @returns The parsed value.\n\t */\n\tpublic static parseTagValue(value: string): unknown {\n\t\tconst trimmedValue = value.trim();\n\t\tif (trimmedValue.length === 0) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tconst startsWithJsonToken =\n\t\t\ttrimmedValue.startsWith(\"{\") ||\n\t\t\ttrimmedValue.startsWith(\"[\") ||\n\t\t\ttrimmedValue.startsWith('\"') ||\n\t\t\ttrimmedValue === \"true\" ||\n\t\t\ttrimmedValue === \"false\" ||\n\t\t\ttrimmedValue === \"null\" ||\n\t\t\t/^-?\\d+(\\.\\d+)?$/u.test(trimmedValue);\n\n\t\tif (!startsWithJsonToken) {\n\t\t\treturn trimmedValue;\n\t\t}\n\n\t\ttry {\n\t\t\treturn JSON.parse(trimmedValue);\n\t\t} catch {\n\t\t\treturn trimmedValue;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright 2026 IOTA Stiftung.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import { GeneralError, Is,
|
|
3
|
+
import { GeneralError, Is, JsonHelper, ObjectHelper, StringHelper } from "@twin.org/core";
|
|
4
4
|
import { JsonSchemaTagNames } from "@twin.org/tools-models";
|
|
5
5
|
import * as ts from "typescript";
|
|
6
6
|
import { Constants } from "./constants.js";
|
|
@@ -11,12 +11,12 @@ import { FileUtils } from "./fileUtils.js";
|
|
|
11
11
|
import { ImportTypeQuerySchemaResolver } from "./importTypeQuerySchemaResolver.js";
|
|
12
12
|
import { IndexSignaturePatternResolver } from "./indexSignaturePatternResolver.js";
|
|
13
13
|
import { IntersectionSchemaMerger } from "./intersectionSchemaMerger.js";
|
|
14
|
+
import { JsDoc } from "./jsDoc.js";
|
|
14
15
|
import { MappedTypeSchemaResolver } from "./mappedTypeSchemaResolver.js";
|
|
15
16
|
import { ObjectTransformer } from "./objectTransformer.js";
|
|
16
17
|
import { RegEx } from "./regEx.js";
|
|
17
18
|
import { Resolver } from "./resolver.js";
|
|
18
19
|
import { TemplateLiteralPatternBuilder } from "./templateLiteralPatternBuilder.js";
|
|
19
|
-
import { Utility } from "./utility.js";
|
|
20
20
|
import { UtilityTypeSchemaMapper } from "./utilityTypeSchemaMapper.js";
|
|
21
21
|
/**
|
|
22
22
|
* Builder for composing JSON schema fragments from TypeScript AST nodes.
|
|
@@ -248,7 +248,11 @@ export class JsonSchemaBuilder {
|
|
|
248
248
|
* @throws GeneralError Thrown when a tag key is not supported by IJsonSchema.
|
|
249
249
|
*/
|
|
250
250
|
static applyJsonSchemaTags(schema, node) {
|
|
251
|
-
const
|
|
251
|
+
const defaultTagComment = JsDoc.getNodeTagComment(node, "default");
|
|
252
|
+
if (defaultTagComment !== undefined && schema.default === undefined) {
|
|
253
|
+
schema.default = JsDoc.parseTagValue(defaultTagComment);
|
|
254
|
+
}
|
|
255
|
+
const tags = JsDoc.getNodeTags(node, "json-schema");
|
|
252
256
|
for (const [rawKey, rawValue] of Object.entries(tags)) {
|
|
253
257
|
const schemaKey = JsonSchemaBuilder.mapJsonSchemaTagKey(rawKey);
|
|
254
258
|
if (!JsonSchemaBuilder.isAllowedJsonSchemaTagKey(schemaKey)) {
|
|
@@ -257,7 +261,7 @@ export class JsonSchemaBuilder {
|
|
|
257
261
|
schemaKey
|
|
258
262
|
});
|
|
259
263
|
}
|
|
260
|
-
const parsedValue =
|
|
264
|
+
const parsedValue = JsDoc.parseTagValue(rawValue);
|
|
261
265
|
ObjectHelper.propertySet(schema, schemaKey, parsedValue);
|
|
262
266
|
}
|
|
263
267
|
}
|
|
@@ -282,7 +286,7 @@ export class JsonSchemaBuilder {
|
|
|
282
286
|
$id: `${namespace}${title}`,
|
|
283
287
|
title
|
|
284
288
|
};
|
|
285
|
-
const description =
|
|
289
|
+
const description = JsDoc.getNodeJsDocDescription(statement);
|
|
286
290
|
if (description) {
|
|
287
291
|
schema.description = description;
|
|
288
292
|
}
|
|
@@ -362,7 +366,7 @@ export class JsonSchemaBuilder {
|
|
|
362
366
|
? JsonSchemaBuilder.mapMemberTypeToSchema(context, member.type)
|
|
363
367
|
: undefined;
|
|
364
368
|
if (memberName && memberTypeSchema) {
|
|
365
|
-
const memberDescription =
|
|
369
|
+
const memberDescription = JsDoc.getNodeJsDocDescription(member);
|
|
366
370
|
if (memberDescription) {
|
|
367
371
|
memberTypeSchema.description = memberDescription;
|
|
368
372
|
}
|
|
@@ -715,7 +719,8 @@ export class JsonSchemaBuilder {
|
|
|
715
719
|
return mappedUnionTypes[0];
|
|
716
720
|
}
|
|
717
721
|
if (JsonSchemaBuilder.isNeverDiscriminatedObjectUnion(context, typeNode.types, mappedUnionTypes) ||
|
|
718
|
-
JsonSchemaBuilder.isLiteralTagDiscriminatedObjectUnion(context, mappedUnionTypes)
|
|
722
|
+
JsonSchemaBuilder.isLiteralTagDiscriminatedObjectUnion(context, mappedUnionTypes) ||
|
|
723
|
+
JsonSchemaBuilder.isDisjointPrimitiveKeywordUnion(typeNode, typeNode.types, mappedUnionTypes)) {
|
|
719
724
|
return {
|
|
720
725
|
oneOf: mappedUnionTypes
|
|
721
726
|
};
|
|
@@ -749,6 +754,52 @@ export class JsonSchemaBuilder {
|
|
|
749
754
|
});
|
|
750
755
|
return undefined;
|
|
751
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* Determine whether a union of primitive keyword branches is pairwise disjoint.
|
|
759
|
+
* @param unionTypeNode The union node being mapped.
|
|
760
|
+
* @param unionTypeNodes The original union branch type nodes.
|
|
761
|
+
* @param unionSchemas The mapped union branch schemas.
|
|
762
|
+
* @returns True if every branch is a primitive keyword schema and no branches overlap.
|
|
763
|
+
*/
|
|
764
|
+
static isDisjointPrimitiveKeywordUnion(unionTypeNode, unionTypeNodes, unionSchemas) {
|
|
765
|
+
if (unionSchemas.length < 2 || unionSchemas.length !== unionTypeNodes.length) {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
let unionContainer = unionTypeNode;
|
|
769
|
+
while (ts.isParenthesizedTypeNode(unionContainer.parent)) {
|
|
770
|
+
unionContainer = unionContainer.parent;
|
|
771
|
+
}
|
|
772
|
+
if (!ts.isTypeAliasDeclaration(unionContainer.parent) ||
|
|
773
|
+
unionContainer.parent.type !== unionContainer) {
|
|
774
|
+
// Keep nested/property unions as anyOf; only top-level alias unions are promoted.
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
const primitiveKinds = new Set([
|
|
778
|
+
ts.SyntaxKind.StringKeyword,
|
|
779
|
+
ts.SyntaxKind.NumberKeyword,
|
|
780
|
+
ts.SyntaxKind.BooleanKeyword,
|
|
781
|
+
ts.SyntaxKind.NullKeyword
|
|
782
|
+
]);
|
|
783
|
+
if (!unionTypeNodes.every(unionBranchType => primitiveKinds.has(unionBranchType.kind))) {
|
|
784
|
+
// Mixed or non-primitive unions can overlap in value space.
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
const schemaTypeDomains = unionSchemas.map(schema => {
|
|
788
|
+
if (schema.type === "integer") {
|
|
789
|
+
return "number";
|
|
790
|
+
}
|
|
791
|
+
return Is.stringValue(schema.type) ? schema.type : undefined;
|
|
792
|
+
});
|
|
793
|
+
if (schemaTypeDomains.some(schemaType => schemaType !== "string" &&
|
|
794
|
+
schemaType !== "number" &&
|
|
795
|
+
schemaType !== "boolean" &&
|
|
796
|
+
schemaType !== "null")) {
|
|
797
|
+
// Only primitive keyword domains are considered disjoint enough for oneOf.
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
// oneOf is safe when every branch maps to a unique primitive domain.
|
|
801
|
+
return new Set(schemaTypeDomains).size === unionSchemas.length;
|
|
802
|
+
}
|
|
752
803
|
/**
|
|
753
804
|
* Determine whether a union of schemas represents mutually exclusive object branches.
|
|
754
805
|
* @param context The generation context.
|
|
@@ -760,38 +811,55 @@ export class JsonSchemaBuilder {
|
|
|
760
811
|
if (unionSchemas.length < 2 || unionSchemas.length !== unionTypeNodes.length) {
|
|
761
812
|
return false;
|
|
762
813
|
}
|
|
814
|
+
const branchInfos = [];
|
|
763
815
|
let hasNeverDiscriminator = false;
|
|
764
816
|
for (const unionTypeNode of unionTypeNodes) {
|
|
765
817
|
const branchMembers = JsonSchemaBuilder.resolveNeverDiscriminatorMembers(context, unionTypeNode);
|
|
766
818
|
if (!branchMembers) {
|
|
767
819
|
return false;
|
|
768
820
|
}
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
821
|
+
const branchInfo = {
|
|
822
|
+
requiredKeys: new Set(),
|
|
823
|
+
forbiddenKeys: new Set()
|
|
824
|
+
};
|
|
825
|
+
for (const member of branchMembers) {
|
|
826
|
+
if (ts.isPropertySignature(member) && member.type && member.name) {
|
|
827
|
+
const propertyName = JsonSchemaBuilder.extractPropertyName(context, member.name);
|
|
828
|
+
if (propertyName) {
|
|
829
|
+
if (member.type.kind === ts.SyntaxKind.NeverKeyword) {
|
|
830
|
+
// never marks a property as impossible in this branch.
|
|
831
|
+
branchInfo.forbiddenKeys.add(propertyName);
|
|
832
|
+
hasNeverDiscriminator = true;
|
|
833
|
+
}
|
|
834
|
+
else if (!member.questionToken) {
|
|
835
|
+
// Required non-never keys are the positive branch signals.
|
|
836
|
+
branchInfo.requiredKeys.add(propertyName);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
772
839
|
}
|
|
773
|
-
return member.type.kind === ts.SyntaxKind.NeverKeyword;
|
|
774
|
-
});
|
|
775
|
-
if (hasNeverProperty) {
|
|
776
|
-
hasNeverDiscriminator = true;
|
|
777
840
|
}
|
|
841
|
+
branchInfos.push(branchInfo);
|
|
778
842
|
}
|
|
779
843
|
if (!hasNeverDiscriminator) {
|
|
780
844
|
return false;
|
|
781
845
|
}
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
if (resolvedSchema.type !== "object" ||
|
|
786
|
-
!Is.array(resolvedSchema.required) ||
|
|
787
|
-
resolvedSchema.required.length !== 1) {
|
|
846
|
+
for (const branchInfo of branchInfos) {
|
|
847
|
+
if (branchInfo.requiredKeys.size === 0 && branchInfo.forbiddenKeys.size === 0) {
|
|
848
|
+
// Require each branch to contribute at least one discriminating signal.
|
|
788
849
|
return false;
|
|
789
850
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
851
|
+
}
|
|
852
|
+
for (let i = 0; i < branchInfos.length; i++) {
|
|
853
|
+
for (let j = i + 1; j < branchInfos.length; j++) {
|
|
854
|
+
const firstBranch = branchInfos[i];
|
|
855
|
+
const secondBranch = branchInfos[j];
|
|
856
|
+
const firstExcludesSecond = [...firstBranch.requiredKeys].some(requiredKey => secondBranch.forbiddenKeys.has(requiredKey));
|
|
857
|
+
const secondExcludesFirst = [...secondBranch.requiredKeys].some(requiredKey => firstBranch.forbiddenKeys.has(requiredKey));
|
|
858
|
+
if (!firstExcludesSecond && !secondExcludesFirst) {
|
|
859
|
+
// Branch pairs must be mutually exclusive in at least one direction.
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
793
862
|
}
|
|
794
|
-
requiredKeys.add(requiredKey);
|
|
795
863
|
}
|
|
796
864
|
return true;
|
|
797
865
|
}
|
|
@@ -847,6 +915,19 @@ export class JsonSchemaBuilder {
|
|
|
847
915
|
* @returns Type members for the branch when resolvable.
|
|
848
916
|
*/
|
|
849
917
|
static resolveNeverDiscriminatorMembers(context, unionTypeNode) {
|
|
918
|
+
const findTypeMembersInSourceFile = (sourceFile, typeName) => {
|
|
919
|
+
for (const statement of sourceFile.statements) {
|
|
920
|
+
if (ts.isInterfaceDeclaration(statement) && statement.name.text === typeName) {
|
|
921
|
+
return statement.members;
|
|
922
|
+
}
|
|
923
|
+
if (ts.isTypeAliasDeclaration(statement) &&
|
|
924
|
+
statement.name.text === typeName &&
|
|
925
|
+
ts.isTypeLiteralNode(statement.type)) {
|
|
926
|
+
return statement.type.members;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return undefined;
|
|
930
|
+
};
|
|
850
931
|
// { discriminator: "a"; other: string } (inline type literal as union branch)
|
|
851
932
|
if (ts.isTypeLiteralNode(unionTypeNode)) {
|
|
852
933
|
return unionTypeNode.members;
|
|
@@ -863,21 +944,24 @@ export class JsonSchemaBuilder {
|
|
|
863
944
|
const typeName = ts.isIdentifier(unionTypeNode.typeName)
|
|
864
945
|
? unionTypeNode.typeName.text
|
|
865
946
|
: unionTypeNode.typeName.right.text;
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
return statement.members;
|
|
870
|
-
}
|
|
871
|
-
if (
|
|
872
|
-
// type BranchType = { discriminator: "a"; other: string }
|
|
873
|
-
ts.isTypeAliasDeclaration(statement) &&
|
|
874
|
-
statement.name.text === typeName &&
|
|
875
|
-
// { discriminator: "a"; other: string } (must be a type literal alias)
|
|
876
|
-
ts.isTypeLiteralNode(statement.type)) {
|
|
877
|
-
return statement.type.members;
|
|
878
|
-
}
|
|
947
|
+
const localMembers = findTypeMembersInSourceFile(sourceFile, typeName);
|
|
948
|
+
if (localMembers) {
|
|
949
|
+
return localMembers;
|
|
879
950
|
}
|
|
880
|
-
|
|
951
|
+
const importedTypeReference = JsonSchemaBuilder.findImportedTypeReference(context, typeName);
|
|
952
|
+
if (!importedTypeReference?.moduleSpecifier.startsWith(".")) {
|
|
953
|
+
return undefined;
|
|
954
|
+
}
|
|
955
|
+
const resolvedImportPath = JsonSchemaBuilder.resolveImportDeclarationSourceFile(sourceFile.fileName, importedTypeReference.moduleSpecifier);
|
|
956
|
+
if (!resolvedImportPath) {
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
const importedSource = FileUtils.readFile(resolvedImportPath);
|
|
960
|
+
if (!importedSource) {
|
|
961
|
+
return undefined;
|
|
962
|
+
}
|
|
963
|
+
const importedSourceFile = ts.createSourceFile(resolvedImportPath, importedSource, ts.ScriptTarget.Latest, true);
|
|
964
|
+
return findTypeMembersInSourceFile(importedSourceFile, importedTypeReference.candidateTypeName);
|
|
881
965
|
}
|
|
882
966
|
/**
|
|
883
967
|
* Resolve a local $ref schema to its stored schema definition.
|
|
@@ -1842,7 +1926,7 @@ export class JsonSchemaBuilder {
|
|
|
1842
1926
|
let restSchema;
|
|
1843
1927
|
let restIndex = -1;
|
|
1844
1928
|
for (const [index, element] of tupleTypeNode.elements.entries()) {
|
|
1845
|
-
const tupleElementType =
|
|
1929
|
+
const tupleElementType = JsonSchemaBuilder.extractTupleElementType(element);
|
|
1846
1930
|
if (!tupleElementType) {
|
|
1847
1931
|
return undefined;
|
|
1848
1932
|
}
|
|
@@ -3185,5 +3269,23 @@ export class JsonSchemaBuilder {
|
|
|
3185
3269
|
}
|
|
3186
3270
|
return key;
|
|
3187
3271
|
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Extract the inner type node from a named or rest tuple element.
|
|
3274
|
+
* Named tuple members and rest elements both wrap an inner type node; this unwraps them.
|
|
3275
|
+
* Plain type nodes are returned as-is.
|
|
3276
|
+
* @param element The tuple element.
|
|
3277
|
+
* @returns The inner type node.
|
|
3278
|
+
*/
|
|
3279
|
+
static extractTupleElementType(element) {
|
|
3280
|
+
// label: string (named tuple member, e.g. [label: string, count: number])
|
|
3281
|
+
if (ts.isNamedTupleMember(element)) {
|
|
3282
|
+
return element.type;
|
|
3283
|
+
}
|
|
3284
|
+
// ...string[] (rest element in a tuple, e.g. [first: string, ...rest: string[]])
|
|
3285
|
+
if (ts.isRestTypeNode(element)) {
|
|
3286
|
+
return element.type;
|
|
3287
|
+
}
|
|
3288
|
+
return element;
|
|
3289
|
+
}
|
|
3188
3290
|
}
|
|
3189
3291
|
//# sourceMappingURL=jsonSchemaBuilder.js.map
|