• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    clear, EntityNameOrEntityNameExpression, forEach, getOwnValues, getSymbolId, Identifier, IndexedAccessType,
3    IndexType, InterfaceType, MappedType, Node, ObjectFlags, ObjectType, ResolvedType, Signature, Symbol, SymbolWalker,
4    SyntaxKind, Type, TypeFlags, TypeParameter, TypePredicate, TypeQueryNode, TypeReference, UnionOrIntersectionType,
5} from "./_namespaces/ts";
6
7/** @internal */
8export function createGetSymbolWalker(
9    getRestTypeOfSignature: (sig: Signature) => Type,
10    getTypePredicateOfSignature: (sig: Signature) => TypePredicate | undefined,
11    getReturnTypeOfSignature: (sig: Signature) => Type,
12    getBaseTypes: (type: Type) => Type[],
13    resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType,
14    getTypeOfSymbol: (sym: Symbol) => Type,
15    getResolvedSymbol: (node: Node) => Symbol,
16    getConstraintOfTypeParameter: (typeParameter: TypeParameter) => Type | undefined,
17    getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier,
18    getTypeArguments: (type: TypeReference) => readonly Type[]) {
19
20    return getSymbolWalker;
21
22    function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker {
23        const visitedTypes: Type[] = []; // Sparse array from id to type
24        const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol
25
26        return {
27            walkType: type => {
28                try {
29                    visitType(type);
30                    return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) };
31                }
32                finally {
33                    clear(visitedTypes);
34                    clear(visitedSymbols);
35                }
36            },
37            walkSymbol: symbol => {
38                try {
39                    visitSymbol(symbol);
40                    return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) };
41                }
42                finally {
43                    clear(visitedTypes);
44                    clear(visitedSymbols);
45                }
46            },
47        };
48
49        function visitType(type: Type | undefined): void {
50            if (!type) {
51                return;
52            }
53
54            if (visitedTypes[type.id]) {
55                return;
56            }
57            visitedTypes[type.id] = type;
58
59            // Reuse visitSymbol to visit the type's symbol,
60            //  but be sure to bail on recuring into the type if accept declines the symbol.
61            const shouldBail = visitSymbol(type.symbol);
62            if (shouldBail) return;
63
64            // Visit the type's related types, if any
65            if (type.flags & TypeFlags.Object) {
66                const objectType = type as ObjectType;
67                const objectFlags = objectType.objectFlags;
68                if (objectFlags & ObjectFlags.Reference) {
69                    visitTypeReference(type as TypeReference);
70                }
71                if (objectFlags & ObjectFlags.Mapped) {
72                    visitMappedType(type as MappedType);
73                }
74                if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) {
75                    visitInterfaceType(type as InterfaceType);
76                }
77                if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) {
78                    visitObjectType(objectType);
79                }
80            }
81            if (type.flags & TypeFlags.TypeParameter) {
82                visitTypeParameter(type as TypeParameter);
83            }
84            if (type.flags & TypeFlags.UnionOrIntersection) {
85                visitUnionOrIntersectionType(type as UnionOrIntersectionType);
86            }
87            if (type.flags & TypeFlags.Index) {
88                visitIndexType(type as IndexType);
89            }
90            if (type.flags & TypeFlags.IndexedAccess) {
91                visitIndexedAccessType(type as IndexedAccessType);
92            }
93        }
94
95        function visitTypeReference(type: TypeReference): void {
96            visitType(type.target);
97            forEach(getTypeArguments(type), visitType);
98        }
99
100        function visitTypeParameter(type: TypeParameter): void {
101            visitType(getConstraintOfTypeParameter(type));
102        }
103
104        function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void {
105            forEach(type.types, visitType);
106        }
107
108        function visitIndexType(type: IndexType): void {
109            visitType(type.type);
110        }
111
112        function visitIndexedAccessType(type: IndexedAccessType): void {
113            visitType(type.objectType);
114            visitType(type.indexType);
115            visitType(type.constraint);
116        }
117
118        function visitMappedType(type: MappedType): void {
119            visitType(type.typeParameter);
120            visitType(type.constraintType);
121            visitType(type.templateType);
122            visitType(type.modifiersType);
123        }
124
125        function visitSignature(signature: Signature): void {
126            const typePredicate = getTypePredicateOfSignature(signature);
127            if (typePredicate) {
128                visitType(typePredicate.type);
129            }
130            forEach(signature.typeParameters, visitType);
131
132            for (const parameter of signature.parameters) {
133                visitSymbol(parameter);
134            }
135            visitType(getRestTypeOfSignature(signature));
136            visitType(getReturnTypeOfSignature(signature));
137        }
138
139        function visitInterfaceType(interfaceT: InterfaceType): void {
140            visitObjectType(interfaceT);
141            forEach(interfaceT.typeParameters, visitType);
142            forEach(getBaseTypes(interfaceT), visitType);
143            visitType(interfaceT.thisType);
144        }
145
146        function visitObjectType(type: ObjectType): void {
147            const resolved = resolveStructuredTypeMembers(type);
148            for (const info of resolved.indexInfos) {
149                visitType(info.keyType);
150                visitType(info.type);
151            }
152            for (const signature of resolved.callSignatures) {
153                visitSignature(signature);
154            }
155            for (const signature of resolved.constructSignatures) {
156                visitSignature(signature);
157            }
158            for (const p of resolved.properties) {
159                visitSymbol(p);
160            }
161        }
162
163        function visitSymbol(symbol: Symbol | undefined): boolean {
164            if (!symbol) {
165                return false;
166            }
167            const symbolId = getSymbolId(symbol);
168            if (visitedSymbols[symbolId]) {
169                return false;
170            }
171            visitedSymbols[symbolId] = symbol;
172            if (!accept(symbol)) {
173                return true;
174            }
175            const t = getTypeOfSymbol(symbol);
176            visitType(t); // Should handle members on classes and such
177            if (symbol.exports) {
178                symbol.exports.forEach(visitSymbol);
179            }
180            forEach(symbol.declarations, d => {
181                // Type queries are too far resolved when we just visit the symbol's type
182                //  (their type resolved directly to the member deeply referenced)
183                // So to get the intervening symbols, we need to check if there's a type
184                // query node on any of the symbol's declarations and get symbols there
185                if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) {
186                    const query = (d as any).type as TypeQueryNode;
187                    const entity = getResolvedSymbol(getFirstIdentifier(query.exprName));
188                    visitSymbol(entity);
189                }
190            });
191            return false;
192        }
193    }
194}