Use the same logic for completion entry details that we do for getting completion entries.

This commit is contained in:
Cyrus Najmabadi
2015-03-23 21:26:29 -07:00
parent bce9b5ab5a
commit a1cbfdf01d
3 changed files with 134 additions and 80 deletions
+87 -47
View File
@@ -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(<SourceFile>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 ((<FunctionExpression>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(<SourceFile>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 ((<FunctionExpression>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 {
+4 -1
View File
@@ -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;
+43 -32
View File
@@ -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.