Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2332,7 +2332,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;
Expand Down Expand Up @@ -2370,6 +2370,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even know that the current location is in a class right here?

Copy link
Contributor Author

@z0gSh1u z0gSh1u Apr 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Narrowing this fix to inside a ClassLike should be more reliable. I've edited the code.

}

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)
Expand All @@ -2378,6 +2405,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;
}
Expand Down Expand Up @@ -2831,6 +2864,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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/// <reference path="fourslash.ts" />

//// // situations that `constructor` is partly present
//// class A {
//// blah; con/*1*/
//// }
//// class B {
//// blah
//// con/*2*/
//// }
//// class C {
//// blah: number
//// con/*3*/
//// }
//// class D {
//// blah = 123
//// con/*4*/
//// }
//// class E {
//// blah = [123]
//// con/*5*/
//// }
//// class F {
//// blah = {key: 123}
//// con/*6*/
//// }
//// // situations that `constructor` is fully present
//// class G {
//// blah; constructor/*7*/
//// }
//// class H {
//// blah
//// constructor/*8*/
//// }
//// class I {
//// blah: number
//// constructor/*9*/
//// }
//// class J {
//// blah = 123
//// constructor/*10*/
//// }
//// class K {
//// blah = [123]
//// constructor/*11*/
//// }
//// class L {
//// blah = {key: 123}
//// constructor/*12*/
//// }
//// // situations that `constructor` isn't present, but we should offer it
//// class M {
//// blah; /*13*/
//// }
//// class N {
//// blah
//// /*14*/
//// }
//// // situations that `constructor` should not be suggested
//// class O {
//// blah /*15*/
//// }
//// class P {
//// blah con/*16*/
//// }
//// class Q {
//// blah: number con/*17*/
//// }
//// class R {
//// blah = 123 con/*18*/
//// }
//// class S {
//// blah = {key: 123} con/*19*/
//// }
//// type SomeType = number
//// class T {
//// blah: SomeType con/*20*/
//// }
//// const SomeValue = 123
//// class U {
//// blah = SomeValue con/*21*/
//// }

function generateRange(l: number, r: number) {
return Array.from(Array(r - l + 1), (_, i) => String(i + l)); // [l, r]
}

verify.completions({
marker: generateRange(1, 14),
includes: { name: "constructor", sortText: completion.SortText.GlobalsOrKeywords },
isNewIdentifierLocation: true,
});

verify.completions({
marker: generateRange(15, 21),
exact: [],
isNewIdentifierLocation: true,
});