Skip to content

Commit

Permalink
Merge pull request #17844 from amcasey/SymbolWalker
Browse files Browse the repository at this point in the history
Resuscitate the SymbolWalker API
  • Loading branch information
amcasey authored Aug 23, 2017
2 parents 2b9aba4 + 8944774 commit 49676c5
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 0 deletions.
1 change: 1 addition & 0 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference path="moduleNameResolver.ts"/>
/// <reference path="binder.ts"/>
/// <reference path="symbolWalker.ts" />

/* @internal */
namespace ts {
Expand Down Expand Up @@ -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);
Expand Down
191 changes: 191 additions & 0 deletions src/compiler/symbolWalker.ts
Original file line number Diff line number Diff line change
@@ -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<Type>(); // Key is id as string
const visitedSymbols = createMap<Symbol>(); // 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);
}
});
}
}
}
}
1 change: 1 addition & 0 deletions src/compiler/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"parser.ts",
"utilities.ts",
"binder.ts",
"symbolWalker.ts",
"checker.ts",
"factory.ts",
"visitor.ts",
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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<Type>, visitedSymbols: ReadonlyArray<Symbol> };
/** Note: Return values are not ordered. */
walkSymbol(root: Symbol): { visitedTypes: ReadonlyArray<Type>, visitedSymbols: ReadonlyArray<Symbol> };
}

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;
Expand Down Expand Up @@ -3367,6 +3377,7 @@ namespace ts {

// Type parameters (TypeFlags.TypeParameter)
export interface TypeParameter extends TypeVariable {
/** Retrieve using getConstraintFromTypeParameter */
constraint: Type; // Constraint
default?: Type;
/* @internal */
Expand Down
2 changes: 2 additions & 0 deletions src/harness/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
51 changes: 51 additions & 0 deletions src/harness/unittests/symbolWalker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// <reference path="..\harness.ts" />

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
});
});
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 49676c5

Please sign in to comment.