mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 03:20:56 -06:00
parent
b217d96cc1
commit
053b915061
@ -140,6 +140,7 @@ var harnessSources = harnessCoreSources.concat([
|
||||
"transform.ts",
|
||||
"customTransforms.ts",
|
||||
"programMissingFiles.ts",
|
||||
"symbolWalker.ts",
|
||||
].map(function (f) {
|
||||
return path.join(unittestsDirectory, f);
|
||||
})).concat([
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/// <reference path="moduleNameResolver.ts"/>
|
||||
/// <reference path="binder.ts"/>
|
||||
/// <reference path="symbolWalker.ts" />
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
@ -204,6 +205,7 @@ namespace ts {
|
||||
getEmitResolver,
|
||||
getExportsOfModule: getExportsOfModuleAsArray,
|
||||
getExportsAndPropertiesOfModule,
|
||||
getSymbolWalker: createGetSymbolWalker(getRestTypeOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getIndexTypeOfStructuredType),
|
||||
getAmbientModules,
|
||||
getAllAttributesTypeFromJsxOpeningLikeElement: node => {
|
||||
node = getParseTreeNode(node, isJsxOpeningLikeElement);
|
||||
|
||||
163
src/compiler/symbolWalker.ts
Normal file
163
src/compiler/symbolWalker.ts
Normal file
@ -0,0 +1,163 @@
|
||||
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) {
|
||||
|
||||
return getSymbolWalker;
|
||||
|
||||
function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker {
|
||||
let visited: Type[] = [];
|
||||
let visitedSymbols: Symbol[] = [];
|
||||
|
||||
return {
|
||||
visitType,
|
||||
visitSymbol,
|
||||
reset: (newCallback: (symbol: Symbol) => boolean = () => true) => {
|
||||
accept = newCallback;
|
||||
visited = [];
|
||||
visitedSymbols = [];
|
||||
}
|
||||
};
|
||||
|
||||
function visitType(type: Type): void {
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
if (contains(visited, type)) {
|
||||
return;
|
||||
}
|
||||
visited.push(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.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);
|
||||
}
|
||||
}
|
||||
|
||||
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(type.constraint);
|
||||
}
|
||||
|
||||
function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void {
|
||||
visitTypeList(type.types);
|
||||
}
|
||||
|
||||
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.String);
|
||||
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;
|
||||
}
|
||||
if (contains(visitedSymbols, symbol)) {
|
||||
return;
|
||||
}
|
||||
visitedSymbols.push(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 = leftmostSymbol(query.exprName);
|
||||
visitSymbol(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function leftmostSymbol(expr: QualifiedName | Identifier): Symbol {
|
||||
if (expr.kind === SyntaxKind.Identifier) {
|
||||
return getResolvedSymbol(expr as Identifier);
|
||||
}
|
||||
else {
|
||||
return leftmostSymbol((expr as QualifiedName).left);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@
|
||||
"parser.ts",
|
||||
"utilities.ts",
|
||||
"binder.ts",
|
||||
"symbolWalker.ts",
|
||||
"checker.ts",
|
||||
"factory.ts",
|
||||
"visitor.ts",
|
||||
|
||||
@ -2625,6 +2625,8 @@ namespace ts {
|
||||
|
||||
/* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol | undefined;
|
||||
|
||||
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,12 @@ namespace ts {
|
||||
InTypeAlias = 1 << 23, // Writing type in type alias declaration
|
||||
}
|
||||
|
||||
export interface SymbolWalker {
|
||||
visitType(type: Type): void;
|
||||
visitSymbol(symbol: Symbol): void;
|
||||
reset(accept?: (symbol: Symbol) => boolean): void;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -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",
|
||||
|
||||
53
src/harness/unittests/symbolWalker.ts
Normal file
53
src/harness/unittests/symbolWalker.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
|
||||
namespace ts {
|
||||
describe("Symbol Walker", () => {
|
||||
function test(description: string, source: string, verifier: (file: SourceFile, checker: TypeChecker, walker: SymbolWalker) => 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();
|
||||
let walker = checker.getSymbolWalker();
|
||||
verifier(file, checker, walker);
|
||||
|
||||
result = undefined;
|
||||
file = undefined;
|
||||
checker = undefined;
|
||||
walker = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
test("can be created", `
|
||||
interface Bar {
|
||||
x: number;
|
||||
y: number;
|
||||
history: Bar[];
|
||||
}
|
||||
export default function foo(a: number, b: Bar): void {}`, (file, checker, walker) => {
|
||||
let foundCount = 0;
|
||||
let stdLibRefSymbols = 0;
|
||||
const expectedSymbols = ["default", "a", "b", "Bar", "x", "y", "history"];
|
||||
walker.reset(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.visitSymbol(symbol);
|
||||
}
|
||||
assert.equal(foundCount, expectedSymbols.length);
|
||||
assert.equal(stdLibRefSymbols, 1); // Expect 1 stdlib entry symbol - the implicit Array referenced by Bar.history
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user