Skip to content
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

Fix organize imports overlap #43228

Merged
merged 6 commits into from
Apr 19, 2021

Conversation

armanio123
Copy link
Member

Fixes #43107

Text changes will now include multiline comments if they are considered trailing comments from the previous node.

@typescript-bot typescript-bot added the For Milestone Bug PRs that fix a bug with a specific milestone label Mar 13, 2021
Copy link
Contributor

@jessetrinity jessetrinity left a comment

Choose a reason for hiding this comment

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

I think you're on the right track - I'm pretty sure the overlap issue is just coming from newlines at this point.

if (hasTrailingComment) {
const comment = getTrailingCommentRanges(sourceFile.text, fullStart)?.[0];
if (comment) {
return comment.end;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return comment.end;
return skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true);

Copy link
Contributor

Choose a reason for hiding this comment

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

Since you call skipTrivia when you do getAdjustedEndPosition, it looks like you would need to do the same for the comment range for getAdjustedStartPosition, otherwise the carriage return / new lines will overlap.

This seems to fix the issue except when the trailing comment is on the first import, so there might be another place where you have to do this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I understand in which condition this is an issue. Do you have a sample code I can test?

const commentEndLine = getLineOfLocalPosition(sourceFile, comment.end);
if (commentEndLine > nodeEndLine) {
const newEnd = skipTrivia(sourceFile.text, comment.end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ true);
return { end: newEnd, hasTrailingComment: true };
Copy link
Contributor

Choose a reason for hiding this comment

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

Can hasTrailingComment be determined by the trivia option outside of this function, such as in the deleteNodes loop? It seems awkward to return this new object if we're almost always interested in only the end position.

If refactoring is too difficult I would at minimum change the name of the function since getAdjustedEndPosition implies we're still just getting the position.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. The new method name is a little confusing but I wasn't sure how to name it. Suggestions are welcome.

for (const comment of comments) {
// Single line can break the loop as trivia will only be this line.
// Comments on subsequest lines are also ignored.
if (comment.kind === SyntaxKind.SingleLineCommentTrivia || getLineOfLocalPosition(sourceFile, comment.pos) > nodeEndLine) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This warrants a test example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// When deleting multiple nodes we need to track if the end position is including multiline trailing comments.
let hasTrailingComment = false;

for (const node of nodes) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems pretty specific to import declaration nodes. What happens when a node with a trailing multiline comment is between two import statements? for example:

import * as b from "b";
console.log();/**
 * what happens to me?
 */
import * as a from "a"

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately, the result would be console.log();/** as we have no way of recognizing what the previous node is about.

I think this is a design flaw and suggest addressing it on another ticket. So far this PR will not break that scenario.

//// import * as AnotherThingElse from "someotherpath";


verify.organizeImports('');
Copy link
Contributor

Choose a reason for hiding this comment

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

All of these imports are unused so they will just be deleted. We probably do want to test that but you will want to also add tests in which the imports are used, and with non-imports between the imports, also with trailing trivia.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch I was so focused on the issue that I didn't even considered. I did and the crash still happens. Working on it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

@jessetrinity jessetrinity left a comment

Choose a reason for hiding this comment

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

Just a couple more small comments from me.

@@ -345,6 405,13 @@ namespace ts.textChanges {
this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options);
}

public replaceAndDeleteNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: readonly Node[], deleteNodes: Node[], replaceOptions: ChangeNodeOptions = useNonAdjustedPositions, deleteOptions: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

This name is a bit confusing - I probably wouldn't even extract this to a function. It's pretty simple and it looks like you only use it once anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree, done.


// Regression test for GH#43107

//// import * as something from "path"; /* small comment */ // single line one.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you want this single line comment test to also not delete the import so you can see that the comment is preserved (by adding usages of something and somethingElse below).

Copy link
Member Author

Choose a reason for hiding this comment

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

It was actually meant to test that the single line is deleted. I found I used to have that problem. It also makes sense to test the contrary so I have updated to do it for both.

@armanio123 armanio123 merged commit 1a04b17 into microsoft:master Apr 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Milestone Bug PRs that fix a bug with a specific milestone
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Overlapping changes error for organize imports
3 participants