@openpkg-ts/extract 0.11.3 → 0.12.0
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/bin/tspec.js +1 -1
- package/dist/shared/chunk-wddga8ye.js +1148 -0
- package/dist/src/index.d.ts +44 -20
- package/dist/src/index.js +10 -27
- package/package.json +2 -2
- package/dist/shared/chunk-9x67bae3.js +0 -431
package/dist/src/index.d.ts
CHANGED
|
@@ -2,10 +2,17 @@ import { SpecType } from "@openpkg-ts/spec";
|
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
declare class TypeRegistry {
|
|
4
4
|
private types;
|
|
5
|
+
private processing;
|
|
5
6
|
add(type: SpecType): void;
|
|
6
7
|
get(id: string): SpecType | undefined;
|
|
7
8
|
has(id: string): boolean;
|
|
8
9
|
getAll(): SpecType[];
|
|
10
|
+
/**
|
|
11
|
+
* Register a type from a ts.Type, extracting its structure.
|
|
12
|
+
* Returns the type ID if registered, undefined if skipped.
|
|
13
|
+
*/
|
|
14
|
+
registerType(type: ts.Type, checker: ts.TypeChecker, exportedIds: Set<string>): string | undefined;
|
|
15
|
+
private buildSpecType;
|
|
9
16
|
registerFromSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): SpecType | undefined;
|
|
10
17
|
}
|
|
11
18
|
import { SpecExample, SpecSource, SpecTag } from "@openpkg-ts/spec";
|
|
@@ -17,14 +24,12 @@ declare function getJSDocComment(node: ts2.Node): {
|
|
|
17
24
|
};
|
|
18
25
|
declare function getSourceLocation(node: ts2.Node, sourceFile: ts2.SourceFile): SpecSource;
|
|
19
26
|
import { OpenPkg } from "@openpkg-ts/spec";
|
|
20
|
-
import { TypeChecker as TypeChecker_vexmldwuwp } from "typescript";
|
|
21
|
-
import { Program as Program_lhgjafqgga } from "typescript";
|
|
22
|
-
import { SourceFile as SourceFile_sameteuoys } from "typescript";
|
|
23
27
|
interface ExtractOptions {
|
|
24
28
|
entryFile: string;
|
|
25
29
|
baseDir?: string;
|
|
26
30
|
content?: string;
|
|
27
31
|
maxTypeDepth?: number;
|
|
32
|
+
maxExternalTypeDepth?: number;
|
|
28
33
|
resolveExternalTypes?: boolean;
|
|
29
34
|
schemaExtraction?: "static" | "hybrid";
|
|
30
35
|
}
|
|
@@ -41,13 +46,6 @@ interface Diagnostic {
|
|
|
41
46
|
column?: number;
|
|
42
47
|
};
|
|
43
48
|
}
|
|
44
|
-
interface SerializerContext {
|
|
45
|
-
typeChecker: TypeChecker_vexmldwuwp;
|
|
46
|
-
program: Program_lhgjafqgga;
|
|
47
|
-
sourceFile: SourceFile_sameteuoys;
|
|
48
|
-
maxTypeDepth: number;
|
|
49
|
-
resolveExternalTypes: boolean;
|
|
50
|
-
}
|
|
51
49
|
declare function extract(options: ExtractOptions): Promise<ExtractResult>;
|
|
52
50
|
import ts3 from "typescript";
|
|
53
51
|
interface ProgramOptions {
|
|
@@ -88,40 +86,66 @@ declare function extractStandardSchemas(_program: ts5.Program, _entryFile: strin
|
|
|
88
86
|
import { SpecExport } from "@openpkg-ts/spec";
|
|
89
87
|
import ts7 from "typescript";
|
|
90
88
|
import ts6 from "typescript";
|
|
91
|
-
interface
|
|
89
|
+
interface SerializerContext {
|
|
92
90
|
typeChecker: ts6.TypeChecker;
|
|
93
91
|
program: ts6.Program;
|
|
94
92
|
sourceFile: ts6.SourceFile;
|
|
95
93
|
maxTypeDepth: number;
|
|
94
|
+
maxExternalTypeDepth: number;
|
|
95
|
+
currentDepth: number;
|
|
96
96
|
resolveExternalTypes: boolean;
|
|
97
97
|
typeRegistry: TypeRegistry;
|
|
98
|
+
exportedIds: Set<string>;
|
|
99
|
+
/** Track visited types to prevent infinite recursion */
|
|
100
|
+
visitedTypes: Set<ts6.Type>;
|
|
98
101
|
}
|
|
99
|
-
declare function serializeClass(node: ts7.ClassDeclaration, ctx:
|
|
102
|
+
declare function serializeClass(node: ts7.ClassDeclaration, ctx: SerializerContext): SpecExport | null;
|
|
100
103
|
import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
|
|
101
104
|
import ts8 from "typescript";
|
|
102
|
-
declare function serializeEnum(node: ts8.EnumDeclaration, ctx:
|
|
105
|
+
declare function serializeEnum(node: ts8.EnumDeclaration, ctx: SerializerContext): SpecExport2 | null;
|
|
103
106
|
import { SpecExport as SpecExport3 } from "@openpkg-ts/spec";
|
|
104
107
|
import ts9 from "typescript";
|
|
105
|
-
declare function serializeFunctionExport(node: ts9.FunctionDeclaration | ts9.ArrowFunction, ctx:
|
|
108
|
+
declare function serializeFunctionExport(node: ts9.FunctionDeclaration | ts9.ArrowFunction, ctx: SerializerContext): SpecExport3 | null;
|
|
106
109
|
import { SpecExport as SpecExport4 } from "@openpkg-ts/spec";
|
|
107
110
|
import ts10 from "typescript";
|
|
108
|
-
declare function serializeInterface(node: ts10.InterfaceDeclaration, ctx:
|
|
111
|
+
declare function serializeInterface(node: ts10.InterfaceDeclaration, ctx: SerializerContext): SpecExport4 | null;
|
|
109
112
|
import { SpecExport as SpecExport5 } from "@openpkg-ts/spec";
|
|
110
113
|
import ts11 from "typescript";
|
|
111
|
-
declare function serializeTypeAlias(node: ts11.TypeAliasDeclaration, ctx:
|
|
114
|
+
declare function serializeTypeAlias(node: ts11.TypeAliasDeclaration, ctx: SerializerContext): SpecExport5 | null;
|
|
112
115
|
import { SpecExport as SpecExport6 } from "@openpkg-ts/spec";
|
|
113
116
|
import ts12 from "typescript";
|
|
114
|
-
declare function serializeVariable(node: ts12.VariableDeclaration, statement: ts12.VariableStatement, ctx:
|
|
117
|
+
declare function serializeVariable(node: ts12.VariableDeclaration, statement: ts12.VariableStatement, ctx: SerializerContext): SpecExport6 | null;
|
|
115
118
|
import ts13 from "typescript";
|
|
116
119
|
declare function formatTypeReference(type: ts13.Type, checker: ts13.TypeChecker): string;
|
|
117
120
|
declare function collectReferencedTypes(type: ts13.Type, checker: ts13.TypeChecker, visited?: Set<string>): string[];
|
|
118
121
|
import { SpecSignatureParameter } from "@openpkg-ts/spec";
|
|
119
122
|
import ts14 from "typescript";
|
|
120
|
-
declare function extractParameters(signature: ts14.Signature,
|
|
123
|
+
declare function extractParameters(signature: ts14.Signature, ctx: SerializerContext): SpecSignatureParameter[];
|
|
124
|
+
/**
|
|
125
|
+
* Recursively register types referenced by a ts.Type.
|
|
126
|
+
* Uses ctx.visitedTypes to prevent infinite recursion on circular types.
|
|
127
|
+
*/
|
|
128
|
+
declare function registerReferencedTypes(type: ts14.Type, ctx: SerializerContext): void;
|
|
121
129
|
import { SpecSchema as SpecSchema3 } from "@openpkg-ts/spec";
|
|
122
130
|
import ts15 from "typescript";
|
|
123
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Check if a name is a primitive type
|
|
133
|
+
*/
|
|
134
|
+
declare function isPrimitiveName(name: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Check if a name is a built-in generic type
|
|
137
|
+
*/
|
|
138
|
+
declare function isBuiltinGeneric(name: string): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Check if a type is anonymous (no meaningful symbol name)
|
|
141
|
+
*/
|
|
142
|
+
declare function isAnonymous(type: ts15.Type): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Build a structured SpecSchema from a TypeScript type.
|
|
145
|
+
* Uses $ref for named types and typeArguments for generics.
|
|
146
|
+
*/
|
|
147
|
+
declare function buildSchema(type: ts15.Type, checker: ts15.TypeChecker, ctx?: SerializerContext, _depth?: number): SpecSchema3;
|
|
124
148
|
import ts16 from "typescript";
|
|
125
149
|
declare function isExported(node: ts16.Node): boolean;
|
|
126
150
|
declare function getNodeName(node: ts16.Node): string | undefined;
|
|
127
|
-
export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerAdapter, isSchemaType, isExported, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
|
|
151
|
+
export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerReferencedTypes, registerAdapter, isSchemaType, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
|
package/dist/src/index.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TypeRegistry,
|
|
3
|
+
buildSchema,
|
|
3
4
|
createProgram,
|
|
4
5
|
extract,
|
|
5
6
|
extractParameters,
|
|
6
7
|
getJSDocComment,
|
|
7
8
|
getSourceLocation,
|
|
9
|
+
isAnonymous,
|
|
10
|
+
isBuiltinGeneric,
|
|
11
|
+
isPrimitiveName,
|
|
12
|
+
registerReferencedTypes,
|
|
8
13
|
serializeClass,
|
|
9
14
|
serializeEnum,
|
|
10
15
|
serializeFunctionExport,
|
|
11
16
|
serializeInterface,
|
|
12
17
|
serializeTypeAlias,
|
|
13
18
|
serializeVariable
|
|
14
|
-
} from "../shared/chunk-
|
|
19
|
+
} from "../shared/chunk-wddga8ye.js";
|
|
15
20
|
// src/schema/adapters/arktype.ts
|
|
16
21
|
var arktypeAdapter = {
|
|
17
22
|
name: "arktype",
|
|
@@ -69,32 +74,6 @@ function collectReferencedTypes(type, checker, visited = new Set) {
|
|
|
69
74
|
visited.add(name);
|
|
70
75
|
return [name];
|
|
71
76
|
}
|
|
72
|
-
// src/types/schema-builder.ts
|
|
73
|
-
function buildSchema(type, checker, depth = 0) {
|
|
74
|
-
if (depth > 10) {
|
|
75
|
-
return { type: checker.typeToString(type) };
|
|
76
|
-
}
|
|
77
|
-
const typeString = checker.typeToString(type);
|
|
78
|
-
if (type.flags & 4)
|
|
79
|
-
return { type: "string" };
|
|
80
|
-
if (type.flags & 8)
|
|
81
|
-
return { type: "number" };
|
|
82
|
-
if (type.flags & 16)
|
|
83
|
-
return { type: "boolean" };
|
|
84
|
-
if (type.flags & 32768)
|
|
85
|
-
return { type: "undefined" };
|
|
86
|
-
if (type.flags & 65536)
|
|
87
|
-
return { type: "null" };
|
|
88
|
-
if (type.flags & 16384)
|
|
89
|
-
return { type: "void" };
|
|
90
|
-
if (type.flags & 1)
|
|
91
|
-
return { type: "any" };
|
|
92
|
-
if (type.flags & 2)
|
|
93
|
-
return { type: "unknown" };
|
|
94
|
-
if (type.flags & 131072)
|
|
95
|
-
return { type: "never" };
|
|
96
|
-
return { type: typeString };
|
|
97
|
-
}
|
|
98
77
|
// src/types/utils.ts
|
|
99
78
|
function isExported(node) {
|
|
100
79
|
const modifiers = node.modifiers;
|
|
@@ -119,9 +98,13 @@ export {
|
|
|
119
98
|
serializeFunctionExport,
|
|
120
99
|
serializeEnum,
|
|
121
100
|
serializeClass,
|
|
101
|
+
registerReferencedTypes,
|
|
122
102
|
registerAdapter,
|
|
123
103
|
isSchemaType,
|
|
104
|
+
isPrimitiveName,
|
|
124
105
|
isExported,
|
|
106
|
+
isBuiltinGeneric,
|
|
107
|
+
isAnonymous,
|
|
125
108
|
getSourceLocation,
|
|
126
109
|
getNodeName,
|
|
127
110
|
getJSDocComment,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openpkg-ts/extract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "TypeScript export extraction to OpenPkg spec",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openpkg",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"format": "biome format --write src/"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@openpkg-ts/spec": "^0.
|
|
43
|
+
"@openpkg-ts/spec": "^0.12.0",
|
|
44
44
|
"commander": "^12.0.0",
|
|
45
45
|
"typescript": "^5.0.0"
|
|
46
46
|
},
|
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
// src/ast/registry.ts
|
|
2
|
-
class TypeRegistry {
|
|
3
|
-
types = new Map;
|
|
4
|
-
add(type) {
|
|
5
|
-
this.types.set(type.id, type);
|
|
6
|
-
}
|
|
7
|
-
get(id) {
|
|
8
|
-
return this.types.get(id);
|
|
9
|
-
}
|
|
10
|
-
has(id) {
|
|
11
|
-
return this.types.has(id);
|
|
12
|
-
}
|
|
13
|
-
getAll() {
|
|
14
|
-
return Array.from(this.types.values());
|
|
15
|
-
}
|
|
16
|
-
registerFromSymbol(symbol, checker) {
|
|
17
|
-
const name = symbol.getName();
|
|
18
|
-
if (this.has(name))
|
|
19
|
-
return this.get(name);
|
|
20
|
-
const type = {
|
|
21
|
-
id: name,
|
|
22
|
-
name,
|
|
23
|
-
kind: "type"
|
|
24
|
-
};
|
|
25
|
-
this.add(type);
|
|
26
|
-
return type;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// src/ast/utils.ts
|
|
31
|
-
import ts from "typescript";
|
|
32
|
-
function parseExamplesFromTags(tags) {
|
|
33
|
-
const examples = [];
|
|
34
|
-
for (const tag of tags) {
|
|
35
|
-
if (tag.name !== "example")
|
|
36
|
-
continue;
|
|
37
|
-
const text = tag.text.trim();
|
|
38
|
-
const fenceMatch = text.match(/^```(\w*)\n([\s\S]*?)\n?```$/);
|
|
39
|
-
if (fenceMatch) {
|
|
40
|
-
const lang = fenceMatch[1] || undefined;
|
|
41
|
-
const code = fenceMatch[2].trim();
|
|
42
|
-
const example = { code };
|
|
43
|
-
if (lang && ["ts", "js", "tsx", "jsx", "shell", "json"].includes(lang)) {
|
|
44
|
-
example.language = lang;
|
|
45
|
-
}
|
|
46
|
-
examples.push(example);
|
|
47
|
-
} else if (text) {
|
|
48
|
-
examples.push({ code: text });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return examples;
|
|
52
|
-
}
|
|
53
|
-
function getJSDocComment(node) {
|
|
54
|
-
const jsDocTags = ts.getJSDocTags(node);
|
|
55
|
-
const tags = jsDocTags.map((tag) => ({
|
|
56
|
-
name: tag.tagName.text,
|
|
57
|
-
text: typeof tag.comment === "string" ? tag.comment : ts.getTextOfJSDocComment(tag.comment) ?? ""
|
|
58
|
-
}));
|
|
59
|
-
const jsDocComments = node.jsDoc;
|
|
60
|
-
let description;
|
|
61
|
-
if (jsDocComments && jsDocComments.length > 0) {
|
|
62
|
-
const firstDoc = jsDocComments[0];
|
|
63
|
-
if (firstDoc.comment) {
|
|
64
|
-
description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts.getTextOfJSDocComment(firstDoc.comment);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const examples = parseExamplesFromTags(tags);
|
|
68
|
-
return { description, tags, examples };
|
|
69
|
-
}
|
|
70
|
-
function getSourceLocation(node, sourceFile) {
|
|
71
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
72
|
-
return {
|
|
73
|
-
file: sourceFile.fileName,
|
|
74
|
-
line: line + 1
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/compiler/program.ts
|
|
79
|
-
import * as path from "node:path";
|
|
80
|
-
import ts2 from "typescript";
|
|
81
|
-
var DEFAULT_COMPILER_OPTIONS = {
|
|
82
|
-
target: ts2.ScriptTarget.Latest,
|
|
83
|
-
module: ts2.ModuleKind.CommonJS,
|
|
84
|
-
lib: ["lib.es2021.d.ts"],
|
|
85
|
-
declaration: true,
|
|
86
|
-
moduleResolution: ts2.ModuleResolutionKind.NodeJs
|
|
87
|
-
};
|
|
88
|
-
function createProgram({
|
|
89
|
-
entryFile,
|
|
90
|
-
baseDir = path.dirname(entryFile),
|
|
91
|
-
content
|
|
92
|
-
}) {
|
|
93
|
-
const configPath = ts2.findConfigFile(baseDir, ts2.sys.fileExists, "tsconfig.json");
|
|
94
|
-
let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
|
|
95
|
-
if (configPath) {
|
|
96
|
-
const configFile = ts2.readConfigFile(configPath, ts2.sys.readFile);
|
|
97
|
-
const parsedConfig = ts2.parseJsonConfigFileContent(configFile.config, ts2.sys, path.dirname(configPath));
|
|
98
|
-
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
|
|
99
|
-
}
|
|
100
|
-
const allowJsVal = compilerOptions.allowJs;
|
|
101
|
-
if (typeof allowJsVal === "boolean" && allowJsVal) {
|
|
102
|
-
compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
|
|
103
|
-
}
|
|
104
|
-
const compilerHost = ts2.createCompilerHost(compilerOptions, true);
|
|
105
|
-
let inMemorySource;
|
|
106
|
-
if (content !== undefined) {
|
|
107
|
-
inMemorySource = ts2.createSourceFile(entryFile, content, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TS);
|
|
108
|
-
const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
|
|
109
|
-
compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
110
|
-
if (fileName === entryFile) {
|
|
111
|
-
return inMemorySource;
|
|
112
|
-
}
|
|
113
|
-
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
const program = ts2.createProgram([entryFile], compilerOptions, compilerHost);
|
|
117
|
-
const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
|
|
118
|
-
return {
|
|
119
|
-
program,
|
|
120
|
-
compilerHost,
|
|
121
|
-
compilerOptions,
|
|
122
|
-
sourceFile,
|
|
123
|
-
configPath
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// src/serializers/classes.ts
|
|
128
|
-
function serializeClass(node, ctx) {
|
|
129
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
130
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
131
|
-
if (!name)
|
|
132
|
-
return null;
|
|
133
|
-
const declSourceFile = node.getSourceFile();
|
|
134
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
135
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
136
|
-
return {
|
|
137
|
-
id: name,
|
|
138
|
-
name,
|
|
139
|
-
kind: "class",
|
|
140
|
-
description,
|
|
141
|
-
tags,
|
|
142
|
-
source,
|
|
143
|
-
members: [],
|
|
144
|
-
...examples.length > 0 ? { examples } : {}
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// src/serializers/enums.ts
|
|
149
|
-
function serializeEnum(node, ctx) {
|
|
150
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
151
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
152
|
-
if (!name)
|
|
153
|
-
return null;
|
|
154
|
-
const declSourceFile = node.getSourceFile();
|
|
155
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
156
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
157
|
-
const members = node.members.map((member) => {
|
|
158
|
-
const memberSymbol = ctx.typeChecker.getSymbolAtLocation(member.name);
|
|
159
|
-
const memberName = memberSymbol?.getName() ?? member.name.getText();
|
|
160
|
-
return {
|
|
161
|
-
id: memberName,
|
|
162
|
-
name: memberName,
|
|
163
|
-
kind: "enum-member"
|
|
164
|
-
};
|
|
165
|
-
});
|
|
166
|
-
return {
|
|
167
|
-
id: name,
|
|
168
|
-
name,
|
|
169
|
-
kind: "enum",
|
|
170
|
-
description,
|
|
171
|
-
tags,
|
|
172
|
-
source,
|
|
173
|
-
members,
|
|
174
|
-
...examples.length > 0 ? { examples } : {}
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// src/types/parameters.ts
|
|
179
|
-
function extractParameters(signature, checker) {
|
|
180
|
-
return signature.getParameters().map((param) => {
|
|
181
|
-
const type = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
|
|
182
|
-
return {
|
|
183
|
-
name: param.getName(),
|
|
184
|
-
schema: { type: checker.typeToString(type) },
|
|
185
|
-
required: !(param.flags & 16777216)
|
|
186
|
-
};
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/serializers/functions.ts
|
|
191
|
-
function serializeFunctionExport(node, ctx) {
|
|
192
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
193
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
194
|
-
if (!name)
|
|
195
|
-
return null;
|
|
196
|
-
const declSourceFile = node.getSourceFile();
|
|
197
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
198
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
199
|
-
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
200
|
-
const callSignatures = type.getCallSignatures();
|
|
201
|
-
const signatures = callSignatures.map((sig) => {
|
|
202
|
-
const params = extractParameters(sig, ctx.typeChecker);
|
|
203
|
-
const returnType = ctx.typeChecker.getReturnTypeOfSignature(sig);
|
|
204
|
-
return {
|
|
205
|
-
parameters: params,
|
|
206
|
-
returns: {
|
|
207
|
-
schema: { type: ctx.typeChecker.typeToString(returnType) }
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
});
|
|
211
|
-
return {
|
|
212
|
-
id: name,
|
|
213
|
-
name,
|
|
214
|
-
kind: "function",
|
|
215
|
-
description,
|
|
216
|
-
tags,
|
|
217
|
-
source,
|
|
218
|
-
signatures,
|
|
219
|
-
...examples.length > 0 ? { examples } : {}
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// src/serializers/interfaces.ts
|
|
224
|
-
function serializeInterface(node, ctx) {
|
|
225
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
226
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
227
|
-
if (!name)
|
|
228
|
-
return null;
|
|
229
|
-
const declSourceFile = node.getSourceFile();
|
|
230
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
231
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
232
|
-
return {
|
|
233
|
-
id: name,
|
|
234
|
-
name,
|
|
235
|
-
kind: "interface",
|
|
236
|
-
description,
|
|
237
|
-
tags,
|
|
238
|
-
source,
|
|
239
|
-
members: [],
|
|
240
|
-
...examples.length > 0 ? { examples } : {}
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// src/serializers/type-aliases.ts
|
|
245
|
-
function serializeTypeAlias(node, ctx) {
|
|
246
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
247
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
248
|
-
if (!name)
|
|
249
|
-
return null;
|
|
250
|
-
const declSourceFile = node.getSourceFile();
|
|
251
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
252
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
253
|
-
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
254
|
-
const typeString = ctx.typeChecker.typeToString(type);
|
|
255
|
-
return {
|
|
256
|
-
id: name,
|
|
257
|
-
name,
|
|
258
|
-
kind: "type",
|
|
259
|
-
description,
|
|
260
|
-
tags,
|
|
261
|
-
source,
|
|
262
|
-
...typeString !== name ? { type: typeString } : {},
|
|
263
|
-
...examples.length > 0 ? { examples } : {}
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// src/serializers/variables.ts
|
|
268
|
-
function serializeVariable(node, statement, ctx) {
|
|
269
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name);
|
|
270
|
-
const name = symbol?.getName() ?? node.name.getText();
|
|
271
|
-
if (!name)
|
|
272
|
-
return null;
|
|
273
|
-
const declSourceFile = node.getSourceFile();
|
|
274
|
-
const { description, tags, examples } = getJSDocComment(statement);
|
|
275
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
276
|
-
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
277
|
-
const typeString = ctx.typeChecker.typeToString(type);
|
|
278
|
-
return {
|
|
279
|
-
id: name,
|
|
280
|
-
name,
|
|
281
|
-
kind: "variable",
|
|
282
|
-
description,
|
|
283
|
-
tags,
|
|
284
|
-
source,
|
|
285
|
-
...typeString && typeString !== name ? { type: typeString } : {},
|
|
286
|
-
...examples.length > 0 ? { examples } : {}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// src/builder/spec-builder.ts
|
|
291
|
-
import * as fs from "node:fs";
|
|
292
|
-
import * as path2 from "node:path";
|
|
293
|
-
import { SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
294
|
-
import ts3 from "typescript";
|
|
295
|
-
|
|
296
|
-
// src/serializers/context.ts
|
|
297
|
-
function createContext(program, sourceFile, options = {}) {
|
|
298
|
-
return {
|
|
299
|
-
typeChecker: program.getTypeChecker(),
|
|
300
|
-
program,
|
|
301
|
-
sourceFile,
|
|
302
|
-
maxTypeDepth: options.maxTypeDepth ?? 20,
|
|
303
|
-
resolveExternalTypes: options.resolveExternalTypes ?? true,
|
|
304
|
-
typeRegistry: new TypeRegistry
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// src/builder/spec-builder.ts
|
|
309
|
-
async function extract(options) {
|
|
310
|
-
const { entryFile, baseDir, content, maxTypeDepth, resolveExternalTypes } = options;
|
|
311
|
-
const diagnostics = [];
|
|
312
|
-
const exports = [];
|
|
313
|
-
const result = createProgram({ entryFile, baseDir, content });
|
|
314
|
-
const { program, sourceFile } = result;
|
|
315
|
-
if (!sourceFile) {
|
|
316
|
-
return {
|
|
317
|
-
spec: createEmptySpec(entryFile),
|
|
318
|
-
diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
const ctx = createContext(program, sourceFile, { maxTypeDepth, resolveExternalTypes });
|
|
322
|
-
const typeChecker = program.getTypeChecker();
|
|
323
|
-
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
324
|
-
if (!moduleSymbol) {
|
|
325
|
-
return {
|
|
326
|
-
spec: createEmptySpec(entryFile),
|
|
327
|
-
diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
|
|
331
|
-
for (const symbol of exportedSymbols) {
|
|
332
|
-
try {
|
|
333
|
-
const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
|
|
334
|
-
if (!declaration)
|
|
335
|
-
continue;
|
|
336
|
-
const exportName = symbol.getName();
|
|
337
|
-
const exp = serializeDeclaration(declaration, targetSymbol, exportName, ctx);
|
|
338
|
-
if (exp)
|
|
339
|
-
exports.push(exp);
|
|
340
|
-
} catch (err) {
|
|
341
|
-
diagnostics.push({
|
|
342
|
-
message: `Failed to serialize ${symbol.getName()}: ${err}`,
|
|
343
|
-
severity: "warning"
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const meta = await getPackageMeta(entryFile, baseDir);
|
|
348
|
-
const spec = {
|
|
349
|
-
openpkg: SCHEMA_VERSION,
|
|
350
|
-
meta,
|
|
351
|
-
exports,
|
|
352
|
-
types: ctx.typeRegistry.getAll(),
|
|
353
|
-
generation: {
|
|
354
|
-
generator: "@openpkg-ts/extract",
|
|
355
|
-
timestamp: new Date().toISOString()
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
return { spec, diagnostics };
|
|
359
|
-
}
|
|
360
|
-
function resolveExportTarget(symbol, checker) {
|
|
361
|
-
let targetSymbol = symbol;
|
|
362
|
-
if (symbol.flags & ts3.SymbolFlags.Alias) {
|
|
363
|
-
const aliasTarget = checker.getAliasedSymbol(symbol);
|
|
364
|
-
if (aliasTarget && aliasTarget !== symbol) {
|
|
365
|
-
targetSymbol = aliasTarget;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
const declarations = targetSymbol.declarations ?? [];
|
|
369
|
-
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts3.SyntaxKind.ExportSpecifier) || declarations[0];
|
|
370
|
-
return { declaration, targetSymbol };
|
|
371
|
-
}
|
|
372
|
-
function serializeDeclaration(declaration, symbol, exportName, ctx) {
|
|
373
|
-
let result = null;
|
|
374
|
-
if (ts3.isFunctionDeclaration(declaration)) {
|
|
375
|
-
result = serializeFunctionExport(declaration, ctx);
|
|
376
|
-
} else if (ts3.isClassDeclaration(declaration)) {
|
|
377
|
-
result = serializeClass(declaration, ctx);
|
|
378
|
-
} else if (ts3.isInterfaceDeclaration(declaration)) {
|
|
379
|
-
result = serializeInterface(declaration, ctx);
|
|
380
|
-
} else if (ts3.isTypeAliasDeclaration(declaration)) {
|
|
381
|
-
result = serializeTypeAlias(declaration, ctx);
|
|
382
|
-
} else if (ts3.isEnumDeclaration(declaration)) {
|
|
383
|
-
result = serializeEnum(declaration, ctx);
|
|
384
|
-
} else if (ts3.isVariableDeclaration(declaration)) {
|
|
385
|
-
const varStatement = declaration.parent?.parent;
|
|
386
|
-
if (varStatement && ts3.isVariableStatement(varStatement)) {
|
|
387
|
-
result = serializeVariable(declaration, varStatement, ctx);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (result) {
|
|
391
|
-
result = withExportName(result, exportName);
|
|
392
|
-
}
|
|
393
|
-
return result;
|
|
394
|
-
}
|
|
395
|
-
function withExportName(entry, exportName) {
|
|
396
|
-
if (entry.name === exportName) {
|
|
397
|
-
return entry;
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
...entry,
|
|
401
|
-
id: exportName,
|
|
402
|
-
name: entry.name
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
function createEmptySpec(entryFile) {
|
|
406
|
-
return {
|
|
407
|
-
openpkg: SCHEMA_VERSION,
|
|
408
|
-
meta: { name: path2.basename(entryFile, path2.extname(entryFile)) },
|
|
409
|
-
exports: [],
|
|
410
|
-
generation: {
|
|
411
|
-
generator: "@openpkg-ts/extract",
|
|
412
|
-
timestamp: new Date().toISOString()
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
async function getPackageMeta(entryFile, baseDir) {
|
|
417
|
-
const searchDir = baseDir ?? path2.dirname(entryFile);
|
|
418
|
-
const pkgPath = path2.join(searchDir, "package.json");
|
|
419
|
-
try {
|
|
420
|
-
if (fs.existsSync(pkgPath)) {
|
|
421
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
422
|
-
return {
|
|
423
|
-
name: pkg.name ?? path2.basename(searchDir),
|
|
424
|
-
version: pkg.version,
|
|
425
|
-
description: pkg.description
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
} catch {}
|
|
429
|
-
return { name: path2.basename(searchDir) };
|
|
430
|
-
}
|
|
431
|
-
export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, extractParameters, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
|