Skip to content

Commit 49676c5

Browse files
authored
Merge pull request #17844 from amcasey/SymbolWalker
Resuscitate the SymbolWalker API
2 parents 2b9aba4 + 8944774 commit 49676c5

File tree

8 files changed

+260
-0
lines changed

8 files changed

+260
-0
lines changed

Jakefile.js

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ var harnessSources = harnessCoreSources.concat([
142142
"transform.ts",
143143
"customTransforms.ts",
144144
"programMissingFiles.ts",
145+
"symbolWalker.ts",
145146
].map(function (f) {
146147
return path.join(unittestsDirectory, f);
147148
})).concat([

src/compiler/checker.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference path="moduleNameResolver.ts"/>
22
/// <reference path="binder.ts"/>
3+
/// <reference path="symbolWalker.ts" />
34

45
/* @internal */
56
namespace ts {
@@ -204,6 +205,7 @@ namespace ts {
204205
getEmitResolver,
205206
getExportsOfModule: getExportsOfModuleAsArray,
206207
getExportsAndPropertiesOfModule,
208+
getSymbolWalker: createGetSymbolWalker(getRestTypeOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getIndexTypeOfStructuredType, getConstraintFromTypeParameter, getFirstIdentifier),
207209
getAmbientModules,
208210
getAllAttributesTypeFromJsxOpeningLikeElement: node => {
209211
node = getParseTreeNode(node, isJsxOpeningLikeElement);

src/compiler/symbolWalker.ts

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

src/compiler/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"parser.ts",
1515
"utilities.ts",
1616
"binder.ts",
17+
"symbolWalker.ts",
1718
"checker.ts",
1819
"factory.ts",
1920
"visitor.ts",

src/compiler/types.ts

+11
Original file line numberDiff line numberDiff line change
@@ -2625,6 +2625,8 @@ namespace ts {
26252625

26262626
/* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined;
26272627

2628+
/* @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker;
2629+
26282630
// Should not be called directly. Should only be accessed through the Program instance.
26292631
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
26302632
/* @internal */ getGlobalDiagnostics(): Diagnostic[];
@@ -2669,6 +2671,14 @@ namespace ts {
26692671
InTypeAlias = 1 << 23, // Writing type in type alias declaration
26702672
}
26712673

2674+
/* @internal */
2675+
export interface SymbolWalker {
2676+
/** Note: Return values are not ordered. */
2677+
walkType(root: Type): { visitedTypes: ReadonlyArray<Type>, visitedSymbols: ReadonlyArray<Symbol> };
2678+
/** Note: Return values are not ordered. */
2679+
walkSymbol(root: Symbol): { visitedTypes: ReadonlyArray<Type>, visitedSymbols: ReadonlyArray<Symbol> };
2680+
}
2681+
26722682
export interface SymbolDisplayBuilder {
26732683
buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void;
26742684
buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void;
@@ -3367,6 +3377,7 @@ namespace ts {
33673377

33683378
// Type parameters (TypeFlags.TypeParameter)
33693379
export interface TypeParameter extends TypeVariable {
3380+
/** Retrieve using getConstraintFromTypeParameter */
33703381
constraint: Type; // Constraint
33713382
default?: Type;
33723383
/* @internal */

src/harness/tsconfig.json

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"../compiler/parser.ts",
2222
"../compiler/utilities.ts",
2323
"../compiler/binder.ts",
24+
"../compiler/symbolWalker.ts",
2425
"../compiler/checker.ts",
2526
"../compiler/factory.ts",
2627
"../compiler/visitor.ts",
@@ -103,6 +104,7 @@
103104
"./unittests/services/preProcessFile.ts",
104105
"./unittests/services/patternMatcher.ts",
105106
"./unittests/session.ts",
107+
"./unittests/symbolWalker.ts",
106108
"./unittests/versionCache.ts",
107109
"./unittests/convertToBase64.ts",
108110
"./unittests/transpile.ts",

src/harness/unittests/symbolWalker.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// <reference path="..\harness.ts" />
2+
3+
namespace ts {
4+
describe("Symbol Walker", () => {
5+
function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) {
6+
it(description, () => {
7+
let {result} = Harness.Compiler.compileFiles([{
8+
unitName: "main.ts",
9+
content: source
10+
}], [], {}, {}, "/");
11+
let file = result.program.getSourceFile("main.ts");
12+
let checker = result.program.getTypeChecker();
13+
verifier(file, checker);
14+
15+
result = undefined;
16+
file = undefined;
17+
checker = undefined;
18+
});
19+
}
20+
21+
test("can be created", `
22+
interface Bar {
23+
x: number;
24+
y: number;
25+
history: Bar[];
26+
}
27+
export default function foo(a: number, b: Bar): void {}`, (file, checker) => {
28+
let foundCount = 0;
29+
let stdLibRefSymbols = 0;
30+
const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"];
31+
const walker = checker.getSymbolWalker(symbol => {
32+
const isStdLibSymbol = forEach(symbol.declarations, d => {
33+
return getSourceFileOfNode(d).hasNoDefaultLib;
34+
});
35+
if (isStdLibSymbol) {
36+
stdLibRefSymbols++;
37+
return false; // Don't traverse into the stdlib. That's unnecessary for this test.
38+
}
39+
assert.equal(symbol.name, expectedSymbols[foundCount]);
40+
foundCount++;
41+
return true;
42+
});
43+
const symbols = checker.getExportsOfModule(file.symbol);
44+
for (const symbol of symbols) {
45+
walker.walkSymbol(symbol);
46+
}
47+
assert.equal(foundCount, expectedSymbols.length);
48+
assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history
49+
});
50+
});
51+
}

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"../compiler/parser.ts",
1515
"../compiler/utilities.ts",
1616
"../compiler/binder.ts",
17+
"../compiler/symbolWalker.ts",
1718
"../compiler/checker.ts",
1819
"../compiler/factory.ts",
1920
"../compiler/visitor.ts",

0 commit comments

Comments
 (0)