Skip to content

Commit 8cdd59f

Browse files
Merge pull request microsoft#3727 from Microsoft/elipsisMeansQuiet
Don't give back completions after stray dots
2 parents f2731e4 + 15962e0 commit 8cdd59f

File tree

6 files changed

+150
-124
lines changed

6 files changed

+150
-124
lines changed

src/services/services.ts

Lines changed: 118 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2892,33 +2892,36 @@ namespace ts {
28922892
log("getCompletionData: Get previous token 2: " + (new Date().getTime() - start));
28932893
}
28942894

2895-
// Check if this is a valid completion location
2896-
if (contextToken && isCompletionListBlocker(contextToken)) {
2897-
log("Returning an empty list because completion was requested in an invalid position.");
2898-
return undefined;
2899-
}
2900-
2901-
let options = program.getCompilerOptions();
2902-
let jsx = options.jsx !== JsxEmit.None;
2903-
let target = options.target;
2904-
2905-
// Find the node where completion is requested on, in the case of a completion after
2906-
// a dot, it is the member access expression other wise, it is a request for all
2907-
// visible symbols in the scope, and the node is the current location.
2895+
// Find the node where completion is requested on.
2896+
// Also determine whether we are trying to complete with members of that node
2897+
// or attributes of a JSX tag.
29082898
let node = currentToken;
29092899
let isRightOfDot = false;
29102900
let isRightOfOpenTag = false;
29112901

