Skip to content

Commit 053b915

Browse files
weswighamamcasey
authored andcommitted
Rebase SymbolWalker change onto master
From PR microsoft#9847.
1 parent b217d96 commit 053b915

File tree

8 files changed

+231
-0
lines changed

8 files changed

+231
-0
lines changed

Diff for: Jakefile.js

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ var harnessSources = harnessCoreSources.concat([
140140
"transform.ts",
141141
"customTransforms.ts",
142142
"programMissingFiles.ts",
143+
"symbolWalker.ts",
143144
].map(function (f) {
144145
return path.join(unittestsDirectory, f);
145146
})).concat([

Diff for: 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),
207209
getAmbientModules,
208210
getAllAttributesTypeFromJsxOpeningLikeElement: node => {
209211
node = getParseTreeNode(node, isJsxOpeningLikeElement);

Diff for: src/compiler/symbolWalker.ts

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

Diff for: 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",

Diff for: src/compiler/types.ts

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

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

2628+
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,12 @@ namespace ts {
26692671
InTypeAlias = 1 << 23, // Writing type in type alias declaration
26702672
}
26712673

2674+
export interface SymbolWalker {
2675+
visitType(type: Type): void;
2676+
visitSymbol(symbol: Symbol): void;
2677+
reset(accept?: (symbol: Symbol) => boolean): void;
2678+
}
2679+
26722680
export interface SymbolDisplayBuilder {
26732681
buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void;
26742682
buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void;

Diff for: 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",

Diff for: src/harness/unittests/symbolWalker.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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, walker: SymbolWalker) => 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+
let walker = checker.getSymbolWalker();
14+
verifier(file, checker, walker);
15+
16+
result = undefined;
17+
file = undefined;
18+
checker = undefined;
19+
walker = undefined;
20+
});
21+
}
22+
23+
test("can be created", `
24+
interface Bar {
25+
x: number;
26+
y: number;
27+
history: Bar[];
28+
}
29+
export default function foo(a: number, b: Bar): void {}`, (file, checker, walker) => {
30+
let foundCount = 0;
31+
let stdLibRefSymbols = 0;
32+
const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"];
33+
walker.reset(symbol => {
34+
const isStdLibSymbol = forEach(symbol.declarations, d => {
35+
return getSourceFileOfNode(d).hasNoDefaultLib;
36+
});
37+
if (isStdLibSymbol) {
38+
stdLibRefSymbols++;
39+
return false; // Don't traverse into the stdlib. That's unnecessary for this test.
40+
}
41+
assert.equal(symbol.name, expectedSymbols[foundCount]);
42+
foundCount++;
43+
return true;
44+
});
45+
const symbols = checker.getExportsOfModule(file.symbol);
46+
for (const symbol of symbols) {
47+
walker.visitSymbol(symbol);
48+
}
49+
assert.equal(foundCount, expectedSymbols.length);
50+
assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history
51+
});
52+
});
53+
}

Diff for: 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)