diff --git a/Jakefile.js b/Jakefile.js
index 5ae887ee2385e..b3e18e8cb1a3d 100644
--- a/Jakefile.js
+++ b/Jakefile.js
@@ -142,6 +142,7 @@ var harnessSources = harnessCoreSources.concat([
"transform.ts",
"customTransforms.ts",
"programMissingFiles.ts",
+ "symbolWalker.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 551b7063169d1..af45b00c518ef 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -1,5 +1,6 @@
///
///
+///
/* @internal */
namespace ts {
@@ -204,6 +205,7 @@ namespace ts {
getEmitResolver,
getExportsOfModule: getExportsOfModuleAsArray,
getExportsAndPropertiesOfModule,
+ getSymbolWalker: createGetSymbolWalker(getRestTypeOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getIndexTypeOfStructuredType, getConstraintFromTypeParameter, getFirstIdentifier),
getAmbientModules,
getAllAttributesTypeFromJsxOpeningLikeElement: node => {
node = getParseTreeNode(node, isJsxOpeningLikeElement);
diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts
new file mode 100644
index 0000000000000..e20ef9f9d9877
--- /dev/null
+++ b/src/compiler/symbolWalker.ts
@@ -0,0 +1,191 @@
+/** @internal */
+namespace ts {
+ export function createGetSymbolWalker(
+ getRestTypeOfSignature: (sig: Signature) => Type,
+ getReturnTypeOfSignature: (sig: Signature) => Type,
+ getBaseTypes: (type: Type) => Type[],
+ resolveStructuredTypeMembers: (type: ObjectType) => ResolvedType,
+ getTypeOfSymbol: (sym: Symbol) => Type,
+ getResolvedSymbol: (node: Node) => Symbol,
+ getIndexTypeOfStructuredType: (type: Type, kind: IndexKind) => Type,
+ getConstraintFromTypeParameter: (typeParameter: TypeParameter) => Type,
+ getFirstIdentifier: (node: EntityNameOrEntityNameExpression) => Identifier) {
+
+ return getSymbolWalker;
+
+ function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker {
+ const visitedTypes = createMap(); // Key is id as string
+ const visitedSymbols = createMap(); // Key is id as string
+
+ return {
+ walkType: type => {
+ visitedTypes.clear();
+ visitedSymbols.clear();
+ visitType(type);
+ return { visitedTypes: arrayFrom(visitedTypes.values()), visitedSymbols: arrayFrom(visitedSymbols.values()) };
+ },
+ walkSymbol: symbol => {
+ visitedTypes.clear();
+ visitedSymbols.clear();
+ visitSymbol(symbol);
+ return { visitedTypes: arrayFrom(visitedTypes.values()), visitedSymbols: arrayFrom(visitedSymbols.values()) };
+ },
+ };
+
+ function visitType(type: Type): void {
+ if (!type) {
+ return;
+ }
+
+ const typeIdString = type.id.toString();
+ if (visitedTypes.has(typeIdString)) {
+ return;
+ }
+ visitedTypes.set(typeIdString, type);
+
+ // Reuse visitSymbol to visit the type's symbol,
+ // but be sure to bail on recuring into the type if accept declines the symbol.
+ const shouldBail = visitSymbol(type.symbol);
+ if (shouldBail) return;
+
+ // Visit the type's related types, if any
+ if (type.flags & TypeFlags.Object) {
+ const objectType = type as ObjectType;
+ const objectFlags = objectType.objectFlags;
+ if (objectFlags & ObjectFlags.Reference) {
+ visitTypeReference(type as TypeReference);
+ }
+ if (objectFlags & ObjectFlags.Mapped) {
+ visitMappedType(type as MappedType);
+ }
+ if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) {
+ visitInterfaceType(type as InterfaceType);
+ }
+ if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) {
+ visitObjectType(objectType);
+ }
+ }
+ if (type.flags & TypeFlags.TypeParameter) {
+ visitTypeParameter(type as TypeParameter);
+ }
+ if (type.flags & TypeFlags.UnionOrIntersection) {
+ visitUnionOrIntersectionType(type as UnionOrIntersectionType);
+ }
+ if (type.flags & TypeFlags.Index) {
+ visitIndexType(type as IndexType);
+ }
+ if (type.flags & TypeFlags.IndexedAccess) {
+ visitIndexedAccessType(type as IndexedAccessType);
+ }
+ }
+
+ function visitTypeList(types: Type[]): void {
+ if (!types) {
+ return;
+ }
+ for (let i = 0; i < types.length; i++) {
+ visitType(types[i]);
+ }
+ }
+
+ function visitTypeReference(type: TypeReference): void {
+ visitType(type.target);
+ visitTypeList(type.typeArguments);
+ }
+
+ function visitTypeParameter(type: TypeParameter): void {
+ visitType(getConstraintFromTypeParameter(type));
+ }
+
+ function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void {
+ visitTypeList(type.types);
+ }
+
+ function visitIndexType(type: IndexType): void {
+ visitType(type.type);
+ }
+
+ function visitIndexedAccessType(type: IndexedAccessType): void {
+ visitType(type.objectType);
+ visitType(type.indexType);
+ visitType(type.constraint);
+ }
+
+ function visitMappedType(type: MappedType): void {
+ visitType(type.typeParameter);
+ visitType(type.constraintType);
+ visitType(type.templateType);
+ visitType(type.modifiersType);
+ }
+
+ function visitSignature(signature: Signature): void {
+ if (signature.typePredicate) {
+ visitType(signature.typePredicate.type);
+ }
+ visitTypeList(signature.typeParameters);
+
+ for (const parameter of signature.parameters){
+ visitSymbol(parameter);
+ }
+ visitType(getRestTypeOfSignature(signature));
+ visitType(getReturnTypeOfSignature(signature));
+ }
+
+ function visitInterfaceType(interfaceT: InterfaceType): void {
+ visitObjectType(interfaceT);
+ visitTypeList(interfaceT.typeParameters);
+ visitTypeList(getBaseTypes(interfaceT));
+ visitType(interfaceT.thisType);
+ }
+
+ function visitObjectType(type: ObjectType): void {
+ const stringIndexType = getIndexTypeOfStructuredType(type, IndexKind.String);
+ visitType(stringIndexType);
+ const numberIndexType = getIndexTypeOfStructuredType(type, IndexKind.Number);
+ visitType(numberIndexType);
+
+ // The two checks above *should* have already resolved the type (if needed), so this should be cached
+ const resolved = resolveStructuredTypeMembers(type);
+ for (const signature of resolved.callSignatures) {
+ visitSignature(signature);
+ }
+ for (const signature of resolved.constructSignatures) {
+ visitSignature(signature);
+ }
+ for (const p of resolved.properties) {
+ visitSymbol(p);
+ }
+ }
+
+ function visitSymbol(symbol: Symbol): boolean {
+ if (!symbol) {
+ return;
+ }
+ const symbolIdString = getSymbolId(symbol).toString();
+ if (visitedSymbols.has(symbolIdString)) {
+ return;
+ }
+ visitedSymbols.set(symbolIdString, symbol);
+ if (!accept(symbol)) {
+ return true;
+ }
+ const t = getTypeOfSymbol(symbol);
+ visitType(t); // Should handle members on classes and such
+ if (symbol.flags & SymbolFlags.HasExports) {
+ symbol.exports.forEach(visitSymbol);
+ }
+ forEach(symbol.declarations, d => {
+ // Type queries are too far resolved when we just visit the symbol's type
+ // (their type resolved directly to the member deeply referenced)
+ // So to get the intervening symbols, we need to check if there's a type
+ // query node on any of the symbol's declarations and get symbols there
+ if ((d as any).type && (d as any).type.kind === SyntaxKind.TypeQuery) {
+ const query = (d as any).type as TypeQueryNode;
+ const entity = getResolvedSymbol(getFirstIdentifier(query.exprName));
+ visitSymbol(entity);
+ }
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json
index 3709d65b7fd23..c048359fcb7a4 100644
--- a/src/compiler/tsconfig.json
+++ b/src/compiler/tsconfig.json
@@ -14,6 +14,7 @@
"parser.ts",
"utilities.ts",
"binder.ts",
+ "symbolWalker.ts",
"checker.ts",
"factory.ts",
"visitor.ts",
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 608bc779042df..26bf5e1a91f85 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -2625,6 +2625,8 @@ namespace ts {
/* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined;
+ /* @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker;
+
// Should not be called directly. Should only be accessed through the Program instance.
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
/* @internal */ getGlobalDiagnostics(): Diagnostic[];
@@ -2669,6 +2671,14 @@ namespace ts {
InTypeAlias = 1 << 23, // Writing type in type alias declaration
}
+ /* @internal */
+ export interface SymbolWalker {
+ /** Note: Return values are not ordered. */
+ walkType(root: Type): { visitedTypes: ReadonlyArray, visitedSymbols: ReadonlyArray };
+ /** Note: Return values are not ordered. */
+ walkSymbol(root: Symbol): { visitedTypes: ReadonlyArray, visitedSymbols: ReadonlyArray };
+ }
+
export interface SymbolDisplayBuilder {
buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void;
buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void;
@@ -3367,6 +3377,7 @@ namespace ts {
// Type parameters (TypeFlags.TypeParameter)
export interface TypeParameter extends TypeVariable {
+ /** Retrieve using getConstraintFromTypeParameter */
constraint: Type; // Constraint
default?: Type;
/* @internal */
diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json
index 66ca2fc3f4837..9165e59cb0a1a 100644
--- a/src/harness/tsconfig.json
+++ b/src/harness/tsconfig.json
@@ -21,6 +21,7 @@
"../compiler/parser.ts",
"../compiler/utilities.ts",
"../compiler/binder.ts",
+ "../compiler/symbolWalker.ts",
"../compiler/checker.ts",
"../compiler/factory.ts",
"../compiler/visitor.ts",
@@ -103,6 +104,7 @@
"./unittests/services/preProcessFile.ts",
"./unittests/services/patternMatcher.ts",
"./unittests/session.ts",
+ "./unittests/symbolWalker.ts",
"./unittests/versionCache.ts",
"./unittests/convertToBase64.ts",
"./unittests/transpile.ts",
diff --git a/src/harness/unittests/symbolWalker.ts b/src/harness/unittests/symbolWalker.ts
new file mode 100644
index 0000000000000..6d38fbb5198c9
--- /dev/null
+++ b/src/harness/unittests/symbolWalker.ts
@@ -0,0 +1,51 @@
+///
+
+namespace ts {
+ describe("Symbol Walker", () => {
+ function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker) => void) {
+ it(description, () => {
+ let {result} = Harness.Compiler.compileFiles([{
+ unitName: "main.ts",
+ content: source
+ }], [], {}, {}, "/");
+ let file = result.program.getSourceFile("main.ts");
+ let checker = result.program.getTypeChecker();
+ verifier(file, checker);
+
+ result = undefined;
+ file = undefined;
+ checker = undefined;
+ });
+ }
+
+ test("can be created", `
+interface Bar {
+ x: number;
+ y: number;
+ history: Bar[];
+}
+export default function foo(a: number, b: Bar): void {}`, (file, checker) => {
+ let foundCount = 0;
+ let stdLibRefSymbols = 0;
+ const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"];
+ const walker = checker.getSymbolWalker(symbol => {
+ const isStdLibSymbol = forEach(symbol.declarations, d => {
+ return getSourceFileOfNode(d).hasNoDefaultLib;
+ });
+ if (isStdLibSymbol) {
+ stdLibRefSymbols++;
+ return false; // Don't traverse into the stdlib. That's unnecessary for this test.
+ }
+ assert.equal(symbol.name, expectedSymbols[foundCount]);
+ foundCount++;
+ return true;
+ });
+ const symbols = checker.getExportsOfModule(file.symbol);
+ for (const symbol of symbols) {
+ walker.walkSymbol(symbol);
+ }
+ assert.equal(foundCount, expectedSymbols.length);
+ assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history
+ });
+ });
+}
\ No newline at end of file
diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json
index f4ca2a7f130f5..d73014a93a24a 100644
--- a/src/services/tsconfig.json
+++ b/src/services/tsconfig.json
@@ -14,6 +14,7 @@
"../compiler/parser.ts",
"../compiler/utilities.ts",
"../compiler/binder.ts",
+ "../compiler/symbolWalker.ts",
"../compiler/checker.ts",
"../compiler/factory.ts",
"../compiler/visitor.ts",