cargo-json-docs 0.5.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/LICENCE +21 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -0
- package/dist/item.d.ts +100 -0
- package/dist/item.d.ts.map +1 -0
- package/dist/item.js +589 -0
- package/dist/item.js.map +1 -0
- package/dist/json.d.ts +81 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +226 -0
- package/dist/json.js.map +1 -0
- package/dist/lib.d.ts +21 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +36 -0
- package/dist/lib.js.map +1 -0
- package/package.json +26 -0
- package/readme.md +95 -0
- package/src/errors.ts +31 -0
- package/src/item.ts +658 -0
- package/src/json.ts +274 -0
- package/src/lib.ts +30 -0
- package/src/types.d.ts +84 -0
- package/test/item.test.d.ts +2 -0
- package/test/item.test.d.ts.map +1 -0
- package/test/item.test.js +107 -0
- package/test/item.test.js.map +1 -0
- package/test/item.test.ts +128 -0
- package/test/json.test.d.ts +2 -0
- package/test/json.test.d.ts.map +1 -0
- package/test/json.test.js +24 -0
- package/test/json.test.js.map +1 -0
- package/test/json.test.ts +29 -0
- package/test/test_crate/Cargo.lock +74 -0
- package/test/test_crate/Cargo.toml +7 -0
- package/test/test_crate/src/lib.rs +50 -0
- package/test/test_crate/src/module_a.rs +16 -0
- package/test/test_crate/src/module_b/inner_b.rs +10 -0
- package/test/test_crate/src/module_b.rs +1 -0
- package/tsconfig.json +23 -0
package/src/item.ts
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { ResolutionError, CargoFormatError } from "./errors";
|
|
2
|
+
import type {
|
|
3
|
+
ModuleProps, StructProps, EnumProps, FunctionProps, TraitProps, UseProps,
|
|
4
|
+
IndexEntry, CrateEntry, PathEntry,
|
|
5
|
+
DocumentedItem, ItemKind,
|
|
6
|
+
} from "./types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A node in the cargo item tree.
|
|
10
|
+
* Used for resolving item paths.
|
|
11
|
+
*/
|
|
12
|
+
class CargoItemNode {
|
|
13
|
+
id: number;
|
|
14
|
+
name: string;
|
|
15
|
+
kind: ItemKind;
|
|
16
|
+
parentKind: ItemKind;
|
|
17
|
+
children: Map<string, CargoItemNode>;
|
|
18
|
+
docs_md: string | null;
|
|
19
|
+
|
|
20
|
+
/** The full path to this node, populated during tree walks. */
|
|
21
|
+
public path: string;
|
|
22
|
+
|
|
23
|
+
/** Whether the children of this node have been populated. */
|
|
24
|
+
public childrenPopulated: boolean = false;
|
|
25
|
+
|
|
26
|
+
/** If true, start the path over from the given crate */
|
|
27
|
+
public shouldJumpToCrate: string | null = null;
|
|
28
|
+
|
|
29
|
+
/** Whether to suppress presence in urls */
|
|
30
|
+
public silent: boolean = false;
|
|
31
|
+
|
|
32
|
+
/** Creates a new CargoItemNode. */
|
|
33
|
+
constructor(id: number, name: string, kind: ItemKind, parentKind: ItemKind, path: string, docs_md: string | null = null) {
|
|
34
|
+
this.id = id;
|
|
35
|
+
this.name = name;
|
|
36
|
+
this.kind = kind;
|
|
37
|
+
this.parentKind = parentKind;
|
|
38
|
+
this.children = new Map<string, CargoItemNode>();
|
|
39
|
+
this.docs_md = docs_md;
|
|
40
|
+
this.path = path;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Adds a child node. */
|
|
44
|
+
addChild(child: CargoItemNode): void {
|
|
45
|
+
if (child.name === undefined || child.name === null || child.name.length === 0) {
|
|
46
|
+
throw new Error(`Cannot add child with empty name to node ${this.name}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.children.set(child.name, child);
|
|
50
|
+
this.childrenPopulated = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Gets a child node by name. */
|
|
54
|
+
childNamed(name: string): CargoItemNode | null {
|
|
55
|
+
let child = this.children.get(name);
|
|
56
|
+
if (child) return child;
|
|
57
|
+
|
|
58
|
+
// Check for a path ending in the name
|
|
59
|
+
for (let [childName, childNode] of this.children) {
|
|
60
|
+
if (childName.endsWith('::' + name)) {
|
|
61
|
+
return childNode;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses the cargo JSON documentation format into a clean graph structure.
|
|
70
|
+
*/
|
|
71
|
+
class CargoJsonParser {
|
|
72
|
+
index: Map<string, IndexEntry> = new Map<string, IndexEntry>();
|
|
73
|
+
crates: Map<string, CrateEntry> = new Map<string, CrateEntry>();
|
|
74
|
+
paths: Map<string, PathEntry> = new Map<string, PathEntry>();
|
|
75
|
+
|
|
76
|
+
next_id: number = 0;
|
|
77
|
+
lookup_tree: CargoItemNode = new CargoItemNode(-1, 'root', 'module', 'module', '');
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new CrateDocs instance.
|
|
81
|
+
* @param name The name of the crate.
|
|
82
|
+
* @param docs_root The root URL for the crate's documentation.
|
|
83
|
+
* @param source The JSON source. You can get this from DocumentationSource.getJson().
|
|
84
|
+
* @returns A new CrateDocs instance.
|
|
85
|
+
*/
|
|
86
|
+
constructor(name: string, docs_root: string, source: any) {
|
|
87
|
+
if (!docs_root.endsWith('/')) {
|
|
88
|
+
docs_root += '/';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//
|
|
92
|
+
// Get the crate root entry, to find the top-level items
|
|
93
|
+
let rootEntry = source.index[source.root.toString()].inner['module'] as ModuleProps;
|
|
94
|
+
let rootEntries: Set<string> = new Set<string>();
|
|
95
|
+
for (let itemId of rootEntry.items) {
|
|
96
|
+
let item = source.index[itemId.toString()];
|
|
97
|
+
if (item.name) rootEntries.add(item.name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//
|
|
101
|
+
// First the current crate will be added to the crate registry and as a root node
|
|
102
|
+
this.lookup_tree.addChild(new CargoItemNode(source.root, name, 'crate', 'module', name));
|
|
103
|
+
this.crates.set(name, {
|
|
104
|
+
id: source.root.toString(),
|
|
105
|
+
name: name,
|
|
106
|
+
html_root_url: docs_root,
|
|
107
|
+
could_be_root_module: rootEntries.has(name),
|
|
108
|
+
});
|
|
109
|
+
if (source.root > this.next_id) this.next_id = source.root + 1;
|
|
110
|
+
|
|
111
|
+
//
|
|
112
|
+
// Do the same with the external crates
|
|
113
|
+
for (let id in source.external_crates) {
|
|
114
|
+
let item = source.external_crates[id];
|
|
115
|
+
item.id = id;
|
|
116
|
+
|
|
117
|
+
let crateId = parseInt(id);
|
|
118
|
+
if (crateId > this.next_id) this.next_id = crateId + 1;
|
|
119
|
+
|
|
120
|
+
if (!item.html_root_url.endsWith('/')) {
|
|
121
|
+
item.html_root_url += '/';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.lookup_tree.addChild(new CargoItemNode(crateId, item.name, 'crate', 'module', item.name));
|
|
125
|
+
this.crates.set(item.name, item);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//
|
|
129
|
+
// Next we build the index
|
|
130
|
+
for (let id in source.index) {
|
|
131
|
+
let item = source.index[id];
|
|
132
|
+
|
|
133
|
+
let indexId = parseInt(id);
|
|
134
|
+
if (indexId > this.next_id) this.next_id = indexId + 1;
|
|
135
|
+
|
|
136
|
+
// The first key in `inner` is the kind
|
|
137
|
+
let kindKey = Object.keys(item.inner)[0];
|
|
138
|
+
item.kind = kindKey as unknown as ItemKind;
|
|
139
|
+
|
|
140
|
+
if (item.kind == 'module') {
|
|
141
|
+
item.is_crate = item.inner['module'].is_crate;
|
|
142
|
+
} else {
|
|
143
|
+
item.is_crate = false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.index.set(id, item);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//
|
|
150
|
+
// Next we build the paths
|
|
151
|
+
this.paths = new Map<string, PathEntry>();
|
|
152
|
+
for (let id in source.paths) {
|
|
153
|
+
let item = source.paths[id];
|
|
154
|
+
|
|
155
|
+
let pathId = parseInt(id);
|
|
156
|
+
if (pathId > this.next_id) this.next_id = pathId + 1;
|
|
157
|
+
|
|
158
|
+
this.paths.set(id, item);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Gets the URL component for a given CargoItemNode. */
|
|
163
|
+
public urlComponentEncode(node: CargoItemNode): string {
|
|
164
|
+
if (node.silent) return '';
|
|
165
|
+
|
|
166
|
+
switch (node.kind) {
|
|
167
|
+
case 'crate': {
|
|
168
|
+
return this.crates.get(node.name)!.html_root_url + node.name + '/';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
case 'module': {
|
|
172
|
+
return `${node.name}/`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
case 'function': {
|
|
176
|
+
if (node.parentKind === 'module' || node.parentKind === 'crate') {
|
|
177
|
+
return `fn.${node.name}.html`;
|
|
178
|
+
} else {
|
|
179
|
+
return `#method.${node.name}`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case 'functiontype': {
|
|
184
|
+
if (node.parentKind === 'module' || node.parentKind === 'crate') {
|
|
185
|
+
return `fn.${node.name}.html`;
|
|
186
|
+
} else {
|
|
187
|
+
return `#tymethod.${node.name}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'variant': {
|
|
192
|
+
return `#variant.${node.name}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case 'struct_field': {
|
|
196
|
+
return `#structfield.${node.name}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case 'assoc_type': {
|
|
200
|
+
return `#associatedtype.${node.name}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case 'assoc_const': {
|
|
204
|
+
return `#associatedconstant.${node.name}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case 'type_alias': {
|
|
208
|
+
return `type.${node.name}.html`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case 'constant':
|
|
212
|
+
case 'macro':
|
|
213
|
+
case 'struct':
|
|
214
|
+
case 'enum':
|
|
215
|
+
case 'trait':
|
|
216
|
+
case 'impl': {
|
|
217
|
+
return `${node.kind}.${node.name}.html`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
default:
|
|
221
|
+
throw new CargoFormatError(`Unhandled item kind for URL encoding: ${node.kind} (name = ${node.name})`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Searches for a path entry by its path components
|
|
227
|
+
* If nothing is found, invents a module
|
|
228
|
+
*/
|
|
229
|
+
private pathEntryByPath(path: string[]) {
|
|
230
|
+
path = path.slice(); // Clone
|
|
231
|
+
|
|
232
|
+
for (let [id, entry] of this.paths) {
|
|
233
|
+
let entryPath = entry.path;
|
|
234
|
+
if (entryPath.length != path.length) continue;
|
|
235
|
+
|
|
236
|
+
let matches = true;
|
|
237
|
+
for (let i=0; i < path.length; i++) {
|
|
238
|
+
if (entryPath[i] !== path[i]) {
|
|
239
|
+
matches = false;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (matches) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Not found - invent a module
|
|
250
|
+
let id = this.next_id++;
|
|
251
|
+
let crate = this.crates.get(path[0]!)!;
|
|
252
|
+
this.paths.set(id.toString(), {
|
|
253
|
+
crate_id: parseInt(this.crates.get(path[0]!)!.id),
|
|
254
|
+
path: path,
|
|
255
|
+
kind: 'module',
|
|
256
|
+
silent: true,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// If the parent is a module in the index, add this to its items
|
|
260
|
+
if (path.length > 1) {
|
|
261
|
+
let node = this.walkTree(path.slice(0, path.length - 1));
|
|
262
|
+
let parentNode = node[node.length - 1]!;
|
|
263
|
+
let parentEntry = this.index.get(parentNode.id.toString());
|
|
264
|
+
if (parentEntry && parentEntry.kind == 'module') {
|
|
265
|
+
let moduleInner = parentEntry.inner['module'] as ModuleProps;
|
|
266
|
+
moduleInner.items.push(id);
|
|
267
|
+
|
|
268
|
+
this.populateChildren(parentNode);
|
|
269
|
+
let child = new CargoItemNode(
|
|
270
|
+
id, path[path.length - 1]!, 'module', 'module', path.join('::')
|
|
271
|
+
);
|
|
272
|
+
child.silent = true;
|
|
273
|
+
parentNode.addChild(child);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let entry = this.pathEntry(id)!;
|
|
278
|
+
entry.name = path[path.length - 1]!;
|
|
279
|
+
if (path.length > 1) entry.is_crate = false;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private pathEntry(id: number|string): IndexEntry | null {
|
|
284
|
+
id = id.toString();
|
|
285
|
+
let containerPath = this.paths.get(id);
|
|
286
|
+
if (!containerPath) {
|
|
287
|
+
for (let crate of this.crates.values()) {
|
|
288
|
+
if (crate.id === id) containerPath = {
|
|
289
|
+
crate_id: parseInt(crate.id),
|
|
290
|
+
path: [crate.name],
|
|
291
|
+
kind: 'crate',
|
|
292
|
+
silent: false,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!containerPath) return null;
|
|
297
|
+
|
|
298
|
+
let children: number[] = [];
|
|
299
|
+
for (let pathId of this.paths.keys()) {
|
|
300
|
+
let item = this.paths.get(pathId.toString());
|
|
301
|
+
if (!item || item.path.length != containerPath.path.length + 1) continue;
|
|
302
|
+
|
|
303
|
+
let matches = true;
|
|
304
|
+
for (let i=0; i < containerPath.path.length; i++) {
|
|
305
|
+
if (item.path[i] !== containerPath.path[i]) {
|
|
306
|
+
matches = false;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (matches) {
|
|
312
|
+
children.push(parseInt(pathId));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let inner: any = {};
|
|
317
|
+
switch (containerPath.kind) {
|
|
318
|
+
case 'crate':
|
|
319
|
+
case 'module': {
|
|
320
|
+
inner['items'] = children;
|
|
321
|
+
} break;
|
|
322
|
+
|
|
323
|
+
case 'struct': {
|
|
324
|
+
inner['tuple'] = children;
|
|
325
|
+
} break;
|
|
326
|
+
|
|
327
|
+
case 'enum': {
|
|
328
|
+
inner['variants'] = children;
|
|
329
|
+
} break;
|
|
330
|
+
|
|
331
|
+
case 'trait': {
|
|
332
|
+
inner['items'] = children;
|
|
333
|
+
} break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
inner = { [containerPath.kind]: inner };
|
|
337
|
+
let name = containerPath.path.join('::');
|
|
338
|
+
let entry: IndexEntry = {
|
|
339
|
+
id: parseInt(id),
|
|
340
|
+
crate_id: containerPath.crate_id,
|
|
341
|
+
is_crate: false,
|
|
342
|
+
name: name,
|
|
343
|
+
kind: containerPath.kind,
|
|
344
|
+
docs: null,
|
|
345
|
+
silent: containerPath.silent,
|
|
346
|
+
inner: inner,
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return entry;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private indexEntryContents(id: number|string, is_extern_crate: boolean = false): IndexEntry[] | null {
|
|
353
|
+
id = id.toString();
|
|
354
|
+
let parentEntry = null;
|
|
355
|
+
if (!is_extern_crate) parentEntry = this.index.get(id);
|
|
356
|
+
if (!parentEntry) {
|
|
357
|
+
let pathEntry = this.pathEntry(id);
|
|
358
|
+
if (pathEntry) parentEntry = pathEntry;
|
|
359
|
+
else return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
switch (parentEntry.kind) {
|
|
363
|
+
case 'crate':
|
|
364
|
+
case 'module': {
|
|
365
|
+
let moduleInner = parentEntry.inner[parentEntry.kind] as ModuleProps;
|
|
366
|
+
|
|
367
|
+
let entries: IndexEntry[] = [];
|
|
368
|
+
for (let itemId of moduleInner.items) {
|
|
369
|
+
let entry = this.index.get(itemId.toString())!;
|
|
370
|
+
if (!entry) {
|
|
371
|
+
// try path entry
|
|
372
|
+
let pathEntry = this.pathEntry(itemId);
|
|
373
|
+
if (pathEntry) {
|
|
374
|
+
// This is always a fake module we invented - strip the path
|
|
375
|
+
let name = pathEntry.name.split('::').pop()!;
|
|
376
|
+
pathEntry.name = name;
|
|
377
|
+
-
|
|
378
|
+
entries.push(pathEntry);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (entry.kind == 'use') {
|
|
385
|
+
let useInner = entry.inner['use'] as UseProps;
|
|
386
|
+
let pathEntry = this.pathEntry(useInner.id);
|
|
387
|
+
|
|
388
|
+
if (pathEntry) {
|
|
389
|
+
entries.push(pathEntry);
|
|
390
|
+
} else if (useInner.is_glob) {
|
|
391
|
+
let usedModule = this.index.get(useInner.id.toString())?.inner['module'] as ModuleProps;
|
|
392
|
+
for (let usedItemId of usedModule.items) {
|
|
393
|
+
let usedEntry = this.index.get(usedItemId.toString());
|
|
394
|
+
if (usedEntry) entries.push(usedEntry);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
let useEntry = this.index.get(useInner.id.toString());
|
|
398
|
+
if (useEntry) entries.push(useEntry);
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
entries.push(entry);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return entries;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
case 'struct': {
|
|
409
|
+
let structInner = parentEntry.inner['struct'] as StructProps;
|
|
410
|
+
|
|
411
|
+
let ownImpls: IndexEntry[] = [];
|
|
412
|
+
for (let itemId of structInner.impls) {
|
|
413
|
+
let entry = this.index.get(itemId.toString());
|
|
414
|
+
let implInner = entry?.inner['impl'];
|
|
415
|
+
if (implInner.trait !== null) continue;
|
|
416
|
+
|
|
417
|
+
for (let itemId of implInner.items) {
|
|
418
|
+
let item = this.index.get(itemId.toString());
|
|
419
|
+
if (item) ownImpls.push(item);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let fields: IndexEntry[] = [];
|
|
424
|
+
if (structInner.kind['plain']) {
|
|
425
|
+
for (let fieldId of structInner.kind['plain']['fields']) {
|
|
426
|
+
let field = this.index.get(fieldId.toString());
|
|
427
|
+
if (field) fields.push(field);
|
|
428
|
+
}
|
|
429
|
+
} else if (structInner.kind['tuple']) {
|
|
430
|
+
for (let fieldId of structInner.kind['tuple']) {
|
|
431
|
+
let field = this.index.get(fieldId.toString());
|
|
432
|
+
if (field) fields.push(field);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return ownImpls.concat(fields);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
case 'enum': {
|
|
440
|
+
let enumInner = parentEntry.inner['enum'] as EnumProps;
|
|
441
|
+
let variants = enumInner.variants.map(id => this.index.get(id.toString())).filter((item): item is IndexEntry => item !== undefined);
|
|
442
|
+
|
|
443
|
+
let ownImpls: IndexEntry[] = [];
|
|
444
|
+
for (let itemId of enumInner.impls) {
|
|
445
|
+
let entry = this.index.get(itemId.toString());
|
|
446
|
+
let implInner = entry?.inner['impl'];
|
|
447
|
+
if (implInner.trait !== null) continue;
|
|
448
|
+
|
|
449
|
+
for (let itemId of implInner.items) {
|
|
450
|
+
let item = this.index.get(itemId.toString());
|
|
451
|
+
if (item) ownImpls.push(item);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return variants.concat(ownImpls);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
case 'trait': {
|
|
459
|
+
let traitInner = parentEntry.inner['trait'] as TraitProps;
|
|
460
|
+
|
|
461
|
+
let items: IndexEntry[] = [];
|
|
462
|
+
for (let itemId of traitInner.items) {
|
|
463
|
+
let item = this.index.get(itemId.toString());
|
|
464
|
+
if (!item) continue;
|
|
465
|
+
|
|
466
|
+
if (item.kind == 'function') {
|
|
467
|
+
let funcInner = item.inner['function'] as FunctionProps;
|
|
468
|
+
if (!funcInner.has_body) {
|
|
469
|
+
item.kind = 'functiontype'
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
items.push(item);
|
|
474
|
+
}
|
|
475
|
+
return items;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
default:
|
|
479
|
+
throw new CargoFormatError(`Unhandled parent kind: ${parentEntry.kind} (name = ${parentEntry.name}, id = ${parentEntry.id})`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* First we make sure the start is a crate name
|
|
485
|
+
* If not, append the current crate name
|
|
486
|
+
* If 'crate' replaced with current crate name
|
|
487
|
+
* If it's a crate that cannot be a root module only try that crate
|
|
488
|
+
* Else try current crate first, then other one
|
|
489
|
+
* @param path Path to fix
|
|
490
|
+
* @returns All viable candidate paths.
|
|
491
|
+
*/
|
|
492
|
+
private fixPathCrate(path: string[]): string[][] {
|
|
493
|
+
let crateName = path[0]!;
|
|
494
|
+
if (crateName == 'crate') crateName = this.ownCrateName();
|
|
495
|
+
let crateDetails = this.crates.get(crateName);
|
|
496
|
+
let paths: string[][] = [];
|
|
497
|
+
if (crateDetails) {
|
|
498
|
+
if (crateDetails.could_be_root_module) {
|
|
499
|
+
// Try current crate first
|
|
500
|
+
let newPath = [this.ownCrateName()].concat(path);
|
|
501
|
+
let result = this.walkTree(newPath);
|
|
502
|
+
if (result) paths.push(newPath);
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
crateName = this.ownCrateName();
|
|
506
|
+
path = [crateName].concat(path);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
paths.push(path);
|
|
510
|
+
return paths;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** Populates the children of a given CargoItemNode. */
|
|
514
|
+
private populateChildren(node: CargoItemNode, is_extern_crate: boolean = false) {
|
|
515
|
+
if (!node.childrenPopulated) {
|
|
516
|
+
// Load the children for this node
|
|
517
|
+
let children = this.indexEntryContents(node.id, is_extern_crate) || [];
|
|
518
|
+
|
|
519
|
+
for (let childEntry of children) {
|
|
520
|
+
let path = node.path + '::' + childEntry.name;
|
|
521
|
+
let childNode = new CargoItemNode(
|
|
522
|
+
childEntry.id, childEntry.name, childEntry.kind, node.kind, path, childEntry.docs
|
|
523
|
+
);
|
|
524
|
+
if (childEntry.is_crate) childNode.shouldJumpToCrate = childEntry.name;
|
|
525
|
+
node.addChild(childNode);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* When going through paths, we may come across modules that are not directly represented in the index.
|
|
532
|
+
* This walks up the path to populate missing modules as needed.
|
|
533
|
+
*/
|
|
534
|
+
private populateMissingPathModules(path: string[]) {
|
|
535
|
+
let currentNode: CargoItemNode = this.lookup_tree;
|
|
536
|
+
path = path.slice(); // Clone
|
|
537
|
+
if (path.length === 0) throw new Error("Cannot walk empty path");
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
let remainingPath = path.reverse();
|
|
541
|
+
let crateName = remainingPath.pop()!;
|
|
542
|
+
currentNode = currentNode.childNamed(crateName)!;
|
|
543
|
+
|
|
544
|
+
let shortestPath = [crateName];
|
|
545
|
+
while (remainingPath.length > 0) {
|
|
546
|
+
let nextName = remainingPath.pop()!;
|
|
547
|
+
shortestPath.push(nextName);
|
|
548
|
+
|
|
549
|
+
this.pathEntryByPath(shortestPath);
|
|
550
|
+
this.populateChildren(currentNode, shortestPath.length == 2);
|
|
551
|
+
currentNode = currentNode.childNamed(nextName)!;
|
|
552
|
+
if (!currentNode) throw new ResolutionError(shortestPath.slice(0, -1).join('::'), nextName);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Walks the item tree to find a node by path.
|
|
558
|
+
* @param path The path to walk.
|
|
559
|
+
* @returns The path to the found CargoItemNode.
|
|
560
|
+
* @throws ResolutionError if the path cannot be resolved.
|
|
561
|
+
*/
|
|
562
|
+
public walkTree(path: string[]): CargoItemNode[] {
|
|
563
|
+
let currentNode: CargoItemNode = this.lookup_tree;
|
|
564
|
+
path = path.slice(); // Clone
|
|
565
|
+
if (path.length === 0) throw new Error("Cannot walk empty path");
|
|
566
|
+
|
|
567
|
+
//
|
|
568
|
+
// Fix the crate prefix
|
|
569
|
+
let paths = this.fixPathCrate(path);
|
|
570
|
+
if (paths.length > 1) {
|
|
571
|
+
for (let p of paths.slice(1)) {
|
|
572
|
+
try {
|
|
573
|
+
return this.walkTree(p);
|
|
574
|
+
} catch (e) { }
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
path = paths[0]!;
|
|
578
|
+
|
|
579
|
+
let remainingPath = path.reverse();
|
|
580
|
+
currentNode = currentNode.childNamed(remainingPath.pop()!)!;
|
|
581
|
+
|
|
582
|
+
let shortestPath = [currentNode];
|
|
583
|
+
while (remainingPath.length > 0) {
|
|
584
|
+
this.populateChildren(currentNode);
|
|
585
|
+
|
|
586
|
+
let nextName = remainingPath.pop()!;
|
|
587
|
+
let nextNode = currentNode.childNamed(nextName);
|
|
588
|
+
if (!nextNode) throw new ResolutionError(currentNode.name, nextName);
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
if (nextNode.shouldJumpToCrate) {
|
|
592
|
+
let newPath = nextNode.shouldJumpToCrate.split('::').concat(remainingPath.reverse());
|
|
593
|
+
this.populateMissingPathModules(newPath);``
|
|
594
|
+
return this.walkTree(newPath);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
currentNode = nextNode;
|
|
598
|
+
|
|
599
|
+
let returnedNode = JSON.parse(JSON.stringify(currentNode));
|
|
600
|
+
returnedNode.name = returnedNode.name.split('::').pop();
|
|
601
|
+
shortestPath.push(returnedNode);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return shortestPath;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** Gets the current crate name. */
|
|
608
|
+
public ownCrateName(): string {
|
|
609
|
+
// It's the first crate in the registry
|
|
610
|
+
return Array.from(this.crates.keys())[0]!;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export class CrateDocs {
|
|
615
|
+
parser: CargoJsonParser;
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Creates a new CrateDocs instance.
|
|
619
|
+
* @param name The name of the crate.
|
|
620
|
+
* @param docs_root The root URL for the crate's documentation.
|
|
621
|
+
* @param source The JSON source. You can get this from DocumentationSource.getJson().
|
|
622
|
+
* @returns A new CrateDocs instance.
|
|
623
|
+
*/
|
|
624
|
+
constructor(name: string, docs_root: string, source: any) {
|
|
625
|
+
this.parser = new CargoJsonParser(name, docs_root, source);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Gets a documented item by its path.
|
|
630
|
+
* @param path The item path, e.g., "crate::module::Item" or "Item".
|
|
631
|
+
* @returns The documented item.
|
|
632
|
+
* @throws ResolutionError if the item cannot be found.
|
|
633
|
+
*/
|
|
634
|
+
public get(path: string): DocumentedItem {
|
|
635
|
+
let pathParts = path.split('::');
|
|
636
|
+
let nodes = this.parser.walkTree(pathParts);
|
|
637
|
+
let targetNode = nodes[nodes.length - 1]!;
|
|
638
|
+
|
|
639
|
+
let urlParts: string[] = [];
|
|
640
|
+
for (let node of nodes) {
|
|
641
|
+
let part = this.parser.urlComponentEncode(node);
|
|
642
|
+
urlParts.push(part);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
let url = urlParts.join('');
|
|
646
|
+
if (url.endsWith('/')) {
|
|
647
|
+
url += 'index.html';
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
name: targetNode.name,
|
|
652
|
+
path: targetNode.path,
|
|
653
|
+
kind: targetNode.kind,
|
|
654
|
+
url: url,
|
|
655
|
+
docs: targetNode.docs_md,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
}
|