@apify/docusaurus-plugin-typedoc-api 4.2.10 → 4.2.11-1

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.
Files changed (77) hide show
  1. package/lib/components/ApiItem.js +97 -2
  2. package/lib/components/ApiItem.js.map +1 -1
  3. package/lib/components/ApiPage.js +0 -3
  4. package/lib/components/ApiPage.js.map +1 -1
  5. package/lib/components/Markdown.js +0 -2
  6. package/lib/components/Markdown.js.map +1 -1
  7. package/lib/components/MemberGetterSetter.js +0 -1
  8. package/lib/components/MemberGetterSetter.js.map +1 -1
  9. package/lib/components/MemberSignatureBody.js +2 -7
  10. package/lib/components/MemberSignatureBody.js.map +1 -1
  11. package/lib/components/Reflection.js +0 -1
  12. package/lib/components/Reflection.js.map +1 -1
  13. package/lib/components/SourceLink.js +5 -1
  14. package/lib/components/SourceLink.js.map +1 -1
  15. package/lib/components/Type.js +2 -3
  16. package/lib/components/Type.js.map +1 -1
  17. package/lib/index.js +23 -10
  18. package/lib/index.js.map +1 -1
  19. package/lib/plugin/data.js +1 -9
  20. package/lib/plugin/data.js.map +1 -1
  21. package/lib/plugin/python/consts.js +47 -0
  22. package/lib/plugin/python/consts.js.map +1 -0
  23. package/lib/plugin/python/index.js +36 -0
  24. package/lib/plugin/python/index.js.map +1 -0
  25. package/lib/plugin/python/inheritance.js +71 -0
  26. package/lib/plugin/python/inheritance.js.map +1 -0
  27. package/lib/plugin/python/packageVersions.js +46 -0
  28. package/lib/plugin/python/packageVersions.js.map +1 -0
  29. package/lib/plugin/python/transformation.js +359 -0
  30. package/lib/plugin/python/transformation.js.map +1 -0
  31. package/lib/plugin/python/type-parsing/index.js +79 -0
  32. package/lib/plugin/python/type-parsing/index.js.map +1 -0
  33. package/lib/plugin/python/types.js +2 -0
  34. package/lib/plugin/python/types.js.map +1 -0
  35. package/lib/plugin/python/utils.js +106 -0
  36. package/lib/plugin/python/utils.js.map +1 -0
  37. package/lib/plugin/structure/0.23.js +0 -2
  38. package/lib/plugin/structure/0.23.js.map +1 -1
  39. package/lib/utils/icons.js +1 -2
  40. package/lib/utils/icons.js.map +1 -1
  41. package/lib/utils/reexports.js +96 -0
  42. package/lib/utils/reexports.js.map +1 -0
  43. package/package.json +5 -3
  44. package/src/components/ApiItem.tsx +103 -9
  45. package/src/components/ApiItemLayout.tsx +4 -2
  46. package/src/components/ApiOptionsLayout.tsx +18 -16
  47. package/src/components/ApiPage.tsx +0 -2
  48. package/src/components/DefaultValue.tsx +0 -1
  49. package/src/components/Flags.tsx +1 -1
  50. package/src/components/Markdown.tsx +0 -1
  51. package/src/components/Member.tsx +19 -17
  52. package/src/components/MemberGetterSetter.tsx +0 -1
  53. package/src/components/MemberSignatureBody.tsx +42 -38
  54. package/src/components/MemberSignatureTitle.tsx +18 -15
  55. package/src/components/Reflection.tsx +1 -1
  56. package/src/components/SourceLink.tsx +6 -8
  57. package/src/components/Type.tsx +6 -14
  58. package/src/components/VersionBanner.tsx +5 -1
  59. package/src/index.ts +39 -9
  60. package/src/plugin/data.ts +6 -12
  61. package/src/plugin/python/consts.ts +50 -0
  62. package/src/plugin/python/docspec-gen/__init__.py +0 -0
  63. package/src/plugin/python/docspec-gen/generate_ast.py +73 -0
  64. package/src/plugin/python/docspec-gen/google_docstring_processor.py +185 -0
  65. package/src/plugin/python/index.ts +47 -0
  66. package/src/plugin/python/inheritance.ts +80 -0
  67. package/src/plugin/python/packageVersions.ts +43 -0
  68. package/src/plugin/python/transformation.ts +444 -0
  69. package/src/plugin/python/type-parsing/index.ts +88 -0
  70. package/src/plugin/python/type-parsing/parse_types.py +82 -0
  71. package/src/plugin/python/types.ts +83 -0
  72. package/src/plugin/python/utils.ts +123 -0
  73. package/src/plugin/structure/0.23.ts +0 -2
  74. package/src/plugin/version.ts +2 -2
  75. package/src/types.ts +9 -0
  76. package/src/utils/icons.ts +4 -3
  77. package/src/utils/reexports.ts +105 -0
