-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Added alphabetization logic to missing import quick fixes #21876
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
Added alphabetization logic to missing import quick fixes #21876
Conversation
I think I'll need it for inserting a node before the first in a list.
Searches through the elements until one with a "greater" (using native string compares) one is found, and inserts at that position. Test cases 8, 10, and 11 are failing. Copied them as 13, 14, and 15 for inserting in the middle of a list. Copied them as 16, 17, and 18 for inserting at the end of a list.
* Mismatched test case commas in 8, 12, 13, 16 * Mismatched test case endlines in 11, 15 * Was using `sampleIndex - 1` but passing `0` as that index for the before-first insertion. Switched to just using `sampleIndex`. * Used last node instead of end of the first node for the ending range when determining line separator style for inserting after the first. * Inserting before the first in a multiline list needs to insert at position - indentation Also forgot to `return this;` at the end.
Public first, then private.
src/services/textChanges.ts
Outdated
* Linear search is used instead of binary as the (generally few) nodes are not guaranteed to be in order. | ||
*/ | ||
public insertNodeInListAlphabetically(sourceFile: SourceFile, containingList: NodeArray<ImportSpecifier>, newNode: ImportSpecifier): this { | ||
if (newNode.name.text < containingList[0].name.text) { |
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 way we're using this, containingList
will contain at least one element, but it's not obvious if you were just looking at this method.
This may be too specific to belong in textChanges
and could just go in importFixes
. Though I think insertNodeBefore
could be adapted so importFixes
doesn't have to call a special insertNodeInListBeforeFirst
method or have to call insertNodeInListAfter
on a previous element.
src/services/textChanges.ts
Outdated
} | ||
|
||
for (let i = 1; i < containingList.length; i += 1) { | ||
if (newNode.name.text < containingList[i].name.text) { |
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.
Not sure, but perhaps compareStringsCaseInsensitive
would be appropriate?
What is the sort order of an aliased imported symbol like |
In this PR, it goes by the |
We need to use #21909 for this change. |
I'll wait :) |
#21909 has been merged. What's the status of this pull request? |
Thanks for the ping @Ghabriel - I'll take a stab at resolving the merge conflicts hopefully this week. |
@Andy-MS have I addressed your comments? |
# Conflicts: # src/services/textChanges.ts
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.
Still a few concerns.
/** | ||
* This function should be used to insert nodes alphabetically in lists when nodes don't carry separators as the part of the node range, | ||
* e.g. export lists, import lists, etc. | ||
* Linear search is used instead of binary as the (generally few) nodes are not guaranteed to be in order. |
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 would have thought there was nothing to do if the nodes were not already in order. Would it make sense to do a pre-pass to check whether they're in order then binary search if they are and add to the end if they're not?
*/ | ||
function insertNodeInsideListNodesAlphabetically(context: SymbolContext, sourceFile: SourceFile, containingList: NodeArray<ImportSpecifier>, newNode: ImportSpecifier) { | ||
return ChangeTracker.with(context, t => { | ||
if (compareStringsCaseInsensitive(newNode.name.text, containingList[0].name.text) === Comparison.LessThan) { |
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 can see why either the first iteration or the last iteration has to be special, but not why both need to be pulled out.
@@ -483,107 +483,150 @@ namespace ts.textChanges { | |||
if (index < 0) { | |||
return this; | |||
} | |||
const end = after.getEnd(); | |||
if (index !== containingList.length - 1) { | |||
// any element except the last one | |||
// use next sibling as an anchor | |||
const nextToken = getTokenAtPosition(sourceFile, after.end, /*includeJsDocComment*/ false); | |||
if (nextToken && isSeparator(after, nextToken)) { |
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 don't think this actually changed, but does the insertion just silently fail if there's no next token or it's not a separator?
} | ||
} | ||
|
||
public insertNodeInListBeforeFirst(sourceFile: SourceFile, containingList: NodeArray<Node>, first: Node, newNode: Node): void { |
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.
Will first
always be containingList[0]
?
*/ | ||
function insertNodeInsideListNodesAlphabetically(context: SymbolContext, sourceFile: SourceFile, containingList: NodeArray<ImportSpecifier>, newNode: ImportSpecifier) { | ||
return ChangeTracker.with(context, t => { | ||
if (compareStringsCaseInsensitive(newNode.name.text, containingList[0].name.text) === Comparison.LessThan) { |
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'd be happier if this were more tightly coupled to organizeImports so that they don't get out of sync. Maybe we could export compareIdentifiers
from organizeImports.ts
and use it here?
let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken; | ||
let multilineList = false; | ||
|
||
// insert element after the last element in the list that has more than one item |
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'm not sure this comment makes sense here. Does it need to be moved or rephrased?
const afterStart = after.getStart(sourceFile); | ||
const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); | ||
|
||
const { multilineList, separator } = this.getMultilineAndSeparatorOfList(sourceFile, containingList, afterStartLinePosition, index - 1, after); |
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.
Why - 1
?
const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[sampleIndex].getStart(sourceFile), sourceFile); | ||
multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; | ||
} | ||
if (hasCommentsBeforeLineBreak(sourceFile.text, sampleNode.end)) { |
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.
!multilineList &&
?
} | ||
|
||
public insertNodeInListBeforeFirst(sourceFile: SourceFile, containingList: NodeArray<Node>, first: Node, newNode: Node): void { | ||
const startPosition = first.getStart(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.
It looks like this might be after leading trivia on first
. Does that matter?
} | ||
|
||
const last = containingList[containingList.length - 1]; | ||
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, last.end, sourceFile, this.formatContext.options); |
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.
Why continue until last.end
? Isn't startPosition
sufficient?
@JoshuaKGoldberg can you follow up with Andrew's comments/questions? Thanks! |
Will try to get to this soon, thanks for the ping! I'll close the issue if I don't think I'll have time within a month. |
😢 |
When generating a codefix to add a missing import to an imports list, instead of always adding to the end, the node will be inserted before the first node its name is
<
alphabetically (or at the end if<
none). Linear search is used instead of binary as the (generally few) nodes are not guaranteed to be in order.Fixes #16119