Skip to content

feat(45010): handle unclosed fragment in getJsxClosingTagAtPosition #45532

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

Merged

Conversation

hi-ogawa
Copy link
Contributor

@hi-ogawa hi-ogawa commented Aug 21, 2021

Fixes #45010

This PR attempts to handle unclosed jsx fragment in language service so that it will auto-complete on vscode.
I used NodeFlags.ThisNodeHasError directly to detect the parse error related to the closing fragment, which might not be the most reliable way to check unclosed fragment, but at least for the test cases I added (mostly the copy of existing autoCloseTag.ts), it seems to be working.
Please let me know if this is a right approach or if there's a better way to do it.
I would appreciate any feedback, thanks a lot!

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Aug 21, 2021
@ghost
Copy link

ghost commented Aug 21, 2021

CLA assistant check
All CLA requirements met.

@@ -2088,10 +2088,15 @@ namespace ts {
const token = findPrecedingToken(position, sourceFile);
if (!token) return undefined;
const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent
: isJsxText(token) ? token.parent : undefined;
: isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined;
Copy link
Contributor Author

@hi-ogawa hi-ogawa Aug 22, 2021

Choose a reason for hiding this comment

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

To comment on why this change is necessary, I found that JsxText.parent is not necessarily JsxElement but it also can be JsxFragment, even though typing-wise it's supposed to be JsxElement according to

export interface JsxText extends LiteralLikeNode {
readonly kind: SyntaxKind.JsxText;
readonly parent: JsxElement;
readonly containsOnlyTriviaWhiteSpaces: boolean;
}

Without this change, isUnclosedTag({ openingElement, ... }) below will lead to runtime error since openingElement becomes undefined, for example, in the test case const x = <> foo/*1*/ </> (see @Filename: /1.tsx in autoCloseFragment.ts).

Probably, a legitimate fix would be something like defining type JsxParent = JsxElement | JsxFragment and replacing existing member types parent: JsxElement with parent: JsxParant, but I'm not so sure yet that is what's supposed to be since I'm very new to the code base.
I would appreciate if someone familar with these parts can comment on this.

Copy link
Member

Choose a reason for hiding this comment

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

As far as I can tell, I think this is a good way to fix this problem. I think maybe the original code just didn't consider JsxFragments when it was written.

Copy link
Member

Choose a reason for hiding this comment

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

@hi-ogawa did you try changing the type of parent here to be JsxElement | JsxFragment? I agree we should really fix that type. Would you mind trying it and seeing if you can resolve any other errors that come up?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback!
Well, I didn't try anything yet, but I'll see what I can do and get back to you with my findings.

Copy link
Contributor Author

@hi-ogawa hi-ogawa Sep 8, 2021

Choose a reason for hiding this comment

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

@andrewbranch Following your request, I addressed the issue with this commit 90a35e0.
There wasn't any type error related to JsxText.parent so it looks alright.
I also changed the type of JsxExpression.parent to include JsxFragment since I believe that's what it should be, then there was one type error in src/services/completions.ts, so I did a trivial fix for that.
Overall, I think there aren't much use JsxText.parent or JsxExpression.parent in the code base currently, so hopefully the type change won't have much impact.
But, one thing I worry about is that it's going to change public API (as you can see from baselines/references/api), so the library users will be also affected by this change.

Copy link

@melisarichmomnd melisarichmomnd left a comment

Choose a reason for hiding this comment

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


@ShuiRuTian
Copy link
Contributor

ShuiRuTian commented Aug 22, 2021

Could I ask an code-unrelated question?
How did you debug this? I tried to fix this issue too, but found it seems this feature is not done through tsserver.
And even now I could not trigger the breakpoint.
Here is how I tried to debug:

  1. run powershell command under typescript repo $env:TSS_DEBUG = 5859; code --user-data-dir ../vscode-debug/
  2. set tsserver path of opened vscode to built tsserver.js of typescript reposiotry
  3. restart tsserver of opened vscode.
  4. choose "Attach to VS Code TS Server via Port" in typescript repo vscode debug menu.
  5. choose 5859 port
  6. set breakpoint and make changes on opened vscode.

And there is no related info on tsserver log, which you could see by run command Open ts server log when you press "ctrl+shift+p" on opened vscode.

Copy link
Member

@gabritto gabritto left a comment

Choose a reason for hiding this comment

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

Looks very good, thanks! :)

@@ -2088,10 +2088,15 @@ namespace ts {
const token = findPrecedingToken(position, sourceFile);
if (!token) return undefined;
const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent
: isJsxText(token) ? token.parent : undefined;
: isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined;
Copy link
Member

Choose a reason for hiding this comment

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

As far as I can tell, I think this is a good way to fix this problem. I think maybe the original code just didn't consider JsxFragments when it was written.

@@ -2088,10 +2088,15 @@ namespace ts {
const token = findPrecedingToken(position, sourceFile);
if (!token) return undefined;
const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent
: isJsxText(token) ? token.parent : undefined;
: isJsxText(token) && isJsxElement(token.parent) ? token.parent : undefined;
Copy link
Member

Choose a reason for hiding this comment

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

@hi-ogawa did you try changing the type of parent here to be JsxElement | JsxFragment? I agree we should really fix that type. Would you mind trying it and seeing if you can resolve any other errors that come up?

@andrewbranch
Copy link
Member

@ShuiRuTian I’m not sure, but the debug issue you were seeing might be solved by typescript.tsserver.useSeparateSyntaxServer: false. (You might also be interested in https://marketplace.visualstudio.com/items?itemName=andrewbranch.vscode-tsserver-debug for convenience 🙂)

@hi-ogawa hi-ogawa force-pushed the 45010-auto-complete-jsx-fragment branch from 19cc4a3 to 90a35e0 Compare September 8, 2021 15:21
Copy link
Member

@andrewbranch andrewbranch left a comment

Choose a reason for hiding this comment

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

Thanks! I’m not concerned about the public API change—the API was wrong, and you’re fixing it. Consumers might have type errors, but it should help them catch bugs in their own code.

@hi-ogawa
Copy link
Contributor Author

hi-ogawa commented Sep 8, 2021

Awesome! Thank you two for the code review and triaging my first PR!

@andrewbranch andrewbranch merged commit 617251f into microsoft:main Sep 8, 2021
@hi-ogawa hi-ogawa deleted the 45010-auto-complete-jsx-fragment branch September 8, 2021 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

[FR] Auto-complete Fragments
6 participants