@@ -0,0 +1,80 @@
1
+ import type { TypeDocObject } from './types';
2
+ import { getGroupName, getOID } from './utils';
3
+
4
+ /**
5
+ * Given an ancestor and a descendant objects, injects the children of the ancestor into the descendant.
6
+ *
7
+ * Sets the `extendedTypes` / `extendedBy` properties.
8
+ * @param ancestor
9
+ * @param descendant
10
+ */
11
+ export function resolveInheritedSymbols(ancestor: TypeDocObject, descendant: TypeDocObject) {
12
+ descendant.children ??= [];
13
+
14
+ descendant.extendedTypes = [
15
+ ...(descendant.extendedTypes ?? []),
16
+ {
17
+ name: ancestor.name,
18
+ target: ancestor.id,
19
+ type: 'reference',
20
+ },
21
+ ];
22
+
23
+ ancestor.extendedBy = [
24
+ ...(ancestor.extendedBy ?? []),
25
+ {
26
+ name: descendant.name,
27
+ target: descendant.id,
28
+ type: 'reference',
29
+ },
30
+ ];
31
+
32
+ for (const inheritedChild of ancestor.children ?? []) {
33
+ const ownChild = descendant.children?.find((x) => x.name === inheritedChild.name);
34
+
35
+ if (!ownChild) {
36
+ const childId = getOID();
37
+
38
+ const { groupName } = getGroupName(inheritedChild);
39
+ if (!groupName) {
40
+ throw new Error(
41
+ `Couldn't resolve the group name for ${inheritedChild.name} (inherited child of ${ancestor.name})`,
42
+ );
43
+ }
44
+
45
+ const group = descendant.groups?.find((g) => g.title === groupName);
46
+
47
+ if (group) {
48
+ group.children.push(inheritedChild.id);
49
+ } else {
50
+ descendant.groups?.push({
51
+ children: [inheritedChild.id],
52
+ title: groupName,
53
+ });
54
+ }
55
+
56
+ descendant.children.push({
57
+ ...inheritedChild,
58
+ id: childId,
59
+ inheritedFrom: {
60
+ name: `${ancestor.name}.${inheritedChild.name}`,
61
+ target: inheritedChild.id,
62
+ type: 'reference',
63
+ },
64
+ });
65
+ } else if (!ownChild.comment?.summary?.[0]?.text) {
66
+ ownChild.inheritedFrom = {
67
+ name: `${ancestor.name}.${inheritedChild.name}`,
68
+ target: inheritedChild.id,
69
+ type: 'reference',
70
+ };
71
+
72
+ for (const key of Object.keys(inheritedChild)) {
73
+ if (key !== 'id' && key !== 'inheritedFrom') {
74
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
75
+ ownChild[key as keyof typeof ownChild] = inheritedChild[key as keyof typeof inheritedChild];
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,43 @@
1
+ import childProcess from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Looks for the installed versions of the given packages and returns them as a dictionary.
7
+ */
8
+ export function getPackageGitHubTags(packageNames: string[]): Record<string, string> {
9
+ // For each package, get the installed version, and set the tag to the corresponding version
10
+ const packageTags: Record<string, string> = {};
11
+
12
+ for (const pkg of packageNames) {
13
+ const spawnResult = childProcess.spawnSync('python', [
14
+ '-c',
15
+ `import ${pkg}; print(${pkg}.__version__)`,
16
+ ]);
17
+ if (spawnResult.status === 0) {
18
+ packageTags[pkg] = `v${spawnResult.stdout.toString().trim()}`;
19
+ }
20
+ }
21
+
22
+ return packageTags;
23
+ }
24
+
25
+ export function findNearestInParent(currentPath: string, filename: string) {
26
+ let parentPath = currentPath;
27
+ while (parentPath !== '/') {
28
+ parentPath = path.dirname(parentPath);
29
+ if (fs.existsSync(path.join(parentPath, filename))) {
30
+ return path.join(parentPath, filename);
31
+ }
32
+ }
33
+
34
+ throw new Error(`No ${filename} found in any parent directory`);
35
+ }
36
+
37
+ export function getCurrentPackageName(pyprojectTomlPath?: string) {
38
+ const currentPath = path.dirname(__dirname);
39
+ pyprojectTomlPath ??= findNearestInParent(currentPath, 'pyproject.toml');
40
+ const pyprojectToml = fs.readFileSync(pyprojectTomlPath, 'utf8');
41
+
42
+ return pyprojectToml.match(/^name = "(.+)"$/m)?.[1];
43
+ }
@@ -0,0 +1,444 @@
1
+ import { REPO_ROOT_PLACEHOLDER, TYPEDOC_KINDS } from './consts';
2
+ import { resolveInheritedSymbols } from './inheritance';
3
+ import { PythonTypeResolver } from './type-parsing';
4
+ import type {
5
+ DocspecDocstring,
6
+ DocspecObject,
7
+ TypeDocDocstring,
8
+ TypeDocObject,
9
+ TypeDocType,
10
+ } from './types';
11
+ import { getGroupName, getOID, groupSort, isHidden, projectUsesDocsGroupDecorator } from './utils';
12
+
13
+ interface TransformObjectOptions {
14
+ /**
15
+ * The current docspec (`pydoc-markdown`) object to transform.
16
+ */
17
+ currentDocspecNode: DocspecObject;
18
+ /**
19
+ * The already (partially) transformed parent Typedoc object.
20
+ */
21
+ parentTypeDoc: TypeDocObject;
22
+ /**
23
+ * The full name of the module the current object is in.
24
+ */
25
+ moduleName: string;
26
+ }
27
+
28
+ interface DocspecTransformerOptions {
29
+ /**
30
+ * A map of module shortcuts, where the key is the full name of the module, and the value is the shortened name.
31
+ */
32
+ moduleShortcuts?: Record<string, string>;
33
+ }
34
+
35
+ export class DocspecTransformer {
36
+ private pythonTypeResolver: PythonTypeResolver;
37
+
38
+ private symbolIdMap: Record<number, { qualifiedName: string; sourceFileName: string }> = {};
39
+
40
+ private namesToIds: Record<string, number> = {};
41
+
42
+ private moduleShortcuts: Record<string, string>;
43
+
44
+ /**
45
+ * Maps the name of the class to the list of Typedoc objects representing the classes that extend it.
46
+ *
47
+ * This is used for resolving the references to the base classes - in case the base class is encountered after the class that extends it.
48
+ */
49
+ private forwardAncestorRefs = new Map<string, TypeDocObject[]>();
50
+
51
+ /**
52
+ * Maps the name of the class to the reference to the Typedoc object representing the class.
53
+ *
54
+ * This is used to resolve the references to the base classes of a class using the name.
55
+ */
56
+ private backwardAncestorRefs = new Map<string, TypeDocObject>();
57
+
58
+ /**
59
+ * Stack of the docstrings of the current context.
60
+ *
61
+ * Used to read the class Google-style docstrings from the class' properties and methods.
62
+ */
63
+ private contextStack: TypeDocDocstring[] = [];
64
+
65
+ private settings: { useDocsGroup: boolean } = { useDocsGroup: false };
66
+
67
+ constructor({ moduleShortcuts }: DocspecTransformerOptions) {
68
+ this.pythonTypeResolver = new PythonTypeResolver();
69
+ this.moduleShortcuts = moduleShortcuts ?? {};
70
+ }
71
+
72
+ transform(docspecModules: DocspecObject[]): TypeDocObject {
73
+ // Root object of the Typedoc structure, accumulator for the recursive walk
74
+ const typedocApiReference: TypeDocObject = {
75
+ children: [],
76
+ flags: {},
77
+ groups: [],
78
+ id: 0,
79
+ kind: 1,
80
+ kindString: 'Project',
81
+ name: 'apify-client',
82
+ sources: [
83
+ {
84
+ character: 0,
85
+ fileName: 'src/index.ts',
86
+ line: 1,
87
+ },
88
+ ],
89
+ symbolIdMap: this.symbolIdMap,
90
+ };
91
+
92
+ this.settings.useDocsGroup = projectUsesDocsGroupDecorator(
93
+ docspecModules as unknown as { name: string },
94
+ );
95
+
96
+ // Convert all the modules, store them in the root object
97
+ for (const module of docspecModules) {
98
+ this.walkAndTransform({
99
+ currentDocspecNode: module,
100
+ moduleName: module.name,
101
+ parentTypeDoc: typedocApiReference,
102
+ });
103
+ }
104
+
105
+ this.pythonTypeResolver.resolveTypes();
106
+
107
+ this.namesToIds = Object.entries(this.symbolIdMap).reduce<Record<string, number>>(
108
+ (acc, [id, { qualifiedName }]) => {
109
+ acc[qualifiedName] = Number(id);
110
+ return acc;
111
+ },
112
+ {},
113
+ );
114
+
115
+ this.fixRefs(typedocApiReference);
116
+ this.sortChildren(typedocApiReference);
117
+
118
+ return typedocApiReference;
119
+ }
120
+
121
+ private getContext() {
122
+ return this.contextStack[this.contextStack.length - 1];
123
+ }
124
+
125
+ private popContext() {
126
+ this.contextStack.pop();
127
+ }
128
+
129
+ private newContext(context: TypeDocDocstring) {
130
+ this.contextStack.push(context);
131
+ }
132
+
133
+ /**
134
+ * Recursively traverse the Typedoc structure and fix the references to the named entities.
135
+ *
136
+ * Searches for the {@link TypeDocType} structure with the `type` property set to `reference`, and replaces the `target` property
137
+ * with the corresponding ID of the named entity.
138
+ */
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ private fixRefs(obj: Record<string, any>) {
141
+ for (const key of Object.keys(obj)) {
142
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
143
+ if (key === 'name' && obj?.type === 'reference' && this.namesToIds[obj?.name]) {
144
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
145
+ obj.target = this.namesToIds[obj?.name];
146
+ }
147
+ if (typeof obj[key] === 'object' && obj[key] !== null) {
148
+ this.fixRefs(obj[key] as TypeDocObject);
149
+ }
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Given a docspec object outputted by `pydoc-markdown`, transforms this object into the Typedoc structure,
155
+ * and appends it as a child of the `parentTypeDoc` Typedoc object (which serves as an accumulator for the recursion).
156
+ * @param obj
157
+ * @param parent
158
+ * @param module
159
+ */
160
+ private walkAndTransform({
161
+ currentDocspecNode,
162
+ parentTypeDoc,
163
+ moduleName,
164
+ }: TransformObjectOptions) {
165
+ if (isHidden(currentDocspecNode)) {
166
+ for (const docspecMember of currentDocspecNode.members ?? []) {
167
+ this.walkAndTransform({
168
+ currentDocspecNode: docspecMember,
169
+ moduleName,
170
+ // Skips the hidden member, i.e. its children will be appended to the parent of the hidden member
171
+ parentTypeDoc,
172
+ });
173
+ }
174
+
175
+ return;
176
+ }
177
+
178
+ const { typedocType, typedocKind } = this.getTypedocType(currentDocspecNode, parentTypeDoc);
179
+ const { filePathInRepo } = this.getGitHubUrls(currentDocspecNode);
180
+
181
+ const docstring = this.parseDocstring(currentDocspecNode);
182
+ const currentId = getOID();
183
+
184
+ this.symbolIdMap[currentId] = {
185
+ qualifiedName: currentDocspecNode.name,
186
+ sourceFileName: filePathInRepo,
187
+ };
188
+
189
+ // Get the module name of the member, and check if it has a shortcut (reexport from an ancestor module)
190
+ const fullName = `${moduleName}.${currentDocspecNode.name}`;
191
+ if (fullName in this.moduleShortcuts) {
192
+ moduleName = this.moduleShortcuts[fullName].replace(`.${currentDocspecNode.name}`, '');
193
+ }
194
+
195
+ currentDocspecNode.name =
196
+ currentDocspecNode.decorations?.find((d) => d.name === 'docs_name')?.args.slice(2, -2) ??
197
+ currentDocspecNode.name;
198
+
199
+ // Create the Typedoc member object
200
+ const currentTypedocNode: TypeDocObject = {
201
+ ...typedocKind,
202
+ children: [],
203
+ comment: docstring
204
+ ? {
205
+ summary: [
206
+ {
207
+ kind: 'text',
208
+ text: docstring.text,
209
+ },
210
+ ],
211
+ }
212
+ : undefined,
213
+ decorations: currentDocspecNode.decorations?.map(({ name, args }) => ({ args, name })),
214
+ flags: {},
215
+ groups: [],
216
+ id: currentId,
217
+ module: moduleName, // This is an extension to the original Typedoc structure, to support showing where the member is exported from
218
+ name: currentDocspecNode.name,
219
+ sources: [
220
+ {
221
+ character: 1,
222
+ fileName: filePathInRepo,
223
+ line: currentDocspecNode.location.lineno,
224
+ },
225
+ ],
226
+ type: typedocType,
227
+ };
228
+
229
+ if (currentTypedocNode.kindString === 'Method') {
230
+ currentTypedocNode.signatures = [
231
+ {
232
+ comment: docstring.text
233
+ ? {
234
+ blockTags: docstring?.returns
235
+ ? [{ content: [{ kind: 'text', text: docstring.returns }], tag: '@returns' }]
236
+ : undefined,
237
+ summary: [
238
+ {
239
+ kind: 'text',
240
+ text: docstring?.text,
241
+ },
242
+ ],
243
+ }
244
+ : undefined,
245
+ flags: {},
246
+ id: getOID(),
247
+ kind: 4096,
248
+ kindString: 'Call signature',
249
+ modifiers: currentDocspecNode.modifiers ?? [],
250
+ name: currentDocspecNode.name,
251
+ parameters: currentDocspecNode.args
252
+ ?.filter((arg) => arg.name !== 'self' && arg.name !== 'cls')
253
+ .map((arg) => ({
254
+ comment: docstring.args?.[arg.name]
255
+ ? {
256
+ summary: [
257
+ {
258
+ kind: 'text',
259
+ text: docstring.args[arg.name],
260
+ },
261
+ ],
262
+ }
263
+ : undefined,
264
+ defaultValue: arg.default_value as string,
265
+ flags: {
266
+ isOptional: arg.datatype?.includes('Optional'),
267
+ 'keyword-only': arg.type === 'KEYWORD_ONLY',
268
+ },
269
+ id: getOID(),
270
+ kind: 32_768,
271
+ kindString: 'Parameter',
272
+ name: arg.name,
273
+ type: this.pythonTypeResolver.registerType(arg.datatype),
274
+ })),
275
+ type: this.pythonTypeResolver.registerType(currentDocspecNode.return_type),
276
+ },
277
+ ];
278
+ }
279
+
280
+ if (currentTypedocNode.kindString === 'Class') {
281
+ this.newContext(docstring);
282
+ }
283
+
284
+ for (const docspecMember of currentDocspecNode.members ?? []) {
285
+ this.walkAndTransform({
286
+ currentDocspecNode: docspecMember,
287
+ moduleName,
288
+ parentTypeDoc: currentTypedocNode,
289
+ });
290
+ }
291
+
292
+ if (currentTypedocNode.kindString === 'Class') {
293
+ this.popContext();
294
+
295
+ if (currentDocspecNode.bases && currentDocspecNode.bases.length > 0) {
296
+ for (const base of currentDocspecNode.bases) {
297
+ const canonicalAncestorType = this.pythonTypeResolver.getBaseType(base);
298
+
299
+ const baseTypedocMember = this.backwardAncestorRefs.get(canonicalAncestorType);
300
+ if (baseTypedocMember) {
301
+ resolveInheritedSymbols(baseTypedocMember, currentTypedocNode);
302
+ } else {
303
+ this.forwardAncestorRefs.set(canonicalAncestorType, [
304
+ ...(this.forwardAncestorRefs.get(canonicalAncestorType) ?? []),
305
+ currentTypedocNode,
306
+ ]);
307
+ }
308
+ }
309
+ }
310
+
311
+ this.backwardAncestorRefs.set(currentDocspecNode.name, currentTypedocNode);
312
+ }
313
+
314
+ const { groupName, source: groupSource } = getGroupName(currentTypedocNode);
315
+
316
+ if (
317
+ groupName && // If the group comes from a decorator, use it always; otherwise check if the symbol isn't top-level
318
+ (!this.settings.useDocsGroup ||
319
+ groupSource === 'decorator' ||
320
+ parentTypeDoc.kindString !== 'Project')
321
+ ) {
322
+ const group = parentTypeDoc.groups?.find((g) => g.title === groupName);
323
+ if (group) {
324
+ group.children.push(currentTypedocNode.id);
325
+ } else {
326
+ parentTypeDoc.groups?.push({
327
+ children: [currentTypedocNode.id],
328
+ title: groupName,
329
+ });
330
+ }
331
+ }
332
+
333
+ parentTypeDoc.children?.push(currentTypedocNode);
334
+
335
+ this.sortChildren(currentTypedocNode);
336
+
337
+ if (currentTypedocNode.kindString === 'Class') {
338
+ for (const descendant of this.forwardAncestorRefs.get(currentTypedocNode.name) ?? []) {
339
+ resolveInheritedSymbols(currentTypedocNode, descendant);
340
+
341
+ this.sortChildren(descendant);
342
+ }
343
+ }
344
+ }
345
+
346
+ // Get the URL of the member in GitHub
347
+ private getGitHubUrls(docspecMember: DocspecObject): { filePathInRepo: string } {
348
+ const filePathInRepo = docspecMember.location.filename.replace(REPO_ROOT_PLACEHOLDER, '');
349
+
350
+ return { filePathInRepo };
351
+ }
352
+
353
+ /**
354
+ * Sorts the `groups` of `typedocMember` using {@link groupSort} and sorts the children of each group alphabetically.
355
+ */
356
+ private sortChildren(typedocMember: TypeDocObject) {
357
+ if (!typedocMember.groups) return;
358
+
359
+ for (const group of typedocMember.groups) {
360
+ group.children.sort((a, b) => {
361
+ const firstName =
362
+ typedocMember.children?.find((x) => x.id === a || x.inheritedFrom?.target === a)?.name ??
363
+ 'a';
364
+ const secondName =
365
+ typedocMember.children?.find((x) => x.id === b || x.inheritedFrom?.target === b)?.name ??
366
+ 'b';
367
+ return firstName.localeCompare(secondName);
368
+ });
369
+ }
370
+ typedocMember.groups?.sort((a, b) => groupSort(a.title, b.title));
371
+ }
372
+
373
+ /**
374
+ * If possible, parses the `.docstring` property of the passed object. If the docstring is a stringified JSON object,
375
+ * it extracts the `args` and `returns` sections and adds them to the returned object.
376
+ *
377
+ * TODO
378
+ * This structure is created in the `google` docstring format, which is a JSON object with the following structure:
379
+ */
380
+ private parseDocstring(docspecMember: DocspecObject): TypeDocDocstring {
381
+ const docstring: TypeDocDocstring = { text: docspecMember.docstring?.content ?? '' };
382
+
383
+ try {
384
+ const parsedDocstring = JSON.parse(docstring.text) as DocspecDocstring;
385
+
386
+ docstring.text = parsedDocstring.text;
387
+ const parsedArguments = (parsedDocstring.sections?.find(
388
+ (section) => Object.keys(section)[0] === 'Arguments',
389
+ )?.Arguments ?? []) as DocspecDocstring['args'];
390
+
391
+ docstring.args =
392
+ parsedArguments?.reduce<Record<string, string>>((acc, arg) => {
393
+ acc[arg.param] = arg.desc;
394
+ return acc;
395
+ }, {}) ?? {};
396
+
397
+ const returnTypes =
398
+ docstring.sections?.find((section) => Object.keys(section)[0] === 'Returns')?.Returns ?? [];
399
+
400
+ docstring.returns = returnTypes.join('\n');
401
+ } catch {
402
+ // Do nothing
403
+ }
404
+
405
+ if (!docstring.text) {
406
+ docstring.text = this.getContext()?.args?.[docspecMember.name] ?? '';
407
+ }
408
+
409
+ return docstring;
410
+ }
411
+
412
+ /**
413
+ * Given the current Docspec object and the parent Typedoc object, returns the Typedoc type and kind of the current object.
414
+ */
415
+ private getTypedocType(
416
+ docspecMember: DocspecObject,
417
+ parentTypeDoc: TypeDocObject,
418
+ ): { typedocType: TypeDocType; typedocKind: (typeof TYPEDOC_KINDS)[keyof typeof TYPEDOC_KINDS] } {
419
+ let typedocKind = TYPEDOC_KINDS[docspecMember.type];
420
+
421
+ if (docspecMember.bases?.includes('Enum')) {
422
+ typedocKind = TYPEDOC_KINDS.enum;
423
+ }
424
+
425
+ let typedocType = this.pythonTypeResolver.registerType(docspecMember.datatype);
426
+
427
+ if (docspecMember.decorations?.some((d) => ['property', 'dualproperty'].includes(d.name))) {
428
+ typedocKind = TYPEDOC_KINDS.data;
429
+ typedocType = this.pythonTypeResolver.registerType(
430
+ docspecMember.return_type ?? docspecMember.datatype,
431
+ );
432
+ }
433
+
434
+ if (parentTypeDoc.kindString === 'Enumeration') {
435
+ typedocKind = TYPEDOC_KINDS.enumValue;
436
+ typedocType = {
437
+ type: 'literal',
438
+ value: docspecMember.value as string,
439
+ };
440
+ }
441
+
442
+ return { typedocKind, typedocType };
443
+ }
444
+ }
@@ -0,0 +1,88 @@
1
+ import childProcess from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import type { DocspecType, TypeDocType } from '../types';
5
+
6
+ const RAW_TYPES_JSON_FILEPATH = path.join(__dirname, 'typedoc-types.raw');
7
+ const PARSED_TYPES_JSON_FILEPATH = path.join(__dirname, 'typedoc-types-parsed.json');
8
+
9
+ const PYTHON_SCRIPT_FILEPATH = path.join(__dirname, 'parse_types.py');
10
+
11
+ /**
12
+ * Keeps track of Typedoc type objects. When `resolveTypes` is called, it tries to parse
13
+ * the Python types using Python's `ast` module.
14
+ *
15
+ * The parsed types are then applied to the original registered Typedoc type objects.
16
+ */
17
+ export class PythonTypeResolver {
18
+ private typedocTypes: TypeDocType[] = [];
19
+
20
+ /**
21
+ * Register a new Python type to be resolved.
22
+ *
23
+ * Given a string representation of the type, returns a Typedoc type object.
24
+ */
25
+ registerType(docspecType?: DocspecType): TypeDocType {
26
+ const newType: TypeDocType = {
27
+ name: docspecType?.replaceAll(/#.*/g, '').replaceAll('\n', '').trim() ?? 'Undefined',
28
+ type: 'reference',
29
+ };
30
+
31
+ this.typedocTypes.push(newType);
32
+ return newType;
33
+ }
34
+
35
+ /**
36
+ * Parse the registered Python types using Python's ast module.
37
+ * For the actual Python implementation, see `parse_types.py`.
38
+ *
39
+ * Modifies the objects registered with `registerType` in-place.
40
+ *
41
+ * @param typedocTypes The "opaque" Python types to parse.
42
+ * @returns {void} Nothing. The registered types are mutated in-place.
43
+ */
44
+ resolveTypes() {
45
+ fs.writeFileSync(
46
+ RAW_TYPES_JSON_FILEPATH,
47
+ JSON.stringify(
48
+ this.typedocTypes
49
+ .map((x) => {
50
+ if (x.type === 'reference') {
51
+ return x.name;
52
+ }
53
+
54
+ return null;
55
+ })
56
+ .filter(Boolean),
57
+ ),
58
+ );
59
+
60
+ childProcess.spawnSync('python', [PYTHON_SCRIPT_FILEPATH, RAW_TYPES_JSON_FILEPATH]);
61
+
62
+ const parsedTypes = JSON.parse(fs.readFileSync(PARSED_TYPES_JSON_FILEPATH, 'utf8')) as Record<
63
+ string,
64
+ TypeDocType
65
+ >;
66
+
67
+ for (const originalType of this.typedocTypes) {
68
+ if (originalType.type === 'reference') {
69
+ const parsedType = parsedTypes[originalType.name];
70
+
71
+ if (parsedType) {
72
+ for (const key of Object.keys(parsedType)) {
73
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
74
+ originalType[key] = parsedType[key as keyof TypeDocType];
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Strips the Optional[] type from the type string,
83
+ * and replaces generic types with just the main type.
84
+ */
85
+ getBaseType(type: string): string {
86
+ return type?.replace(/Optional\[(.*)]/g, '$1').split('[')[0];
87
+ }
88
+ }