Skip to content
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

Change detection in organizeImports #57267

Merged
merged 38 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f5421cc
add failing test
iisaduan Jan 12, 2024
fd0290b
more tests
iisaduan Jan 12, 2024
a0b3a0e
add detection tests
iisaduan Jan 16, 2024
6f05577
improved test behavior
iisaduan Jan 16, 2024
40722f5
improved test behavior
iisaduan Jan 16, 2024
1dbec4f
add core/utility functions
iisaduan Jan 25, 2024
566f326
update utility docs
iisaduan Jan 25, 2024
321df23
update behavior in tests to the correct detection
iisaduan Feb 1, 2024
4ae222d
update tests since "last" is no longer default
iisaduan Feb 1, 2024
76efd15
update remaining tests and baselines
iisaduan Feb 1, 2024
85198ac
first draft: detection implementation
iisaduan Feb 1, 2024
fbe645d
Merge branch 'main' of https://github.com/microsoft/TypeScript into d…
iisaduan Feb 1, 2024
4b56b44
add detectionByDiff to autoImports, update tests
iisaduan Feb 2, 2024
dadfe54
move diff unit tests to unittests/utilities.ts
iisaduan Feb 3, 2024
8067f5f
in progress
iisaduan Feb 15, 2024
e3ccaf1
change detection method to "isSorted" check
iisaduan Feb 21, 2024
094c607
rename functions and update services/utilities.ts
iisaduan Feb 26, 2024
7f11421
separate detection into module and named
iisaduan Feb 26, 2024
ab4c78c
removed old detection from importFixes and services/utilities
iisaduan Feb 26, 2024
eb54e77
remove old detection
iisaduan Feb 27, 2024
c7c57f7
remove casts
iisaduan Feb 27, 2024
64f1468
clean up
iisaduan Feb 27, 2024
2f0d437
Merge branch 'main' of https://github.com/iisaduan/TypeScript into de…
iisaduan Feb 27, 2024
c5239e7
Merge branch 'main' of https://github.com/microsoft/TypeScript into d…
iisaduan Feb 27, 2024
bd5c01a
fix comments
iisaduan Feb 27, 2024
34e3502
remove unused functions
iisaduan Feb 27, 2024
2593f8c
add type OrganizeImportsTypeOrder
iisaduan Feb 27, 2024
97e3e75
fix autoimport type order bug
iisaduan Feb 28, 2024
5d0517f
remove unused
iisaduan Mar 5, 2024
10f64a2
in progress--moving to codespace
iisaduan Mar 8, 2024
0364735
add detection baselines
iisaduan Mar 8, 2024
5672604
finish updating autoImports
iisaduan Mar 8, 2024
aa57e41
merge main
iisaduan Mar 8, 2024
fa5bbdb
clean up organizeImports file
iisaduan Mar 9, 2024
7442236
clean up organizeImports file
iisaduan Mar 9, 2024
a1954ec
Merge branch 'detect-organize-imports' of https://github.com/iisaduan…
iisaduan Mar 9, 2024
2127d55
correct auto imports tests for auto type promotion
iisaduan Mar 14, 2024
520d4ca
fix importFixes to compare the new imports
iisaduan Mar 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,38 +848,6 @@ export function arrayIsSorted<T>(array: readonly T[], comparer: Comparer<T>) {
return true;
}

/** @internal */
export const enum SortKind {
None = 0,
CaseSensitive = 1 << 0,
CaseInsensitive = 1 << 1,
Both = CaseSensitive | CaseInsensitive,
}

/** @internal */
export function detectSortCaseSensitivity<T>(
array: readonly T[],
getString: (element: T) => string,
compareStringsCaseSensitive: Comparer<string>,
compareStringsCaseInsensitive: Comparer<string>,
): SortKind {
let kind = SortKind.Both;
if (array.length < 2) return kind;

let prevElement = getString(array[0]);
for (let i = 1, len = array.length; i < len && kind !== SortKind.None; i++) {
const element = getString(array[i]);
if (kind & SortKind.CaseSensitive && compareStringsCaseSensitive(prevElement, element) > 0) {
kind &= ~SortKind.CaseSensitive;
}
if (kind & SortKind.CaseInsensitive && compareStringsCaseInsensitive(prevElement, element) > 0) {
kind &= ~SortKind.CaseInsensitive;
}
prevElement = element;
}
return kind;
}

