Complete constructor keyword after property declaration (#43654)

* Complete `constructor` keyword after property declaration.

* Fix logical errors.

* Fix for more universal situations.

* Only provide completions if property declaration is terminated.

* Simplify many logical conditions.

* Make the fix more reliable.

* Narrowing the fix.
This commit is contained in:
Xu Zhuo
2021-04-29 11:16:51 -07:00
committed by GitHub
parent 3e25424652
commit 4ecb563aa4
2 changed files with 139 additions and 1 deletions
+41 -1
View File
@@ -2360,7 +2360,7 @@ namespace ts.Completions {
return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent);
}
// If the previous token is keyword correspoding to class member completion keyword
// If the previous token is keyword corresponding to class member completion keyword
// there will be completion available here
if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) {
return false;
@@ -2398,6 +2398,33 @@ namespace ts.Completions {
return isPropertyDeclaration(contextToken.parent);
}
// If we are inside a class declaration, and `constructor` is totally not present,
// but we request a completion manually at a whitespace...
const ancestorClassLike = findAncestor(contextToken.parent, isClassLike);
if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) {
return false; // Don't block completions.
}
const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration);
// If we are inside a class declaration and typing `constructor` after property declaration...
if (ancestorPropertyDeclaraion
&& contextToken !== previousToken
&& isClassLike(previousToken.parent.parent)
// And the cursor is at the token...
&& position <= previousToken.end) {
// If we are sure that the previous property declaration is terminated according to newline or semicolon...
if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) {
return false; // Don't block completions.
}
else if (contextToken.kind !== SyntaxKind.EqualsToken
// Should not block: `class C { blah = c/**/ }`
// But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }`
&& (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration)
|| hasType(ancestorPropertyDeclaraion))) {
return true;
}
}
return isDeclarationName(contextToken)
&& !isShorthandPropertyAssignment(contextToken.parent)
&& !isJsxAttribute(contextToken.parent)
@@ -2406,6 +2433,12 @@ namespace ts.Completions {
&& !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end));
}
function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) {
return contextToken.kind !== SyntaxKind.EqualsToken &&
(contextToken.kind === SyntaxKind.SemicolonToken
|| !positionsAreOnSameLine(contextToken.end, position, sourceFile));
}
function isFunctionLikeButNotConstructor(kind: SyntaxKind) {
return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor;
}
@@ -2859,6 +2892,13 @@ namespace ts.Completions {
if (!contextToken) return undefined;
// class C { blah; constructor/**/ } and so on
if (location.kind === SyntaxKind.ConstructorKeyword
// class C { blah \n constructor/**/ }
|| (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) {
return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration;
}
switch (contextToken.kind) {
case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ }
return undefined;