@@ -92,33 +92,18 @@ export class SymbolCollector {
92
92
/**
93
93
* Retrieves unexported symbols used by exported symbols.
94
94
*/
95
- private getReferences ( origSymbol : ts . Symbol ) : Reference [ ] {
96
- // We look in the first declaration to retrieve common source file,
97
- // since merged and overloaded declarations are in the same source file.
98
- const sourceFile = origSymbol . declarations [ 0 ] . getSourceFile ( )
99
-
95
+ private getReferences ( origSymbol : ts . Symbol , symbolsChain : ts . Symbol [ ] = [ ] ) : Reference [ ] {
100
96
// Don't search in external symbol declarations.
97
+ // We need to check every declaration because of augmentations that could lead to false negatives.
101
98
if (
102
- this . program . isSourceFileFromExternalLibrary ( sourceFile ) ||
103
- this . program . isSourceFileDefaultLibrary ( sourceFile )
99
+ origSymbol . declarations . some ( ( d ) => this . program . isSourceFileFromExternalLibrary ( d . getSourceFile ( ) ) ) ||
100
+ origSymbol . declarations . some ( ( d ) => this . program . isSourceFileDefaultLibrary ( d . getSourceFile ( ) ) )
104
101
) {
105
102
return [ ]
106
103
}
107
104
108
- const getSubReferences = ( identifier : ts . Identifier ) : Reference [ ] => {
109
- let refSymbol = this . checker . getSymbolAtLocation ( identifier )
110
-
111
- // Avoid infinite loop.
112
- if ( ! refSymbol || refSymbol === origSymbol ) return [ ]
113
-
114
- if ( refSymbol . flags & ts . SymbolFlags . Alias ) {
115
- refSymbol = this . checker . getAliasedSymbol ( refSymbol )
116
- }
117
-
118
- if ( ! refSymbol . declarations || ! refSymbol . declarations . length ) return [ ]
119
-
120
- return this . getReferences ( refSymbol )
121
- }
105
+ // Keep parent symbols in an array to avoid infinite loop when looking for circular subreferences.
106
+ symbolsChain . push ( origSymbol )
122
107
123
108
const refs : Reference [ ] = [ ]
124
109
@@ -128,21 +113,41 @@ export class SymbolCollector {
128
113
129
114
if ( ts . isSourceFile ( declaration ) ) continue
130
115
131
- // Taken from https://github.com/microsoft/rushstack/blob/2cb32ec198/apps/api-extractor/src/analyzer/AstSymbolTable.ts#L298
116
+ const getSubReferences = ( identifier : ts . Identifier ) : Reference [ ] => {
117
+ let refSymbol = this . checker . getSymbolAtLocation ( identifier )
118
+
119
+ // Avoid infinite loop due to circular references.
120
+ if ( ! refSymbol || symbolsChain . includes ( refSymbol ) ) return [ ]
121
+
122
+ if ( refSymbol . flags & ts . SymbolFlags . Alias ) {
123
+ refSymbol = this . checker . getAliasedSymbol ( refSymbol )
124
+ }
125
+
126
+ if ( ! refSymbol . declarations || ! refSymbol . declarations . length ) return [ ]
127
+
128
+ return this . getReferences ( refSymbol , symbolsChain )
129
+ }
130
+
132
131
declaration . forEachChild ( function visit ( child ) {
132
+ // Taken from https://github.com/microsoft/rushstack/blob/2cb32ec198/apps/api-extractor/src/analyzer/AstSymbolTable.ts#L298
133
133
if (
134
134
ts . isTypeReferenceNode ( child ) ||
135
135
ts . isExpressionWithTypeArguments ( child ) || // "extends"
136
136
ts . isComputedPropertyName ( child ) || // [Prop]:
137
137
ts . isTypeQueryNode ( child ) // "typeof X"
138
138
) {
139
139
const identifier = findFirstChild ( child , ts . isIdentifier )
140
+
140
141
if ( identifier ) {
141
- refs . push ( { ref : identifier , subrefs : getSubReferences ( identifier ) , declarationIndex } )
142
+ const subrefs = getSubReferences ( identifier )
143
+
144
+ refs . push ( { ref : identifier , subrefs, declarationIndex } )
142
145
}
143
146
}
144
147
145
- if ( ts . isImportTypeNode ( child ) ) refs . push ( { ref : child , subrefs : [ ] , declarationIndex } )
148
+ if ( ts . isImportTypeNode ( child ) ) {
149
+ refs . push ( { ref : child , subrefs : [ ] , declarationIndex } )
150
+ }
146
151
147
152
child . forEachChild ( visit )
148
153
} )
0 commit comments