From a1cbfdf01d62f6fa39cc1951026b9fe868eefada Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 23 Mar 2015 21:26:29 -0700 Subject: [PATCH] Use the same logic for completion entry details that we do for getting completion entries. --- src/compiler/checker.ts | 134 +++++++++++++++++++++++++-------------- src/compiler/types.ts | 5 +- src/services/services.ts | 75 ++++++++++++---------- 3 files changed, 134 insertions(+), 80 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d50e440c5e9..34b49a33ae3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10547,62 +10547,102 @@ module ts { return false; } - function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { + function getSymbolsInScope(location: Node, meaning: SymbolFlags, predicate?: (symbol: Symbol) => boolean): Symbol[] { let symbols: SymbolTable = {}; let memberFlags: NodeFlags = 0; - function copySymbol(symbol: Symbol, meaning: SymbolFlags) { - if (symbol.flags & meaning) { - let id = symbol.name; - if (!isReservedMemberName(id) && !hasProperty(symbols, id)) { - symbols[id] = symbol; - } - } - } - function copySymbols(source: SymbolTable, meaning: SymbolFlags) { - if (meaning) { - for (let id in source) { - if (hasProperty(source, id)) { - copySymbol(source[id], meaning); - } - } - } - } if (isInsideWithStatementBody(location)) { // We cannot answer semantic questions within a with block, do not proceed any further return []; } - while (location) { - if (location.locals && !isGlobalSourceFile(location)) { - copySymbols(location.locals, meaning); - } - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalModule(location)) break; - case SyntaxKind.ModuleDeclaration: - copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.ModuleMember); - break; - case SyntaxKind.EnumDeclaration: - copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.EnumMember); - break; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - if (!(memberFlags & NodeFlags.Static)) { - copySymbols(getSymbolOfNode(location).members, meaning & SymbolFlags.Type); - } - break; - case SyntaxKind.FunctionExpression: - if ((location).name) { - copySymbol(location.symbol, meaning); - } - break; - } - memberFlags = location.flags; - location = location.parent; - } - copySymbols(globals, meaning); + populateSymbols(); + return mapToArray(symbols); + + function populateSymbols() { + while (location) { + if (location.locals && !isGlobalSourceFile(location)) { + if (copySymbols(location.locals, meaning)) { + return; + } + } + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalModule(location)) { + break; + } + case SyntaxKind.ModuleDeclaration: + if (copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.ModuleMember)) { + return; + } + break; + case SyntaxKind.EnumDeclaration: + if (copySymbols(getSymbolOfNode(location).exports, meaning & SymbolFlags.EnumMember)) { + return; + } + break; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + if (!(memberFlags & NodeFlags.Static)) { + if (copySymbols(getSymbolOfNode(location).members, meaning & SymbolFlags.Type)) { + return; + } + } + break; + case SyntaxKind.FunctionExpression: + if ((location).name) { + if (copySymbol(location.symbol, meaning)) { + return; + } + } + break; + } + memberFlags = location.flags; + location = location.parent; + } + if (copySymbols(globals, meaning)) { + return; + } + } + + // Returns 'true' if we should stop processing symbols. + function copySymbol(symbol: Symbol, meaning: SymbolFlags): boolean { + if (symbol.flags & meaning) { + let id = symbol.name; + if (!isReservedMemberName(id) && !hasProperty(symbols, id)) { + if (predicate) { + // If we were supplied a predicate function, then check if this symbol + // matches with it. If so, we're done and can immediately return. + // Otherwise, just ignore this symbol and keep going. + if (predicate(symbol)) { + symbols[id] = symbol; + return true; + } + } + else { + // If no predicate was supplied, then just add the symbol as is. + symbols[id] = symbol; + } + } + } + + return false; + } + + function copySymbols(source: SymbolTable, meaning: SymbolFlags): boolean { + if (meaning) { + for (let id in source) { + if (hasProperty(source, id)) { + if (copySymbol(source[id], meaning)) { + return true; + } + } + } + } + + return false; + } } function isTypeDeclarationName(name: Node): boolean { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6d0b1309a72..71ddb4d3261 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1092,7 +1092,10 @@ module ts { getSignaturesOfType(type: Type, kind: SignatureKind): Signature[]; getIndexTypeOfType(type: Type, kind: IndexKind): Type; getReturnTypeOfSignature(signature: Signature): Type; - getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; + + // If 'predicate' is supplied, then only the first symbol in scope matching the predicate + // will be returned. Otherwise, all symbols in scope will be returned. + getSymbolsInScope(location: Node, meaning: SymbolFlags, predicate?: (symbol: Symbol) => boolean): Symbol[]; getSymbolAtLocation(node: Node): Symbol; getShorthandAssignmentValueSymbol(location: Node): Symbol; getTypeAtLocation(node: Node): Type; diff --git a/src/services/services.ts b/src/services/services.ts index da14d747b12..5225bca5daf 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2421,7 +2421,6 @@ module ts { isValid = isIdentifierPart(displayName.charCodeAt(i), target); } - if (isValid) { return unescapeIdentifier(displayName); } @@ -2450,7 +2449,21 @@ module ts { }; } - function getCompletionSymbols(fileName: string, position: number): { symbols: Symbol[], isMemberCompletion: boolean, isNewIdentifierLocation: boolean, location: Node }{ + function getCompletionSymbols(fileName: string, position: number, symbolName?: string): { symbols: Symbol[], isMemberCompletion: boolean, isNewIdentifierLocation: boolean, location: Node } { + let result = getCompletionSymbolsWorker(fileName, position, symbolName); + if (!result) { + return undefined; + } + + if (result.symbols && symbolName) { + var target = program.getCompilerOptions().target; + result.symbols = filter(result.symbols, s => getValidCompletionEntryDisplayName(s, target) === symbolName); + } + + return result; + } + + function getCompletionSymbolsWorker(fileName: string, position: number, symbolName: string): { symbols: Symbol[], isMemberCompletion: boolean, isNewIdentifierLocation: boolean, location: Node }{ let syntacticStart = new Date().getTime(); let sourceFile = getValidSourceFile(fileName); @@ -2502,7 +2515,8 @@ module ts { } let location = getTouchingPropertyName(sourceFile, position); - // Populate the completion list + var target = program.getCompilerOptions().target; + let semanticStart = new Date().getTime(); let isMemberCompletion: boolean; let isNewIdentifierLocation: boolean; @@ -2580,7 +2594,12 @@ module ts { /// TODO filter meaning based on the current context let scopeNode = getScopeNode(previousToken, position, sourceFile); let symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias; - symbols = typeInfoResolver.getSymbolsInScope(scopeNode, symbolMeanings); + + // Filter down to the symbol that matches the symbolName if we were given one. + let predicate: (s: Symbol) => boolean = symbolName + ? s => getValidCompletionEntryDisplayName(s, target) === symbolName + : undefined; + symbols = typeInfoResolver.getSymbolsInScope(scopeNode, symbolMeanings, predicate); } } @@ -2938,37 +2957,27 @@ module ts { } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { - // Note: No need to call synchronizeHostData, as we have captured all the data we need - // in the getCompletionsAtPosition earlier - let sourceFile = getValidSourceFile(fileName); + synchronizeHostData(); - let session = activeCompletionSession; - - // Ensure that the current active completion session is still valid for this request - if (!session || session.fileName !== fileName || session.position !== position) { - return undefined; + // Look up a completion symbol with this name. + let result = getCompletionSymbols(fileName, position, entryName); + if (result) { + let { symbols, isMemberCompletion, isNewIdentifierLocation, location } = result; + if (symbols && symbols.length > 0) { + let symbol = symbols[0]; + let displayPartsDocumentationsAndSymbolKind = getSymbolDisplayPartsDocumentationAndSymbolKind(symbol, getValidSourceFile(fileName), location, typeInfoResolver, location, SemanticMeaning.All); + return { + name: entryName, + kind: displayPartsDocumentationsAndSymbolKind.symbolKind, + kindModifiers: getSymbolModifiers(symbol), + displayParts: displayPartsDocumentationsAndSymbolKind.displayParts, + documentation: displayPartsDocumentationsAndSymbolKind.documentation + }; + } } - let symbol = lookUp(activeCompletionSession.symbols, escapeIdentifier(entryName)); - if (symbol) { - let location = getTouchingPropertyName(sourceFile, position); - let completionEntry = createCompletionEntry(symbol, session.typeChecker, location); - // TODO(drosen): Right now we just permit *all* semantic meanings when calling 'getSymbolKind' - // which is permissible given that it is backwards compatible; but really we should consider - // passing the meaning for the node so that we don't report that a suggestion for a value is an interface. - // We COULD also just do what 'getSymbolModifiers' does, which is to use the first declaration. - Debug.assert(session.typeChecker.getTypeOfSymbolAtLocation(symbol, location) !== undefined, "Could not find type for symbol"); - let displayPartsDocumentationsAndSymbolKind = getSymbolDisplayPartsDocumentationAndSymbolKind(symbol, getValidSourceFile(fileName), location, session.typeChecker, location, SemanticMeaning.All); - return { - name: entryName, - kind: displayPartsDocumentationsAndSymbolKind.symbolKind, - kindModifiers: completionEntry.kindModifiers, - displayParts: displayPartsDocumentationsAndSymbolKind.displayParts, - documentation: displayPartsDocumentationsAndSymbolKind.documentation - }; - } - else { - // No symbol, it is a keyword + let keywordCompletion = filter(keywordCompletions, c => c.name === entryName); + if (keywordCompletion) { return { name: entryName, kind: ScriptElementKind.keyword, @@ -2977,6 +2986,8 @@ module ts { documentation: undefined }; } + + return undefined; } // TODO(drosen): use contextual SemanticMeaning.