Skip to content

Commit ef183e9

Browse files
committed
Initial prototype of AstSymbolTable.analyze() which fills the children in the AstDefinition hierarchy
1 parent 5222c91 commit ef183e9

3 files changed

Lines changed: 145 additions & 42 deletions

File tree

apps/api-extractor/src/generators/packageTypings/AstDeclaration.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import * as ts from 'typescript';
55
import { AstSymbol } from './AstSymbol';
6+
import { Span } from '../../utils/Span';
67

78
export interface IAstDeclarationParameters {
89
readonly declaration: ts.Declaration;
@@ -26,14 +27,60 @@ export class AstDeclaration {
2627
/**
2728
* The parent, if this object is nested inside another AstDeclaration.
2829
*/
29-
public readonly parentAstDeclaration: AstDeclaration | undefined;
30+
public readonly parent: AstDeclaration | undefined;
31+
32+
private readonly _knownChildren: AstDeclaration[] = [];
3033

3134
public constructor(parameters: IAstDeclarationParameters) {
3235
this.declaration = parameters.declaration;
3336
this.astSymbol = parameters.astSymbol;
3437
this.typeDirectiveReferences = parameters.typeDirectiveReferences;
35-
this.parentAstDeclaration = parameters.parentAstDeclaration;
38+
this.parent = parameters.parentAstDeclaration;
39+
40+
this.astSymbol.notifyDeclarationAttach(this);
41+
42+
if (this.parent) {
43+
this.parent.notifyChildAttach(this);
44+
}
45+
}
46+
47+
/**
48+
* Returns the children for this AstDeclaration.
49+
* The collection will be empty until AstSymbol.analyzed is true.
50+
*/
51+
public get children(): AstDeclaration[] {
52+
return this.astSymbol.analyzed ? this._knownChildren : [];
53+
}
3654

37-
this.astSymbol.attachDeclaration(this);
55+
public notifyChildAttach(child: AstDeclaration): void {
56+
if (child.parent !== this) {
57+
throw new Error('Program Bug: Invalid call to attachChild()');
58+
}
59+
60+
this._knownChildren.push(child);
61+
}
62+
63+
/**
64+
* Returns a diagnostic dump of the tree, which reports the hierarchy of
65+
* AstDefinition objects.
66+
*/
67+
public getDump(indent: string = ''): string {
68+
const declarationKind: string = ts.SyntaxKind[this.declaration.kind];
69+
let result: string = indent + `${this.astSymbol.localName} (${declarationKind})\n`;
70+
71+
for (const child of this.children) {
72+
result += child.getDump(indent + ' ');
73+
}
74+
75+
return result;
76+
}
77+
78+
/**
79+
* Returns a diagnostic dump using Span.getDump(), which reports the detailed
80+
* compiler structure.
81+
*/
82+
public getSpanDump(indent: string = ''): string {
83+
const span: Span = new Span(this.declaration);
84+
return span.getDump(indent);
3885
}
3986
}

apps/api-extractor/src/generators/packageTypings/AstSymbol.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export class AstSymbol {
2727

2828
private readonly _astDeclarations: AstDeclaration[];
2929

30+
private _analyzed: boolean = false;
31+
3032
public constructor(parameters: IAstSymbolParameters) {
3133
this.followedSymbol = parameters.followedSymbol;
3234
this.localName = parameters.localName;
@@ -43,7 +45,20 @@ export class AstSymbol {
4345
return this._astDeclarations[0];
4446
}
4547

46-
public attachDeclaration(astDeclaration: AstDeclaration): void {
48+
/**
49+
* Returns true if the entire tree of children and parents have been fully
50+
* constructed. This supports partial analysis of symbols for
51+
* external references.
52+
*/
53+
public get analyzed(): boolean {
54+
return this._analyzed;
55+
}
56+
57+
public notifyDeclarationAttach(astDeclaration: AstDeclaration): void {
4758
this._astDeclarations.push(astDeclaration);
4859
}
60+
61+
public notifyAnalyzed(): void {
62+
this._analyzed = true;
63+
}
4964
}

apps/api-extractor/src/generators/packageTypings/AstSymbolTable.ts

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ export class AstSymbolTable {
7373
throw new Error('Unsupported export: ' + exportSymbol.name);
7474
}
7575

76+
this.analyze(astSymbol);
77+
78+
for (const d of astSymbol.astDeclarations) {
79+
console.log('--------------------------------');
80+
console.log(d.getDump());
81+
}
82+
7683
exports.push({ name: exportSymbol.name, astSymbol: astSymbol });
7784
}
7885

@@ -82,6 +89,29 @@ export class AstSymbolTable {
8289
return astEntryPoint;
8390
}
8491

92+
/**
93+
* Ensures that AstSymbol.analyzed is true for the provided symbol. The operation
94+
* locates the root symbol and then fetches all children of all declarations.
95+
*/
96+
public analyze(astSymbol: AstSymbol): void {
97+
if (astSymbol.analyzed) {
98+
return;
99+
}
100+
101+
// Walk up to the parent of the tree
102+
let rootAstSymbol: AstSymbol = astSymbol;
103+
while (rootAstSymbol.mainDeclaration.parent) {
104+
rootAstSymbol = rootAstSymbol.mainDeclaration.parent.astSymbol;
105+
}
106+
107+
// Calculate the full child tree for each definition
108+
for (const astDeclaration of rootAstSymbol.astDeclarations) {
109+
this._analyzeChildTree(astDeclaration.declaration);
110+
}
111+
112+
astSymbol.notifyAnalyzed();
113+
}
114+
85115
/**
86116
* This function determines which node types will generate an AstDeclaration.
87117
*/
@@ -98,13 +128,57 @@ export class AstSymbolTable {
98128
return false;
99129
}
100130

101-
private _fetchAstSymbol(symbol: ts.Symbol): AstSymbol | undefined {
102-
const followAliasesResult: IFollowAliasesResult = SymbolAnalyzer.followAliases(symbol, this._typeChecker);
131+
public tryGetSymbolForAstDeclaration(node: ts.Node): ts.Symbol | undefined {
132+
return TypeScriptHelpers.getSymbolForDeclaration(node as ts.Declaration);
133+
}
103134

104-
if (followAliasesResult.isAmbient) {
105-
return undefined; // we don't care about ambient definitions
135+
/**
136+
* Used by analyze to recursively analyze the entire child tree.
137+
*/
138+
private _analyzeChildTree(node: ts.Node): void {
139+
const childAstSymbol: AstSymbol | undefined = this._fetchAstSymbolForNode(node);
140+
141+
for (const child of node.getChildren()) {
142+
this._analyzeChildTree(child);
143+
}
144+
145+
if (childAstSymbol) {
146+
childAstSymbol.notifyAnalyzed();
147+
}
148+
}
149+
150+
// tslint:disable-next-line:no-unused-variable
151+
private _fetchAstDeclaration(node: ts.Node): AstDeclaration | undefined {
152+
const astSymbol: AstSymbol | undefined = this._fetchAstSymbolForNode(node);
153+
if (!astSymbol) {
154+
return undefined;
106155
}
107156

157+
const astDeclaration: AstDeclaration | undefined
158+
= this._astDeclarationsByDeclaration.get(node);
159+
if (!astDeclaration) {
160+
throw new Error('Program Bug: Unable to find constructed AstDeclaration');
161+
}
162+
163+
return astDeclaration;
164+
}
165+
166+
private _fetchAstSymbolForNode(node: ts.Node): AstSymbol | undefined {
167+
if (!this.isAstDeclaration(node)) {
168+
return undefined;
169+
}
170+
171+
const symbol: ts.Symbol | undefined = this.tryGetSymbolForAstDeclaration(node);
172+
if (!symbol) {
173+
throw new Error('Program Bug: Unable to find symbol for node');
174+
}
175+
176+
return this._fetchAstSymbol(symbol);
177+
}
178+
179+
private _fetchAstSymbol(symbol: ts.Symbol): AstSymbol | undefined {
180+
const followAliasesResult: IFollowAliasesResult = SymbolAnalyzer.followAliases(symbol, this._typeChecker);
181+
108182
const followedSymbol: ts.Symbol = followAliasesResult.followedSymbol;
109183
if (followedSymbol.flags & (ts.SymbolFlags.TypeParameter | ts.SymbolFlags.TypeLiteral)) {
110184
return undefined;
@@ -166,7 +240,7 @@ export class AstSymbolTable {
166240
let parentAstSymbol: AstSymbol | undefined = undefined;
167241

168242
if (arbitaryParent) {
169-
const parentSymbol: ts.Symbol | undefined = this._typeChecker.getSymbolAtLocation(arbitaryParent);
243+
const parentSymbol: ts.Symbol | undefined = this.tryGetSymbolForAstDeclaration(arbitaryParent);
170244
if (!parentSymbol) {
171245
throw new Error('Program bug: missing parent symbol for declaration');
172246
}
@@ -221,39 +295,6 @@ export class AstSymbolTable {
221295
return undefined;
222296
}
223297

224-
private _collectTypes(node: ts.Node): void {
225-
switch (node.kind) {
226-
case ts.SyntaxKind.Block:
227-
// Don't traverse into code
228-
return;
229-
case ts.SyntaxKind.TypeReference: // general type references
230-
case ts.SyntaxKind.ExpressionWithTypeArguments: // special case for e.g. the "extends" keyword
231-
{
232-
// Sometimes the type reference will involve multiple identifiers, e.g. "a.b.C".
233-
// In this case, we only need to worry about importing the first identifier,
234-
// so do a depth-first search for it:
235-
const symbolNode: ts.Node | undefined = TypeScriptHelpers.findFirstChildNode(
236-
node, ts.SyntaxKind.Identifier);
237-
238-
if (!symbolNode) {
239-
break;
240-
}
241-
242-
const symbol: ts.Symbol | undefined = this._typeChecker.getSymbolAtLocation(symbolNode);
243-
if (!symbol) {
244-
throw new Error('Symbol not found for identifier: ' + symbolNode.getText());
245-
}
246-
247-
this._fetchAstSymbol(symbol);
248-
}
249-
break; // keep recursing
250-
}
251-
252-
for (const child of node.getChildren() || []) {
253-
this._collectTypes(child);
254-
}
255-
}
256-
257298
private _getTypeDirectiveReferences(node: ts.Node): ReadonlyArray<string> {
258299
const sourceFile: ts.SourceFile = node.getSourceFile();
259300
if (!sourceFile || !sourceFile.fileName) {

0 commit comments

Comments
 (0)