Organize type imports (#55269)

This commit is contained in:
Isabel Duan
2024-01-09 16:29:16 -08:00
committed by GitHub
parent 2c14a1c225
commit 780097856a
25 changed files with 838 additions and 53 deletions
+1
View File
@@ -10074,6 +10074,7 @@ export interface UserPreferences {
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
readonly organizeImportsTypeOrder?: "first" | "last" | "inline";
readonly excludeLibrarySymbolsInNavTo?: boolean;
}
+6 -4
View File
@@ -3521,10 +3521,12 @@ export class TestState {
actualTextArray.push(text);
// Undo changes to perform next fix
const span = change.textChanges[0].span;
const deletedText = originalContent.substr(span.start, change.textChanges[0].span.length);
const insertedText = change.textChanges[0].newText;
this.editScriptAndUpdateMarkers(fileName, span.start, span.start + insertedText.length, deletedText);
for (const textChange of change.textChanges) {
const span = textChange.span;
const deletedText = originalContent.slice(span.start, span.start + textChange.span.length);
const insertedText = textChange.newText;
this.editScriptAndUpdateMarkers(fileName, span.start, span.start + insertedText.length, deletedText);
}
}
if (expectedTextArray.length !== actualTextArray.length) {
this.raiseError(`Expected ${expectedTextArray.length} import fixes, got ${actualTextArray.length}:\n\n${actualTextArray.join("\n\n" + "-".repeat(20) + "\n\n")}`);
+7
View File
@@ -3643,6 +3643,13 @@ export interface UserPreferences {
* Default: `false`
*/
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
/**
* Indicates where named type-only imports should sort. "inline" sorts named imports without regard to if the import is
* type-only.
*
* Default: `last`
*/
readonly organizeImportsTypeOrder?: "last" | "first" | "inline";
/**
* Indicates whether {@link ReferencesResponseItem.lineText} is supported.
+5 -4
View File
@@ -53,6 +53,7 @@ import {
getSourceFileOfNode,
getSymbolId,
getTokenAtPosition,
getTokenPosOfNode,
getTypeKeywordOfTypeOnlyImport,
getUniqueSymbolId,
hostGetCanonicalFileName,
@@ -1406,14 +1407,14 @@ function promoteFromTypeOnly(
if (aliasDeclaration.parent.elements.length > 1 && sortKind) {
const newSpecifier = factory.updateImportSpecifier(aliasDeclaration, /*isTypeOnly*/ false, aliasDeclaration.propertyName, aliasDeclaration.name);
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, sortKind === SortKind.CaseInsensitive);
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier, comparer);
if (aliasDeclaration.parent.elements.indexOf(aliasDeclaration) !== insertionIndex) {
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier, comparer, preferences);
if (insertionIndex !== aliasDeclaration.parent.elements.indexOf(aliasDeclaration)) {
changes.delete(sourceFile, aliasDeclaration);
changes.insertImportSpecifierAtIndex(sourceFile, newSpecifier, aliasDeclaration.parent, insertionIndex);
return aliasDeclaration;
}
}
changes.deleteRange(sourceFile, aliasDeclaration.getFirstToken()!);
changes.deleteRange(sourceFile, { pos: getTokenPosOfNode(aliasDeclaration.getFirstToken()!), end: getTokenPosOfNode(aliasDeclaration.propertyName ?? aliasDeclaration.name) });
return aliasDeclaration;
}
else {
@@ -1538,7 +1539,7 @@ function doAddExistingFix(
// type-only, there's no need to ask for the insertion index - it's 0.
const insertionIndex = promoteFromTypeOnly && !spec.isTypeOnly
? 0
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec, comparer);
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec, comparer, preferences);
changes.insertImportSpecifierAtIndex(sourceFile, spec, clause.namedBindings as NamedImports, insertionIndex);
}
}
+68 -20
View File
@@ -23,8 +23,10 @@ import {
flatMap,
formatting,
getNewLineOrDefaultFromHost,
getStringComparer,
getUILocale,
group,
groupBy,
Identifier,
identity,
ImportClause,
@@ -94,7 +96,7 @@ export function organizeImports(
const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => {
if (shouldRemove) importGroup = removeUnusedImports(importGroup, sourceFile, program);
if (shouldCombine) importGroup = coalesceImportsWorker(importGroup, comparer, sourceFile);
if (shouldCombine) importGroup = coalesceImportsWorker(importGroup, comparer, sourceFile, preferences);
if (shouldSort) importGroup = stableSort(importGroup, (s1, s2) => compareImportsOrRequireStatements(s1, s2, comparer));
return importGroup;
};
@@ -104,7 +106,7 @@ export function organizeImports(
// Exports are always used
if (mode !== OrganizeImportsMode.RemoveUnused) {
// All of the old ExportDeclarations in the file, in syntactic order.
getTopLevelExportGroups(sourceFile).forEach(exportGroupDecl => organizeImportsWorker(exportGroupDecl, group => coalesceExportsWorker(group, comparer)));
getTopLevelExportGroups(sourceFile).forEach(exportGroupDecl => organizeImportsWorker(exportGroupDecl, group => coalesceExportsWorker(group, comparer, preferences)));
}
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
@@ -116,7 +118,7 @@ export function organizeImports(
// Exports are always used
if (mode !== OrganizeImportsMode.RemoveUnused) {
const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration);
organizeImportsWorker(ambientModuleExportDecls, group => coalesceExportsWorker(group, comparer));
organizeImportsWorker(ambientModuleExportDecls, group => coalesceExportsWorker(group, comparer, preferences));
}
}
@@ -310,12 +312,12 @@ function getExternalModuleName(specifier: Expression | undefined) {
* @deprecated Only used for testing
* @internal
*/
export function coalesceImports(importGroup: readonly ImportDeclaration[], ignoreCase: boolean, sourceFile?: SourceFile): readonly ImportDeclaration[] {
export function coalesceImports(importGroup: readonly ImportDeclaration[], ignoreCase: boolean, sourceFile?: SourceFile, preferences?: UserPreferences): readonly ImportDeclaration[] {
const comparer = getOrganizeImportsOrdinalStringComparer(ignoreCase);
return coalesceImportsWorker(importGroup, comparer, sourceFile);
return coalesceImportsWorker(importGroup, comparer, sourceFile, preferences);
}
function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], comparer: Comparer<string>, sourceFile?: SourceFile): readonly ImportDeclaration[] {
function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], comparer: Comparer<string>, sourceFile?: SourceFile, preferences?: UserPreferences): readonly ImportDeclaration[] {
if (importGroup.length === 0) {
return importGroup;
}
@@ -374,7 +376,7 @@ function coalesceImportsWorker(importGroup: readonly ImportDeclaration[], compar
newImportSpecifiers.push(...getNewImportSpecifiers(namedImports));
const sortedImportSpecifiers = factory.createNodeArray(
sortSpecifiers(newImportSpecifiers, comparer),
sortSpecifiers(newImportSpecifiers, comparer, preferences),
firstNamedImport?.importClause.namedBindings.elements.hasTrailingComma,
);
@@ -491,18 +493,17 @@ function getCategorizedImports(importGroup: readonly ImportDeclaration[]) {
* @deprecated Only used for testing
* @internal
*/
export function coalesceExports(exportGroup: readonly ExportDeclaration[], ignoreCase: boolean) {
export function coalesceExports(exportGroup: readonly ExportDeclaration[], ignoreCase: boolean, preferences?: UserPreferences) {
const comparer = getOrganizeImportsOrdinalStringComparer(ignoreCase);
return coalesceExportsWorker(exportGroup, comparer);
return coalesceExportsWorker(exportGroup, comparer, preferences);
}
function coalesceExportsWorker(exportGroup: readonly ExportDeclaration[], comparer: Comparer<string>) {
function coalesceExportsWorker(exportGroup: readonly ExportDeclaration[], comparer: Comparer<string>, preferences?: UserPreferences) {
if (exportGroup.length === 0) {
return exportGroup;
}
const { exportWithoutClause, namedExports, typeOnlyExports } = getCategorizedExports(exportGroup);
const coalescedExports: ExportDeclaration[] = [];
if (exportWithoutClause) {
@@ -516,7 +517,7 @@ function coalesceExportsWorker(exportGroup: readonly ExportDeclaration[], compar
const newExportSpecifiers: ExportSpecifier[] = [];
newExportSpecifiers.push(...flatMap(exportGroup, i => i.exportClause && isNamedExports(i.exportClause) ? i.exportClause.elements : emptyArray));
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers, comparer);
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers, comparer, preferences);
const exportDecl = exportGroup[0];
coalescedExports.push(
@@ -583,13 +584,20 @@ function updateImportDeclarationAndClause(
);
}
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[], comparer: Comparer<string>) {
return stableSort(specifiers, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, comparer));
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: readonly T[], comparer: Comparer<string>, preferences?: UserPreferences): readonly T[] {
return stableSort(specifiers, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, comparer, preferences));
}
/** @internal */
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T, comparer: Comparer<string>): Comparison {
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) || comparer(s1.name.text, s2.name.text);
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T, comparer: Comparer<string>, preferences?: UserPreferences): Comparison {
switch (preferences?.organizeImportsTypeOrder) {
case "first":
return compareBooleans(s2.isTypeOnly, s1.isTypeOnly) || comparer(s1.name.text, s2.name.text);
case "inline":
return comparer(s1.name.text, s2.name.text);
default:
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly) || comparer(s1.name.text, s2.name.text);
}
}
/**
@@ -721,11 +729,51 @@ class ImportSpecifierSortingCache implements MemoizeCache<[readonly ImportSpecif
/** @internal */
export const detectImportSpecifierSorting = memoizeCached((specifiers: readonly ImportSpecifier[], preferences: UserPreferences): SortKind => {
if (!arrayIsSorted(specifiers, (s1, s2) => compareBooleans(s1.isTypeOnly, s2.isTypeOnly))) {
return SortKind.None;
// If types are not sorted as specified, then imports are assumed to be unsorted.
// If there is no type sorting specification, we default to "last" and move on to case sensitivity detection.
switch (preferences.organizeImportsTypeOrder) {
case "first":
if (!arrayIsSorted(specifiers, (s1, s2) => compareBooleans(s2.isTypeOnly, s1.isTypeOnly))) return SortKind.None;
break;
case "inline":
if (
!arrayIsSorted(specifiers, (s1, s2) => {
const comparer = getStringComparer(/*ignoreCase*/ true);
return comparer(s1.name.text, s2.name.text);
})
) {
return SortKind.None;
}
break;
default:
if (!arrayIsSorted(specifiers, (s1, s2) => compareBooleans(s1.isTypeOnly, s2.isTypeOnly))) return SortKind.None;
break;
}
const collateCaseSensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ false);
const collateCaseInsensitive = getOrganizeImportsComparer(preferences, /*ignoreCase*/ true);
if (preferences.organizeImportsTypeOrder !== "inline") {
const { type: regularImports, regular: typeImports } = groupBy(specifiers, s => s.isTypeOnly ? "type" : "regular");
const regularCaseSensitivity = regularImports?.length
? detectSortCaseSensitivity(regularImports, specifier => specifier.name.text, collateCaseSensitive, collateCaseInsensitive)
: undefined;
const typeCaseSensitivity = typeImports?.length
? detectSortCaseSensitivity(typeImports, specifier => specifier.name.text ?? "", collateCaseSensitive, collateCaseInsensitive)
: undefined;
if (regularCaseSensitivity === undefined) {
return typeCaseSensitivity ?? SortKind.None;
}
if (typeCaseSensitivity === undefined) {
return regularCaseSensitivity;
}
if (regularCaseSensitivity === SortKind.None || typeCaseSensitivity === SortKind.None) {
return SortKind.None;
}
return typeCaseSensitivity & regularCaseSensitivity;
}
// else inline
return detectSortCaseSensitivity(specifiers, specifier => specifier.name.text, collateCaseSensitive, collateCaseInsensitive);
}, new ImportSpecifierSortingCache());
@@ -736,8 +784,8 @@ export function getImportDeclarationInsertionIndex(sortedImports: readonly AnyIm
}
/** @internal */
export function getImportSpecifierInsertionIndex(sortedImports: readonly ImportSpecifier[], newImport: ImportSpecifier, comparer: Comparer<string>) {
const index = binarySearch(sortedImports, newImport, identity, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, comparer));
export function getImportSpecifierInsertionIndex(sortedImports: readonly ImportSpecifier[], newImport: ImportSpecifier, comparer: Comparer<string>, preferences: UserPreferences) {
const index = binarySearch(sortedImports, newImport, identity, (s1, s2) => compareImportOrExportSpecifiers(s1, s2, comparer, preferences));
return index < 0 ? ~index : index;
}
@@ -239,10 +239,10 @@ describe("unittests:: services:: organizeImports", () => {
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
});
it("Sort specifiers - type-only", () => {
it("Sort specifiers - type-only-inline", () => {
const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`);
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true);
const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`);
const actualCoalescedImports = ts.OrganizeImports.coalesceImports(sortedImports, /*ignoreCase*/ true, ts.getSourceFileOfNode(sortedImports[0]), { organizeImportsTypeOrder: "inline" });
const expectedCoalescedImports = parseImports(`import { a, type b, c, type x, y, type z } from "lib";`);
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
});
+8
View File
@@ -2933,6 +2933,13 @@ declare namespace ts {
* Default: `false`
*/
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
/**
* Indicates where named type-only imports should sort. "inline" sorts named imports without regard to if the import is
* type-only.
*
* Default: `last`
*/
readonly organizeImportsTypeOrder?: "last" | "first" | "inline";
/**
* Indicates whether {@link ReferencesResponseItem.lineText} is supported.
*/
@@ -8784,6 +8791,7 @@ declare namespace ts {
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
readonly organizeImportsTypeOrder?: "first" | "last" | "inline";
readonly excludeLibrarySymbolsInNavTo?: boolean;
}
/** Represents a bigint literal value without requiring bigint support */
@@ -0,0 +1,46 @@
/// <reference path="fourslash.ts" />
// @verbatimModuleSyntax: true
// @target: esnext
// @Filename: /foo.ts
//// export const A = 1;
//// export type B = { x: number };
//// export type C = 1;
//// export class D = { y: string };
// @Filename: /test.ts
//// import { A, D, type C } from './foo';
//// const b: B/**/ | C;
//// console.log(A, D);
goTo.marker("");
// importFixes should only place the import in sorted position if the existing imports are sorted as specified,
// otherwise the import should be placed at the end
verify.importFixAtPosition([
`import { A, D, type C, type B } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "inline" }
// `type B` is added to the end since the existing imports are not sorted as specified
);
verify.importFixAtPosition([
`import { A, D, type B, type C } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "last" }
// `type B` is added to the sorted position since the existing imports *are* sorted as specified
);
verify.importFixAtPosition([
`import { A, D, type C, type B } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "first" }
// `type B` is added to the end (default behavior) since the existing imports are not sorted as specified
);
@@ -0,0 +1,43 @@
/// <reference path="fourslash.ts" />
// @verbatimModuleSyntax: true
// @target: esnext
// @Filename: /foo.ts
//// export const A = 1;
//// export type B = { x: number };
//// export type C = 1;
//// export class D = { y: string };
// @Filename: /test.ts
//// import { A, type C, D } from './foo';
//// const b: B/**/ | C;
//// console.log(A, D);
goTo.marker("");
// importFixes should only place the import in sorted position if the existing imports are sorted as specified,
// otherwise the import should be placed at the end
verify.importFixAtPosition([
`import { A, type B, type C, D } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "inline" }
);
verify.importFixAtPosition([
`import { A, type C, D, type B } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "last" }
);
verify.importFixAtPosition([
`import { A, type C, D, type B } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "first" }
);
@@ -0,0 +1,44 @@
/// <reference path="fourslash.ts" />
// @verbatimModuleSyntax: true
// @target: esnext
// @Filename: /foo.ts
//// export const A = 1;
//// export type B = { x: number };
//// export type C = 1;
//// export class D = { y: string };
// @Filename: /test.ts
//// import { A, type B, type C } from './foo';
//// const b: B | C;
//// console.log(A, D/**/);
goTo.marker("");
// importFixes should only place the import in sorted position if the existing imports are sorted as specified,
// otherwise the import should be placed at the end (regardless of if it's a regular or type-only import)
verify.importFixAtPosition([
`import { A, D, type B, type C } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "last" }
);
verify.importFixAtPosition([
`import { A, type B, type C, D } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "inline" }
);
verify.importFixAtPosition([
`import { A, type B, type C, D } from './foo';
const b: B | C;
console.log(A, D);`],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "first" }
// `D` is added to the end since `A, type B, type C` is not sorted to "first"
);
@@ -0,0 +1,111 @@
/// <reference path="fourslash.ts" />
// @verbatimModuleSyntax: true
// @target: esnext
// @Filename: /exports1.ts
//// export const a = 0;
//// export const A = 1;
//// export const b = 2;
//// export const B = 3;
//// export const c = 4;
//// export const C = 5;
//// export type x = 6;
//// export const X = 7;
//// export const Y = 8;
//// export const Z = 9;
// @Filename: /exports2.ts
//// export const d = 0;
//// export const D = 1;
//// export const e = 2;
//// export const E = 3;
// @Filename: /index0.ts
//// import { A, B, C } from "./exports1";
//// a/*0*/;
//// b;
// @Filename: /index1.ts
//// import { A, B, C, type Y, type Z } from "./exports1";
//// a/*1*/;
//// b;
// @Filename: /index2.ts
//// import { A, a, B, b, type Y, type Z } from "./exports1";
//// import { E } from "./exports2";
//// d/*2*/
// addition of correctly sorted type imports should not affect behavior as shown in autoImportSortCaseSensitivity1.ts
goTo.marker("0");
verify.importFixAtPosition([
`import { A, B, C, a } from "./exports1";\na;\nb;`,
`import { A, B, C, b } from "./exports1";\na;\nb;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { a, A, B, C } from "./exports1";\na;\nb;`,
`import { A, b, B, C } from "./exports1";\na;\nb;`
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "last" });
goTo.marker("1");
verify.importFixAtPosition([
`import { A, B, C, a, type Y, type Z } from "./exports1";\na;\nb;`,
`import { A, B, C, b, type Y, type Z } from "./exports1";\na;\nb;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { a, A, B, C, type Y, type Z } from "./exports1";\na;\nb;`,
`import { A, b, B, C, type Y, type Z } from "./exports1";\na;\nb;`
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "last" });
// if we sort inline and sensitive, then all upper case imports should be sorted before any lower case imports
verify.importFixAtPosition([
`import { A, B, C, type Y, type Z, a } from "./exports1";\na;\nb;`,
`import { A, B, C, type Y, type Z, b } from "./exports1";\na;\nb;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "inline" });
verify.importFixAtPosition([
`import { a, A, B, C, type Y, type Z } from "./exports1";\na;\nb;`,
`import { A, b, B, C, type Y, type Z } from "./exports1";\na;\nb;`
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "inline" });
goTo.marker("2");
verify.importFixAtPosition([
`import { A, a, B, b, type Y, type Z } from "./exports1";
import { d, E } from "./exports2";
d`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { A, a, B, b, type Y, type Z } from "./exports1";
import { E, d } from "./exports2";
d`
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: false, organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { A, a, B, b, type Y, type Z } from "./exports1";
import { d, E } from "./exports2";
d`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { A, a, B, b, type Y, type Z } from "./exports1";
import { E, d } from "./exports2";
d`
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: false, organizeImportsTypeOrder : "last" });
@@ -0,0 +1,86 @@
/// <reference path="fourslash.ts" />
// @verbatimModuleSyntax: true
// @target: esnext
// @Filename: /exports1.ts
//// export const a = 0;
//// export const A = 1;
//// export const b = 2;
//// export const B = 3;
//// export const c = 4;
//// export const C = 5;
//// export type x = 6;
//// export const X = 7;
//// export type y = 8
//// export const Y = 9;
//// export const Z = 10;
// @Filename: /exports2.ts
//// export const d = 0;
//// export const D = 1;
//// export const e = 2;
//// export const E = 3;
// @Filename: /index0.ts
//// import { type X, type Y, type Z } from "./exports1";
//// const foo: x/*0*/;
//// const bar: y;
// @Filename: /index1.ts
//// import { A, B, type X, type Y, type Z } from "./exports1";
//// const foo: x/*1*/;
//// const bar: y;
// addition of correctly sorted regular imports should not affect correctly sorted type imports
goTo.marker("0");
verify.importFixAtPosition([
`import { type X, type Y, type Z, type x } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { type X, type Y, type Z, type y } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { type x, type X, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { type X, type y, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { type X, type Y, type Z, type x } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { type X, type Y, type Z, type y } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "inline" });
verify.importFixAtPosition([
`import { type x, type X, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { type X, type y, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "inline" });
verify.importFixAtPosition([
`import { type X, type Y, type Z, type x } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { type X, type Y, type Z, type y } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "first" });
verify.importFixAtPosition([
`import { type x, type X, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { type X, type y, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "first" });
goTo.marker("1");
verify.importFixAtPosition([
`import { A, B, type X, type Y, type Z, type x } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { A, B, type X, type Y, type Z, type y } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder : "last" });
verify.importFixAtPosition([
`import { A, B, type x, type X, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
`import { A, B, type X, type y, type Y, type Z } from "./exports1";\nconst foo: x;\nconst bar: y;`,
],
/*errorCode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder : "last" });
+1
View File
@@ -670,6 +670,7 @@ declare namespace FourSlashInterface {
readonly organizeImportsNumericCollation?: boolean;
readonly organizeImportsAccentCollation?: boolean;
readonly organizeImportsCaseFirst?: "upper" | "lower" | false;
readonly organizeImportsTypeOrder?: "first" | "last" | "inline";
}
interface InlayHintsOptions extends UserPreferences {
readonly includeInlayParameterNameHints?: "none" | "literals" | "all";
@@ -22,3 +22,31 @@ verify.importFixAtPosition([
type SomeInterface,
} from "./exports.js";
new SomePig`]);
verify.importFixAtPosition([
`import {
SomePig,
type SomeInterface,
} from "./exports.js";
new SomePig`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "last" });
verify.importFixAtPosition([
`import {
type SomeInterface,
SomePig,
} from "./exports.js";
new SomePig`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "inline" }
);
verify.importFixAtPosition([
`import {
type SomeInterface,
SomePig,
} from "./exports.js";
new SomePig`],
/*errorCode*/ undefined,
{ organizeImportsTypeOrder: "first" });
+19 -19
View File
@@ -18,26 +18,26 @@
//// console.log(a, B, b, c, C, d, D);
//// console.log(e, f, F, g, G, H, h);
// verify.organizeImports(
// `import {
// a,
// b,
// b as B,
// c,
// c as C,
// d, d as D,
// e,
// f,
// f as F,
// g,
// g as G,
// h, h as H
// } from './foo';
verify.organizeImports(
`import {
a,
b,
b as B,
c,
c as C,
d, d as D,
e,
f,
f as F,
g,
g as G,
h, h as H
} from './foo';
// console.log(a, B, b, c, C, d, D);
// console.log(e, f, F, g, G, H, h);`,
// /*mode*/ undefined,
// { organizeImportsIgnoreCase: true });
console.log(a, B, b, c, C, d, D);
console.log(e, f, F, g, G, H, h);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true });
verify.organizeImports(
`import {
+6 -3
View File
@@ -9,22 +9,25 @@ verify.organizeImports(
interface Use extends A {}
console.log(a, b);`);
edit.replaceLine(0, 'import { a, A, b } from "foo1";');
verify.organizeImports(
`import { a, A, b } from "foo";
`import { a, A, b } from "foo1";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto" });
edit.replaceLine(0, 'import { a, A, b } from "foo2";');
verify.organizeImports(
`import { a, A, b } from "foo";
`import { a, A, b } from "foo2";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true });
edit.replaceLine(0, 'import { a, A, b } from "foo3";');
verify.organizeImports(
`import { A, a, b } from "foo";
`import { A, a, b } from "foo3";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
@@ -0,0 +1,46 @@
/// <reference path='fourslash.ts' />
// @allowSyntheticDefaultImports: true
// @moduleResolution: node
// @noUnusedLocals: true
// @target: es2018
//// import { A } from "foo";
//// import { type B } from "foo";
//// import { C } from "foo";
//// import { type E } from "foo";
//// import { D } from "foo";
////
//// console.log(A, B, C, D, E);
// default behavior is "last"
verify.organizeImports(
`import { A, C, D, type B, type E } from "foo";
console.log(A, B, C, D, E);`
);
verify.organizeImports(
`import { A, type B, C, D, type E } from "foo";
console.log(A, B, C, D, E);`,
undefined,
{ organizeImportsTypeOrder : "inline" }
);
verify.organizeImports(
`import { type B, type E, A, C, D } from "foo";
console.log(A, B, C, D, E);`,
undefined,
{ organizeImportsTypeOrder : "first" }
);
verify.organizeImports(
`import { A, C, D, type B, type E } from "foo";
console.log(A, B, C, D, E);`,
undefined,
{ organizeImportsTypeOrder : "last" }
);
@@ -0,0 +1,50 @@
/// <reference path='fourslash.ts' />
// @allowSyntheticDefaultImports: true
// @moduleResolution: node
// @noUnusedLocals: true
// @target: es2018
//// type A = string;
//// type B = string;
//// const C = "hello";
//// export { A, type B, C };
// default behavior is equivalent to "last"
verify.organizeImports(
`type A = string;
type B = string;
const C = "hello";
export { A, C, type B };
`
);
verify.organizeImports(
`type A = string;
type B = string;
const C = "hello";
export { A, type B, C };
`,
undefined,
{ organizeImportsTypeOrder : "inline" }
);
verify.organizeImports(
`type A = string;
type B = string;
const C = "hello";
export { type B, A, C };
`,
undefined,
{ organizeImportsTypeOrder : "first" }
);
verify.organizeImports(
`type A = string;
type B = string;
const C = "hello";
export { A, C, type B };
`,
undefined,
{ organizeImportsTypeOrder : "last" }
);
@@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
//// import {
//// d,
//// type d as D,
//// type c,
//// c as C,
//// b,
//// b as B,
//// type A,
//// a
//// } from './foo';
//// console.log(A, a, B, b, c, C, d, D);
verify.organizeImports(
`import {
type A,
b as B,
c as C,
type d as D,
a,
b,
type c,
d
} from './foo';
console.log(A, a, B, b, c, C, d, D);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: false, organizeImportsTypeOrder: "inline" }
);
@@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
//// import {
//// d,
//// type d as D,
//// type c,
//// c as C,
//// b,
//// b as B,
//// type A,
//// a
//// } from './foo';
//// console.log(A, a, B, b, c, C, d, D);
verify.organizeImports(
`import {
type A,
a,
b,
b as B,
type c,
c as C,
d,
type d as D
} from './foo';
console.log(A, a, B, b, c, C, d, D);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder: "inline" }
);
@@ -0,0 +1,29 @@
/// <reference path="fourslash.ts" />
//// import {
//// d,
//// type d as D,
//// type c,
//// c as C,
//// b,
//// b as B,
//// type A,
//// a
//// } from './foo';
//// console.log(A, a, B, b, c, C, d, D);
verify.organizeImports(
`import {
type A,
b as B,
c as C,
type d as D,
a,
b,
type c,
d
} from './foo';
console.log(A, a, B, b, c, C, d, D);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "inline" }
);
@@ -0,0 +1,36 @@
/// <reference path="fourslash.ts" />
//// import { type a, A, b } from "foo";
//// interface Use extends A {}
//// console.log(a, b);
verify.organizeImports(
`import { type a, A, b } from "foo";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsTypeOrder: "inline" });
edit.replaceLine(0, 'import { type a, A, b } from "foo1";');
verify.organizeImports(
`import { type a, A, b } from "foo1";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "inline" });
edit.replaceLine(0, 'import { type a, A, b } from "foo2";');
verify.organizeImports(
`import { type a, A, b } from "foo2";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder: "inline" });
edit.replaceLine(0, 'import { type a, A, b } from "foo3";');
verify.organizeImports(
`import { A, type a, b } from "foo3";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: false, organizeImportsTypeOrder: "inline" });
@@ -0,0 +1,36 @@
/// <reference path="fourslash.ts" />
//// import { a, type A, b } from "foo";
//// interface Use extends A {}
//// console.log(a, b);
verify.organizeImports(
`import { a, type A, b } from "foo";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsTypeOrder: "inline" });
edit.replaceLine(0, 'import { a, type A, b } from "foo1";');
verify.organizeImports(
`import { a, type A, b } from "foo1";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "inline" });
edit.replaceLine(0, 'import { a, type A, b } from "foo2";');
verify.organizeImports(
`import { a, type A, b } from "foo2";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true, organizeImportsTypeOrder: "inline"});
edit.replaceLine(0, 'import { a, type A, b } from "foo3";');
verify.organizeImports(
`import { type A, a, b } from "foo3";
interface Use extends A {}
console.log(a, b);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: false, organizeImportsTypeOrder: "inline" });
@@ -0,0 +1,53 @@
/// <reference path="fourslash.ts" />
//// import { type A, type a, b, B } from "foo";
//// console.log(a, b, A, B);
verify.organizeImports(
`import { type A, type a, b, B } from "foo";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "inline" }
);
edit.replaceLine(0, 'import { type A, type a, b, B } from "foo1";');
verify.organizeImports(
`import { type A, type a, b, B } from "foo1";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "first" }
);
edit.replaceLine(0, 'import { type A, type a, b, B } from "foo2";');
verify.organizeImports(
`import { B, b, type A, type a } from "foo2";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "last" }
);
// default behavior is { organizeImportsTypeOrder: "last" }
edit.replaceLine(0, 'import { type A, type a, b, B } from "foo3";');
verify.organizeImports(
`import { B, b, type A, type a } from "foo3";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto" }
);
edit.replaceLine(0, 'import { type A, type a, b, B } from "foo4";');
verify.organizeImports(
`import { b, B, type A, type a } from "foo4";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true }
);
edit.replaceLine(0, 'import { type A, type a, b, B } from "foo5";');
verify.organizeImports(
`import { B, b, type A, type a } from "foo5";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: false }
);
@@ -0,0 +1,48 @@
/// <reference path="fourslash.ts" />
//// import { type a, type A, b, B } from "foo";
//// console.log(a, b, A, B);
verify.organizeImports(
`import { type a, type A, b, B } from "foo";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "inline" });
edit.replaceLine(0, 'import { type a, type A, b, B } from "foo1";');
verify.organizeImports(
`import { type a, type A, b, B } from "foo1";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "first"}
);
edit.replaceLine(0, 'import { type a, type A, b, B } from "foo2";');
verify.organizeImports(
`import { B, b, type A, type a } from "foo2";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto", organizeImportsTypeOrder: "last" }
);
edit.replaceLine(0, 'import { type a, type A, b, B } from "foo3";');
verify.organizeImports(
`import { B, b, type A, type a } from "foo3";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: "auto" }
);
edit.replaceLine(0, 'import { type a, type A, b, B } from "foo4";');
verify.organizeImports(
`import { b, B, type a, type A } from "foo4";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: true });
edit.replaceLine(0, 'import { type a, type A, b, B } from "foo5";');
verify.organizeImports(
`import { B, b, type A, type a } from "foo5";
console.log(a, b, A, B);`,
/*mode*/ undefined,
{ organizeImportsIgnoreCase: false });