@runtime-type-inspector/repl 3.2.2 → 3.2.4
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/index.html +4 -0
- package/index.js +5 -1
- package/package.json +2 -2
- package/ts-morph.js +21765 -0
- package/ts-to-jsdoc-test.js +31 -0
- package/ts-to-jsdoc.js +460 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//import {transpile} from "ts-to-jsdoc";
|
|
2
|
+
import {transpile} from "./ts-to-jsdoc.js";
|
|
3
|
+
const ret = transpile(`
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ImportDeclaration,
|
|
7
|
+
ClassDeclaration,
|
|
8
|
+
FunctionLikeDeclaration,
|
|
9
|
+
InterfaceDeclaration,
|
|
10
|
+
JSDoc,
|
|
11
|
+
JSDocableNode,
|
|
12
|
+
MethodDeclaration,
|
|
13
|
+
ModifierableNode,
|
|
14
|
+
PropertyAssignment,
|
|
15
|
+
PropertyDeclaration,
|
|
16
|
+
PropertySignature,
|
|
17
|
+
TypeAliasDeclaration,
|
|
18
|
+
TypedNode,
|
|
19
|
+
VariableDeclaration,
|
|
20
|
+
} from "ts-morph";
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
A,B
|
|
24
|
+
} from "abc";
|
|
25
|
+
|
|
26
|
+
/***/
|
|
27
|
+
const groups: Record<string, string[]> = {
|
|
28
|
+
test: ['a', 'b']
|
|
29
|
+
} as const;
|
|
30
|
+
`);
|
|
31
|
+
console.log(ret);
|
package/ts-to-jsdoc.js
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
//import * as ts from 'typescript';
|
|
3
|
+
//debugger;
|
|
4
|
+
//const ScriptTarget = ts.ScriptTarget;
|
|
5
|
+
import * as tsDefault from 'typescript';
|
|
6
|
+
const ts = tsDefault.default;
|
|
7
|
+
import {
|
|
8
|
+
Node,
|
|
9
|
+
Project,
|
|
10
|
+
//ScriptTarget,
|
|
11
|
+
//SyntaxKind
|
|
12
|
+
} from "./ts-morph.js";
|
|
13
|
+
const {
|
|
14
|
+
//Project,
|
|
15
|
+
ScriptTarget,
|
|
16
|
+
SyntaxKind,
|
|
17
|
+
} = ts;
|
|
18
|
+
//import { Node, Project, ScriptTarget, SyntaxKind, } from "ts-morph";
|
|
19
|
+
Node.isObjectProperty = (node) => (Node.isPropertyDeclaration(node)
|
|
20
|
+
|| Node.isPropertyAssignment(node)
|
|
21
|
+
|| Node.isPropertySignature(node));
|
|
22
|
+
/**
|
|
23
|
+
* Get children for object node
|
|
24
|
+
* @param {Node} node
|
|
25
|
+
* @returns {ObjectProperty[]}
|
|
26
|
+
*/
|
|
27
|
+
function getChildProperties(node) {
|
|
28
|
+
const properties = node?.getType()?.getProperties();
|
|
29
|
+
const valueDeclarations = properties.map((child) => child.getValueDeclaration())
|
|
30
|
+
// Hacky way to check if the child is actually a defined child in the interface
|
|
31
|
+
// or if it's, e.g. a built-in method of the type (such as array.length)
|
|
32
|
+
?.filter((child) => node.getFullText().includes(child?.getFullText()));
|
|
33
|
+
return (valueDeclarations ?? []);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param {JSDocableNode} node
|
|
37
|
+
* @returns {JSDoc | undefined}
|
|
38
|
+
*/
|
|
39
|
+
function getJsDoc(node) {
|
|
40
|
+
return node.getJsDocs().at(-1);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get JSDoc for a node or create one if there isn't any
|
|
44
|
+
* @param {JSDocableNode} node
|
|
45
|
+
* @returns {JSDoc}
|
|
46
|
+
*/
|
|
47
|
+
function getJsDocOrCreate(node) {
|
|
48
|
+
return getJsDoc(node) || node.addJsDoc({});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* getJsDocOrCreate, but if JSDoc is created, insert a newline at the beginning
|
|
52
|
+
* so that the first line of JSDoc doesn't appear on the same line as `/**`
|
|
53
|
+
* @param {JSDocableNode} node
|
|
54
|
+
* @returns {JSDoc}
|
|
55
|
+
*/
|
|
56
|
+
function getJsDocOrCreateMultiline(node) {
|
|
57
|
+
return getJsDoc(node) || node.addJsDoc({
|
|
58
|
+
description: "\n",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Return the node most suitable for JSDoc for a function, adding JSDoc if there isn't any
|
|
63
|
+
* @param {FunctionLikeDeclaration} functionNode
|
|
64
|
+
* @param {JSDocableNode} [docNode]
|
|
65
|
+
* @returns {JSDocableNode}
|
|
66
|
+
*/
|
|
67
|
+
function getOutputJsDocNodeOrCreate(functionNode, docNode) {
|
|
68
|
+
if (docNode) {
|
|
69
|
+
const funcNodeDocs = functionNode.getJsDocs();
|
|
70
|
+
if (funcNodeDocs.length)
|
|
71
|
+
return functionNode;
|
|
72
|
+
getJsDocOrCreateMultiline(docNode);
|
|
73
|
+
return docNode;
|
|
74
|
+
}
|
|
75
|
+
getJsDocOrCreateMultiline(functionNode);
|
|
76
|
+
return functionNode;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate `@param` documentation from function parameters for functionNode, storing it in docNode
|
|
80
|
+
* @param {FunctionLikeDeclaration} functionNode
|
|
81
|
+
* @param {JSDocableNode} docNode
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
function generateParameterDocumentation(functionNode, docNode) {
|
|
85
|
+
const params = functionNode.getParameters();
|
|
86
|
+
if (!params.length)
|
|
87
|
+
return;
|
|
88
|
+
const jsDoc = getJsDocOrCreateMultiline(docNode);
|
|
89
|
+
// Get existing param tags, store their comments, then remove them
|
|
90
|
+
const paramTags = (jsDoc.getTags() || [])
|
|
91
|
+
.filter((tag) => ["param", "parameter"].includes(tag.getTagName()));
|
|
92
|
+
const commentLookup = Object.fromEntries(paramTags.map((tag) => [
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
tag.compilerNode.name?.getText().replace(/\[|\]|(=.*)/g, "").trim(),
|
|
95
|
+
(tag.getComment() || "").toString().trim(),
|
|
96
|
+
]));
|
|
97
|
+
const preferredTagName = paramTags[0]?.getTagName();
|
|
98
|
+
paramTags.forEach((tag) => tag.remove());
|
|
99
|
+
for (const param of params) {
|
|
100
|
+
const paramType = param.getTypeNode()?.getText() || param.getType().getText();
|
|
101
|
+
if (!paramType)
|
|
102
|
+
continue;
|
|
103
|
+
const paramName = param.compilerNode.name?.getText();
|
|
104
|
+
const isOptional = param.isOptional();
|
|
105
|
+
const isRest = param.isRestParameter();
|
|
106
|
+
// Rest parameters are arrays, but the JSDoc syntax is `...number` instead of `number[]`
|
|
107
|
+
const paramTypeOut = isRest ? `...${paramType.replace(/\[\]\s*$/, "")}` : paramType;
|
|
108
|
+
let defaultValue;
|
|
109
|
+
if (isOptional) {
|
|
110
|
+
const paramInitializer = param.getInitializer();
|
|
111
|
+
defaultValue = paramInitializer?.getText().replaceAll(/(\s|\t)*\n(\s|\t)*/g, " ");
|
|
112
|
+
}
|
|
113
|
+
let paramNameOut = paramName;
|
|
114
|
+
// Skip parameter names if they are present in the type as an object literal
|
|
115
|
+
// e.g. destructuring; { a }: { a: string }
|
|
116
|
+
if (paramNameOut.match(/[{},]/))
|
|
117
|
+
paramNameOut = "";
|
|
118
|
+
if (paramNameOut && isOptional) {
|
|
119
|
+
// Wrap name in square brackets if the parameter is optional
|
|
120
|
+
const defaultValueOut = defaultValue !== undefined ? `=${defaultValue}` : "";
|
|
121
|
+
paramNameOut = `[${paramNameOut}${defaultValueOut}]`;
|
|
122
|
+
}
|
|
123
|
+
paramNameOut = paramNameOut ? ` ${paramNameOut}` : "";
|
|
124
|
+
const comment = commentLookup[paramName.trim()];
|
|
125
|
+
jsDoc.addTag({
|
|
126
|
+
tagName: preferredTagName || "param",
|
|
127
|
+
text: `{${paramTypeOut}}${paramNameOut}${comment ? ` ${comment}` : ""}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Generate `@returns` documentation from function return type for functionNode, storing it in docNode
|
|
133
|
+
* @param {FunctionLikeDeclaration} functionNode
|
|
134
|
+
* @param {JSDocableNode} docNode
|
|
135
|
+
* @returns {void}
|
|
136
|
+
*/
|
|
137
|
+
function generateReturnTypeDocumentation(functionNode, docNode) {
|
|
138
|
+
const returnTypeNode = functionNode.getReturnTypeNode() ?? functionNode.getReturnType();
|
|
139
|
+
const functionReturnType = returnTypeNode.getText(functionNode.getSignature().getDeclaration());
|
|
140
|
+
const jsDoc = getJsDocOrCreate(docNode);
|
|
141
|
+
const returnsTag = (jsDoc?.getTags() || [])
|
|
142
|
+
.find((tag) => ["returns", "return"].includes(tag.getTagName()));
|
|
143
|
+
// Replace tag with one that contains type info if tag exists
|
|
144
|
+
const tagName = returnsTag?.getTagName() || "returns";
|
|
145
|
+
const comment = (returnsTag?.getComment() || "").toString().trim();
|
|
146
|
+
if (returnsTag) {
|
|
147
|
+
returnsTag.remove();
|
|
148
|
+
}
|
|
149
|
+
jsDoc.addTag({
|
|
150
|
+
tagName,
|
|
151
|
+
text: `{${functionReturnType}}${comment ? ` ${comment}` : ""}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Generate documentation for a function, storing it in functionNode or docNode
|
|
156
|
+
* @param {FunctionLikeDeclaration} functionNode
|
|
157
|
+
* @param {JSDocableNode} [docNode]
|
|
158
|
+
* @returns {void}
|
|
159
|
+
*/
|
|
160
|
+
function generateFunctionDocumentation(functionNode, docNode) {
|
|
161
|
+
const outputDocNode = getOutputJsDocNodeOrCreate(functionNode, docNode);
|
|
162
|
+
generateParameterDocumentation(functionNode, outputDocNode);
|
|
163
|
+
generateReturnTypeDocumentation(functionNode, outputDocNode);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Generate modifier documentation for class member
|
|
167
|
+
* @param {ClassMemberNode} classMemberNode
|
|
168
|
+
* @returns {void}
|
|
169
|
+
*/
|
|
170
|
+
function generateModifierDocumentation(classMemberNode) {
|
|
171
|
+
const modifiers = classMemberNode.getModifiers() || [];
|
|
172
|
+
for (const modifier of modifiers) {
|
|
173
|
+
const text = modifier?.getText();
|
|
174
|
+
if (["public", "private", "protected", "readonly", "static"].includes(text)) {
|
|
175
|
+
const jsDoc = getJsDocOrCreateMultiline(classMemberNode);
|
|
176
|
+
jsDoc.addTag({ tagName: text });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Create class property initializer in constructor if it doesn't exist
|
|
182
|
+
* so that documentation is preserved when transpiling
|
|
183
|
+
* @param {ObjectProperty} classPropertyNode
|
|
184
|
+
* @returns {void}
|
|
185
|
+
*/
|
|
186
|
+
function generateInitializerDocumentation(classPropertyNode) {
|
|
187
|
+
if (!classPropertyNode.getStructure()?.initializer) {
|
|
188
|
+
classPropertyNode.setInitializer("undefined");
|
|
189
|
+
}
|
|
190
|
+
const initializer = classPropertyNode.getStructure()?.initializer;
|
|
191
|
+
if (initializer !== "undefined") {
|
|
192
|
+
const jsDoc = getJsDocOrCreate(classPropertyNode);
|
|
193
|
+
jsDoc.addTag({ tagName: "default", text: initializer });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Document the class itself; at the moment just its extends signature
|
|
198
|
+
* @param {ClassDeclaration} classNode
|
|
199
|
+
* @returns {void}
|
|
200
|
+
*/
|
|
201
|
+
function generateClassBaseDocumentation(classNode) {
|
|
202
|
+
const extendedClass = classNode.getExtends();
|
|
203
|
+
if (extendedClass) {
|
|
204
|
+
const jsDoc = getJsDocOrCreate(classNode);
|
|
205
|
+
jsDoc.addTag({ tagName: "extends", text: extendedClass.getText() });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate documentation for class members in general; either property or method
|
|
210
|
+
* @param {ClassMemberNode} classMemberNode
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
function generateClassMemberDocumentation(classMemberNode) {
|
|
214
|
+
generateModifierDocumentation(classMemberNode);
|
|
215
|
+
Node.isObjectProperty(classMemberNode) && generateInitializerDocumentation(classMemberNode);
|
|
216
|
+
Node.isMethodDeclaration(classMemberNode) && generateFunctionDocumentation(classMemberNode);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Generate documentation for a class — itself and its members
|
|
220
|
+
* @param {ClassDeclaration} classNode
|
|
221
|
+
* @returns {void}
|
|
222
|
+
*/
|
|
223
|
+
function generateClassDocumentation(classNode) {
|
|
224
|
+
generateClassBaseDocumentation(classNode);
|
|
225
|
+
classNode.getMembers().forEach(generateClassMemberDocumentation);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Generate `@typedefs` from type aliases
|
|
229
|
+
* @param {TypeAliasDeclaration} typeAlias
|
|
230
|
+
* @return {string} A JSDoc comment containing the typedef
|
|
231
|
+
*/
|
|
232
|
+
function generateTypedefDocumentation(typeAlias) {
|
|
233
|
+
const name = typeAlias.getName();
|
|
234
|
+
const typeNode = typeAlias.getTypeNode();
|
|
235
|
+
const isObjectType = Node.isTypeLiteral(typeNode) && typeAlias.getType().isObject();
|
|
236
|
+
const properties = isObjectType ? typeNode.getProperties() : [];
|
|
237
|
+
const typeParams = typeAlias.getTypeParameters();
|
|
238
|
+
// If we're going to have multiple tags, we need to create a multiline JSDoc
|
|
239
|
+
const jsDoc = properties.length || typeParams.length
|
|
240
|
+
? getJsDocOrCreateMultiline(typeAlias)
|
|
241
|
+
: getJsDocOrCreate(typeAlias);
|
|
242
|
+
if (Node.isTypeLiteral(typeNode) && typeAlias.getType().isObject()) {
|
|
243
|
+
jsDoc.addTag({ tagName: "typedef", text: `{Object} ${name}` });
|
|
244
|
+
typeNode.getProperties().forEach((prop) => {
|
|
245
|
+
generateObjectPropertyDocumentation(prop, jsDoc);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const { type } = typeAlias.getStructure();
|
|
250
|
+
if (typeof type !== "string")
|
|
251
|
+
return jsDoc.getFullText();
|
|
252
|
+
jsDoc.addTag({ tagName: "typedef", text: `{${type}} ${name}` });
|
|
253
|
+
}
|
|
254
|
+
typeParams.forEach((param) => {
|
|
255
|
+
const constraint = param.getConstraint();
|
|
256
|
+
const defaultType = param.getDefault();
|
|
257
|
+
const paramName = param.getName();
|
|
258
|
+
const nameWithDefault = defaultType ? `[${paramName}=${defaultType.getText()}]` : paramName;
|
|
259
|
+
jsDoc.addTag({
|
|
260
|
+
tagName: "template",
|
|
261
|
+
text: `${constraint ? `{${constraint.getText()}} ` : ""}${nameWithDefault}`,
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
return jsDoc.getFullText();
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Generate documentation for object properties; runs recursively for nested objects
|
|
268
|
+
* @param {ObjectProperty} node
|
|
269
|
+
* @param {JSDoc} jsDoc
|
|
270
|
+
* @param {string} [name=""] The name to assign child docs to;
|
|
271
|
+
* "obj" will generate docs for "obj.val1", "obj.val2", etc
|
|
272
|
+
* @param {boolean} [topLevelCall=true] recursive functions are funky
|
|
273
|
+
* @returns {void}
|
|
274
|
+
*/
|
|
275
|
+
function generateObjectPropertyDocumentation(node, jsDoc, name = "", topLevelCall = true) {
|
|
276
|
+
name = name || node.getName();
|
|
277
|
+
if (!topLevelCall)
|
|
278
|
+
name = `${name}.${node.getName()}`;
|
|
279
|
+
let propType = node.getTypeNode()
|
|
280
|
+
?.getText()
|
|
281
|
+
?.replace(/\n/g, "")?.trim();
|
|
282
|
+
const isOptional = node.hasQuestionToken()
|
|
283
|
+
|| node.getJsDocs()?.[0]
|
|
284
|
+
?.getTags()
|
|
285
|
+
?.some((tag) => tag.getTagName() === "optional");
|
|
286
|
+
// Copy over existing description if there is one
|
|
287
|
+
const existingPropDocs = node.getJsDocs()?.[0]?.getDescription()?.trim() || "";
|
|
288
|
+
const children = getChildProperties(node);
|
|
289
|
+
if (children.length)
|
|
290
|
+
propType = "Object";
|
|
291
|
+
jsDoc.addTag({
|
|
292
|
+
tagName: "property",
|
|
293
|
+
text: `{${propType}} ${isOptional ? `[${name}]` : name} ${existingPropDocs}`,
|
|
294
|
+
});
|
|
295
|
+
if (children.length) {
|
|
296
|
+
children.forEach((child) => generateObjectPropertyDocumentation(child, jsDoc, name, false));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Generate `@typedefs` from interfaces
|
|
301
|
+
* @param {InterfaceDeclaration} interfaceNode
|
|
302
|
+
* @returns {string}
|
|
303
|
+
*/
|
|
304
|
+
function generateInterfaceDocumentation(interfaceNode) {
|
|
305
|
+
const name = interfaceNode.getName();
|
|
306
|
+
const jsDoc = getJsDocOrCreateMultiline(interfaceNode);
|
|
307
|
+
jsDoc.addTag({ tagName: "typedef", text: `{Object} ${name}` });
|
|
308
|
+
interfaceNode.getProperties().forEach((prop) => {
|
|
309
|
+
generateObjectPropertyDocumentation(prop, jsDoc);
|
|
310
|
+
});
|
|
311
|
+
return jsDoc.getFullText();
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Generate documentation for top-level var, const, and let declarations
|
|
315
|
+
* @param {VariableDeclaration} varNode
|
|
316
|
+
* @returns {void}
|
|
317
|
+
*/
|
|
318
|
+
function generateTopLevelVariableDocumentation(varNode) {
|
|
319
|
+
const paramType = (varNode.getTypeNode() || varNode.getType())?.getText();
|
|
320
|
+
if (!paramType) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const jsDoc = getJsDoc(varNode.getVariableStatement());
|
|
324
|
+
if (!jsDoc) {
|
|
325
|
+
// Only generate documentation for variables that have an existing comment in JSDoc format
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const tags = jsDoc?.getTags() || [];
|
|
329
|
+
if (tags.find((tag) => ["type"].includes(tag.getTagName()))) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const constTag = tags.find((tag) => ["const", "constant"].includes(tag.getTagName()));
|
|
333
|
+
if (constTag && constTag.getComment()?.length) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
jsDoc.addTag({
|
|
337
|
+
tagName: "type",
|
|
338
|
+
text: `{${paramType}}`,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Transpile.
|
|
343
|
+
* @param {string} src Source code to transpile
|
|
344
|
+
* @param {string} [filename="input.ts"] Filename to use internally when transpiling (can be a path or a name)
|
|
345
|
+
* @param {object} [compilerOptions={}] Options for the compiler.
|
|
346
|
+
* See https://www.typescriptlang.org/tsconfig#compilerOptions
|
|
347
|
+
* @param {boolean} [debug=false] Whether to log errors
|
|
348
|
+
* @return {string} Transpiled code (or the original source code if something went wrong)
|
|
349
|
+
*/
|
|
350
|
+
function transpile(src, filename = "input.ts", compilerOptions = {}, debug = false) {
|
|
351
|
+
// Useless variable to prevent comments from getting removed when code contains just
|
|
352
|
+
// typedefs/interfaces, which get transpiled to nothing but comments
|
|
353
|
+
const protectCommentsHeader = "const __tsToJsdoc_protectCommentsHeader = 1;\n";
|
|
354
|
+
src = protectCommentsHeader + src;
|
|
355
|
+
try {
|
|
356
|
+
const project = new Project({
|
|
357
|
+
compilerOptions: {
|
|
358
|
+
target: ScriptTarget.ESNext,
|
|
359
|
+
esModuleInterop: true,
|
|
360
|
+
...compilerOptions,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
// Preserve blank lines in output
|
|
364
|
+
const blankLineMarker = "// TS-TO-JSDOC BLANK LINE //";
|
|
365
|
+
const code = src.split("\n").map((line) => (line.match(/^[\s\t]*$/) ? (blankLineMarker + line) : line)).join("\n");
|
|
366
|
+
// ts-morph throws a fit if the path already exists
|
|
367
|
+
const sourceFile = project.createSourceFile(`${path.basename(filename, ".ts")}.ts-to-jsdoc.ts`, code);
|
|
368
|
+
sourceFile.getClasses().forEach(generateClassDocumentation);
|
|
369
|
+
// Convert something like:
|
|
370
|
+
// import type {
|
|
371
|
+
// ImportDeclaration,
|
|
372
|
+
// ClassDeclaration,
|
|
373
|
+
// FunctionLikeDeclaration,
|
|
374
|
+
// } from 'ts-morph'
|
|
375
|
+
// Into:
|
|
376
|
+
// @typedef {import('ts-morph').ImportDeclaration } ImportDeclaration
|
|
377
|
+
// @typedef {import('ts-morph').ClassDeclaration } ClassDeclaration
|
|
378
|
+
// @typedef {import('ts-morph').FunctionLikeDeclaration} FunctionLikeDeclaration
|
|
379
|
+
const importDeclarations = sourceFile.getImportDeclarations();
|
|
380
|
+
let importTypesToTypedefs = '';
|
|
381
|
+
for (const importDeclaration of importDeclarations) {
|
|
382
|
+
const {importClause, moduleSpecifier} = importDeclaration.compilerNode;
|
|
383
|
+
if (importClause.isTypeOnly) {
|
|
384
|
+
const {elements} = importClause.namedBindings;
|
|
385
|
+
let out = '';
|
|
386
|
+
for (const element of elements) {
|
|
387
|
+
if (element.kind === ts.SyntaxKind.ImportSpecifier) {
|
|
388
|
+
const moduleSpecifierName = moduleSpecifier.text;
|
|
389
|
+
const elementName = element.name.text;
|
|
390
|
+
out += `/** @typedef {import('${moduleSpecifierName}').${elementName}} ${elementName} */\n`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
importTypesToTypedefs += out;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
//console.log("importDeclarations", importDeclarations);
|
|
397
|
+
|
|
398
|
+
const typedefs = sourceFile.getTypeAliases()
|
|
399
|
+
.map((typeAlias) => generateTypedefDocumentation(typeAlias).trim())
|
|
400
|
+
.join("\n");
|
|
401
|
+
const interfaces = sourceFile.getInterfaces()
|
|
402
|
+
.map((interfaceNode) => generateInterfaceDocumentation(interfaceNode).trim())
|
|
403
|
+
.join("\n");
|
|
404
|
+
const directFunctions = sourceFile.getFunctions();
|
|
405
|
+
directFunctions.forEach((node) => generateFunctionDocumentation(node));
|
|
406
|
+
const varDeclarations = sourceFile.getVariableDeclarations();
|
|
407
|
+
varDeclarations.forEach((varDeclaration) => {
|
|
408
|
+
const initializer = varDeclaration.getInitializerIfKind(SyntaxKind.ArrowFunction)
|
|
409
|
+
|| varDeclaration.getInitializerIfKind(SyntaxKind.FunctionExpression);
|
|
410
|
+
if (initializer) {
|
|
411
|
+
generateFunctionDocumentation(initializer, varDeclaration.getVariableStatement());
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
generateTopLevelVariableDocumentation(varDeclaration);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
let result = project
|
|
418
|
+
.emitToMemory()
|
|
419
|
+
?.getFiles()
|
|
420
|
+
?.find((file) => file.filePath.slice(0, -3) === sourceFile.getFilePath().slice(0, -3))
|
|
421
|
+
?.text;
|
|
422
|
+
if (result) {
|
|
423
|
+
//console.log("RESULT", result);
|
|
424
|
+
if (!result.startsWith(protectCommentsHeader)) {
|
|
425
|
+
throw new Error("Internal error: generated header is missing in output.\n\n"
|
|
426
|
+
+ `Output: ${JSON.stringify(`${result.slice(protectCommentsHeader.length + 100)} ...`)}`);
|
|
427
|
+
}
|
|
428
|
+
result = result.replace(protectCommentsHeader, "");
|
|
429
|
+
result = importTypesToTypedefs + result;
|
|
430
|
+
// Restore blank lines in output
|
|
431
|
+
result = result.split("\n").map((_line) => {
|
|
432
|
+
const line = _line.trim();
|
|
433
|
+
return line.startsWith(blankLineMarker)
|
|
434
|
+
? line.slice(blankLineMarker.length)
|
|
435
|
+
: _line;
|
|
436
|
+
}).join("\n").trim();
|
|
437
|
+
if (typedefs)
|
|
438
|
+
result += `\n\n${typedefs}`;
|
|
439
|
+
if (interfaces)
|
|
440
|
+
result += `\n\n${interfaces}`;
|
|
441
|
+
result = `${result.trim()}\n`;
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
throw new Error("Could not emit output to memory.");
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
debug && console.error(e);
|
|
448
|
+
return src;
|
|
449
|
+
}
|
|
450
|
+
return src;
|
|
451
|
+
}
|
|
452
|
+
export {transpile};
|
|
453
|
+
/**
|
|
454
|
+
* @typedef {JSDocableNode & TypedNode & (
|
|
455
|
+
* | PropertyDeclaration
|
|
456
|
+
* | PropertyAssignment
|
|
457
|
+
* | PropertySignature
|
|
458
|
+
* )} ObjectProperty
|
|
459
|
+
*/
|
|
460
|
+
/** @typedef {JSDocableNode & ModifierableNode & ObjectProperty & MethodDeclaration} ClassMemberNode */
|