696 lines
22 KiB
Go
696 lines
22 KiB
Go
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/stringutil"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
"golang.org/x/text/unicode/norm"
|
|
)
|
|
|
|
// FilterImportDeclarations filters out non-import declarations from a list of statements.
|
|
func FilterImportDeclarations(statements []*ast.Statement) []*ast.Statement {
|
|
return core.Filter(statements, func(stmt *ast.Statement) bool {
|
|
return stmt.Kind == ast.KindImportDeclaration
|
|
})
|
|
}
|
|
|
|
// 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.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),
|
|
getOrganizeImportsStringComparer(preferences, false),
|
|
}
|
|
}
|
|
|
|
if preferences.OrganizeImportsTypeOrder != OrganizeImportsTypeOrderAuto {
|
|
typeOrdersToTest = []OrganizeImportsTypeOrder{preferences.OrganizeImportsTypeOrder}
|
|
} else {
|
|
typeOrdersToTest = []OrganizeImportsTypeOrder{
|
|
OrganizeImportsTypeOrderLast,
|
|
OrganizeImportsTypeOrderInline,
|
|
OrganizeImportsTypeOrderFirst,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
return stringutil.CompareStringsCaseSensitive
|
|
}
|
|
|
|
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()
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
return strings.Compare(a, b)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if accents {
|
|
if cmp := compareOrganizeImportsUnicodeKeys(strings.ToLower(a), strings.ToLower(b), numeric); cmp != 0 {
|
|
return cmp
|
|
}
|
|
}
|
|
|
|
if !ignoreCase {
|
|
if cmp := compareOrganizeImportsCase(a, b, caseFirst); cmp != 0 {
|
|
return cmp
|
|
}
|
|
}
|
|
|
|
return strings.Compare(a, b)
|
|
}
|
|
|
|
func naturalCollationKey(s string) string {
|
|
return strings.ToLower(removeDiacritics(s))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
a = a[aRunEnd:]
|
|
b = b[bRunEnd:]
|
|
continue
|
|
}
|
|
|
|
aRune, aSize := utf8.DecodeRuneInString(a)
|
|
bRune, bSize := utf8.DecodeRuneInString(b)
|
|
if aRune != bRune {
|
|
return cmp.Compare(aRune, bRune)
|
|
}
|
|
|
|
a = a[aSize:]
|
|
b = b[bSize:]
|
|
}
|
|
|
|
return cmp.Compare(len(a), len(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 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 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)
|
|
}
|
|
|
|
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 cmp.Compare(len(aRunes), len(bRunes))
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func getModuleSpecifierExpression(declaration *ast.Statement) *ast.Expression {
|
|
switch declaration.Kind {
|
|
case ast.KindImportEqualsDeclaration:
|
|
importEquals := declaration.AsImportEqualsDeclaration()
|
|
if importEquals.ModuleReference.Kind == ast.KindExternalModuleReference {
|
|
return importEquals.ModuleReference.Expression()
|
|
}
|
|
return nil
|
|
case ast.KindImportDeclaration:
|
|
return declaration.ModuleSpecifier()
|
|
case ast.KindVariableStatement:
|
|
declarations := declaration.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes
|
|
if len(declarations) > 0 {
|
|
initializer := declarations[0].Initializer()
|
|
if initializer != nil && initializer.Kind == ast.KindCallExpression {
|
|
callExpr := initializer.AsCallExpression()
|
|
if len(callExpr.Arguments.Nodes) > 0 {
|
|
return callExpr.Arguments.Nodes[0]
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// GetExternalModuleName returns the module name from a module specifier expression.
|
|
func GetExternalModuleName(specifier *ast.Expression) string {
|
|
if specifier != nil && ast.IsStringLiteralLike(specifier.AsNode()) {
|
|
return specifier.Text()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// CompareModuleSpecifiers compares two module specifiers using the given comparer.
|
|
func CompareModuleSpecifiers(m1 *ast.Expression, m2 *ast.Expression, comparer func(a, b string) int) int {
|
|
name1 := GetExternalModuleName(m1)
|
|
name2 := GetExternalModuleName(m2)
|
|
if cmp := core.CompareBooleans(name1 == "", name2 == ""); cmp != 0 {
|
|
return cmp
|
|
}
|
|
if cmp := core.CompareBooleans(tspath.IsExternalModuleNameRelative(name1), tspath.IsExternalModuleNameRelative(name2)); cmp != 0 {
|
|
return cmp
|
|
}
|
|
return comparer(name1, name2)
|
|
}
|
|
|
|
func compareImportKind(s1 *ast.Statement, s2 *ast.Statement) int {
|
|
return cmp.Compare(getImportKindOrder(s1), getImportKindOrder(s2))
|
|
}
|
|
|
|
// getImportKindOrder returns the sort order for different import kinds:
|
|
// 1. Side-effect imports
|
|
// 2. Type-only imports
|
|
// 3. Namespace imports
|
|
// 4. Default imports
|
|
// 5. Named imports
|
|
// 6. ImportEqualsDeclarations
|
|
// 7. Require variable statements
|
|
const (
|
|
importKindOrderSideEffect = 0
|
|
importKindOrderTypeOnly = 1
|
|
importKindOrderNamespace = 2
|
|
importKindOrderDefault = 3
|
|
importKindOrderNamed = 4
|
|
importKindOrderImportEquals = 5
|
|
importKindOrderRequire = 6
|
|
importKindOrderUnknown = 7
|
|
)
|
|
|
|
func getImportKindOrder(s1 *ast.Statement) int {
|
|
switch s1.Kind {
|
|
case ast.KindImportDeclaration:
|
|
importDecl := s1.AsImportDeclaration()
|
|
if importDecl.ImportClause == nil {
|
|
return importKindOrderSideEffect
|
|
}
|
|
importClause := importDecl.ImportClause.AsImportClause()
|
|
if importClause.IsTypeOnly() {
|
|
return importKindOrderTypeOnly
|
|
}
|
|
if importClause.NamedBindings != nil && importClause.NamedBindings.Kind == ast.KindNamespaceImport {
|
|
return importKindOrderNamespace
|
|
}
|
|
if importClause.Name() != nil {
|
|
return importKindOrderDefault
|
|
}
|
|
return importKindOrderNamed
|
|
case ast.KindImportEqualsDeclaration:
|
|
return importKindOrderImportEquals
|
|
case ast.KindVariableStatement:
|
|
return importKindOrderRequire
|
|
default:
|
|
return importKindOrderUnknown
|
|
}
|
|
}
|
|
|
|
// CompareImportsOrRequireStatements compares two import or require statements.
|
|
func CompareImportsOrRequireStatements(s1 *ast.Statement, s2 *ast.Statement, comparer func(a, b string) int) int {
|
|
if cmp := CompareModuleSpecifiers(getModuleSpecifierExpression(s1), getModuleSpecifierExpression(s2), comparer); cmp != 0 {
|
|
return cmp
|
|
}
|
|
return compareImportKind(s1, s2)
|
|
}
|
|
|
|
func compareImportOrExportSpecifiers(s1 *ast.Node, s2 *ast.Node, comparer func(a, b string) int, preferences UserPreferences) int {
|
|
typeOrder := preferences.OrganizeImportsTypeOrder
|
|
|
|
s1Name := s1.Name().Text()
|
|
s2Name := s2.Name().Text()
|
|
|
|
switch typeOrder {
|
|
case OrganizeImportsTypeOrderFirst:
|
|
if cmp := core.CompareBooleans(s2.IsTypeOnly(), s1.IsTypeOnly()); cmp != 0 {
|
|
return cmp
|
|
}
|
|
return comparer(s1Name, s2Name)
|
|
case OrganizeImportsTypeOrderInline:
|
|
return comparer(s1Name, s2Name)
|
|
default: // OrganizeImportsTypeOrderLast
|
|
if cmp := core.CompareBooleans(s1.IsTypeOnly(), s2.IsTypeOnly()); cmp != 0 {
|
|
return cmp
|
|
}
|
|
return comparer(s1Name, s2Name)
|
|
}
|
|
}
|
|
|
|
// GetNamedImportSpecifierComparer returns a comparer function for sorting import specifiers.
|
|
func GetNamedImportSpecifierComparer(preferences UserPreferences, comparer func(a, b string) int) func(s1, s2 *ast.Node) int {
|
|
if comparer == nil {
|
|
ignoreCase := false
|
|
if !preferences.OrganizeImportsIgnoreCase.IsUnknown() {
|
|
ignoreCase = preferences.OrganizeImportsIgnoreCase.IsTrue()
|
|
}
|
|
comparer = getOrganizeImportsStringComparer(preferences, ignoreCase)
|
|
}
|
|
return func(s1, s2 *ast.Node) int {
|
|
return compareImportOrExportSpecifiers(s1, s2, comparer, preferences)
|
|
}
|
|
}
|
|
|
|
// GetImportSpecifierInsertionIndex returns the index at which to insert a new import specifier.
|
|
func GetImportSpecifierInsertionIndex(sortedImports []*ast.Node, newImport *ast.Node, comparer func(s1, s2 *ast.Node) int) int {
|
|
return core.FirstResult(core.BinarySearchUniqueFunc(sortedImports, func(mid int, value *ast.Node) int {
|
|
return comparer(value, newImport)
|
|
}))
|
|
}
|
|
|
|
// GetImportDeclarationInsertIndex returns the index at which to insert a new import declaration.
|
|
func GetImportDeclarationInsertIndex(sortedImports []*ast.Statement, newImport *ast.Statement, comparer func(a, b *ast.Statement) int) int {
|
|
return core.FirstResult(core.BinarySearchUniqueFunc(sortedImports, func(mid int, value *ast.Statement) int {
|
|
return comparer(value, newImport)
|
|
}))
|
|
}
|
|
|
|
// GetOrganizeImportsStringComparerWithDetection returns a string comparer based on detecting the order of import statements by the module specifier
|
|
func GetOrganizeImportsStringComparerWithDetection(originalImportDecls []*ast.Statement, preferences UserPreferences) (comparer func(a, b string) int, isSorted bool) {
|
|
result, sorted := DetectModuleSpecifierCaseBySort([][]*ast.Statement{originalImportDecls}, getComparers(preferences))
|
|
return result, sorted
|
|
}
|
|
|
|
func getComparers(preferences UserPreferences) []func(a string, b string) int {
|
|
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),
|
|
}
|
|
}
|
|
|
|
type namedImportSortResult struct {
|
|
namedImportComparer func(a, b string) int
|
|
typeOrder OrganizeImportsTypeOrder
|
|
isSorted bool
|
|
}
|
|
|
|
// DetectNamedImportOrganizationBySort detects the order of named imports throughout the file by considering the named imports in each statement as a group
|
|
func DetectNamedImportOrganizationBySort(
|
|
originalGroups []*ast.Statement,
|
|
comparersToTest []func(a, b string) int,
|
|
typesToTest []OrganizeImportsTypeOrder,
|
|
) (comparer func(a, b string) int, typeOrder OrganizeImportsTypeOrder, found bool) {
|
|
result := detectNamedImportOrganizationBySort(originalGroups, comparersToTest, typesToTest)
|
|
if result == nil {
|
|
return nil, OrganizeImportsTypeOrderLast, false
|
|
}
|
|
return result.namedImportComparer, result.typeOrder, true
|
|
}
|
|
|
|
func detectNamedImportOrganizationBySort(
|
|
originalGroups []*ast.Statement,
|
|
comparersToTest []func(a, b string) int,
|
|
typesToTest []OrganizeImportsTypeOrder,
|
|
) *namedImportSortResult {
|
|
var bothNamedImports bool
|
|
var importDeclsWithNamed []*ast.Statement
|
|
|
|
for _, imp := range originalGroups {
|
|
if imp.AsImportDeclaration().ImportClause == nil {
|
|
continue
|
|
}
|
|
clause := imp.AsImportDeclaration().ImportClause.AsImportClause()
|
|
if clause.NamedBindings == nil || clause.NamedBindings.Kind != ast.KindNamedImports {
|
|
continue
|
|
}
|
|
namedImports := clause.NamedBindings.AsNamedImports()
|
|
if len(namedImports.Elements.Nodes) == 0 {
|
|
continue
|
|
}
|
|
|
|
if !bothNamedImports {
|
|
hasTypeOnly := false
|
|
hasRegular := false
|
|
for _, elem := range namedImports.Elements.Nodes {
|
|
if elem.IsTypeOnly() {
|
|
hasTypeOnly = true
|
|
} else {
|
|
hasRegular = true
|
|
}
|
|
}
|
|
if hasTypeOnly && hasRegular {
|
|
bothNamedImports = true
|
|
}
|
|
}
|
|
|
|
importDeclsWithNamed = append(importDeclsWithNamed, imp)
|
|
}
|
|
|
|
if len(importDeclsWithNamed) == 0 {
|
|
return nil
|
|
}
|
|
|
|
namedImportsByDecl := make([][]*ast.Statement, 0, len(importDeclsWithNamed))
|
|
for _, imp := range importDeclsWithNamed {
|
|
clause := imp.AsImportDeclaration().ImportClause.AsImportClause()
|
|
namedImports := clause.NamedBindings.AsNamedImports()
|
|
namedImportsByDecl = append(namedImportsByDecl, namedImports.Elements.Nodes)
|
|
}
|
|
|
|
if !bothNamedImports || len(typesToTest) == 0 {
|
|
namesList := make([][]string, len(namedImportsByDecl))
|
|
for i, imports := range namedImportsByDecl {
|
|
names := make([]string, len(imports))
|
|
for j, imp := range imports {
|
|
names[j] = imp.Name().Text()
|
|
}
|
|
namesList[i] = names
|
|
}
|
|
sortState := detectCaseSensitivityBySort(namesList, comparersToTest)
|
|
typeOrder := OrganizeImportsTypeOrderLast
|
|
if len(typesToTest) == 1 {
|
|
typeOrder = typesToTest[0]
|
|
}
|
|
return &namedImportSortResult{
|
|
namedImportComparer: sortState.comparer,
|
|
typeOrder: typeOrder,
|
|
isSorted: sortState.isSorted,
|
|
}
|
|
}
|
|
|
|
bestDiff := map[OrganizeImportsTypeOrder]int{
|
|
OrganizeImportsTypeOrderFirst: math.MaxInt,
|
|
OrganizeImportsTypeOrderLast: math.MaxInt,
|
|
OrganizeImportsTypeOrderInline: math.MaxInt,
|
|
}
|
|
bestComparer := map[OrganizeImportsTypeOrder]func(a, b string) int{
|
|
OrganizeImportsTypeOrderFirst: comparersToTest[0],
|
|
OrganizeImportsTypeOrderLast: comparersToTest[0],
|
|
OrganizeImportsTypeOrderInline: comparersToTest[0],
|
|
}
|
|
|
|
for _, curComparer := range comparersToTest {
|
|
currDiff := map[OrganizeImportsTypeOrder]int{
|
|
OrganizeImportsTypeOrderFirst: 0,
|
|
OrganizeImportsTypeOrderLast: 0,
|
|
OrganizeImportsTypeOrderInline: 0,
|
|
}
|
|
|
|
for _, importDecl := range namedImportsByDecl {
|
|
for _, typeOrder := range typesToTest {
|
|
prefs := UserPreferences{OrganizeImportsTypeOrder: typeOrder}
|
|
diff := measureSortedness(importDecl, func(n1, n2 *ast.Node) int {
|
|
return compareImportOrExportSpecifiers(n1, n2, curComparer, prefs)
|
|
})
|
|
currDiff[typeOrder] = currDiff[typeOrder] + diff
|
|
}
|
|
}
|
|
|
|
for _, typeOrder := range typesToTest {
|
|
if currDiff[typeOrder] < bestDiff[typeOrder] {
|
|
bestDiff[typeOrder] = currDiff[typeOrder]
|
|
bestComparer[typeOrder] = curComparer
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, bestTypeOrder := range typesToTest {
|
|
isBest := true
|
|
for _, testTypeOrder := range typesToTest {
|
|
if bestDiff[testTypeOrder] < bestDiff[bestTypeOrder] {
|
|
isBest = false
|
|
break
|
|
}
|
|
}
|
|
if isBest {
|
|
return &namedImportSortResult{
|
|
namedImportComparer: bestComparer[bestTypeOrder],
|
|
typeOrder: bestTypeOrder,
|
|
isSorted: bestDiff[bestTypeOrder] == 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
return &namedImportSortResult{
|
|
namedImportComparer: bestComparer[OrganizeImportsTypeOrderLast],
|
|
typeOrder: OrganizeImportsTypeOrderLast,
|
|
isSorted: bestDiff[OrganizeImportsTypeOrderLast] == 0,
|
|
}
|
|
}
|
|
|
|
type caseSensitivityDetectionResult struct {
|
|
comparer func(a, b string) int
|
|
isSorted bool
|
|
}
|
|
|
|
// DetectModuleSpecifierCaseBySort detects the order of module specifiers based on import statements throughout the module/file
|
|
func DetectModuleSpecifierCaseBySort(importDeclsByGroup [][]*ast.Statement, comparersToTest []func(a, b string) int) (comparer func(a, b string) int, isSorted bool) {
|
|
moduleSpecifiersByGroup := make([][]string, 0, len(importDeclsByGroup))
|
|
for _, importGroup := range importDeclsByGroup {
|
|
moduleNames := make([]string, 0, len(importGroup))
|
|
for _, decl := range importGroup {
|
|
if expr := getModuleSpecifierExpression(decl); expr != nil {
|
|
moduleNames = append(moduleNames, GetExternalModuleName(expr))
|
|
} else {
|
|
moduleNames = append(moduleNames, "")
|
|
}
|
|
}
|
|
moduleSpecifiersByGroup = append(moduleSpecifiersByGroup, moduleNames)
|
|
}
|
|
result := detectCaseSensitivityBySort(moduleSpecifiersByGroup, comparersToTest)
|
|
return result.comparer, result.isSorted
|
|
}
|
|
|
|
func detectCaseSensitivityBySort(originalGroups [][]string, comparersToTest []func(a, b string) int) caseSensitivityDetectionResult {
|
|
var bestComparer func(a, b string) int
|
|
bestDiff := math.MaxInt
|
|
|
|
for _, curComparer := range comparersToTest {
|
|
diffOfCurrentComparer := 0
|
|
|
|
for _, listToSort := range originalGroups {
|
|
if len(listToSort) <= 1 {
|
|
continue
|
|
}
|
|
diff := measureSortedness(listToSort, curComparer)
|
|
diffOfCurrentComparer += diff
|
|
}
|
|
|
|
if diffOfCurrentComparer < bestDiff {
|
|
bestDiff = diffOfCurrentComparer
|
|
bestComparer = curComparer
|
|
}
|
|
}
|
|
|
|
if bestComparer == nil && len(comparersToTest) > 0 {
|
|
bestComparer = comparersToTest[0]
|
|
}
|
|
|
|
return caseSensitivityDetectionResult{
|
|
comparer: bestComparer,
|
|
isSorted: bestDiff == 0,
|
|
}
|
|
}
|
|
|
|
func measureSortedness[T any](arr []T, comparer func(a, b T) int) int {
|
|
i := 0
|
|
for j := range len(arr) - 1 {
|
|
if comparer(arr[j], arr[j+1]) > 0 {
|
|
i++
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
|
|
// GetNamedImportSpecifierComparerWithDetection returns a specifier comparer based on detecting the existing sort order within a single import statement
|
|
func GetNamedImportSpecifierComparerWithDetection(importDecl *ast.Node, sourceFile *ast.SourceFile, preferences UserPreferences) (specifierComparer func(s1, s2 *ast.Node) int, isSorted core.Tristate) {
|
|
comparersToTest, typeOrdersToTest := GetDetectionLists(preferences)
|
|
|
|
var importStmt *ast.Statement
|
|
if importDecl.Kind == ast.KindImportDeclaration {
|
|
importStmt = importDecl
|
|
}
|
|
|
|
specifierComparer = GetNamedImportSpecifierComparer(preferences, comparersToTest[0])
|
|
isSorted = core.TSUnknown
|
|
|
|
if (ResolveOrganizeImportsSort(preferences) == OrganizeImportsSortAuto || preferences.OrganizeImportsTypeOrder == OrganizeImportsTypeOrderAuto) && importStmt != nil {
|
|
detectFromDecl := detectNamedImportOrganizationBySort([]*ast.Statement{importStmt}, comparersToTest, typeOrdersToTest)
|
|
if detectFromDecl != nil {
|
|
isSorted = core.BoolToTristate(detectFromDecl.isSorted)
|
|
specifierComparer = GetNamedImportSpecifierComparer(
|
|
UserPreferences{OrganizeImportsTypeOrder: detectFromDecl.typeOrder},
|
|
detectFromDecl.namedImportComparer,
|
|
)
|
|
} else if sourceFile != nil {
|
|
allImports := FilterImportDeclarations(sourceFile.Statements.Nodes)
|
|
detectFromFile := detectNamedImportOrganizationBySort(allImports, comparersToTest, typeOrdersToTest)
|
|
if detectFromFile != nil {
|
|
isSorted = core.BoolToTristate(detectFromFile.isSorted)
|
|
specifierComparer = GetNamedImportSpecifierComparer(
|
|
UserPreferences{OrganizeImportsTypeOrder: detectFromFile.typeOrder},
|
|
detectFromFile.namedImportComparer,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
return specifierComparer, isSorted
|
|
}
|