Skip to content

Commit 4ecb563

Browse files
authored
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.
1 parent 3e25424 commit 4ecb563

File tree

2 files changed

+139
-1
lines changed

2 files changed

+139
-1
lines changed

src/services/completions.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2360,7 +2360,7 @@ namespace ts.Completions {
23602360
return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent);
23612361
}
23622362

2363-
// If the previous token is keyword correspoding to class member completion keyword
2363+
// If the previous token is keyword corresponding to class member completion keyword
23642364
// there will be completion available here
23652365
if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) {
23662366
return false;
@@ -2398,6 +2398,33 @@ namespace ts.Completions {
23982398
return isPropertyDeclaration(contextToken.parent);
23992399
}
24002400

2401+
// If we are inside a class declaration, and `constructor` is totally not present,
2402+
// but we request a completion manually at a whitespace...
2403+
const ancestorClassLike = findAncestor(contextToken.parent, isClassLike);
2404+
if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) {
2405+
return false; // Don't block completions.
2406+
}
2407+
2408+
const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration);
2409+
// If we are inside a class declaration and typing `constructor` after property declaration...
2410+
if (ancestorPropertyDeclaraion
2411+
&& contextToken !== previousToken
2412+
&& isClassLike(previousToken.parent.parent)
2413+
// And the cursor is at the token...
2414+
&& position <= previousToken.end) {
2415+
// If we are sure that the previous property declaration is terminated according to newline or semicolon...
2416+
if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) {
2417+
return false; // Don't block completions.
2418+
}
2419+
else if (contextToken.kind !== SyntaxKind.EqualsToken
2420+
// Should not block: `class C { blah = c/**/ }`
2421+
// But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }`
2422+
&& (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration)
2423+
|| hasType(ancestorPropertyDeclaraion))) {
2424+
return true;
2425+
}
2426+
}
2427+
24012428
return isDeclarationName(contextToken)
24022429
&& !isShorthandPropertyAssignment(contextToken.parent)
24032430
&& !isJsxAttribute(contextToken.parent)
@@ -2406,6 +2433,12 @@ namespace ts.Completions {
24062433
&& !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end));
24072434
}
24082435

2436+
function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) {
2437+
return contextToken.kind !== SyntaxKind.EqualsToken &&
2438+
(contextToken.kind === SyntaxKind.SemicolonToken
2439+
|| !positionsAreOnSameLine(contextToken.end, position, sourceFile));
2440+
}
2441+
24092442
function isFunctionLikeButNotConstructor(kind: SyntaxKind) {
24102443
return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor;
24112444
}
@@ -2859,6 +2892,13 @@ namespace ts.Completions {
28592892

28602893
if (!contextToken) return undefined;
28612894

2895+
// class C { blah; constructor/**/ } and so on
2896+
if (location.kind === SyntaxKind.ConstructorKeyword
2897+
// class C { blah \n constructor/**/ }
2898+
|| (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) {
2899+
return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration;
2900+
}
2901+
28622902
switch (contextToken.kind) {
28632903
case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ }
28642904
return undefined;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// // situations that `constructor` is partly present
4+
//// class A {
5+
//// blah; con/*1*/
6+
//// }
7+
//// class B {
8+
//// blah
9+
//// con/*2*/
10+
//// }
11+
//// class C {
12+
//// blah: number
13+
//// con/*3*/
14+
//// }
15+
//// class D {
16+
//// blah = 123
17+
//// con/*4*/
18+
//// }
19+
//// class E {
20+
//// blah = [123]
21+
//// con/*5*/
22+
//// }
23+
//// class F {
24+
//// blah = {key: 123}
25+
//// con/*6*/
26+
//// }
27+
//// // situations that `constructor` is fully present
28+
//// class G {
29+
//// blah; constructor/*7*/
30+
//// }
31+
//// class H {
32+
//// blah
33+
//// constructor/*8*/
34+
//// }
35+
//// class I {
36+
//// blah: number
37+
//// constructor/*9*/
38+
//// }
39+
//// class J {
40+
//// blah = 123
41+
//// constructor/*10*/
42+
//// }
43+
//// class K {
44+
//// blah = [123]
45+
//// constructor/*11*/
46+
//// }
47+
//// class L {
48+
//// blah = {key: 123}
49+
//// constructor/*12*/
50+
//// }
51+
//// // situations that `constructor` isn't present, but we should offer it
52+
//// class M {
53+
//// blah; /*13*/
54+
//// }
55+
//// class N {
56+
//// blah
57+
//// /*14*/
58+
//// }
59+
//// // situations that `constructor` should not be suggested
60+
//// class O {
61+
//// blah /*15*/
62+
//// }
63+
//// class P {
64+
//// blah con/*16*/
65+
//// }
66+
//// class Q {
67+
//// blah: number con/*17*/
68+
//// }
69+
//// class R {
70+
//// blah = 123 con/*18*/
71+
//// }
72+
//// class S {
73+
//// blah = {key: 123} con/*19*/
74+
//// }
75+
//// type SomeType = number
76+
//// class T {
77+
//// blah: SomeType con/*20*/
78+
//// }
79+
//// const SomeValue = 123
80+
//// class U {
81+
//// blah = SomeValue con/*21*/
82+
//// }
83+
84+
function generateRange(l: number, r: number) {
85+
return Array.from(Array(r - l + 1), (_, i) => String(i + l)); // [l, r]
86+
}
87+
88+
verify.completions({
89+
marker: generateRange(1, 14),
90+
includes: { name: "constructor", sortText: completion.SortText.GlobalsOrKeywords },
91+
isNewIdentifierLocation: true,
92+
});
93+
94+
verify.completions({
95+
marker: generateRange(15, 21),
96+
exact: [],
97+
isNewIdentifierLocation: true,
98+
});

0 commit comments

Comments
 (0)