-
Notifications
You must be signed in to change notification settings - Fork 13k
Complete constructor
keyword after property declaration
#43654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
constructor
keyword after property declarationconstructor
keyword after property declaration
constructor
keyword after property declarationconstructor
keyword after property declaration
This seems like an oddly-specific fix, and I'm not sure if it really identifies the root problem. I think we need to understand why we weren't offering up completions before. |
In my opinion, this is a missing case of getting completions during class declaration. The function TypeScript/src/services/completions.ts Line 2835 in f74f9ca
However, it doesn't consider the class Foo {
bar // <- context token is identifier when w/o semicolon
// bar; // <- context token is semicolon when with semicolon
constructor/**/()
} since there is no switch-case for So it falls back to TypeScript/src/services/completions.ts Line 2737 in f74f9ca
This function doesn't consider the situation that What's more, for case with a class Foo {
bar: number // <- context token w/o semicolon
consturctor/**/()
} The completion is blocked beforehand by TypeScript/src/services/completions.ts Line 1816 in f74f9ca
which is in fact a different problem from the previous one. The fix for this is made referring to a similar issue: TypeScript/src/services/completions.ts Line 2377 in f74f9ca
|
You need to actually check for an ASI-triggering newline if you want to prevent a completion at class C {
blah /*| constructor*/
} I don’t know how important that is, though. |
Hi, I'm not very sure of what you mean. I'm basically allowing instead of preventing completions. And the behavior for cursor at that position should be unchanged. Do these changes affect ASI in some way? |
|
||
////class A { | ||
//// bla | ||
//// constructor/*1*/(props) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The presence of the full constructor
keyword is changing the behavior of the completions. It seems like what you really want to test is
class A {
bla
/**/
}
and
class A {
bla
co/**/
}
right? Those currently don’t work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We’ll have to revisit my comment about newlines after this issue is fixed—you’re right that the behavior is currently unchanged, but I think it’s because of this issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. This is a more universal situation. And I've found that completions are not working either for snippet like this:
class C {
blah = 123 // property with an immediate assignment
constructor/**/
}
I'll take a deeper dive into these problems.
Updated. New fix available now. |
@typescript-bot pack this |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at 59756d3. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
Ok, so this is what I meant about checking newlines: https://www.staging-typescript.org/play?ts=4.3.0-pr-43654-9#code/MYGwhgzhAEDC0G8BQ1oCNwAtrAPQCp9ckBfIA If you trigger completions at the comment, you now get a completion for |
I've adopted a validation using newline or semicolon. The completions for these two cases are blocked now as the original behavior in 4.3.0-beta: class A {
blah /*|con*/
}
class B {
blah: number /*|con*/
} And class C {
blah
/**/
} since class C {
blah
= 123
} is legal. Unless user types a During the debugging, I've found another issue of property with an initializer that might be controversial. In 4.3.0-beta, completions of class C {
blah = blah /*|p*/
}
// PLAYGROUND: https://www.staging-typescript.org/play?jsx=0&ts=4.3.0-beta#code/MYGwhgzhAEDC0G8BQ1oCNwAtoF51egAcB6AKlOKQF8g It looks unlike an intended one. But I'm not sure and haven't fix this since it is guaranteed by some test cases like |
src/services/completions.ts
Outdated
return getLinesBetweenPositions(sourceFile, contextToken.end, previousToken.end) > 0 || | ||
contextToken.kind === SyntaxKind.SemicolonToken; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly cheaper:
return getLinesBetweenPositions(sourceFile, contextToken.end, previousToken.end) > 0 || | |
contextToken.kind === SyntaxKind.SemicolonToken; | |
return contextToken.kind === SyntaxKind.SemicolonToken || | |
positionsAreOnSameLine(contextToken.end, previousToken.end, sourceFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, can you move this function declaration outside of the flow of its parent function? Our loosely-enforced convention is to put helper functions at the bottom of their parent, but it looks like this one could move outside of isSolelyIdentifierDefinitionLocation
entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
+ return contextToken.kind === SyntaxKind.SemicolonToken ||
+ !positionsAreOnSameLine(contextToken.end, previousToken.end, sourceFile);
^~~~~~
exactly.
src/services/completions.ts
Outdated
isPropertyBeforeConstructorInitalized(getAncestor(location, SyntaxKind.ClassDeclaration) as ClassDeclaration, location.parent as ClassElement) || | ||
contextToken.kind === SyntaxKind.SemicolonToken | ||
)) { | ||
return getAncestor(contextToken, SyntaxKind.ClassDeclaration) as ObjectTypeDeclaration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we will not produce a ConstructorKeyword
outside a class (it will just be an Identifier
if it appears), but the class node could be a ClassExpression
, not only a ClassDeclaration
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Replaced using isClassLike
to predicate.
src/services/completions.ts
Outdated
// class c { prop: number \n constructor| } | ||
// class c { prop = somewhat \n constructor| } | ||
// and also for `;` replacing `\n` | ||
if (location.kind === SyntaxKind.ConstructorKeyword && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this whole condition applies only once you’ve typed the entire constructor
keyword, and stops applying as soon as you’ve typed the open parenthesis afterwards, which is not really a time when completions are likely to be triggered, or when they will be valuable. This might be technically correct, but I’m confused about what purpose it’s serving.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the fix for the most original issue. #43650
Manually triggered completion (Ctrl+Space in Windows) doesn't offer constructor
for this:
class C {
blah // w/ w/o semicolon, type, initializer
constructor/**/ // must be fully present
}
It looks like that the full presence of constructor
is changing the behavior of completions in some way.
The cause seems to be a missing case in tryGetObjectTypeDeclarationCompletionContainer
. Removing this will result in no suggestion
. But the conditions here are unnecessarily complex. I'm trying to simplify it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I disagree with the premise that #43650 is worth more than a line of code to fix. From the original issue opened against VS Code, it looks like someone was just hunting for inconsistencies, not experiencing a legitimate UX issue. I’m not against fixing it, but I am against fixing it if no one can explain why it matters and it takes a multiline if
statement, a bunch of explanatory comments, and a dozen fourslash tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😆 Yep. The latest fix is of 2 LOC.
src/services/completions.ts
Outdated
&& isClassLike(previousToken.parent.parent) | ||
&& isPropertyDeclaration(contextToken.parent) | ||
// After considering different contextToken... | ||
&& (isIdentifier(contextToken) || isTypeKeyword(contextToken.kind)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this condition necessary/correct? If the previous property declaration has a type annotation or an initializer, there are lots of ways it might be something other than an identifier or type keyword. But those cases seem to work already... are we able to just drop this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. This is already supported by some other code. Cases like
type SomeType = number
class C {
blah: SomeType; /**/
}
are already working in 4.3.0-beta.
//// } | ||
//// class R { | ||
//// blah | ||
//// /*18*/ // dont complete since `blah \n = 123` is legal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But constructor
is also legal here, so why not offer it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right.
//// class G { | ||
//// blah = 123; con/*7*/ | ||
//// } | ||
//// // situations that `constructor` is fully present |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these are fine to have, and correct, but as far as I understand, essentially pointless. I don’t think they’re particularly important to test, and I definitely think that little to no implementation code should be written to make these cases pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kindly refer to #43654 (comment) maybe.
class T {
blah: number con/**/
}
const SomeConst = 123
class U {
blah = SomeConst con/**/
} I'm still working on this. |
The simplifications are looking great. Let me know when you’re ready for another review. Thanks! |
The fix should be well-rounded and reliable enough now. Please refer to the test case: for detail. What's more, we've got a byproduct that, for 15-21 in the test case of "situations that At last, kindly ping @andrewbranch . |
@@ -2370,6 +2370,31 @@ namespace ts.Completions { | |||
return isPropertyDeclaration(contextToken.parent); | |||
} | |||
|
|||
// If `constructor` is totally not present, but we request a completion manually at a space... | |||
if (contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) { | |||
return false; // Don't block completions. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Thanks for your help. Have a nice day! |
Fixes #43650
and more.
This is my first pull-request here. Help and suggestions are appreciated.
All these various situations are fixed, expanding the case in original issue:
constructor
, partly presentconstructor
, no presence...Please refer to the test case for detail:
tests/cases/fourslash/completionConstructorKeywordAfterPropertyDeclaration.ts