Skip to content

Refine extends-to-implements code fix #20991

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
merged 2 commits into from
Jan 6, 2018
Merged

Conversation

amcasey
Copy link
Member

@amcasey amcasey commented Jan 3, 2018

  1. Retain surrounding trivia when swapping the keyword.
  2. Insert commas at the full-starts, rather than starts, of existing
    keywords when merging with existing implements clauses.

Fixes #18794

@amcasey amcasey requested review from a user and sandersn January 3, 2018 23:04
@amcasey
Copy link
Member Author

amcasey commented Jan 3, 2018

FYI @sandersn who periodically has to deal with trivia issues in code fixes and refactorings.

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

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

You could try something like this to avoid dealing directly with positions:

if (heritageClauses.length === 1) {
    changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
}
else {
    changes.deleteNode(sourceFile, heritageClauses[0]);
    // Move types from 'extends' to 'implements' clause
    for (let i = heritageClauses[0].types.length - 1; i >= 0; i--) {
        changes.insertNodeBefore(sourceFile, first(heritageClauses[1].types), heritageClauses[0].types[i]);
    }
}

(and in textChanges.ts add || isExpressionWithTypeArguments(before) to the else if in getOptionsForInsertNodeBefore)

@@ -27,11 +27,22 @@ namespace ts.codefix {
}

function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: ReadonlyArray<HeritageClause>): void {
changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
changes.replaceRange(sourceFile, { pos: extendsToken.getStart(), end: extendsToken.end }, createToken(SyntaxKind.ImplementsKeyword));
// We replace existing keywords with commas.
for (let i = 1; i < heritageClauses.length; i++) {
Copy link

Choose a reason for hiding this comment

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

It was this way before, but shouldn't this only apply to heritageClauses[1] since the others will have commas already?

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 thought that implements I1, I2 counted as a single heritage clause (and that, in correct code, heritageClauses would have 0, 1, or 2 elements).

Copy link

Choose a reason for hiding this comment

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

OK, but then we don't need the loop, just an if.

@@ -27,11 +27,22 @@ namespace ts.codefix {
}

function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: ReadonlyArray<HeritageClause>): void {
changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
changes.replaceRange(sourceFile, { pos: extendsToken.getStart(), end: extendsToken.end }, createToken(SyntaxKind.ImplementsKeyword));
// We replace existing keywords with commas.
for (let i = 1; i < heritageClauses.length; i++) {
const keywordToken = heritageClauses[i].getFirstToken()!;
Copy link

Choose a reason for hiding this comment

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

Might want to assert that this is SyntaxKind.ImplementsKeyword.

Copy link
Member Author

Choose a reason for hiding this comment

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

My thinking was that that assertion would fail in invalid code (e.g. multiple extends clauses or an out-of-order extends clause). I'm not convinced it's worthwhile to harden a best-effort code fix against that sort of bad code (the user will already be getting a diagnostic and the change is undoable), but I don't feel strongly about it. However, I'd only add the assert if we were also adding actual checks.

@amcasey
Copy link
Member Author

amcasey commented Jan 4, 2018

@Andy-MS I don't think I can use the replaceNode call you suggest because it will clobber the trivia of the replaced keyword. I did consider updating the list of types in the existing implements clause, but it wasn't clear where all the trivia would go in that case. @sandersn and I discussed it offline (before I implemented it) and concluded that this was the closest to what a user would do by hand.

 1. Retain surrounding trivia when swapping the keyword.
 2. Insert commas at the full-starts, rather than starts, of existing
 keywords when merging with existing implements clauses.

Fixes microsoft#18794
@amcasey
Copy link
Member Author

amcasey commented Jan 5, 2018

Good to go?

@amcasey amcasey merged commit 1957dfe into microsoft:master Jan 6, 2018
@amcasey amcasey deleted the GH18794 branch January 6, 2018 01:52
@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant