mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-18 07:29:16 -05:00
Merge pull request #2396 from Microsoft/completionsInIncompleteConstructs
Better completions in incomplete constructs
This commit is contained in:
@@ -620,7 +620,7 @@ module FourSlash {
|
||||
this.scenarioActions.push('<VerifyCompletionDoesNotContainItem ItemName="' + escapeXmlAttributeValue(symbol) + '" />');
|
||||
|
||||
var members = this.getMemberListAtCaret();
|
||||
if (members.entries.filter(e => e.name === symbol).length !== 0) {
|
||||
if (members && members.entries.filter(e => e.name === symbol).length !== 0) {
|
||||
this.raiseError('Member list did contain ' + symbol);
|
||||
}
|
||||
}
|
||||
@@ -691,7 +691,12 @@ module FourSlash {
|
||||
|
||||
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
|
||||
var completions = this.getCompletionListAtCaret();
|
||||
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind);
|
||||
if (completions) {
|
||||
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind);
|
||||
}
|
||||
else {
|
||||
this.raiseError(`No completions at position '${ this.currentCaretPosition }' when looking for '${ symbol }'.`);
|
||||
}
|
||||
}
|
||||
|
||||
public verifyCompletionListDoesNotContain(symbol: string) {
|
||||
@@ -699,7 +704,7 @@ module FourSlash {
|
||||
this.scenarioActions.push('<VerifyCompletionDoesNotContainItem ItemName="' + escapeXmlAttributeValue(symbol) + '" />');
|
||||
|
||||
var completions = this.getCompletionListAtCaret();
|
||||
if (completions && completions.entries && completions.entries.filter(e => e.name === symbol).length !== 0) {
|
||||
if (completions && completions.entries.filter(e => e.name === symbol).length !== 0) {
|
||||
this.raiseError('Completion list did contain ' + symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,11 +215,7 @@ module ts.formatting {
|
||||
function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter {
|
||||
return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));
|
||||
}
|
||||
|
||||
function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
|
||||
|
||||
export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFile): boolean {
|
||||
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
|
||||
let elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile);
|
||||
@@ -403,128 +399,5 @@ module ts.formatting {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if node ends with 'expectedLastToken'.
|
||||
* If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
|
||||
*/
|
||||
function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean {
|
||||
let children = n.getChildren(sourceFile);
|
||||
if (children.length) {
|
||||
let last = children[children.length - 1];
|
||||
if (last.kind === expectedLastToken) {
|
||||
return true;
|
||||
}
|
||||
else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) {
|
||||
return children[children.length - 2].kind === expectedLastToken;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is always called when position of the cursor is located after the node
|
||||
*/
|
||||
function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
|
||||
if (n.getFullWidth() === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (n.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.CaseBlock:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile);
|
||||
case SyntaxKind.CatchClause:
|
||||
return isCompletedNode((<CatchClause>n).block, sourceFile);
|
||||
case SyntaxKind.NewExpression:
|
||||
if (!(<NewExpression>n).arguments) {
|
||||
return true;
|
||||
}
|
||||
// fall through
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
case SyntaxKind.ParenthesizedType:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
|
||||
case SyntaxKind.FunctionType:
|
||||
case SyntaxKind.ConstructorType:
|
||||
return isCompletedNode((<SignatureDeclaration>n).type, sourceFile);
|
||||
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
if ((<FunctionLikeDeclaration>n).body) {
|
||||
return isCompletedNode((<FunctionLikeDeclaration>n).body, sourceFile);
|
||||
}
|
||||
|
||||
if ((<FunctionLikeDeclaration>n).type) {
|
||||
return isCompletedNode((<FunctionLikeDeclaration>n).type, sourceFile);
|
||||
}
|
||||
|
||||
// Even though type parameters can be unclosed, we can get away with
|
||||
// having at least a closing paren.
|
||||
return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return (<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile);
|
||||
|
||||
case SyntaxKind.IfStatement:
|
||||
if ((<IfStatement>n).elseStatement) {
|
||||
return isCompletedNode((<IfStatement>n).elseStatement, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<IfStatement>n).thenStatement, sourceFile);
|
||||
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
return isCompletedNode((<ExpressionStatement>n).expression, sourceFile);
|
||||
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
case SyntaxKind.ComputedPropertyName:
|
||||
case SyntaxKind.TupleType:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||
|
||||
case SyntaxKind.IndexSignature:
|
||||
if ((<IndexSignatureDeclaration>n).type) {
|
||||
return isCompletedNode((<IndexSignatureDeclaration>n).type, sourceFile);
|
||||
}
|
||||
|
||||
return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
// there is no such thing as terminator token for CaseClause/DefaultClause so for simplicitly always consider them non-completed
|
||||
return false;
|
||||
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return isCompletedNode((<IterationStatement>n).statement, sourceFile);
|
||||
case SyntaxKind.DoStatement:
|
||||
// rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')';
|
||||
let hasWhileKeyword = findChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile);
|
||||
if (hasWhileKeyword) {
|
||||
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<DoStatement>n).statement, sourceFile);
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2597,8 +2597,9 @@ module ts {
|
||||
isNewIdentifierLocation = isNewIdentifierDefinitionLocation(previousToken);
|
||||
|
||||
/// TODO filter meaning based on the current context
|
||||
let scopeNode = getScopeNode(previousToken, position, sourceFile);
|
||||
let symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
|
||||
let symbols = typeInfoResolver.getSymbolsInScope(node, symbolMeanings);
|
||||
let symbols = typeInfoResolver.getSymbolsInScope(scopeNode, symbolMeanings);
|
||||
|
||||
getCompletionEntriesFromSymbols(symbols, activeCompletionSession);
|
||||
}
|
||||
@@ -2613,10 +2614,22 @@ module ts {
|
||||
return {
|
||||
isMemberCompletion,
|
||||
isNewIdentifierLocation,
|
||||
isBuilder : isNewIdentifierDefinitionLocation, // temporary property used to match VS implementation
|
||||
isBuilder: isNewIdentifierDefinitionLocation, // temporary property used to match VS implementation
|
||||
entries: activeCompletionSession.entries
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the first node that "embraces" the position, so that one may
|
||||
* accurately aggregate locals from the closest containing scope.
|
||||
*/
|
||||
function getScopeNode(initialToken: Node, position: number, sourceFile: SourceFile) {
|
||||
var scope = initialToken;
|
||||
while (scope && !positionBelongsToNode(scope, position, sourceFile)) {
|
||||
scope = scope.parent;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
function getCompletionEntriesFromSymbols(symbols: Symbol[], session: CompletionSession): void {
|
||||
let start = new Date().getTime();
|
||||
forEach(symbols, symbol => {
|
||||
|
||||
@@ -59,6 +59,157 @@ module ts {
|
||||
return start < end;
|
||||
}
|
||||
|
||||
export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
|
||||
export function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
|
||||
if (nodeIsMissing(n)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (n.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.CaseBlock:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile);
|
||||
case SyntaxKind.CatchClause:
|
||||
return isCompletedNode((<CatchClause>n).block, sourceFile);
|
||||
case SyntaxKind.NewExpression:
|
||||
if (!(<NewExpression>n).arguments) {
|
||||
return true;
|
||||
}
|
||||
// fall through
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
case SyntaxKind.ParenthesizedType:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
|
||||
case SyntaxKind.FunctionType:
|
||||
case SyntaxKind.ConstructorType:
|
||||
return isCompletedNode((<SignatureDeclaration>n).type, sourceFile);
|
||||
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
if ((<FunctionLikeDeclaration>n).body) {
|
||||
return isCompletedNode((<FunctionLikeDeclaration>n).body, sourceFile);
|
||||
}
|
||||
|
||||
if ((<FunctionLikeDeclaration>n).type) {
|
||||
return isCompletedNode((<FunctionLikeDeclaration>n).type, sourceFile);
|
||||
}
|
||||
|
||||
// Even though type parameters can be unclosed, we can get away with
|
||||
// having at least a closing paren.
|
||||
return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return (<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile);
|
||||
|
||||
case SyntaxKind.IfStatement:
|
||||
if ((<IfStatement>n).elseStatement) {
|
||||
return isCompletedNode((<IfStatement>n).elseStatement, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<IfStatement>n).thenStatement, sourceFile);
|
||||
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
return isCompletedNode((<ExpressionStatement>n).expression, sourceFile);
|
||||
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
case SyntaxKind.ComputedPropertyName:
|
||||
case SyntaxKind.TupleType:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||
|
||||
case SyntaxKind.IndexSignature:
|
||||
if ((<IndexSignatureDeclaration>n).type) {
|
||||
return isCompletedNode((<IndexSignatureDeclaration>n).type, sourceFile);
|
||||
}
|
||||
|
||||
return hasChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
// there is no such thing as terminator token for CaseClause/DefaultClause so for simplicitly always consider them non-completed
|
||||
return false;
|
||||
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
return isCompletedNode((<IterationStatement>n).statement, sourceFile);
|
||||
case SyntaxKind.DoStatement:
|
||||
// rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')';
|
||||
let hasWhileKeyword = findChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile);
|
||||
if (hasWhileKeyword) {
|
||||
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<DoStatement>n).statement, sourceFile);
|
||||
|
||||
case SyntaxKind.TypeQuery:
|
||||
return isCompletedNode((<TypeQueryNode>n).exprName, sourceFile);
|
||||
|
||||
case SyntaxKind.TypeOfExpression:
|
||||
case SyntaxKind.DeleteExpression:
|
||||
case SyntaxKind.VoidExpression:
|
||||
case SyntaxKind.YieldExpression:
|
||||
case SyntaxKind.SpreadElementExpression:
|
||||
let unaryWordExpression = (<TypeOfExpression|DeleteExpression|VoidExpression|YieldExpression|SpreadElementExpression>n);
|
||||
return isCompletedNode(unaryWordExpression.expression, sourceFile);
|
||||
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
return isCompletedNode((<TaggedTemplateExpression>n).template, sourceFile);
|
||||
case SyntaxKind.TemplateExpression:
|
||||
let lastSpan = lastOrUndefined((<TemplateExpression>n).templateSpans);
|
||||
return isCompletedNode(lastSpan, sourceFile);
|
||||
case SyntaxKind.TemplateSpan:
|
||||
return nodeIsPresent((<TemplateSpan>n).literal);
|
||||
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
return isCompletedNode((<PrefixUnaryExpression>n).operand, sourceFile);
|
||||
case SyntaxKind.BinaryExpression:
|
||||
return isCompletedNode((<BinaryExpression>n).right, sourceFile);
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
return isCompletedNode((<ConditionalExpression>n).whenFalse, sourceFile);
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if node ends with 'expectedLastToken'.
|
||||
* If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
|
||||
*/
|
||||
function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean {
|
||||
let children = n.getChildren(sourceFile);
|
||||
if (children.length) {
|
||||
let last = children[children.length - 1];
|
||||
if (last.kind === expectedLastToken) {
|
||||
return true;
|
||||
}
|
||||
else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) {
|
||||
return children[children.length - 2].kind === expectedLastToken;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function findListItemInfo(node: Node): ListItemInfo {
|
||||
let list = findContainingList(node);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user