/
extractor.ts
120 lines (98 loc) Β· 4.52 KB
/
extractor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {extractEnum} from '@angular/compiler-cli/src/ngtsc/docs/src/enum_extractor';
import {FunctionExtractor} from '@angular/compiler-cli/src/ngtsc/docs/src/function_extractor';
import ts from 'typescript';
import {MetadataReader} from '../../metadata';
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {extractClass, extractInterface} from './class_extractor';
import {extractConstant, isSyntheticAngularConstant} from './constant_extractor';
import {DocEntry} from './entities';
import {isAngularPrivateName} from './filters';
import {extractTypeAlias} from './type_alias_extractor';
type DeclarationWithExportName = readonly[string, ts.Declaration];
/**
* Extracts all information from a source file that may be relevant for generating
* public API documentation.
*/
export class DocsExtractor {
constructor(private typeChecker: ts.TypeChecker, private metadataReader: MetadataReader) {}
/**
* Gets the set of all documentable entries from a source file, including
* declarations that are re-exported from this file as an entry-point.
*
* @param sourceFile The file from which to extract documentable entries.
*/
extractAll(sourceFile: ts.SourceFile): DocEntry[] {
const entries: DocEntry[] = [];
const exportedDeclarations = this.getExportedDeclarations(sourceFile);
for (const [exportName, node] of exportedDeclarations) {
// Skip any symbols with an Angular-internal name.
if (isAngularPrivateName(exportName)) continue;
const entry = this.extractDeclaration(node);
if (entry) {
// The exported name of an API may be different from its declaration name, so
// use the declaration name.
entries.push({...entry, name: exportName});
}
}
return entries;
}
/** Extract the doc entry for a single declaration. */
private extractDeclaration(node: ts.Declaration): DocEntry|null {
// Ignore anonymous classes.
if (isNamedClassDeclaration(node)) {
return extractClass(node, this.metadataReader, this.typeChecker);
}
if (ts.isInterfaceDeclaration(node)) {
return extractInterface(node, this.typeChecker);
}
if (ts.isFunctionDeclaration(node)) {
const functionExtractor = new FunctionExtractor(node, this.typeChecker);
return functionExtractor.extract();
}
if (ts.isVariableDeclaration(node) && !isSyntheticAngularConstant(node)) {
return extractConstant(node, this.typeChecker);
}
if (ts.isTypeAliasDeclaration(node)) {
return extractTypeAlias(node);
}
if (ts.isEnumDeclaration(node)) {
return extractEnum(node, this.typeChecker);
}
return null;
}
/** Gets the list of exported declarations for doc extraction. */
private getExportedDeclarations(sourceFile: ts.SourceFile): DeclarationWithExportName[] {
// Use the reflection host to get all the exported declarations from this
// source file entry point.
const reflector = new TypeScriptReflectionHost(this.typeChecker);
const exportedDeclarationMap = reflector.getExportsOfModule(sourceFile);
// Augment each declaration with the exported name in the public API.
let exportedDeclarations =
Array.from(exportedDeclarationMap?.entries() ?? [])
.map(([exportName, declaration]) => [exportName, declaration.node] as const);
// Cache the declaration count since we're going to be appending more declarations as
// we iterate.
const declarationCount = exportedDeclarations.length;
// The exported declaration map only includes one function declaration in situations
// where a function has overloads, so we add the overloads here.
for (let i = 0; i < declarationCount; i++) {
const [exportName, declaration] = exportedDeclarations[i];
if (ts.isFunctionDeclaration(declaration)) {
const extractor = new FunctionExtractor(declaration, this.typeChecker);
const overloads = extractor.getOverloads().map(overload => [exportName, overload] as const);
exportedDeclarations.push(...overloads);
}
}
// Sort the declaration nodes into declaration position because their order is lost in
// reading from the export map. This is primarily useful for testing and debugging.
return exportedDeclarations.sort(
([a, declarationA], [b, declarationB]) => declarationA.pos - declarationB.pos);
}
}