/** @internal */
export function arrayIsEqualTo<T>(array1: readonly T[] | undefined, array2: readonly T[] | undefined, equalityComparer: (a: T, b: T, index: number) => boolean = equateValues): boolean {
if (!array1 || !array2) {
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9969,7 +9969,7 @@ export interface UserPreferences {
*
* Default: `last`
*/
readonly organizeImportsTypeOrder?: "first" | "last" | "inline";
readonly organizeImportsTypeOrder?: OrganizeImportsTypeOrder;
/**
* Indicates whether to exclude standard library and node_modules file symbols from navTo results.
*/
Expand All @@ -9980,6 +9980,8 @@ export interface UserPreferences {
readonly disableLineTextInReferences?: boolean;
}

export type OrganizeImportsTypeOrder = "last" | "inline" | "first";

/** Represents a bigint literal value without requiring bigint support */
export interface PseudoBigInt {
negative: boolean;
Expand Down
52 changes: 18 additions & 34 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ import {
removeFileExtension,
removeSuffix,
RequireVariableStatement,
sameMap,
ScriptTarget,
SemanticMeaning,
shouldUseUriStyleNodeCoreModules,
single,
skipAlias,
some,
sort,
SortKind,
SourceFile,
stableSort,
startsWith,
Expand Down Expand Up @@ -1394,11 +1394,10 @@ function promoteFromTypeOnly(
switch (aliasDeclaration.kind) {
case SyntaxKind.ImportSpecifier:
if (aliasDeclaration.isTypeOnly) {
const sortKind = OrganizeImports.detectImportSpecifierSorting(aliasDeclaration.parent.elements, preferences);
if (aliasDeclaration.parent.elements.length > 1 && sortKind) {
if (aliasDeclaration.parent.elements.length > 1) {
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, preferences);
const { specifierComparer } = OrganizeImports.getNamedImportSpecifierComparerWithDetection(aliasDeclaration.parent.parent.parent, preferences, sourceFile);
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier, specifierComparer);
if (insertionIndex !== aliasDeclaration.parent.elements.indexOf(aliasDeclaration)) {
changes.delete(sourceFile, aliasDeclaration);
changes.insertImportSpecifierAtIndex(sourceFile, newSpecifier, aliasDeclaration.parent, insertionIndex);
Expand Down Expand Up @@ -1440,8 +1439,9 @@ function promoteFromTypeOnly(
if (convertExistingToTypeOnly) {
const namedImports = tryCast(importClause.namedBindings, isNamedImports);
if (namedImports && namedImports.elements.length > 1) {
const sortState = OrganizeImports.getNamedImportSpecifierComparerWithDetection(importClause.parent, preferences, sourceFile);
if (
OrganizeImports.detectImportSpecifierSorting(namedImports.elements, preferences) &&
(sortState.isSorted !== false) &&
aliasDeclaration.kind === SyntaxKind.ImportSpecifier &&
namedImports.elements.indexOf(aliasDeclaration) !== 0
) {
Expand Down Expand Up @@ -1478,6 +1478,7 @@ function doAddExistingFix(
return;
}

// promoteFromTypeOnly = true if we need to promote the entire original clause from type only
const promoteFromTypeOnly = clause.isTypeOnly && some([defaultImport, ...namedImports], i => i?.addAsTypeOnly === AddAsTypeOnly.NotAllowed);
const existingSpecifiers = clause.namedBindings && tryCast(clause.namedBindings, isNamedImports)?.elements;

Expand All @@ -1487,25 +1488,7 @@ function doAddExistingFix(
}

if (namedImports.length) {
// sort case sensitivity:
// - if the user preference is explicit, use that
// - otherwise, if there are enough existing import specifiers in this import to detect unambiguously, use that
// - otherwise, detect from other imports in the file
let ignoreCaseForSorting: boolean | undefined;
if (typeof preferences.organizeImportsIgnoreCase === "boolean") {
ignoreCaseForSorting = preferences.organizeImportsIgnoreCase;
}
else if (existingSpecifiers) {
const targetImportSorting = OrganizeImports.detectImportSpecifierSorting(existingSpecifiers, preferences);
if (targetImportSorting !== SortKind.Both) {
ignoreCaseForSorting = targetImportSorting === SortKind.CaseInsensitive;
}
}
if (ignoreCaseForSorting === undefined) {
ignoreCaseForSorting = OrganizeImports.detectSorting(sourceFile, preferences) === SortKind.CaseInsensitive;
}

const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, ignoreCaseForSorting);
const { specifierComparer, isSorted } = OrganizeImports.getNamedImportSpecifierComparerWithDetection(clause.parent, preferences, sourceFile);
const newSpecifiers = stableSort(
namedImports.map(namedImport =>
factory.createImportSpecifier(
Expand All @@ -1514,23 +1497,24 @@ function doAddExistingFix(
factory.createIdentifier(namedImport.name),
)
),
(s1, s2) => OrganizeImports.compareImportOrExportSpecifiers(s1, s2, comparer),
specifierComparer,
);

// The sorting preference computed earlier may or may not have validated that these particular
// import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return
// nonsense. So if there are existing specifiers, even if we know the sorting preference, we
// need to ensure that the existing specifiers are sorted according to the preference in order
// to do a sorted insertion.
const specifierSort = existingSpecifiers?.length && OrganizeImports.detectImportSpecifierSorting(existingSpecifiers, preferences);
if (specifierSort && !(ignoreCaseForSorting && specifierSort === SortKind.CaseSensitive)) {

// changed to check if existing specifiers are sorted
if (existingSpecifiers?.length && isSorted !== false) {
// if we're promoting the clause from type-only, we need to transform the existing imports before attempting to insert the new named imports
const transformedExistingSpecifiers = (promoteFromTypeOnly && existingSpecifiers) ? factory.updateNamedImports(
clause.namedBindings as NamedImports,
sameMap(existingSpecifiers, e => factory.updateImportSpecifier(e, /*isTypeOnly*/ true, e.propertyName, e.name)),
).elements : existingSpecifiers;
for (const spec of newSpecifiers) {
// Organize imports puts type-only import specifiers last, so if we're
// adding a non-type-only specifier and converting all the other ones to
// 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, preferences);
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(transformedExistingSpecifiers, spec, specifierComparer);
changes.insertImportSpecifierAtIndex(sourceFile, spec, clause.namedBindings as NamedImports, insertionIndex);
}
}
Expand Down
Loading
Loading