Add option to sort imports like purs-ide (#71)

* Add option to sort imports like purs-ide

* Add dep
This commit is contained in:
Nathan Faubion 2021-10-16 14:11:19 -07:00 committed by GitHub
parent b6ed20df8c
commit 75836c7d5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 223 additions and 13 deletions

View File

@ -13,10 +13,11 @@ import Data.Argonaut.Encode (assoc, encodeJson, extend)
import Data.Either (Either)
import Data.Maybe (Maybe(..), fromMaybe, maybe)
import Data.Traversable (traverse)
import Tidy (ImportWrapOption(..), TypeArrowOption(..), UnicodeOption(..))
import Tidy (ImportSortOption(..), ImportWrapOption(..), TypeArrowOption(..), UnicodeOption(..))
type FormatOptions =
{ importWrap :: ImportWrapOption
{ importSort :: ImportSortOption
, importWrap :: ImportWrapOption
, indent :: Int
, operatorsFile :: Maybe String
, ribbon :: Number
@ -27,7 +28,8 @@ type FormatOptions =
defaults :: FormatOptions
defaults =
{ importWrap: ImportWrapSource
{ importSort: ImportSortSource
, importWrap: ImportWrapSource
, indent: 2
, operatorsFile: Nothing
, ribbon: 1.0
@ -39,7 +41,17 @@ defaults =
formatOptions :: ArgParser FormatOptions
formatOptions =
Arg.fromRecord
{ importWrap:
{ importSort:
Arg.choose "import sort"
[ Arg.flag [ "--import-sort-source", "-iss" ]
"Imports are not automatically sorted and keep the same order as in the source.\nDefault."
$> ImportSortSource
, Arg.flag [ "--import-sort-ide", "-isi" ]
"Imports are automatically sorted like purs-ide."
$> ImportSortIde
]
# Arg.default defaults.importSort
, importWrap:
Arg.choose "import wrap"
[ Arg.flag [ "--import-wrap-source", "-iws" ]
"Imports are wrapped only when breaks are in the source.\nDefault. Works well with IDE imports."
@ -100,6 +112,7 @@ unicodeOption =
fromJson :: Json -> Either JsonDecodeError FormatOptions
fromJson json = do
obj <- decodeJson json
importSort <- traverse importSortFromString =<< obj .:? "importSort"
importWrap <- traverse importWrapFromString =<< obj .:? "importWrap"
indent <- obj .:? "indent"
operatorsFile <- obj .:? "operatorsFile"
@ -108,7 +121,8 @@ fromJson json = do
unicode <- traverse unicodeFromString =<< obj .:? "unicode"
width <- obj .:? "width"
pure
{ importWrap: fromMaybe defaults.importWrap importWrap
{ importSort: fromMaybe defaults.importSort importSort
, importWrap: fromMaybe defaults.importWrap importWrap
, indent: fromMaybe defaults.indent indent
, operatorsFile: operatorsFile <|> defaults.operatorsFile
, ribbon: fromMaybe defaults.ribbon ribbon
@ -162,3 +176,14 @@ importWrapToString :: ImportWrapOption -> String
importWrapToString = case _ of
ImportWrapSource -> "source"
ImportWrapAuto -> "auto"
importSortFromString :: String -> Either JsonDecodeError ImportSortOption
importSortFromString = case _ of
"source" -> pure ImportSortSource
"ide" -> pure ImportSortIde
other -> throwError $ UnexpectedValue (Json.fromString other)
importSortToString :: ImportSortOption -> String
importSortToString = case _ of
ImportSortSource -> "source"
ImportSortIde -> "ide"

View File

@ -30,7 +30,8 @@ import Tidy.Operators (parseOperatorTable)
import Tidy.Precedence (PrecedenceMap, remapOperators)
type WorkerConfig =
{ importWrap :: String
{ importSort :: String
, importWrap :: String
, indent :: Int
, operatorsFile :: String
, ribbon :: Number
@ -41,7 +42,8 @@ type WorkerConfig =
toWorkerConfig :: FormatOptions -> WorkerConfig
toWorkerConfig options =
{ importWrap: FormatOptions.importWrapToString options.importWrap
{ importSort: FormatOptions.importSortToString options.importSort
, importWrap: FormatOptions.importWrapToString options.importWrap
, indent: options.indent
, operatorsFile: fromMaybe ".tidyoperators.default" options.operatorsFile
, ribbon: options.ribbon
@ -105,7 +107,10 @@ formatInPlaceCommand shouldCheck operators { filePath, config } = do
let
formatOptions :: FormatOptions
formatOptions =
{ importWrap:
{ importSort:
fromRight' (\_ -> unsafeCrashWith "Unknown importSort value") do
FormatOptions.importSortFromString config.importSort
, importWrap:
fromRight' (\_ -> unsafeCrashWith "Unknown importWrap value") do
FormatOptions.importWrapFromString config.importWrap
, indent: config.indent

View File

@ -27,6 +27,7 @@
, "node-workerbees"
, "numbers"
, "ordered-collections"
, "orders"
, "parallel"
, "partial"
, "prelude"

View File

@ -8,6 +8,7 @@
, "lists"
, "maybe"
, "ordered-collections"
, "orders"
, "partial"
, "prelude"
, "purescript-language-cst-parser"

View File

@ -2,6 +2,7 @@ module Tidy
( FormatOptions
, defaultFormatOptions
, TypeArrowOption(..)
, ImportSortOption(..)
, ImportWrapOption(..)
, Format
, formatModule
@ -26,14 +27,17 @@ import Data.Map as Map
import Data.Maybe (Maybe(..), maybe)
import Data.Monoid (power)
import Data.Monoid as Monoid
import Data.Newtype (un)
import Data.Ord.Down (Down(..))
import Data.String.CodeUnits as SCU
import Data.Tuple (Tuple(..), fst)
import Data.Tuple (Tuple(..), fst, snd)
import Dodo as Dodo
import Partial.Unsafe (unsafeCrashWith)
import PureScript.CST.Errors (RecoveredError(..))
import PureScript.CST.Types (Binder(..), ClassFundep(..), ClassHead, Comment(..), DataCtor(..), DataHead, DataMembers(..), Declaration(..), Delimited, DelimitedNonEmpty, DoStatement(..), Export(..), Expr(..), FixityOp(..), Foreign(..), Guarded(..), GuardedExpr(..), Ident, IfThenElse, Import(..), ImportDecl(..), Instance(..), InstanceBinding(..), InstanceHead, Label, Labeled(..), LetBinding(..), LineFeed, Module(..), ModuleBody(..), ModuleHeader(..), Name(..), OneOrDelimited(..), Operator, PatternGuard(..), Proper, QualifiedName(..), RecordLabeled(..), RecordUpdate(..), Row(..), Separated(..), SourceStyle(..), SourceToken, Token(..), Type(..), TypeVarBinding(..), ValueBindingFields, Where(..), Wrapped(..))
import PureScript.CST.Types (Binder(..), ClassFundep(..), ClassHead, Comment(..), DataCtor(..), DataHead, DataMembers(..), Declaration(..), Delimited, DelimitedNonEmpty, DoStatement(..), Export(..), Expr(..), FixityOp(..), Foreign(..), Guarded(..), GuardedExpr(..), Ident, IfThenElse, Import(..), ImportDecl(..), Instance(..), InstanceBinding(..), InstanceHead, Label, Labeled(..), LetBinding(..), LineFeed, Module(..), ModuleBody(..), ModuleHeader(..), ModuleName, Name(..), OneOrDelimited(..), Operator, PatternGuard(..), Proper, QualifiedName(..), RecordLabeled(..), RecordUpdate(..), Row(..), Separated(..), SourceStyle(..), SourceToken, Token(..), Type(..), TypeVarBinding(..), ValueBindingFields, Where(..), Wrapped(..))
import Tidy.Doc (FormatDoc, align, alignCurrentColumn, anchor, break, flattenMax, flexDoubleBreak, flexGroup, flexSoftBreak, flexSpaceBreak, forceMinSourceBreaks, fromDoc, indent, joinWith, joinWithMap, leadingBlockComment, leadingLineComment, locally, softBreak, softSpace, sourceBreak, space, spaceBreak, text, trailingBlockComment, trailingLineComment)
import Tidy.Doc (FormatDoc, toDoc) as Exports
import Tidy.Doc as Doc
import Tidy.Hang (HangingDoc, HangingOp(..), hang, hangApp, hangBreak, hangOps, hangWithIndent)
import Tidy.Hang as Hang
import Tidy.Precedence (OperatorNamespace(..), OperatorTree(..), PrecedenceMap, QualifiedOperator(..), toOperatorTree)
@ -53,11 +57,18 @@ data ImportWrapOption
derive instance eqImportWrapOption :: Eq ImportWrapOption
data ImportSortOption
= ImportSortSource
| ImportSortIde
derive instance eqImportSortOpion :: Eq ImportSortOption
type FormatOptions e a =
{ formatError :: e -> FormatDoc a
, unicode :: UnicodeOption
, typeArrowPlacement :: TypeArrowOption
, operators :: PrecedenceMap
, importSort :: ImportSortOption
, importWrap :: ImportWrapOption
}
@ -67,6 +78,7 @@ defaultFormatOptions =
, unicode: UnicodeSource
, typeArrowPlacement: TypeArrowFirst
, operators: Map.empty
, importSort: ImportSortSource
, importWrap: ImportWrapSource
}
@ -203,8 +215,55 @@ formatModule conf (Module { header: ModuleHeader header, body: ModuleBody body }
, foldr (formatComment leadingLineComment leadingBlockComment) mempty body.trailingComments
]
where
formatImports k =
joinWithMap break (k <<< formatImportDecl conf)
imports =
joinWithMap break (formatImportDecl conf) header.imports
case conf.importSort of
ImportSortSource ->
formatImports identity header.imports
ImportSortIde -> do
let { init, rest } = Array.span importOpen sorted
formatImports Doc.flatten init
<> forceMinSourceBreaks 2 (formatImports Doc.flatten rest)
where
sorted =
Array.sortBy
( comparing (Down <<< importOpen)
<> comparing importModuleName
<> comparing importQualified
)
header.imports
importOpen (ImportDecl a) = case a.qualified, a.names of
Nothing, Nothing ->
true
Nothing, Just (Tuple (Just _) _) ->
true
_, _ ->
false
importModuleName (ImportDecl { module: Name { name } }) =
name
importQualified (ImportDecl a) = case a.qualified of
Nothing ->
ImportExplicit Nothing
Just (Tuple _ (Name { name })) ->
case a.names of
Just (Tuple Nothing _) ->
ImportExplicit (Just name)
Just (Tuple (Just _) _) ->
ImportHiding name
_ -> ImportAll name
data ImportModuleComparison
= ImportExplicit (Maybe ModuleName)
| ImportAll ModuleName
| ImportHiding ModuleName
derive instance eqImportModuleComparison :: Eq ImportModuleComparison
derive instance ordImportModuleComparison :: Ord ImportModuleComparison
formatExport :: forall e a. Format (Export e) e a
formatExport conf = case _ of
@ -240,11 +299,11 @@ formatImportDecl conf (ImportDecl imp) =
Just (Tuple (Just hiding) nameList) ->
formatName conf imp."module"
`space` anchor (formatToken conf hiding)
`flexSpaceBreak` anchor (formatParenListNonEmpty NotGrouped formatImport conf nameList)
`flexSpaceBreak` anchor (formatParenListNonEmpty NotGrouped formatImport conf (sortImports nameList))
`space` anchor (foldMap formatImportQualified imp.qualified)
Just (Tuple Nothing nameList) ->
formatName conf imp."module"
`flexSpaceBreak` anchor (formatParenListNonEmpty NotGrouped formatImport conf nameList)
`flexSpaceBreak` anchor (formatParenListNonEmpty NotGrouped formatImport conf (sortImports nameList))
`space` anchor (foldMap formatImportQualified imp.qualified)
Nothing ->
formatName conf imp."module"
@ -253,6 +312,64 @@ formatImportDecl conf (ImportDecl imp) =
formatImportQualified (Tuple as qualName) =
formatToken conf as `space` anchor (formatName conf qualName)
sortImports imports@(Wrapped { open, value: Separated { head, tail }, close }) = case conf.importSort of
ImportSortSource ->
imports
ImportSortIde ->
Wrapped
{ open
, value: Separated
{ head: sorted.head
, tail: Array.zip commas sorted.tail
}
, close
}
where
Tuple commas tail' =
Array.unzip tail
sorted =
NonEmptyArray.cons' head tail'
# NonEmptyArray.sortBy (comparing toComparison)
# NonEmptyArray.uncons
toComparison = case _ of
ImportValue (Name { name }) ->
ImportValueCmp name
ImportOp (Name { name }) ->
ImportOpCmp name
ImportType (Name { name }) Nothing ->
ImportTypeCmp name
ImportType (Name { name }) (Just (DataEnumerated (Wrapped { value }))) ->
case value of
Nothing ->
ImportTypeEnumeratedCmp name []
Just (Separated ctors) ->
ImportTypeEnumeratedCmp name $ (_.name <<< un Name) <$> Array.cons ctors.head (map snd ctors.tail)
ImportType (Name { name }) (Just (DataAll _)) ->
ImportTypeAllCmp name
ImportTypeOp _ (Name { name }) ->
ImportTypeOpCmp name
ImportClass _ (Name { name }) ->
ImportClassCmp name
ImportKind _ (Name { name }) ->
ImportTypeCmp name
ImportError _ ->
ImportErrorCmp
data ImportComparison
= ImportClassCmp Proper
| ImportTypeOpCmp Operator
| ImportTypeAllCmp Proper
| ImportTypeCmp Proper
| ImportTypeEnumeratedCmp Proper (Array Proper)
| ImportValueCmp Ident
| ImportOpCmp Operator
| ImportErrorCmp
derive instance eqImportComparison :: Eq ImportComparison
derive instance ordImportComparison :: Ord ImportComparison
formatImport :: forall e a. Format (Import e) e a
formatImport conf = case _ of
ImportValue n ->

View File

@ -112,6 +112,7 @@ parseDirectivesFromModule (Module { header: ModuleHeader header, body }) =
, operators: default.operators
, unicode: opts.unicode
, typeArrowPlacement: opts.typeArrowPlacement
, importSort: opts.importSort
, importWrap: opts.importWrap
}
}

View File

@ -0,0 +1,38 @@
module ImportSortIde where
import Prim hiding (Type, Row)
import Data.Array (sortBy) as Z
import Data.Array (sortBy) as Array
import Data.Array (sortBy) as A
import Data.Array (sortBy)
import Data.Array hiding (sortBy) as Z
import Data.Array hiding (sortBy) as Array
import Data.Array hiding (sortBy) as A
import Data.Array as Z
import Data.Array as Array
import Data.Array as A
import Example (Test, type (+), Test(BBB, AAA), Test(..), Test(), test, class Test)
import Prelude
-- @format --import-sort-ide
module ImportSortIde where
import Prelude
import Prim hiding (Row, Type)
import Data.Array (sortBy)
import Data.Array (sortBy) as A
import Data.Array (sortBy) as Array
import Data.Array (sortBy) as Z
import Data.Array as A
import Data.Array as Array
import Data.Array as Z
import Data.Array hiding (sortBy) as A
import Data.Array hiding (sortBy) as Array
import Data.Array hiding (sortBy) as Z
import Example (class Test, type (+), Test(..), Test, Test(), Test(BBB, AAA), test)

View File

@ -0,0 +1,21 @@
-- @format --import-sort-ide
module ImportSortIde where
import Prim hiding (Type, Row)
import Data.Array (sortBy) as Z
import Data.Array (sortBy) as Array
import Data.Array (sortBy) as A
import Data.Array (sortBy)
import Data.Array hiding (sortBy) as Z
import Data.Array hiding (sortBy) as Array
import Data.Array hiding (sortBy) as A
import Data.Array as Z
import Data.Array as Array
import Data.Array as A
import Example (Test, type (+), Test(BBB, AAA), Test(..), Test(), test, class Test)
import Prelude

View File

@ -27,6 +27,7 @@
, "node-process"
, "node-workerbees"
, "ordered-collections"
, "orders"
, "partial"
, "posix-types"
, "prelude"