diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index 9c25c520f66..ab9d9596254 100755 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -2695,6 +2695,32 @@ function parseOrganizeImportsArgs(args: readonly ts.Expression[]): [VerifyOrgani throw new Error(`Unsupported value for organizeImportsIgnoreCase: ${propValue.getText()}`); } } + else if (propName === "organizeImportsSort") { + if (ts.isStringLiteral(propValue)) { + switch (propValue.text) { + case "auto": + prefsFields.push(`${goFieldName}: lsutil.OrganizeImportsSortAuto`); + break; + case "ordinal": + prefsFields.push(`${goFieldName}: lsutil.OrganizeImportsSortOrdinal`); + break; + case "ordinalIgnoreCase": + prefsFields.push(`${goFieldName}: lsutil.OrganizeImportsSortOrdinalIgnoreCase`); + break; + case "natural": + prefsFields.push(`${goFieldName}: lsutil.OrganizeImportsSortNatural`); + break; + case "naturalIgnoreCase": + prefsFields.push(`${goFieldName}: lsutil.OrganizeImportsSortNaturalIgnoreCase`); + break; + default: + throw new Error(`Unsupported value for organizeImportsSort: ${propValue.text}`); + } + } + else { + throw new Error(`Expected string literal for organizeImportsSort, got ${propValue.getText()}`); + } + } else if (propName === "organizeImportsCollation") { if (ts.isStringLiteral(propValue)) { if (propValue.text === "unicode") { diff --git a/internal/fourslash/_scripts/manualTests.txt b/internal/fourslash/_scripts/manualTests.txt index 05618afd339..31d174381bb 100644 --- a/internal/fourslash/_scripts/manualTests.txt +++ b/internal/fourslash/_scripts/manualTests.txt @@ -110,6 +110,8 @@ navto_excludeLib3 navto_excludeLib4 navto_serverExcludeLib nodeModulesImportCompletions1 +organizeImportsPathsUnicode3 +organizeImportsUnicode3 outliningForNonCompleteInterfaceDeclaration outliningHintSpansForFunction overloadOnConstCallSignature diff --git a/internal/fourslash/tests/gen/organizeImportsPathsUnicode3_test.go b/internal/fourslash/tests/manual/organizeImportsPathsUnicode3_test.go similarity index 86% rename from internal/fourslash/tests/gen/organizeImportsPathsUnicode3_test.go rename to internal/fourslash/tests/manual/organizeImportsPathsUnicode3_test.go index 2f9260c263b..41f38684401 100644 --- a/internal/fourslash/tests/gen/organizeImportsPathsUnicode3_test.go +++ b/internal/fourslash/tests/manual/organizeImportsPathsUnicode3_test.go @@ -1,6 +1,3 @@ -// Code generated by convertFourslash; DO NOT EDIT. -// To modify this test, run "npm run makemanual organizeImportsPathsUnicode3" - package fourslash_test import ( @@ -14,7 +11,6 @@ import ( ) func TestOrganizeImportsPathsUnicode3(t *testing.T) { - fourslash.SkipIfFailing(t) t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `import * as B from "./B"; @@ -25,8 +21,8 @@ console.log(A, À, B);` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() f.VerifyOrganizeImports(t, - `import * as À from "./À"; -import * as A from "./A"; + `import * as A from "./A"; +import * as À from "./À"; import * as B from "./B"; console.log(A, À, B);`, diff --git a/internal/fourslash/tests/gen/organizeImportsUnicode3_test.go b/internal/fourslash/tests/manual/organizeImportsUnicode3_test.go similarity index 89% rename from internal/fourslash/tests/gen/organizeImportsUnicode3_test.go rename to internal/fourslash/tests/manual/organizeImportsUnicode3_test.go index 97975f81edf..ae1ce80db39 100644 --- a/internal/fourslash/tests/gen/organizeImportsUnicode3_test.go +++ b/internal/fourslash/tests/manual/organizeImportsUnicode3_test.go @@ -1,6 +1,3 @@ -// Code generated by convertFourslash; DO NOT EDIT. -// To modify this test, run "npm run makemanual organizeImportsUnicode3" - package fourslash_test import ( @@ -14,7 +11,6 @@ import ( ) func TestOrganizeImportsUnicode3(t *testing.T) { - fourslash.SkipIfFailing(t) t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `import { @@ -28,8 +24,8 @@ console.log(A, À, B);` defer done() f.VerifyOrganizeImports(t, `import { - À, A, + À, B, } from './foo'; diff --git a/internal/fourslash/tests/organizeImports_coalesceExports_test.go b/internal/fourslash/tests/organizeImports_coalesceExports_test.go index e744b03aac3..c0dfddc9683 100644 --- a/internal/fourslash/tests/organizeImports_coalesceExports_test.go +++ b/internal/fourslash/tests/organizeImports_coalesceExports_test.go @@ -3,7 +3,6 @@ package fourslash_test import ( "testing" - "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/fourslash" "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/lsp/lsproto" @@ -22,7 +21,7 @@ void 0;` `export { B, default as M, a as n, Z as O, y } from "lib"; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -39,7 +38,7 @@ void 0;` `export * from "lib"; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -58,7 +57,7 @@ void 0;` export { x, z as y }; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -75,7 +74,7 @@ void 0;` `export { x, y as z } from "lib"; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -96,7 +95,7 @@ export * from "lib"; export { y } from "lib"; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -116,7 +115,7 @@ void 0;` export { z as default, q as w, x, w as y }; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -135,7 +134,7 @@ void 0;` export { x as a, z as b, y } from "lib"; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -160,7 +159,7 @@ export { x }; export type { y }; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -181,6 +180,6 @@ type y = number; export type { x, y }; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } diff --git a/internal/fourslash/tests/organizeImports_coalesceImports_test.go b/internal/fourslash/tests/organizeImports_coalesceImports_test.go index fe0af8a4e76..17ebba013f2 100644 --- a/internal/fourslash/tests/organizeImports_coalesceImports_test.go +++ b/internal/fourslash/tests/organizeImports_coalesceImports_test.go @@ -3,7 +3,6 @@ package fourslash_test import ( "testing" - "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/fourslash" "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/lsp/lsproto" @@ -22,7 +21,7 @@ M; n; B; y; O;` `import { B, default as M, a as n, Z as O, y } from "lib"; M; n; B; y; O;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -39,7 +38,7 @@ void 0;` `import "lib"; void 0;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -60,7 +59,7 @@ import * as x from "lib"; import * as y from "lib"; x; y; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -77,7 +76,7 @@ x; y;` `import { default as x, default as y } from "lib"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -94,7 +93,7 @@ x; z;` `import { x, y as z } from "lib"; x; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -115,7 +114,7 @@ import "lib"; import * as x from "lib"; x; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -136,7 +135,7 @@ import "lib"; import x from "lib"; x; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -157,7 +156,7 @@ import "lib"; import { x } from "lib"; x; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -175,7 +174,7 @@ x; y;` `import y, * as x from "lib"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -196,7 +195,7 @@ import * as x from "lib"; import { y } from "lib"; x; y; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -214,7 +213,7 @@ x; y;` `import x, { y } from "lib"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -240,7 +239,7 @@ import * as y from "lib"; import { a, b, default as w, default as z } from "lib"; w; x; y; z; a; b;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -263,7 +262,7 @@ import * as y from "lib"; import z from "lib"; x; y; z; w;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -283,7 +282,7 @@ x; y; z;` import { z } from "lib"; x; y; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -304,7 +303,7 @@ import type z from "lib"; import type { x } from "lib"; x; y; z;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -321,8 +320,8 @@ z; y; x; c; b; a;` z; y; x; c; b; a;`, lsproto.CodeActionKindSourceSortImports, &lsutil.UserPreferences{ - OrganizeImportsIgnoreCase: core.TSTrue, - OrganizeImportsTypeOrder: lsutil.OrganizeImportsTypeOrderInline, + OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase, + OrganizeImportsTypeOrder: lsutil.OrganizeImportsTypeOrderInline, }, ) } diff --git a/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go b/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go index 350c7186119..b3eff84f6d6 100644 --- a/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go +++ b/internal/fourslash/tests/organizeImports_sortModuleSpecifiers_test.go @@ -3,7 +3,6 @@ package fourslash_test import ( "testing" - "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/fourslash" "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/lsp/lsproto" @@ -24,7 +23,7 @@ x; y;` import x from "lib2"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -42,7 +41,7 @@ x; y;` import x from "./lib2"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -60,7 +59,7 @@ x; y;` import x from "./lib"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -79,7 +78,7 @@ x; y;` import x from "Z"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } @@ -98,6 +97,6 @@ x; y;` import x from "z"; x; y;`, lsproto.CodeActionKindSourceSortImports, - &lsutil.UserPreferences{OrganizeImportsIgnoreCase: core.TSTrue}, + &lsutil.UserPreferences{OrganizeImportsSort: lsutil.OrganizeImportsSortOrdinalIgnoreCase}, ) } diff --git a/internal/ls/lsutil/organizeimports.go b/internal/ls/lsutil/organizeimports.go index 9ae778810aa..08176faabd2 100644 --- a/internal/ls/lsutil/organizeimports.go +++ b/internal/ls/lsutil/organizeimports.go @@ -3,24 +3,15 @@ package lsutil import ( "cmp" "math" + "strings" "unicode" + "unicode/utf8" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" - "github.com/microsoft/typescript-go/internal/locale" "github.com/microsoft/typescript-go/internal/stringutil" "github.com/microsoft/typescript-go/internal/tspath" - "golang.org/x/text/collate" - "golang.org/x/text/language" -) - -var ( - caseInsensitiveOrganizeImportsComparer = []func(a, b string) int{getOrganizeImportsOrdinalStringComparer(true)} - caseSensitiveOrganizeImportsComparer = []func(a, b string) int{getOrganizeImportsOrdinalStringComparer(false)} - organizeImportsComparers = []func(a, b string) int{ - caseInsensitiveOrganizeImportsComparer[0], - caseSensitiveOrganizeImportsComparer[0], - } + "golang.org/x/text/unicode/norm" ) // FilterImportDeclarations filters out non-import declarations from a list of statements. @@ -32,9 +23,10 @@ func FilterImportDeclarations(statements []*ast.Statement) []*ast.Statement { // GetDetectionLists returns the lists of comparers and type orders to test for organize imports detection. func GetDetectionLists(preferences UserPreferences) (comparersToTest []func(a, b string) int, typeOrdersToTest []OrganizeImportsTypeOrder) { - if !preferences.OrganizeImportsIgnoreCase.IsUnknown() { - ignoreCase := preferences.OrganizeImportsIgnoreCase.IsTrue() - comparersToTest = []func(a, b string) int{getOrganizeImportsStringComparer(preferences, ignoreCase)} + if preferences.OrganizeImportsSort != OrganizeImportsSortAuto { + comparersToTest = []func(a, b string) int{getOrganizeImportsPresetStringComparer(preferences.OrganizeImportsSort)} + } else if !preferences.OrganizeImportsIgnoreCase.IsUnknown() { + comparersToTest = []func(a, b string) int{getOrganizeImportsStringComparer(preferences, preferences.OrganizeImportsIgnoreCase.IsTrue())} } else { comparersToTest = []func(a, b string) int{ getOrganizeImportsStringComparer(preferences, true), @@ -55,6 +47,32 @@ func GetDetectionLists(preferences UserPreferences) (comparersToTest []func(a, b return comparersToTest, typeOrdersToTest } +func ResolveOrganizeImportsSort(preferences UserPreferences) OrganizeImportsSort { + if preferences.OrganizeImportsSort != OrganizeImportsSortAuto { + return preferences.OrganizeImportsSort + } + + if preferences.OrganizeImportsCollation == OrganizeImportsCollationUnicode { + switch preferences.OrganizeImportsIgnoreCase { + case core.TSTrue: + return OrganizeImportsSortNaturalIgnoreCase + case core.TSFalse: + return OrganizeImportsSortNatural + default: + return OrganizeImportsSortAuto + } + } + + switch preferences.OrganizeImportsIgnoreCase { + case core.TSTrue: + return OrganizeImportsSortOrdinalIgnoreCase + case core.TSFalse: + return OrganizeImportsSortOrdinal + default: + return OrganizeImportsSortAuto + } +} + func getOrganizeImportsOrdinalStringComparer(ignoreCase bool) func(a, b string) int { if ignoreCase { return stringutil.CompareStringsCaseInsensitiveEslintCompatible @@ -62,122 +80,189 @@ func getOrganizeImportsOrdinalStringComparer(ignoreCase bool) func(a, b string) return stringutil.CompareStringsCaseSensitive } -func getOrganizeImportsUnicodeStringComparer(ignoreCase bool, preferences UserPreferences) func(a, b string) int { - resolvedLocale := getOrganizeImportsLocale(preferences) +func getOrganizeImportsNaturalStringComparer(caseSensitive bool) func(a, b string) int { + return func(a, b string) int { + return compareOrganizeImportsNaturalStrings(a, b, caseSensitive) + } +} +func getOrganizeImportsUnicodeStringComparer(ignoreCase bool, preferences UserPreferences) func(a, b string) int { caseFirst := preferences.OrganizeImportsCaseFirst numeric := preferences.OrganizeImportsNumericCollation.IsTrue() accents := !preferences.OrganizeImportsAccentCollation.IsFalse() - tag, _ := language.Parse(resolvedLocale) + return func(a, b string) int { + return compareOrganizeImportsUnicodeStrings(a, b, ignoreCase, caseFirst, numeric, accents) + } +} + +func compareOrganizeImportsNaturalStrings(a string, b string, caseSensitive bool) int { + if cmp := compareStringsNumeric(naturalCollationKey(a), naturalCollationKey(b)); cmp != 0 { + return cmp + } + + if caseSensitive { + if cmp := compareOrganizeImportsCaseUpperFirst(a, b); cmp != 0 { + return cmp + } + } - var opts []collate.Option + return strings.Compare(a, b) +} - if numeric { - opts = append(opts, collate.Numeric) +func compareOrganizeImportsUnicodeStrings(a string, b string, ignoreCase bool, caseFirst OrganizeImportsCaseFirst, numeric bool, accents bool) int { + if cmp := compareOrganizeImportsUnicodeKeys(naturalCollationKey(a), naturalCollationKey(b), numeric); cmp != 0 { + return cmp } - looseOpts := append([]collate.Option{}, opts...) - looseOpts = append(looseOpts, collate.Loose) - looseCollator := collate.New(tag, looseOpts...) + if accents { + if cmp := compareOrganizeImportsUnicodeKeys(strings.ToLower(a), strings.ToLower(b), numeric); cmp != 0 { + return cmp + } + } if !ignoreCase { - caseInsensitiveOpts := append([]collate.Option{}, opts...) - caseInsensitiveOpts = append(caseInsensitiveOpts, collate.IgnoreCase) - caseInsensitiveCollator := collate.New(tag, caseInsensitiveOpts...) + if cmp := compareOrganizeImportsCase(a, b, caseFirst); cmp != 0 { + return cmp + } + } - fullCollator := collate.New(tag, opts...) + return strings.Compare(a, b) +} - return func(a, b string) int { - var primaryCmp int - if !accents { - primaryCmp = looseCollator.CompareString(a, b) - } else { - primaryCmp = caseInsensitiveCollator.CompareString(a, b) - } - if primaryCmp != 0 { - return primaryCmp - } +func naturalCollationKey(s string) string { + return strings.ToLower(removeDiacritics(s)) +} - aRunes := []rune(a) - bRunes := []rune(b) - minLen := min(len(aRunes), len(bRunes)) - - for i := range minLen { - aUpper := unicode.IsUpper(aRunes[i]) - bUpper := unicode.IsUpper(bRunes[i]) - if aUpper != bUpper { - switch caseFirst { - case OrganizeImportsCaseFirstUpper: - if aUpper { - return -1 - } - return 1 - case OrganizeImportsCaseFirstLower: - if !aUpper { - return -1 - } - return 1 - default: - if aUpper { - return 1 - } - return -1 - } - } - } +func removeDiacritics(s string) string { + return strings.Map(func(r rune) rune { + if unicode.Is(unicode.Mn, r) { + return -1 + } + return r + }, norm.NFD.String(s)) +} - if !accents { - if len(aRunes) != len(bRunes) { - return len(aRunes) - len(bRunes) - } - return 0 +func compareOrganizeImportsUnicodeKeys(a string, b string, numeric bool) int { + if numeric { + return compareStringsNumeric(a, b) + } + return strings.Compare(a, b) +} + +func compareStringsNumeric(a string, b string) int { + for len(a) > 0 && len(b) > 0 { + if isASCIIDigit(a[0]) && isASCIIDigit(b[0]) { + aRunEnd := asciiDigitRunEnd(a) + bRunEnd := asciiDigitRunEnd(b) + + if cmp := compareNumericText(a[:aRunEnd], b[:bRunEnd]); cmp != 0 { + return cmp } - return fullCollator.CompareString(a, b) + a = a[aRunEnd:] + b = b[bRunEnd:] + continue } - } - if ignoreCase { - opts = append(opts, collate.IgnoreCase) - if !accents { - opts = append(opts, collate.Loose) + aRune, aSize := utf8.DecodeRuneInString(a) + bRune, bSize := utf8.DecodeRuneInString(b) + if aRune != bRune { + return cmp.Compare(aRune, bRune) } + + a = a[aSize:] + b = b[bSize:] } - collator := collate.New(tag, opts...) + return cmp.Compare(len(a), len(b)) +} - return func(a, b string) int { - return collator.CompareString(a, b) +func isASCIIDigit(ch byte) bool { + return ch >= '0' && ch <= '9' +} + +func asciiDigitRunEnd(s string) int { + i := 0 + for i < len(s) && isASCIIDigit(s[i]) { + i++ } + return i } -func getOrganizeImportsLocale(preferences UserPreferences) string { - localeStr := "en" - if preferences.OrganizeImportsLocale != "" { - localeStr = preferences.OrganizeImportsLocale +func compareNumericText(a string, b string) int { + aDigits := strings.TrimLeft(a, "0") + bDigits := strings.TrimLeft(b, "0") + if aDigits == "" { + aDigits = "0" + } + if bDigits == "" { + bDigits = "0" } - if localeStr == "auto" { - if locale.Default != (locale.Locale{}) { - tag := language.Tag(locale.Default) - return tag.String() - } - return "en" + if len(aDigits) != len(bDigits) { + return cmp.Compare(len(aDigits), len(bDigits)) } + if cmp := strings.Compare(aDigits, bDigits); cmp != 0 { + return cmp + } + return strings.Compare(a, b) +} - if locale, ok := locale.Parse(localeStr); ok { - tag := language.Tag(locale) - return tag.String() +func compareOrganizeImportsCaseUpperFirst(a string, b string) int { + return compareOrganizeImportsCase(a, b, OrganizeImportsCaseFirstUpper) +} + +func compareOrganizeImportsCase(a string, b string, caseFirst OrganizeImportsCaseFirst) int { + aRunes := []rune(a) + bRunes := []rune(b) + minLen := min(len(aRunes), len(bRunes)) + + for i := range minLen { + aUpper := unicode.IsUpper(aRunes[i]) + bUpper := unicode.IsUpper(bRunes[i]) + if aUpper != bUpper { + switch caseFirst { + case OrganizeImportsCaseFirstUpper: + if aUpper { + return -1 + } + return 1 + case OrganizeImportsCaseFirstLower: + if !aUpper { + return -1 + } + return 1 + default: + if aUpper { + return 1 + } + return -1 + } + } } - return "en" + return cmp.Compare(len(aRunes), len(bRunes)) } -func getOrganizeImportsStringComparer(preferences UserPreferences, ignoreCase bool) func(a, b string) int { - collation := preferences.OrganizeImportsCollation +func getOrganizeImportsPresetStringComparer(sort OrganizeImportsSort) func(a, b string) int { + switch sort { + case OrganizeImportsSortOrdinalIgnoreCase: + return getOrganizeImportsOrdinalStringComparer(true) + case OrganizeImportsSortNatural: + return getOrganizeImportsNaturalStringComparer(true) + case OrganizeImportsSortNaturalIgnoreCase: + return getOrganizeImportsNaturalStringComparer(false) + default: + return getOrganizeImportsOrdinalStringComparer(false) + } +} - if collation == OrganizeImportsCollationUnicode { +func getOrganizeImportsStringComparer(preferences UserPreferences, ignoreCase bool) func(a, b string) int { + if preferences.OrganizeImportsSort != OrganizeImportsSortAuto { + return getOrganizeImportsPresetStringComparer(preferences.OrganizeImportsSort) + } + if preferences.OrganizeImportsCollation == OrganizeImportsCollationUnicode { return getOrganizeImportsUnicodeStringComparer(ignoreCase, preferences) } return getOrganizeImportsOrdinalStringComparer(ignoreCase) @@ -318,7 +403,7 @@ func GetNamedImportSpecifierComparer(preferences UserPreferences, comparer func( if !preferences.OrganizeImportsIgnoreCase.IsUnknown() { ignoreCase = preferences.OrganizeImportsIgnoreCase.IsTrue() } - comparer = getOrganizeImportsOrdinalStringComparer(ignoreCase) + comparer = getOrganizeImportsStringComparer(preferences, ignoreCase) } return func(s1, s2 *ast.Node) int { return compareImportOrExportSpecifiers(s1, s2, comparer, preferences) @@ -346,14 +431,17 @@ func GetOrganizeImportsStringComparerWithDetection(originalImportDecls []*ast.St } func getComparers(preferences UserPreferences) []func(a string, b string) int { - switch preferences.OrganizeImportsIgnoreCase { - case core.TSTrue: - return caseInsensitiveOrganizeImportsComparer - case core.TSFalse: - return caseSensitiveOrganizeImportsComparer + if preferences.OrganizeImportsSort != OrganizeImportsSortAuto || !preferences.OrganizeImportsIgnoreCase.IsUnknown() { + ignoreCase := false + if !preferences.OrganizeImportsIgnoreCase.IsUnknown() { + ignoreCase = preferences.OrganizeImportsIgnoreCase.IsTrue() + } + return []func(a, b string) int{getOrganizeImportsStringComparer(preferences, ignoreCase)} + } + return []func(a, b string) int{ + getOrganizeImportsStringComparer(preferences, true), + getOrganizeImportsStringComparer(preferences, false), } - - return organizeImportsComparers } type namedImportSortResult struct { @@ -582,7 +670,7 @@ func GetNamedImportSpecifierComparerWithDetection(importDecl *ast.Node, sourceFi specifierComparer = GetNamedImportSpecifierComparer(preferences, comparersToTest[0]) isSorted = core.TSUnknown - if (preferences.OrganizeImportsIgnoreCase.IsUnknown() || preferences.OrganizeImportsTypeOrder == OrganizeImportsTypeOrderAuto) && importStmt != nil { + if (ResolveOrganizeImportsSort(preferences) == OrganizeImportsSortAuto || preferences.OrganizeImportsTypeOrder == OrganizeImportsTypeOrderAuto) && importStmt != nil { detectFromDecl := detectNamedImportOrganizationBySort([]*ast.Statement{importStmt}, comparersToTest, typeOrdersToTest) if detectFromDecl != nil { isSorted = core.BoolToTristate(detectFromDecl.isSorted) diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go index 2b54bda0158..124f5f1b276 100644 --- a/internal/ls/lsutil/userpreferences.go +++ b/internal/ls/lsutil/userpreferences.go @@ -84,19 +84,21 @@ type UserPreferences struct { // ------- OrganizeImports ------- + // Indicates which deterministic preset should be used to sort imports. + // "auto" detects the existing ordinal case sensitivity where possible. + OrganizeImportsSort OrganizeImportsSort `raw:"organizeImportsSort" config:"preferences.organizeImports.sort"` // !!! // Indicates whether imports should be organized in a case-insensitive manner. // // Default: TSUnknown ("auto" in strada), will perform detection OrganizeImportsIgnoreCase core.Tristate `raw:"organizeImportsIgnoreCase" config:"preferences.organizeImports.caseSensitivity"` // !!! // Indicates whether imports should be organized via an "ordinal" (binary) comparison using the numeric value of their - // code points, or via "unicode" collation (via the Unicode Collation Algorithm (https://unicode.org/reports/tr10/#Scope)) - // - // using rules associated with the locale specified in organizeImportsCollationLocale. + // code points, or via "unicode" natural sorting. This implementation is locale-agnostic and approximates the practical + // import-sorting behavior rather than the full Unicode Collation Algorithm. // // Default: Ordinal OrganizeImportsCollation OrganizeImportsCollation `raw:"organizeImportsCollation" config:"preferences.organizeImports.unicodeCollation"` // !!! - // Indicates the locale to use for "unicode" collation. If not specified, the locale `"en"` is used as an invariant - // for the sake of consistent sorting. Use `"auto"` to use the detected UI locale. + // Indicates the locale to use for "unicode" collation in legacy clients. This is accepted for compatibility, but + // currently ignored because organize-import sorting is deterministic and locale-agnostic. // // This preference is ignored if organizeImportsCollation is not `unicode`. // @@ -109,16 +111,13 @@ type UserPreferences struct { // // Default: `false` OrganizeImportsNumericCollation core.Tristate `raw:"organizeImportsNumericCollation" config:"preferences.organizeImports.numericCollation"` // !!! - // Indicates whether accents and other diacritic marks are considered unequal for the purpose of collation. When - // `true`, characters with accents and other diacritics will be collated in the order defined by the locale specified - // in organizeImportsCollationLocale. + // Indicates whether accents and other diacritic marks are considered unequal for the purpose of sorting. // // This preference is ignored if organizeImportsCollation is not `unicode`. // // Default: `true` OrganizeImportsAccentCollation core.Tristate `raw:"organizeImportsAccentCollation" config:"preferences.organizeImports.accentCollation"` // !!! - // Indicates whether upper case or lower case should sort first. When `false`, the default order for the locale - // specified in organizeImportsCollationLocale is used. + // Indicates whether upper case or lower case should sort first. // // This permission is ignored if: // - organizeImportsCollation is not `unicode` @@ -240,6 +239,16 @@ const ( IncludeInlayParameterNameHintsLiterals IncludeInlayParameterNameHints = "literals" ) +type OrganizeImportsSort int + +const ( + OrganizeImportsSortAuto OrganizeImportsSort = iota + OrganizeImportsSortOrdinal + OrganizeImportsSortOrdinalIgnoreCase + OrganizeImportsSortNatural + OrganizeImportsSortNaturalIgnoreCase +) + type OrganizeImportsCollation bool const ( @@ -318,6 +327,21 @@ var typeParsers = map[reflect.Type]func(any) any{ } return IncludeInlayParameterNameHintsNone }, + reflect.TypeFor[OrganizeImportsSort](): func(val any) any { + if s, ok := val.(string); ok { + switch strings.ToLower(s) { + case "ordinal": + return OrganizeImportsSortOrdinal + case "ordinalignorecase": + return OrganizeImportsSortOrdinalIgnoreCase + case "natural": + return OrganizeImportsSortNatural + case "naturalignorecase": + return OrganizeImportsSortNaturalIgnoreCase + } + } + return OrganizeImportsSortAuto + }, reflect.TypeFor[OrganizeImportsCollation](): func(val any) any { if s, ok := val.(string); ok && strings.ToLower(s) == "unicode" { return OrganizeImportsCollationUnicode @@ -389,6 +413,20 @@ var typeSerializers = map[reflect.Type]func(any) any{ return nil } }, + reflect.TypeFor[OrganizeImportsSort](): func(val any) any { + switch val.(OrganizeImportsSort) { + case OrganizeImportsSortOrdinal: + return "ordinal" + case OrganizeImportsSortOrdinalIgnoreCase: + return "ordinalIgnoreCase" + case OrganizeImportsSortNatural: + return "natural" + case OrganizeImportsSortNaturalIgnoreCase: + return "naturalIgnoreCase" + default: + return "auto" + } + }, reflect.TypeFor[OrganizeImportsCollation](): func(val any) any { if val.(OrganizeImportsCollation) == OrganizeImportsCollationUnicode { return "unicode" @@ -546,27 +584,34 @@ func setNestedValue(config map[string]any, path string, value any) { current[parts[len(parts)-1]] = value } +func setRawFieldsFromConfig(v reflect.Value, infos []fieldInfo, settings map[string]any) { + index := unstableNameIndex() + for name, value := range settings { + if idx, found := index[name]; found { + info := infos[idx] + field := getFieldByPath(v, info.fieldPath) + if info.rawInvert { + if b, ok := value.(bool); ok { + value = !b + } + } + setFieldFromValue(field, value) + } + } +} + func (p UserPreferences) withConfig(config map[string]any) UserPreferences { v := reflect.ValueOf(&p).Elem() infos := fieldInfoCache() + // Raw UserPreferences can be provided directly, notably via LSP initializationOptions. + setRawFieldsFromConfig(v, infos, config) + // Process "unstable" section first - allows any field to be set by raw name. // This mirrors VS Code's behavior: { ...config.get('unstable'), ...stableOptions } // where stable options are spread after and take precedence. if unstable, ok := config["unstable"].(map[string]any); ok { - index := unstableNameIndex() - for name, value := range unstable { - if idx, found := index[name]; found { - info := infos[idx] - field := getFieldByPath(v, info.fieldPath) - if info.rawInvert { - if b, ok := value.(bool); ok { - value = !b - } - } - setFieldFromValue(field, value) - } - } + setRawFieldsFromConfig(v, infos, unstable) } // Process path-based config (VS Code style nested paths). @@ -618,7 +663,7 @@ func setFieldFromValue(field reflect.Value, val any) { return } - // Check custom parsers first (for types like Tristate, OrganizeImportsCollation, etc.) + // Check custom parsers first (for types like Tristate, enums, etc.) if parser, ok := typeParsers[field.Type()]; ok { field.Set(reflect.ValueOf(parser(val))) return @@ -687,7 +732,7 @@ func (p *UserPreferences) MarshalJSONTo(enc *json.Encoder) error { } func serializeField(field reflect.Value) any { - // Check custom serializers first (for types like Tristate, OrganizeImportsCollation, etc.) + // Check custom serializers first (for types like Tristate, enums, etc.) if serializer, ok := typeSerializers[field.Type()]; ok { return serializer(field.Interface()) } diff --git a/internal/ls/lsutil/userpreferences_test.go b/internal/ls/lsutil/userpreferences_test.go index d7ea9dcc6ea..13e2b57ce60 100644 --- a/internal/ls/lsutil/userpreferences_test.go +++ b/internal/ls/lsutil/userpreferences_test.go @@ -309,6 +309,105 @@ func TestUserPreferencesParseUnstable(t *testing.T) { }, }, }, + { + name: "old raw organize imports unicode preferences load as raw state", + json: `{ + "unstable": { + "organizeImportsCollation": "unicode", + "organizeImportsCaseFirst": "upper", + "organizeImportsIgnoreCase": false, + "organizeImportsNumericCollation": true + } + }`, + expected: UserPreferences{ + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsCaseFirst: OrganizeImportsCaseFirstUpper, + OrganizeImportsIgnoreCase: core.TSFalse, + OrganizeImportsNumericCollation: core.TSTrue, + }, + }, + { + name: "old top-level raw organize imports unicode preferences load as raw state", + json: `{ + "organizeImportsCollation": "unicode", + "organizeImportsIgnoreCase": true + }`, + expected: UserPreferences{ + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSTrue, + }, + }, + { + name: "new top-level raw organize imports sort is accepted", + json: `{ + "organizeImportsSort": "natural" + }`, + expected: UserPreferences{ + OrganizeImportsSort: OrganizeImportsSortNatural, + }, + }, + { + name: "old raw organize imports ignore case loads as raw state", + json: `{ + "unstable": { + "organizeImportsIgnoreCase": true + } + }`, + expected: UserPreferences{ + OrganizeImportsIgnoreCase: core.TSTrue, + }, + }, + { + name: "new raw organize imports sort loads alongside old raw preferences", + json: `{ + "unstable": { + "organizeImportsSort": "ordinal", + "organizeImportsCollation": "unicode", + "organizeImportsIgnoreCase": true + } + }`, + expected: UserPreferences{ + OrganizeImportsSort: OrganizeImportsSortOrdinal, + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSTrue, + }, + }, + { + name: "old nested organize imports unicode preferences load as raw state", + json: `{ + "preferences": { + "organizeImports": { + "unicodeCollation": "unicode", + "caseSensitivity": "caseSensitive", + "numericCollation": true, + "caseFirst": "upper" + } + } + }`, + expected: UserPreferences{ + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSFalse, + OrganizeImportsNumericCollation: core.TSTrue, + OrganizeImportsCaseFirst: OrganizeImportsCaseFirstUpper, + }, + }, + { + name: "new nested organize imports sort loads alongside old nested preferences", + json: `{ + "preferences": { + "organizeImports": { + "sort": "ordinalIgnoreCase", + "unicodeCollation": "unicode", + "caseSensitivity": "caseSensitive" + } + } + }`, + expected: UserPreferences{ + OrganizeImportsSort: OrganizeImportsSortOrdinalIgnoreCase, + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSFalse, + }, + }, } for _, tt := range tests { diff --git a/internal/ls/lsutil/utilities_test.go b/internal/ls/lsutil/utilities_test.go index 51292adfbbb..1f5b67426c3 100644 --- a/internal/ls/lsutil/utilities_test.go +++ b/internal/ls/lsutil/utilities_test.go @@ -65,3 +65,136 @@ let c = 3; }) } } + +func TestResolveOrganizeImportsSort(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + preferences UserPreferences + want OrganizeImportsSort + }{ + { + name: "explicit sort wins", + preferences: UserPreferences{ + OrganizeImportsSort: OrganizeImportsSortOrdinal, + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSTrue, + }, + want: OrganizeImportsSortOrdinal, + }, + { + name: "unicode case-sensitive maps to natural", + preferences: UserPreferences{ + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSFalse, + }, + want: OrganizeImportsSortNatural, + }, + { + name: "unicode ignore case maps to natural ignore case", + preferences: UserPreferences{ + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + OrganizeImportsIgnoreCase: core.TSTrue, + }, + want: OrganizeImportsSortNaturalIgnoreCase, + }, + { + name: "unicode unknown case sensitivity stays auto for detection", + preferences: UserPreferences{ + OrganizeImportsCollation: OrganizeImportsCollationUnicode, + }, + want: OrganizeImportsSortAuto, + }, + { + name: "ordinal ignore case maps to ordinal ignore case", + preferences: UserPreferences{ + OrganizeImportsIgnoreCase: core.TSTrue, + }, + want: OrganizeImportsSortOrdinalIgnoreCase, + }, + { + name: "ordinal case sensitive maps to ordinal", + preferences: UserPreferences{ + OrganizeImportsIgnoreCase: core.TSFalse, + }, + want: OrganizeImportsSortOrdinal, + }, + { + name: "unknown ordinal stays auto", + want: OrganizeImportsSortAuto, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := ResolveOrganizeImportsSort(tt.preferences); got != tt.want { + t.Fatalf("ResolveOrganizeImportsSort() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCompareOrganizeImportsNaturalStrings(t *testing.T) { + t.Parallel() + + comparer := getOrganizeImportsPresetStringComparer(OrganizeImportsSortNaturalIgnoreCase) + tests := []struct { + name string + a string + b string + want int + }{ + { + name: "numeric runs sort by numeric value", + a: "a2", + b: "a100", + want: -1, + }, + { + name: "numeric runs with equal value use raw tie break", + a: "a02", + b: "a2", + want: -1, + }, + { + name: "accents are folded for primary comparison", + a: "À", + b: "B", + want: -1, + }, + { + name: "raw comparison breaks accent ties", + a: "A", + b: "À", + want: -1, + }, + { + name: "hyphen sorts before slash like Intl.Collator fallback", + a: "app-init", + b: "app/app", + want: -1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := cmpSign(comparer(tt.a, tt.b)); got != tt.want { + t.Fatalf("comparer(%q, %q) = %v, want sign %v", tt.a, tt.b, got, tt.want) + } + }) + } +} + +func cmpSign(value int) int { + switch { + case value < 0: + return -1 + case value > 0: + return 1 + default: + return 0 + } +} diff --git a/internal/ls/organizeimports.go b/internal/ls/organizeimports.go index ea558136d66..ddea88bffd1 100644 --- a/internal/ls/organizeimports.go +++ b/internal/ls/organizeimports.go @@ -37,24 +37,25 @@ func (l *LanguageService) OrganizeImports( preferences := l.UserPreferences() comparersToTest, typeOrdersToTest := lsutil.GetDetectionLists(preferences) defaultComparer := comparersToTest[0] + sort := lsutil.ResolveOrganizeImportsSort(preferences) var moduleSpecifierComparer func(a, b string) int var namedImportComparer func(a, b string) int - if !preferences.OrganizeImportsIgnoreCase.IsUnknown() { + if sort != lsutil.OrganizeImportsSortAuto { moduleSpecifierComparer = defaultComparer namedImportComparer = defaultComparer } typeOrder := preferences.OrganizeImportsTypeOrder - if preferences.OrganizeImportsIgnoreCase.IsUnknown() { + if sort == lsutil.OrganizeImportsSortAuto { result, _ := lsutil.DetectModuleSpecifierCaseBySort(topLevelImportGroupDecls, comparersToTest) moduleSpecifierComparer = result } - if typeOrder == lsutil.OrganizeImportsTypeOrderAuto || preferences.OrganizeImportsIgnoreCase.IsUnknown() { + if typeOrder == lsutil.OrganizeImportsTypeOrderAuto || sort == lsutil.OrganizeImportsSortAuto { namedImportComparer2, typeOrder2, found := lsutil.DetectNamedImportOrganizationBySort(topLevelImportDecls, comparersToTest, typeOrdersToTest) if found { - if namedImportComparer == nil || preferences.OrganizeImportsIgnoreCase.IsUnknown() { + if namedImportComparer == nil || sort == lsutil.OrganizeImportsSortAuto { namedImportComparer = namedImportComparer2 } if typeOrder == lsutil.OrganizeImportsTypeOrderAuto { diff --git a/internal/project/session_test.go b/internal/project/session_test.go index f33db22f9f7..955bea1a1d0 100644 --- a/internal/project/session_test.go +++ b/internal/project/session_test.go @@ -1423,7 +1423,7 @@ export const value = content;`, "quoteStyle": "single", }, "unstable": map[string]any{ - "organizeImportsIgnoreCase": true, + "organizeImportsSort": "ordinalIgnoreCase", }, } session.Configure(lsutil.ParseUserPreferences(map[string]any{"js/ts": configMap1})) @@ -1431,7 +1431,7 @@ export const value = content;`, expectedPrefs1 := lsutil.NewDefaultUserPreferences() expectedPrefs1.UseAliasesForRename = core.TSTrue expectedPrefs1.QuotePreference = lsutil.QuotePreferenceSingle - expectedPrefs1.OrganizeImportsIgnoreCase = core.TSTrue + expectedPrefs1.OrganizeImportsSort = lsutil.OrganizeImportsSortOrdinalIgnoreCase assert.DeepEqual(t, actualConfig1, expectedPrefs1) @@ -1441,7 +1441,7 @@ export const value = content;`, "quoteStyle": "double", }, "unstable": map[string]any{ - "organizeImportsIgnoreCase": false, + "organizeImportsSort": "ordinal", }, } session.Configure(lsutil.ParseUserPreferences(map[string]any{"js/ts": configMap2})) @@ -1449,7 +1449,7 @@ export const value = content;`, expectedPrefs2 := lsutil.NewDefaultUserPreferences() expectedPrefs2.UseAliasesForRename = core.TSFalse expectedPrefs2.QuotePreference = lsutil.QuotePreferenceDouble - expectedPrefs2.OrganizeImportsIgnoreCase = core.TSFalse + expectedPrefs2.OrganizeImportsSort = lsutil.OrganizeImportsSortOrdinal assert.DeepEqual(t, actualConfig2, expectedPrefs2) }) diff --git a/testdata/baselines/reference/fourslash/state/codeLensAcrossProjects.baseline b/testdata/baselines/reference/fourslash/state/codeLensAcrossProjects.baseline index 529c89ae2cc..093bf66c7bc 100644 --- a/testdata/baselines/reference/fourslash/state/codeLensAcrossProjects.baseline +++ b/testdata/baselines/reference/fourslash/state/codeLensAcrossProjects.baseline @@ -190,6 +190,7 @@ Config File Names:: "organizeImports": { "caseFirst": "default", "locale": "", + "sort": "auto", "typeOrder": "auto", "unicodeCollation": "ordinal" }, @@ -642,6 +643,7 @@ Config:: "organizeImports": { "caseFirst": "default", "locale": "", + "sort": "auto", "typeOrder": "auto", "unicodeCollation": "ordinal" },