diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ab391639d0..e3cd0da60d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -90,10 +90,12 @@ namespace ts { let lastContainer: Node; let symbolCount = 0; let Symbol = objectAllocator.getSymbolConstructor(); + let classifiableNames: Map = {}; if (!file.locals) { bind(file); file.symbolCount = symbolCount; + file.classifiableNames = classifiableNames; } return; @@ -194,6 +196,11 @@ namespace ts { symbol = hasProperty(symbolTable, name) ? symbolTable[name] : (symbolTable[name] = createSymbol(SymbolFlags.None, name)); + + if (name && (includes & SymbolFlags.Classifiable)) { + classifiableNames[name] = name; + } + if (symbol.flags & excludes) { if (node.name) { node.name.parent = node; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index aa8a413aaa..8ab3e73858 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -148,6 +148,7 @@ namespace ts { let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; + let classifiableNames: Map; let start = new Date().getTime(); @@ -172,6 +173,7 @@ namespace ts { getDeclarationDiagnostics, getCompilerOptionsDiagnostics, getTypeChecker, + getClassifiableNames, getDiagnosticsProducingTypeChecker, getCommonSourceDirectory: () => commonSourceDirectory, emit, @@ -183,6 +185,20 @@ namespace ts { }; return program; + function getClassifiableNames() { + if (!classifiableNames) { + // Initialize a checker so that all our files are bound. + getTypeChecker(); + classifiableNames = {}; + + for (let sourceFile of files) { + copyMap(sourceFile.classifiableNames, classifiableNames); + } + } + + return classifiableNames; + } + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { return { getCanonicalFileName: fileName => host.getCanonicalFileName(fileName), diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cf7a6dff2d..8be7c1792a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1172,6 +1172,8 @@ namespace ts { // Stores a line map for the file. // This field should never be used directly to obtain line map, use getLineMap function instead. /* @internal */ lineMap: number[]; + + /* @internal */ classifiableNames?: Map; } export interface ScriptReferenceHost { @@ -1223,6 +1225,8 @@ namespace ts { // language service). /* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker; + /* @internal */ getClassifiableNames(): Map; + /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; /* @internal */ getSymbolCount(): number; @@ -1519,6 +1523,11 @@ namespace ts { PropertyOrAccessor = Property | Accessor, Export = ExportNamespace | ExportType | ExportValue, + + /* @internal */ + // The set of things we consider semantically classifiable. Used to speed up the LS during + // classification. + Classifiable = Class | Enum | TypeAlias | Interface | TypeParameter | Module, } export interface Symbol { diff --git a/src/services/services.ts b/src/services/services.ts index 105859f7cc..3939e62cfd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5992,6 +5992,7 @@ namespace ts { let typeChecker = program.getTypeChecker(); let result: number[] = []; + let classifiableNames = program.getClassifiableNames(); processNode(sourceFile); return { spans: result, endOfLineState: EndOfLineState.None }; @@ -6004,6 +6005,9 @@ namespace ts { function classifySymbol(symbol: Symbol, meaningAtPosition: SemanticMeaning): ClassificationType { let flags = symbol.getFlags(); + if ((flags & SymbolFlags.Classifiable) === SymbolFlags.None) { + return; + } if (flags & SymbolFlags.Class) { return ClassificationType.className; @@ -6048,11 +6052,18 @@ namespace ts { // Only walk into nodes that intersect the requested span. if (node && textSpanIntersectsWith(span, node.getFullStart(), node.getFullWidth())) { if (node.kind === SyntaxKind.Identifier && !nodeIsMissing(node)) { - let symbol = typeChecker.getSymbolAtLocation(node); - if (symbol) { - let type = classifySymbol(symbol, getMeaningFromLocation(node)); - if (type) { - pushClassification(node.getStart(), node.getWidth(), type); + let identifier = node; + + // Only bother calling into the typechecker if this is an identifier that + // could possibly resolve to a type name. This makes classification run + // in a third of the time it would normally take. + if (classifiableNames[identifier.text]) { + let symbol = typeChecker.getSymbolAtLocation(node); + if (symbol) { + let type = classifySymbol(symbol, getMeaningFromLocation(node)); + if (type) { + pushClassification(node.getStart(), node.getWidth(), type); + } } } }