Skip to content

Sort exports when organizeImports is run #24237

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 4 commits into from
May 18, 2018
Merged

Conversation

amcasey
Copy link
Member

@amcasey amcasey commented May 18, 2018

Note that there's no attempt to remove unused exports. I also didn't move the existing import tests into a corresponding describe for consistency because I wanted to avoid blowing up the diff.

Fixes #23640

Note that there's no attempt to remove unused exports.

Fixes microsoft#23640
@amcasey amcasey requested a review from a user May 18, 2018 01:44
@amcasey
Copy link
Member Author

amcasey commented May 18, 2018

Just for you, @DanielRosenwasser.

assertListEqual(actualCoalescedExports, expectedCoalescedExports);
});

it("Sort specifiers - case-insensitive", () => {
Copy link

Choose a reason for hiding this comment

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

Seems like the test above this one is unnecessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, deleted.

@@ -509,6 +736,13 @@ import { React, Other } from "react";
return imports;
}

function parseExports(...exportStrings: string[]): ReadonlyArray<ExportDeclaration> {
const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
const exports = filter(sourceFile.statements, isExportDeclaration);
Copy link

Choose a reason for hiding this comment

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

Since this isn't really a filter, this and parseImports could use a helper function:

function castEvery<T, U extends T>(inputs: ReadonlyArray<T>, pred: (t: T) => t is U): ReadonlyArray<U> {
    for (const input of inputs)
        Debug.assert(pred(input));
    return inputs as ReadonlyArray<U>;
}

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 what benefit this provides.

}

return changeTracker.getChanges();

function organizeImportsWorker(oldImportDecls: ReadonlyArray<ImportDeclaration>) {
function organizeImportsWorker(oldImportDecls: ReadonlyArray<ImportDeclaration | ExportDeclaration>) {
Copy link

Choose a reason for hiding this comment

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

To keep it type-safe this could be written as:

function organizeImportsWorker<T extends ImportDeclaration | ExportDeclaration>(oldImportDecls: ReadonlyArray<T>, coalesce: (t: ReadonlyArray<T>) => ReadonlyArray<T>)

And called with:

const coalesceAndOrganizeImports = (importGroup: ReadonlyArray<ImportDeclaration>) => coalesceImports(removeUnusedImports(importGroup, sourceFile, program));
...
organizeImportsWorker(topLevelImportDecls, coalesceAndOrganizeImports);
...
organizeImportsWorker(topLevelExportDecls, coalesceExports);

Then inside the function you can avoid the casts and just:

const newImportDecls = flatMap(sortedImportGroups, importGroup => getExternalModuleName(importGroup[0].moduleSpecifier) ? coalesce(importGroup) : importGroup);

Copy link
Member Author

Choose a reason for hiding this comment

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

Personally, I find that pulling a single line out of a method and moving it offscreen makes the method harder to read, but I agree that runtime type checks are undesirable (even though we're using JS).

Having said that, I found that I was fighting the type system for most of this change. For instance, I would have liked to type the parameter as ReadonlyArray<ImportDeclaration> | ReadonlyArray<ExportDeclaration>, but that caused even more problems.

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've implemented it and it looks reasonable.

const newExportSpecifiers: ExportSpecifier[] = [];
newExportSpecifiers.push(...flatMap(namedExports, i => (i.exportClause).elements));

const sortedExportSpecifiers = stableSort(newExportSpecifiers, (s1, s2) =>
Copy link

Choose a reason for hiding this comment

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

There's duplicate code in coalesceImports, could they share?

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 made getCategorizedImports shared and I find the result much uglier than what I started with. Did you have something specific in mind?

Copy link

@ghost ghost May 18, 2018

Choose a reason for hiding this comment

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

Sorry, not getCategorizedX, just the stableSort:

function sortSpecifiers<T extends ImportSpecifier | ExportSpecifier>(specifiers: ReadonlyArray<T>): ReadonlyArray<T> {
    return stableSort(specifiers, (s1, s2) =>
        compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) ||
        compareIdentifiers(s1.name, s2.name));
}

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.

@amcasey amcasey merged commit 04a3512 into microsoft:master May 18, 2018
@amcasey amcasey deleted the GH23640 branch May 18, 2018 20:39
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 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