29122902
let location = getTouchingPropertyName(sourceFile, position);
2913-
if(contextToken) {
2914-
let kind = contextToken.kind;
2915-
if (kind === SyntaxKind.DotToken && contextToken.parent.kind === SyntaxKind.PropertyAccessExpression) {
2916-
node = (<PropertyAccessExpression>contextToken.parent).expression;
2917-
isRightOfDot = true;
2903+
if (contextToken) {
2904+
// Bail out if this is a known invalid completion location
2905+
if (isCompletionListBlocker(contextToken)) {
2906+
log("Returning an empty list because completion was requested in an invalid position.");
2907+
return undefined;
29182908
}
2919-
else if (kind === SyntaxKind.DotToken && contextToken.parent.kind === SyntaxKind.QualifiedName) {
2920-
node = (<QualifiedName>contextToken.parent).left;
2921-
isRightOfDot = true;
2909+
2910+
let { parent, kind } = contextToken;
2911+
if (kind === SyntaxKind.DotToken) {
2912+
if (parent.kind === SyntaxKind.PropertyAccessExpression) {
2913+
node = (<PropertyAccessExpression>contextToken.parent).expression;
2914+
isRightOfDot = true;
2915+
}
2916+
else if (parent.kind === SyntaxKind.QualifiedName) {
2917+
node = (<QualifiedName>contextToken.parent).left;
2918+
isRightOfDot = true;
2919+
}
2920+
else {
2921+
// There is nothing that precedes the dot, so this likely just a stray character
2922+
// or leading into a '...' token. Just bail out instead.
2923+
return undefined;
2924+
}
29222925
}
29232926
else if (kind === SyntaxKind.LessThanToken && sourceFile.languageVariant === LanguageVariant.JSX) {
29242927
isRightOfOpenTag = true;
@@ -3097,11 +3100,11 @@ namespace ts {
30973100
return scope;
30983101
}
30993102

3100-
function isCompletionListBlocker(previousToken: Node): boolean {
3103+
function isCompletionListBlocker(contextToken: Node): boolean {
31013104
let start = new Date().getTime();
3102-
let result = isInStringOrRegularExpressionOrTemplateLiteral(previousToken) ||
3103-
isIdentifierDefinitionLocation(previousToken) ||
3104-
isRightOfIllegalDot(previousToken);
3105+
let result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) ||
3106+
isIdentifierDefinitionLocation(contextToken) ||
3107+
isDotOfNumericLiteral(contextToken);
31053108
log("getCompletionsAtPosition: isCompletionListBlocker: " + (new Date().getTime() - start));
31063109
return result;
31073110
}
@@ -3180,12 +3183,12 @@ namespace ts {
31803183
return false;
31813184
}
31823185

3183-
function isInStringOrRegularExpressionOrTemplateLiteral(previousToken: Node): boolean {
3184-
if (previousToken.kind === SyntaxKind.StringLiteral
3185-
|| previousToken.kind === SyntaxKind.RegularExpressionLiteral
3186-
|| isTemplateLiteralKind(previousToken.kind)) {
3187-
let start = previousToken.getStart();
3188-
let end = previousToken.getEnd();
3186+
function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean {
3187+
if (contextToken.kind === SyntaxKind.StringLiteral
3188+
|| contextToken.kind === SyntaxKind.RegularExpressionLiteral
3189+
|| isTemplateLiteralKind(contextToken.kind)) {
3190+
let start = contextToken.getStart();
3191+
let end = contextToken.getEnd();
31893192

31903193
// To be "in" one of these literals, the position has to be:
31913194
// 1. entirely within the token text.
@@ -3196,8 +3199,8 @@ namespace ts {
31963199
}
31973200

31983201
if (position === end) {
3199-
return !!(<LiteralExpression>previousToken).isUnterminated ||
3200-
previousToken.kind === SyntaxKind.RegularExpressionLiteral;
3202+
return !!(<LiteralExpression>contextToken).isUnterminated
3203+
|| contextToken.kind === SyntaxKind.RegularExpressionLiteral;
32013204
}
32023205
}
32033206

@@ -3351,101 +3354,98 @@ namespace ts {
33513354
return false;
33523355
}
33533356

3354-
function isIdentifierDefinitionLocation(previousToken: Node): boolean {
3355-
if (previousToken) {
3356-
let containingNodeKind = previousToken.parent.kind;
3357-
switch (previousToken.kind) {
3358-
case SyntaxKind.CommaToken:
3359-
return containingNodeKind === SyntaxKind.VariableDeclaration ||
3360-
containingNodeKind === SyntaxKind.VariableDeclarationList ||
3361-
containingNodeKind === SyntaxKind.VariableStatement ||
3362-
containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, |
3363-
isFunction(containingNodeKind) ||
3364-
containingNodeKind === SyntaxKind.ClassDeclaration || // class A<T, |
3365-
containingNodeKind === SyntaxKind.FunctionDeclaration || // function A<T, |
3366-
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, |
3367-
containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x, y|
3357+
function isIdentifierDefinitionLocation(contextToken: Node): boolean {
3358+
let containingNodeKind = contextToken.parent.kind;
3359+
switch (contextToken.kind) {
3360+
case SyntaxKind.CommaToken:
3361+
return containingNodeKind === SyntaxKind.VariableDeclaration ||
3362+
containingNodeKind === SyntaxKind.VariableDeclarationList ||
3363+
containingNodeKind === SyntaxKind.VariableStatement ||
3364+
containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, |
3365+
isFunction(containingNodeKind) ||
3366+
containingNodeKind === SyntaxKind.ClassDeclaration || // class A<T, |
3367+
containingNodeKind === SyntaxKind.FunctionDeclaration || // function A<T, |
3368+
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, |
3369+
containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x, y|
33683370

3369-
case SyntaxKind.DotToken:
3370-
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.|
3371+
case SyntaxKind.DotToken:
3372+
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.|
33713373

3372-
case SyntaxKind.ColonToken:
3373-
return containingNodeKind === SyntaxKind.BindingElement; // var {x :html|
3374+
case SyntaxKind.ColonToken:
3375+
return containingNodeKind === SyntaxKind.BindingElement; // var {x :html|
33743376

3375-
case SyntaxKind.OpenBracketToken:
3376-
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x|
3377+
case SyntaxKind.OpenBracketToken:
3378+
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x|
33773379

3378-
case SyntaxKind.OpenParenToken:
3379-
return containingNodeKind === SyntaxKind.CatchClause ||
3380-
isFunction(containingNodeKind);
3381-
3382-
case SyntaxKind.OpenBraceToken:
3383-
return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { |
3384-
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface a { |
3385-
containingNodeKind === SyntaxKind.TypeLiteral; // let x : { |
3386-
3387-
case SyntaxKind.SemicolonToken:
3388-
return containingNodeKind === SyntaxKind.PropertySignature &&
3389-
previousToken.parent && previousToken.parent.parent &&
3390-
(previousToken.parent.parent.kind === SyntaxKind.InterfaceDeclaration || // interface a { f; |
3391-
previousToken.parent.parent.kind === SyntaxKind.TypeLiteral); // let x : { a; |
3392-
3393-
case SyntaxKind.LessThanToken:
3394-
return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< |
3395-
containingNodeKind === SyntaxKind.FunctionDeclaration || // function A< |
3396-
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< |
3397-
isFunction(containingNodeKind);
3398-
3399-
case SyntaxKind.StaticKeyword:
3400-
return containingNodeKind === SyntaxKind.PropertyDeclaration;
3401-
3402-
case SyntaxKind.DotDotDotToken:
3403-
return containingNodeKind === SyntaxKind.Parameter ||
3404-
containingNodeKind === SyntaxKind.Constructor ||
3405-
(previousToken.parent && previousToken.parent.parent &&
3406-
previousToken.parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z|
3407-
3408-
case SyntaxKind.PublicKeyword:
3409-
case SyntaxKind.PrivateKeyword:
3410-
case SyntaxKind.ProtectedKeyword:
3411-
return containingNodeKind === SyntaxKind.Parameter;
3412-
3413-
case SyntaxKind.ClassKeyword:
3414-
case SyntaxKind.EnumKeyword:
3415-
case SyntaxKind.InterfaceKeyword:
3416-
case SyntaxKind.FunctionKeyword:
3417-
case SyntaxKind.VarKeyword:
3418-
case SyntaxKind.GetKeyword:
3419-
case SyntaxKind.SetKeyword:
3420-
case SyntaxKind.ImportKeyword:
3421-
case SyntaxKind.LetKeyword:
3422-
case SyntaxKind.ConstKeyword:
3423-
case SyntaxKind.YieldKeyword:
3424-
case SyntaxKind.TypeKeyword: // type htm|
3425-
return true;
3426-
}
3380+
case SyntaxKind.OpenParenToken:
3381+
return containingNodeKind === SyntaxKind.CatchClause ||
3382+
isFunction(containingNodeKind);
3383+
3384+
case SyntaxKind.OpenBraceToken:
3385+
return containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { |
3386+
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface a { |
3387+
containingNodeKind === SyntaxKind.TypeLiteral; // let x : { |
3388+
3389+
case SyntaxKind.SemicolonToken:
3390+
return containingNodeKind === SyntaxKind.PropertySignature &&
3391+
contextToken.parent && contextToken.parent.parent &&
3392+
(contextToken.parent.parent.kind === SyntaxKind.InterfaceDeclaration || // interface a { f; |
3393+
contextToken.parent.parent.kind === SyntaxKind.TypeLiteral); // let x : { a; |
3394+
3395+
case SyntaxKind.LessThanToken:
3396+
return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< |
3397+
containingNodeKind === SyntaxKind.FunctionDeclaration || // function A< |
3398+
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< |
3399+
isFunction(containingNodeKind);
3400+
3401+
case SyntaxKind.StaticKeyword:
3402+
return containingNodeKind === SyntaxKind.PropertyDeclaration;
3403+
3404+
case SyntaxKind.DotDotDotToken:
3405+
return containingNodeKind === SyntaxKind.Parameter ||
3406+
(contextToken.parent && contextToken.parent.parent &&
3407+
contextToken.parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z|
3408+
3409+
case SyntaxKind.PublicKeyword:
3410+
case SyntaxKind.PrivateKeyword:
3411+
case SyntaxKind.ProtectedKeyword:
3412+
return containingNodeKind === SyntaxKind.Parameter;
3413+
3414+
case SyntaxKind.ClassKeyword:
3415+
case SyntaxKind.EnumKeyword:
3416+
case SyntaxKind.InterfaceKeyword:
3417+
case SyntaxKind.FunctionKeyword:
3418+
case SyntaxKind.VarKeyword:
3419+
case SyntaxKind.GetKeyword:
3420+
case SyntaxKind.SetKeyword:
3421+
case SyntaxKind.ImportKeyword:
3422+
case SyntaxKind.LetKeyword:
3423+
case SyntaxKind.ConstKeyword:
3424+
case SyntaxKind.YieldKeyword:
3425+
case SyntaxKind.TypeKeyword: // type htm|
3426+
return true;
3427+
}
34273428

3428-
// Previous token may have been a keyword that was converted to an identifier.
3429-
switch (previousToken.getText()) {
3430-
case "class":
3431-
case "interface":
3432-
case "enum":
3433-
case "function":
3434-
case "var":
3435-
case "static":
3436-
case "let":
3437-
case "const":
3438-
case "yield":
3439-
return true;
3440-
}
3429+
// Previous token may have been a keyword that was converted to an identifier.
3430+
switch (contextToken.getText()) {
3431+
case "class":
3432+
case "interface":
3433+
case "enum":
3434+
case "function":
3435+
case "var":
3436+
case "static":
3437+
case "let":
3438+
case "const":
3439+
case "yield":
3440+
return true;
34413441
}
34423442

34433443
return false;
34443444
}
34453445

3446-
function isRightOfIllegalDot(previousToken: Node): boolean {
3447-
if (previousToken && previousToken.kind === SyntaxKind.NumericLiteral) {
3448-
let text = previousToken.getFullText();
3446+
function isDotOfNumericLiteral(contextToken: Node): boolean {
3447+
if (contextToken.kind === SyntaxKind.NumericLiteral) {
3448+
let text = contextToken.getFullText();
34493449
return text.charAt(text.length - 1) === ".";
34503450
}
34513451

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////let v = [1,2,3,4];
4+
////let x = [.../**/
5+
6+
goTo.marker();
7+
verify.completionListContains("v");

tests/cases/fourslash/completionListBuilderLocations_Modules.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55

66
////module A./*moduleName2*/
77

8+
goTo.marker("moduleName1");
9+
verify.not.completionListIsEmpty();
810

9-
test.markers().forEach((m) => {
10-
goTo.position(m.position, m.fileName);
11-
verify.not.completionListIsEmpty();
12-
verify.completionListAllowsNewIdentifier();
13-
});
11+
goTo.marker("moduleName2");
12+
verify.completionListIsEmpty();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////../**/
4+
5+
goTo.marker();
6+
verify.memberListIsEmpty();

tests/cases/fourslash/memberListAfterSingleDot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
////./**/
44

55
goTo.marker();
6-
verify.not.memberListIsEmpty();
6+
verify.memberListIsEmpty();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//@Filename: file.tsx
4+
//// declare namespace JSX {
5+
//// interface Element { }
6+
//// interface IntrinsicElements {
7+
//// div: { one; two; }
8+
//// }
9+
//// }
10+
//// let bag = { x: 100, y: 200 };
11+
//// <div {.../**/
12+
13+
goTo.marker();
14+
verify.completionListContains("bag");

0 commit comments

Comments
 (0)