@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.
- package/lib/components/ApiItem.js +97 -2
- package/lib/components/ApiItem.js.map +1 -1
- package/lib/components/ApiPage.js +0 -3
- package/lib/components/ApiPage.js.map +1 -1
- package/lib/components/Markdown.js +0 -2
- package/lib/components/Markdown.js.map +1 -1
- package/lib/components/MemberGetterSetter.js +0 -1
- package/lib/components/MemberGetterSetter.js.map +1 -1
- package/lib/components/MemberSignatureBody.js +2 -7
- package/lib/components/MemberSignatureBody.js.map +1 -1
- package/lib/components/Reflection.js +0 -1
- package/lib/components/Reflection.js.map +1 -1
- package/lib/components/SourceLink.js +5 -1
- package/lib/components/SourceLink.js.map +1 -1
- package/lib/components/Type.js +2 -3
- package/lib/components/Type.js.map +1 -1
- package/lib/index.js +23 -10
- package/lib/index.js.map +1 -1
- package/lib/plugin/data.js +1 -9
- package/lib/plugin/data.js.map +1 -1
- package/lib/plugin/python/consts.js +47 -0
- package/lib/plugin/python/consts.js.map +1 -0
- package/lib/plugin/python/index.js +36 -0
- package/lib/plugin/python/index.js.map +1 -0
- package/lib/plugin/python/inheritance.js +71 -0
- package/lib/plugin/python/inheritance.js.map +1 -0
- package/lib/plugin/python/packageVersions.js +46 -0
- package/lib/plugin/python/packageVersions.js.map +1 -0
- package/lib/plugin/python/transformation.js +359 -0
- package/lib/plugin/python/transformation.js.map +1 -0
- package/lib/plugin/python/type-parsing/index.js +79 -0
- package/lib/plugin/python/type-parsing/index.js.map +1 -0
- package/lib/plugin/python/types.js +2 -0
- package/lib/plugin/python/types.js.map +1 -0
- package/lib/plugin/python/utils.js +106 -0
- package/lib/plugin/python/utils.js.map +1 -0
- package/lib/plugin/structure/0.23.js +0 -2
- package/lib/plugin/structure/0.23.js.map +1 -1
- package/lib/utils/icons.js +1 -2
- package/lib/utils/icons.js.map +1 -1
- package/lib/utils/reexports.js +96 -0
- package/lib/utils/reexports.js.map +1 -0
- package/package.json +5 -3
- package/src/components/ApiItem.tsx +103 -9
- package/src/components/ApiItemLayout.tsx +4 -2
- package/src/components/ApiOptionsLayout.tsx +18 -16
- package/src/components/ApiPage.tsx +0 -2
- package/src/components/DefaultValue.tsx +0 -1
- package/src/components/Flags.tsx +1 -1
- package/src/components/Markdown.tsx +0 -1
- package/src/components/Member.tsx +19 -17
- package/src/components/MemberGetterSetter.tsx +0 -1
- package/src/components/MemberSignatureBody.tsx +42 -38
- package/src/components/MemberSignatureTitle.tsx +18 -15
- package/src/components/Reflection.tsx +1 -1
- package/src/components/SourceLink.tsx +6 -8
- package/src/components/Type.tsx +6 -14
- package/src/components/VersionBanner.tsx +5 -1
- package/src/index.ts +39 -9
- package/src/plugin/data.ts +6 -12
- package/src/plugin/python/consts.ts +50 -0
- package/src/plugin/python/docspec-gen/__init__.py +0 -0
- package/src/plugin/python/docspec-gen/generate_ast.py +73 -0
- package/src/plugin/python/docspec-gen/google_docstring_processor.py +185 -0
- package/src/plugin/python/index.ts +47 -0
- package/src/plugin/python/inheritance.ts +80 -0
- package/src/plugin/python/packageVersions.ts +43 -0
- package/src/plugin/python/transformation.ts +444 -0
- package/src/plugin/python/type-parsing/index.ts +88 -0
- package/src/plugin/python/type-parsing/parse_types.py +82 -0
- package/src/plugin/python/types.ts +83 -0
- package/src/plugin/python/utils.ts +123 -0
- package/src/plugin/structure/0.23.ts +0 -2
- package/src/plugin/version.ts +2 -2
- package/src/types.ts +9 -0
- package/src/utils/icons.ts +4 -3
- 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
|
+
}
|