mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 21:36:50 -05:00
Separate OrganizeImports into its own namespace and file
This commit is contained in:
@@ -6,12 +6,12 @@ namespace ts {
|
||||
describe("Organize imports", () => {
|
||||
describe("Sort imports", () => {
|
||||
it("No imports", () => {
|
||||
assert.isEmpty(sortImports([]));
|
||||
assert.isEmpty(OrganizeImports.sortImports([]));
|
||||
});
|
||||
|
||||
it("One import", () => {
|
||||
const unsortedImports = parseImports(`import "lib";`);
|
||||
const actualSortedImports = sortImports(unsortedImports);
|
||||
const actualSortedImports = OrganizeImports.sortImports(unsortedImports);
|
||||
const expectedSortedImports = unsortedImports;
|
||||
assertListEqual(expectedSortedImports, actualSortedImports);
|
||||
});
|
||||
@@ -84,27 +84,27 @@ namespace ts {
|
||||
|
||||
function assertUnaffectedBySort(...importStrings: string[]) {
|
||||
const unsortedImports1 = parseImports(...importStrings);
|
||||
assertListEqual(unsortedImports1, sortImports(unsortedImports1));
|
||||
assertListEqual(unsortedImports1, OrganizeImports.sortImports(unsortedImports1));
|
||||
|
||||
const unsortedImports2 = reverse(unsortedImports1);
|
||||
assertListEqual(unsortedImports2, sortImports(unsortedImports2));
|
||||
assertListEqual(unsortedImports2, OrganizeImports.sortImports(unsortedImports2));
|
||||
}
|
||||
|
||||
function assertSortsBefore(importString1: string, importString2: string) {
|
||||
const imports = parseImports(importString1, importString2);
|
||||
assertListEqual(imports, sortImports(imports));
|
||||
assertListEqual(imports, sortImports(reverse(imports)));
|
||||
assertListEqual(imports, OrganizeImports.sortImports(imports));
|
||||
assertListEqual(imports, OrganizeImports.sortImports(reverse(imports)));
|
||||
}
|
||||
});
|
||||
|
||||
describe("Coalesce imports", () => {
|
||||
it("No imports", () => {
|
||||
assert.isEmpty(coalesceImports([]));
|
||||
assert.isEmpty(OrganizeImports.coalesceImports([]));
|
||||
});
|
||||
|
||||
it("Sort specifiers", () => {
|
||||
const sortedImports = parseImports(`import { default as m, a as n, b, y, z as o } from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(`import { a as n, b, default as m, y, z as o } from "lib";`);
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -113,7 +113,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(`import "lib";`);
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -122,7 +122,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import * as x from "lib";`,
|
||||
`import * as y from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -131,7 +131,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import x from "lib";`,
|
||||
`import y from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`);
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -140,7 +140,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import { x } from "lib";`,
|
||||
`import { y as z } from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`);
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -149,7 +149,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import * as x from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -158,7 +158,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import x from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -167,7 +167,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import { x } from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -176,7 +176,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import * as x from "lib";`,
|
||||
`import y from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import y, * as x from "lib";`);
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
@@ -186,7 +186,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import * as x from "lib";`,
|
||||
`import { y } from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
@@ -195,7 +195,7 @@ namespace ts {
|
||||
const sortedImports = parseImports(
|
||||
`import x from "lib";`,
|
||||
`import { y } from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import x, { y } from "lib";`);
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
@@ -211,7 +211,7 @@ namespace ts {
|
||||
`import * as x from "lib";`,
|
||||
`import z from "lib";`,
|
||||
`import { a } from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import "lib";`,
|
||||
`import * as x from "lib";`,
|
||||
@@ -226,7 +226,7 @@ namespace ts {
|
||||
`import { b } from "lib1";`,
|
||||
`import { c } from "lib2";`,
|
||||
`import { a } from "lib2";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = parseImports(
|
||||
`import { b, d } from "lib1";`,
|
||||
`import { a, c } from "lib2";`);
|
||||
@@ -239,7 +239,7 @@ namespace ts {
|
||||
`import * as x from "lib";`,
|
||||
`import * as y from "lib";`,
|
||||
`import z from "lib";`);
|
||||
const actualCoalescedImports = coalesceImports(sortedImports);
|
||||
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
|
||||
const expectedCoalescedImports = sortedImports;
|
||||
assertListEqual(expectedCoalescedImports, actualCoalescedImports);
|
||||
});
|
||||
|
||||
291
src/services/organizeImports.ts
Normal file
291
src/services/organizeImports.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
/* @internal */
|
||||
namespace ts.OrganizeImports {
|
||||
export function organizeImports(
|
||||
sourceFile: SourceFile,
|
||||
formatContext: formatting.FormatContext,
|
||||
host: LanguageServiceHost,
|
||||
cancellationToken: CancellationToken) {
|
||||
|
||||
// All of the (old) ImportDeclarations in the file, in syntactic order.
|
||||
const oldImportDecls: ImportDeclaration[] = [];
|
||||
|
||||
forEachChild(sourceFile, node => {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
if (isImportDeclaration(node)) {
|
||||
oldImportDecls.push(node);
|
||||
}
|
||||
// TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
|
||||
});
|
||||
|
||||
if (oldImportDecls.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const usedImportDecls = removeUnusedImports(oldImportDecls);
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
const sortedImportDecls = sortImports(usedImportDecls);
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
const coalescedImportDecls = coalesceImports(sortedImportDecls);
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
// All of the (new) ImportDeclarations in the file, in sorted order.
|
||||
const newImportDecls = coalescedImportDecls;
|
||||
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext });
|
||||
|
||||
// NB: Stopping before i === 0
|
||||
for (let i = oldImportDecls.length - 1; i > 0; i--) {
|
||||
changeTracker.deleteNode(sourceFile, oldImportDecls[i]);
|
||||
}
|
||||
|
||||
if (newImportDecls.length === 0) {
|
||||
changeTracker.deleteNode(sourceFile, oldImportDecls[0]);
|
||||
}
|
||||
else {
|
||||
// Delete the surrounding trivia because it will have been retained in newImportDecls.
|
||||
const replaceOptions = {
|
||||
useNonAdjustedStartPosition: false,
|
||||
useNonAdjustedEndPosition: false,
|
||||
suffix: getNewLineOrDefaultFromHost(host, formatContext.options),
|
||||
};
|
||||
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions);
|
||||
}
|
||||
|
||||
const changes = changeTracker.getChanges();
|
||||
return changes;
|
||||
}
|
||||
|
||||
function removeUnusedImports(oldImports: ReadonlyArray<ImportDeclaration>) {
|
||||
return oldImports; // TODO (https://github.com/Microsoft/TypeScript/issues/10020)
|
||||
}
|
||||
|
||||
/* @internal */ // Internal for testing
|
||||
export function sortImports(oldImports: ReadonlyArray<ImportDeclaration>) {
|
||||
if (oldImports.length < 2) {
|
||||
return oldImports;
|
||||
}
|
||||
|
||||
// NB: declaration order determines sort order
|
||||
const enum ModuleNameKind {
|
||||
NonRelative,
|
||||
Relative,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
const importRecords = oldImports.map(createImportRecord);
|
||||
|
||||
const sortedRecords = stableSort(importRecords, (import1, import2) => {
|
||||
const { name: name1, kind: kind1 } = import1;
|
||||
const { name: name2, kind: kind2 } = import2;
|
||||
|
||||
if (kind1 !== kind2) {
|
||||
return kind1 < kind2
|
||||
? Comparison.LessThan
|
||||
: Comparison.GreaterThan;
|
||||
}
|
||||
|
||||
// Note that we're using simple equality, retaining case-sensitivity.
|
||||
if (name1 !== name2) {
|
||||
return name1 < name2
|
||||
? Comparison.LessThan
|
||||
: Comparison.GreaterThan;
|
||||
}
|
||||
|
||||
return Comparison.EqualTo;
|
||||
});
|
||||
|
||||
return sortedRecords.map(r => r.importDeclaration);
|
||||
|
||||
function createImportRecord(importDeclaration: ImportDeclaration) {
|
||||
const specifier = importDeclaration.moduleSpecifier;
|
||||
const name = getExternalModuleName(specifier);
|
||||
if (name) {
|
||||
const isRelative = isExternalModuleNameRelative(name);
|
||||
return { importDeclaration, name, kind: isRelative ? ModuleNameKind.Relative : ModuleNameKind.NonRelative };
|
||||
}
|
||||
|
||||
return { importDeclaration, name: specifier.getText(), kind: ModuleNameKind.Invalid };
|
||||
}
|
||||
}
|
||||
|
||||
function getExternalModuleName(specifier: Expression) {
|
||||
return isStringLiteral(specifier) || isNoSubstitutionTemplateLiteral(specifier)
|
||||
? specifier.text
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sortedImports a non-empty list of ImportDeclarations, sorted by module name.
|
||||
*/
|
||||
function groupSortedImports(sortedImports: ReadonlyArray<ImportDeclaration>): ReadonlyArray<ReadonlyArray<ImportDeclaration>> {
|
||||
Debug.assert(length(sortedImports) > 0);
|
||||
|
||||
const groups: ImportDeclaration[][] = [];
|
||||
|
||||
let groupName: string | undefined = getExternalModuleName(sortedImports[0].moduleSpecifier);
|
||||
let group: ImportDeclaration[] = [];
|
||||
|
||||
for (const importDeclaration of sortedImports) {
|
||||
const moduleName = getExternalModuleName(importDeclaration.moduleSpecifier);
|
||||
if (moduleName && moduleName === groupName) {
|
||||
group.push(importDeclaration);
|
||||
}
|
||||
else if (group.length) {
|
||||
groups.push(group);
|
||||
|
||||
groupName = moduleName;
|
||||
group = [importDeclaration];
|
||||
}
|
||||
}
|
||||
|
||||
if (group.length) {
|
||||
groups.push(group);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
/* @internal */ // Internal for testing
|
||||
/**
|
||||
* @param sortedImports a list of ImportDeclarations, sorted by module name.
|
||||
*/
|
||||
export function coalesceImports(sortedImports: ReadonlyArray<ImportDeclaration>) {
|
||||
if (sortedImports.length === 0) {
|
||||
return sortedImports;
|
||||
}
|
||||
|
||||
const coalescedImports: ImportDeclaration[] = [];
|
||||
|
||||
const groupedImports = groupSortedImports(sortedImports);
|
||||
for (const importGroup of groupedImports) {
|
||||
|
||||
let seenImportWithoutClause = false;
|
||||
|
||||
const defaultImports: Identifier[] = [];
|
||||
const namespaceImports: NamespaceImport[] = [];
|
||||
const namedImports: NamedImports[] = [];
|
||||
|
||||
for (const importDeclaration of importGroup) {
|
||||
if (importDeclaration.importClause === undefined) {
|
||||
// Only the first such import is interesting - the others are redundant.
|
||||
// Note: Unfortunately, we will lose trivia that was on this node.
|
||||
if (!seenImportWithoutClause) {
|
||||
coalescedImports.push(importDeclaration);
|
||||
}
|
||||
|
||||
seenImportWithoutClause = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, namedBindings } = importDeclaration.importClause;
|
||||
|
||||
if (name) {
|
||||
defaultImports.push(name);
|
||||
}
|
||||
|
||||
if (namedBindings) {
|
||||
if (isNamespaceImport(namedBindings)) {
|
||||
namespaceImports.push(namedBindings);
|
||||
}
|
||||
else {
|
||||
namedImports.push(namedBindings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normally, we don't combine default and namespace imports, but it would be silly to
|
||||
// produce two import declarations in this special case.
|
||||
if (defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) {
|
||||
// Add the namespace import to the existing default ImportDeclaration.
|
||||
const defaultImportClause = defaultImports[0].parent as ImportClause;
|
||||
coalescedImports.push(
|
||||
updateImportDeclarationAndClause(defaultImportClause, defaultImportClause.name, namespaceImports[0]));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// For convenience, we cheat and do a little sorting during coalescing.
|
||||
// Seems reasonable since we're restructuring so much anyway.
|
||||
const sortedNamespaceImports = stableSort(namespaceImports, (n1, n2) => compareIdentifiers(n1.name, n2.name));
|
||||
|
||||
for (const namespaceImport of sortedNamespaceImports) {
|
||||
// Drop the name, if any
|
||||
coalescedImports.push(
|
||||
updateImportDeclarationAndClause(namespaceImport.parent, /*name*/ undefined, namespaceImport));
|
||||
}
|
||||
|
||||
if (defaultImports.length === 0 && namedImports.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newDefaultImport: Identifier = undefined;
|
||||
const newImportSpecifiers: ImportSpecifier[] = [];
|
||||
if (defaultImports.length === 1) {
|
||||
newDefaultImport = defaultImports[0];
|
||||
}
|
||||
else {
|
||||
for (const defaultImport of defaultImports) {
|
||||
newImportSpecifiers.push(
|
||||
createImportSpecifier(createIdentifier("default"), defaultImport));
|
||||
}
|
||||
}
|
||||
|
||||
for (const namedImport of namedImports) {
|
||||
for (const specifier of namedImport.elements) {
|
||||
newImportSpecifiers.push(specifier);
|
||||
}
|
||||
}
|
||||
|
||||
const sortedImportSpecifiers = stableSort(newImportSpecifiers, (s1, s2) => {
|
||||
const nameComparison = compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name);
|
||||
return nameComparison !== Comparison.EqualTo
|
||||
? nameComparison
|
||||
: compareIdentifiers(s1.name, s2.name);
|
||||
});
|
||||
|
||||
const importClause = defaultImports.length > 0
|
||||
? defaultImports[0].parent as ImportClause
|
||||
: namedImports[0].parent;
|
||||
|
||||
const newNamedImports = sortedImportSpecifiers.length === 0
|
||||
? undefined
|
||||
: namedImports.length === 0
|
||||
? createNamedImports(sortedImportSpecifiers)
|
||||
: updateNamedImports(namedImports[0], sortedImportSpecifiers);
|
||||
|
||||
coalescedImports.push(
|
||||
updateImportDeclarationAndClause(importClause, newDefaultImport, newNamedImports));
|
||||
}
|
||||
|
||||
return coalescedImports;
|
||||
|
||||
// `undefined` is the min value.
|
||||
function compareIdentifiers(s1: Identifier | undefined, s2: Identifier | undefined) {
|
||||
return s1 === undefined
|
||||
? s2 === undefined
|
||||
? Comparison.EqualTo
|
||||
: Comparison.LessThan
|
||||
: s2 === undefined
|
||||
? Comparison.GreaterThan
|
||||
: s1.text < s2.text
|
||||
? Comparison.LessThan
|
||||
: s1.text > s2.text
|
||||
? Comparison.GreaterThan
|
||||
: Comparison.EqualTo;
|
||||
}
|
||||
|
||||
function updateImportDeclarationAndClause(
|
||||
importClause: ImportClause,
|
||||
name: Identifier | undefined,
|
||||
namedBindings: NamedImportBindings | undefined) {
|
||||
|
||||
const importDeclaration = importClause.parent;
|
||||
return updateImportDeclaration(
|
||||
importDeclaration,
|
||||
importDeclaration.decorators,
|
||||
importDeclaration.modifiers,
|
||||
updateImportClause(importClause, name, namedBindings),
|
||||
importDeclaration.moduleSpecifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
/// <reference path='jsTyping.ts' />
|
||||
/// <reference path='navigateTo.ts' />
|
||||
/// <reference path='navigationBar.ts' />
|
||||
/// <reference path='organizeImports.ts' />
|
||||
/// <reference path='outliningElementsCollector.ts' />
|
||||
/// <reference path='patternMatcher.ts' />
|
||||
/// <reference path='preProcess.ts' />
|
||||
@@ -1854,50 +1855,7 @@ namespace ts {
|
||||
const sourceFile = getValidSourceFile(scope.fileName);
|
||||
const formatContext = formatting.getFormatContext(formatOptions);
|
||||
|
||||
// All of the (old) ImportDeclarations in the file, in syntactic order.
|
||||
const oldImportDecls: ImportDeclaration[] = [];
|
||||
|
||||
forEachChild(sourceFile, node => {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
if (isImportDeclaration(node)) {
|
||||
oldImportDecls.push(node);
|
||||
}
|
||||
// TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
|
||||
});
|
||||
|
||||
if (oldImportDecls.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const usedImportDecls = removeUnusedImports(oldImportDecls);
|
||||
const sortedImportDecls = sortImports(usedImportDecls);
|
||||
const coalescedImportDecls = coalesceImports(sortedImportDecls);
|
||||
|
||||
// All of the (new) ImportDeclarations in the file, in sorted order.
|
||||
const newImportDecls = coalescedImportDecls;
|
||||
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext });
|
||||
|
||||
// NB: Stopping before i === 0
|
||||
for (let i = oldImportDecls.length - 1; i > 0; i--) {
|
||||
changeTracker.deleteNode(sourceFile, oldImportDecls[i]);
|
||||
}
|
||||
|
||||
if (newImportDecls.length === 0) {
|
||||
changeTracker.deleteNode(sourceFile, oldImportDecls[0]);
|
||||
}
|
||||
else {
|
||||
// Delete the surrounding trivia because it will have been retained in newImportDecls.
|
||||
const replaceOptions = {
|
||||
useNonAdjustedStartPosition: false,
|
||||
useNonAdjustedEndPosition: false,
|
||||
suffix: getNewLineOrDefaultFromHost(host, formatOptions),
|
||||
};
|
||||
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, replaceOptions);
|
||||
}
|
||||
|
||||
const changes = changeTracker.getChanges();
|
||||
return changes;
|
||||
return OrganizeImports.organizeImports(sourceFile, formatContext, host, cancellationToken);
|
||||
}
|
||||
|
||||
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
|
||||
@@ -2321,238 +2279,4 @@ namespace ts {
|
||||
}
|
||||
|
||||
objectAllocator = getServicesObjectAllocator();
|
||||
|
||||
function removeUnusedImports(oldImports: ReadonlyArray<ImportDeclaration>) {
|
||||
return oldImports; // TODO (https://github.com/Microsoft/TypeScript/issues/10020)
|
||||
}
|
||||
|
||||
/* @internal */ // Internal for testing
|
||||
export function sortImports(oldImports: ReadonlyArray<ImportDeclaration>) {
|
||||
if (oldImports.length < 2) {
|
||||
return oldImports;
|
||||
}
|
||||
|
||||
// NB: declaration order determines sort order
|
||||
const enum ModuleNameKind {
|
||||
NonRelative,
|
||||
Relative,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
const importRecords = oldImports.map(createImportRecord);
|
||||
|
||||
const sortedRecords = stableSort(importRecords, (import1, import2) => {
|
||||
const { name: name1, kind: kind1 } = import1;
|
||||
const { name: name2, kind: kind2 } = import2;
|
||||
|
||||
if (kind1 !== kind2) {
|
||||
return kind1 < kind2
|
||||
? Comparison.LessThan
|
||||
: Comparison.GreaterThan;
|
||||
}
|
||||
|
||||
// Note that we're using simple equality, retaining case-sensitivity.
|
||||
if (name1 !== name2) {
|
||||
return name1 < name2
|
||||
? Comparison.LessThan
|
||||
: Comparison.GreaterThan;
|
||||
}
|
||||
|
||||
return Comparison.EqualTo;
|
||||
});
|
||||
|
||||
return sortedRecords.map(r => r.importDeclaration);
|
||||
|
||||
function createImportRecord(importDeclaration: ImportDeclaration) {
|
||||
const specifier = importDeclaration.moduleSpecifier;
|
||||
const name = getExternalModuleName(specifier);
|
||||
if (name) {
|
||||
const isRelative = isExternalModuleNameRelative(name);
|
||||
return { importDeclaration, name, kind: isRelative ? ModuleNameKind.Relative : ModuleNameKind.NonRelative };
|
||||
}
|
||||
|
||||
return { importDeclaration, name: specifier.getText(), kind: ModuleNameKind.Invalid };
|
||||
}
|
||||
}
|
||||
|
||||
function getExternalModuleName(specifier: Expression) {
|
||||
return isStringLiteral(specifier) || isNoSubstitutionTemplateLiteral(specifier)
|
||||
? specifier.text
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sortedImports a non-empty list of ImportDeclarations, sorted by module name.
|
||||
*/
|
||||
function groupSortedImports(sortedImports: ReadonlyArray<ImportDeclaration>): ReadonlyArray<ReadonlyArray<ImportDeclaration>> {
|
||||
Debug.assert(length(sortedImports) > 0);
|
||||
|
||||
const groups: ImportDeclaration[][] = [];
|
||||
|
||||
let groupName: string | undefined = getExternalModuleName(sortedImports[0].moduleSpecifier);
|
||||
let group: ImportDeclaration[] = [];
|
||||
|
||||
for (const importDeclaration of sortedImports) {
|
||||
const moduleName = getExternalModuleName(importDeclaration.moduleSpecifier);
|
||||
if (moduleName && moduleName === groupName) {
|
||||
group.push(importDeclaration);
|
||||
}
|
||||
else if (group.length) {
|
||||
groups.push(group);
|
||||
|
||||
groupName = moduleName;
|
||||
group = [importDeclaration];
|
||||
}
|
||||
}
|
||||
|
||||
if (group.length) {
|
||||
groups.push(group);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
/* @internal */ // Internal for testing
|
||||
/**
|
||||
* @param sortedImports a list of ImportDeclarations, sorted by module name.
|
||||
*/
|
||||
export function coalesceImports(sortedImports: ReadonlyArray<ImportDeclaration>) {
|
||||
if (sortedImports.length === 0) {
|
||||
return sortedImports;
|
||||
}
|
||||
|
||||
const coalescedImports: ImportDeclaration[] = [];
|
||||
|
||||
const groupedImports = groupSortedImports(sortedImports);
|
||||
for (const importGroup of groupedImports) {
|
||||
|
||||
let seenImportWithoutClause = false;
|
||||
|
||||
const defaultImports: Identifier[] = [];
|
||||
const namespaceImports: NamespaceImport[] = [];
|
||||
const namedImports: NamedImports[] = [];
|
||||
|
||||
for (const importDeclaration of importGroup) {
|
||||
if (importDeclaration.importClause === undefined) {
|
||||
// Only the first such import is interesting - the others are redundant.
|
||||
// Note: Unfortunately, we will lose trivia that was on this node.
|
||||
if (!seenImportWithoutClause) {
|
||||
coalescedImports.push(importDeclaration);
|
||||
}
|
||||
|
||||
seenImportWithoutClause = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, namedBindings } = importDeclaration.importClause;
|
||||
|
||||
if (name) {
|
||||
defaultImports.push(name);
|
||||
}
|
||||
|
||||
if (namedBindings) {
|
||||
if (isNamespaceImport(namedBindings)) {
|
||||
namespaceImports.push(namedBindings);
|
||||
}
|
||||
else {
|
||||
namedImports.push(namedBindings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normally, we don't combine default and namespace imports, but it would be silly to
|
||||
// produce two import declarations in this special case.
|
||||
if (defaultImports.length === 1 && namespaceImports.length === 1 && namedImports.length === 0) {
|
||||
// Add the namespace import to the existing default ImportDeclaration.
|
||||
const defaultImportClause = defaultImports[0].parent as ImportClause;
|
||||
coalescedImports.push(
|
||||
updateImportDeclarationAndClause(defaultImportClause, defaultImportClause.name, namespaceImports[0]));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// For convenience, we cheat and do a little sorting during coalescing.
|
||||
// Seems reasonable since we're restructuring so much anyway.
|
||||
const sortedNamespaceImports = stableSort(namespaceImports, (n1, n2) => compareIdentifiers(n1.name, n2.name));
|
||||
|
||||
for (const namespaceImport of sortedNamespaceImports) {
|
||||
// Drop the name, if any
|
||||
coalescedImports.push(
|
||||
updateImportDeclarationAndClause(namespaceImport.parent, /*name*/ undefined, namespaceImport));
|
||||
}
|
||||
|
||||
if (defaultImports.length === 0 && namedImports.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newDefaultImport: Identifier = undefined;
|
||||
const newImportSpecifiers: ImportSpecifier[] = [];
|
||||
if (defaultImports.length === 1) {
|
||||
newDefaultImport = defaultImports[0];
|
||||
}
|
||||
else {
|
||||
for (const defaultImport of defaultImports) {
|
||||
newImportSpecifiers.push(
|
||||
createImportSpecifier(createIdentifier("default"), defaultImport));
|
||||
}
|
||||
}
|
||||
|
||||
for (const namedImport of namedImports) {
|
||||
for (const specifier of namedImport.elements) {
|
||||
newImportSpecifiers.push(specifier);
|
||||
}
|
||||
}
|
||||
|
||||
const sortedImportSpecifiers = stableSort(newImportSpecifiers, (s1, s2) => {
|
||||
const nameComparison = compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name);
|
||||
return nameComparison !== Comparison.EqualTo
|
||||
? nameComparison
|
||||
: compareIdentifiers(s1.name, s2.name);
|
||||
});
|
||||
|
||||
const importClause = defaultImports.length > 0
|
||||
? defaultImports[0].parent as ImportClause
|
||||
: namedImports[0].parent;
|
||||
|
||||
const newNamedImports = sortedImportSpecifiers.length === 0
|
||||
? undefined
|
||||
: namedImports.length === 0
|
||||
? createNamedImports(sortedImportSpecifiers)
|
||||
: updateNamedImports(namedImports[0], sortedImportSpecifiers);
|
||||
|
||||
coalescedImports.push(
|
||||
updateImportDeclarationAndClause(importClause, newDefaultImport, newNamedImports));
|
||||
}
|
||||
|
||||
return coalescedImports;
|
||||
|
||||
// `undefined` is the min value.
|
||||
function compareIdentifiers(s1: Identifier | undefined, s2: Identifier | undefined) {
|
||||
return s1 === undefined
|
||||
? s2 === undefined
|
||||
? Comparison.EqualTo
|
||||
: Comparison.LessThan
|
||||
: s2 === undefined
|
||||
? Comparison.GreaterThan
|
||||
: s1.text < s2.text
|
||||
? Comparison.LessThan
|
||||
: s1.text > s2.text
|
||||
? Comparison.GreaterThan
|
||||
: Comparison.EqualTo;
|
||||
}
|
||||
|
||||
function updateImportDeclarationAndClause(
|
||||
importClause: ImportClause,
|
||||
name: Identifier | undefined,
|
||||
namedBindings: NamedImportBindings | undefined) {
|
||||
|
||||
const importDeclaration = importClause.parent;
|
||||
return updateImportDeclaration(
|
||||
importDeclaration,
|
||||
importDeclaration.decorators,
|
||||
importDeclaration.modifiers,
|
||||
updateImportClause(importClause, name, namedBindings),
|
||||
importDeclaration.moduleSpecifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user