vendor tsgo
This commit is contained in:
695
tools/tsgo/internal/ls/lsutil/organizeimports.go
Normal file
695
tools/tsgo/internal/ls/lsutil/organizeimports.go
Normal file
@@ -0,0 +1,695 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user