hsimport 0.4 → 0.5
raw patch · 28 files changed
+651/−159 lines, 28 filesdep +dyredep ~lensPVP ok
version bump matches the API change (PVP)
Dependencies added: dyre
Dependency ranges changed: lens
API changes (from Hackage documentation)
- HsImport: data HsImportArgs
- HsImport: hsImportArgs :: IO HsImportArgs
- HsImport.Main: hsImport :: ImportSpec -> IO ()
+ HsImport: After :: ImportDecl -> ImportPos
+ HsImport: Before :: ImportDecl -> ImportPos
+ HsImport: data ImportPos
+ HsImport.Config: Config :: (ImportDecl -> String) -> (ImportDecl -> [ImportDecl] -> Maybe ImportPos) -> Maybe String -> Config
+ HsImport.Config: configError :: Config -> Maybe String
+ HsImport.Config: data Config
+ HsImport.Config: defaultConfig :: Config
+ HsImport.Config: findImportPos :: Config -> ImportDecl -> [ImportDecl] -> Maybe ImportPos
+ HsImport.Config: prettyPrint :: Config -> ImportDecl -> String
+ HsImport.ImportPos: After :: ImportDecl -> ImportPos
+ HsImport.ImportPos: Before :: ImportDecl -> ImportPos
+ HsImport.ImportPos: bestMatchingImport :: ModuleName -> [ImportDecl] -> Maybe ImportDecl
+ HsImport.ImportPos: data ImportPos
+ HsImport.ImportPos: findImportPos :: ImportDecl -> [ImportDecl] -> Maybe ImportPos
+ HsImport.ImportPos: instance Eq ImportPos
+ HsImport.ImportPos: instance Show ImportPos
+ HsImport.ImportPos: matchingImports :: ModuleName -> [ImportDecl] -> [ImportDecl]
+ HsImport.Main: hsimport :: Config -> IO ()
+ HsImport.Main: hsimport_ :: Config -> ImportSpec -> IO ()
Files
- README.md +45/−0
- exe/Main.hs +1/−8
- hsimport.cabal +11/−4
- lib/HsImport.hs +5/−8
- lib/HsImport/Config.hs +27/−0
- lib/HsImport/ImportChange.hs +29/−112
- lib/HsImport/ImportPos.hs +71/−0
- lib/HsImport/Main.hs +56/−13
- lib/HsImport/Parse.hs +48/−11
- lib/HsImport/PrettyPrint.hs +16/−0
- lib/HsImport/Utils.hs +50/−0
- tests/Main.hs +53/−3
- tests/goldenFiles/ModuleTest27.hs +18/−0
- tests/goldenFiles/ModuleTest28.hs +18/−0
- tests/goldenFiles/ModuleTest29.hs +18/−0
- tests/goldenFiles/ModuleTest30.hs +18/−0
- tests/goldenFiles/SymbolTest26.hs +3/−0
- tests/goldenFiles/SymbolTest27.hs +15/−0
- tests/goldenFiles/SymbolTest28.hs +13/−0
- tests/goldenFiles/SymbolTest29.hs +17/−0
- tests/inputFiles/ModuleTest27.hs +17/−0
- tests/inputFiles/ModuleTest28.hs +17/−0
- tests/inputFiles/ModuleTest29.hs +17/−0
- tests/inputFiles/ModuleTest30.hs +17/−0
- tests/inputFiles/SymbolTest26.hs +3/−0
- tests/inputFiles/SymbolTest27.hs +16/−0
- tests/inputFiles/SymbolTest28.hs +16/−0
- tests/inputFiles/SymbolTest29.hs +16/−0
README.md view
@@ -38,6 +38,51 @@ $> hsimport -m 'Data.Maybe' -s 'Maybe' -w 'Just' -w 'Nothing' => import Data.Maybe (Maybe(Just, Nothing)) +Configuration+-------------++You can configure how the import declarations are pretty printed and where they're placed+by writing a configuration file like:++ -- ~/.config/hsimport/hsimport.hs+ import qualified Language.Haskell.Exts as HS+ import HsImport++ main :: IO ()+ main = hsimport $ defaultConfig { prettyPrint = prettyPrint, findImportPos = findImportPos }+ where+ -- This is a bogus implementation of prettyPrint, because it doesn't handle the+ -- qualified import case nor does it considers any explicitely imported or hidden symbols.+ prettyPrint :: HS.ImportDecl -> String+ prettyPrint (HS.ImportDecl { HS.importModule = HS.ModuleName modName }) =+ "import " ++ modName++ -- This findImportPos implementation will always add the new import declaration+ -- at the end of the current ones. The data type ImportPos has the two constructors+ -- After and Before.+ findImportPos :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe ImportPos+ findImportPos _ [] = Nothing+ findImportPos newImport currentImports = Just . After . last $ currentImports++The position of the configuration file depends on the result of `getUserConfigDir "hsimport"`,+which is a function from the package [xdg-basedir](<https://hackage.haskell.org/package/xdg-basedir>),+on linux like systems it is `~/.config/hsimport/hsimport.hs`.++If you've modified the configuration file, then the next call of `hsimport` will ensure a rebuild.+If you've installed `hsimport` with `cabal install`, without using a sandbox, then this should just work.++If you've build `hsimport` inside of a sandbox, then you most likely have to temporary modify the+`GHC_PACKAGE_PATH` for the next call of `hsimport`, to point `ghc` to the global database and+to the package database of the sandbox.++ # global package database+ $> export GLOBAL_PKG_DB=/usr/lib/ghc/package.conf.d/++ # hsimport sandbox package database+ $> export SANDBOX_PKG_DB=/home/you/hsimport-build-dir/.cabal-sandbox/*-packages.conf.d/++ $> GHC_PACKAGE_PATH=$GLOBAL_PKG_DB:$SANDBOX_PKG_DB hsimport --help+ Text Editor Integration -----------------------
exe/Main.hs view
@@ -1,14 +1,7 @@ module Main where -import System.Exit (exitFailure, exitSuccess)-import System.IO (hPutStrLn, stderr) import HsImport main :: IO ()-main = do- args <- hsImportArgs- maybeSpec <- hsImportSpec args- case maybeSpec of- Left error -> hPutStrLn stderr ("hsimport: " ++ error) >> exitFailure- Right spec -> hsImport spec >> exitSuccess+main = hsimport defaultConfig
hsimport.cabal view
@@ -1,5 +1,5 @@ name: hsimport-version: 0.4+version: 0.5 cabal-version: >=1.9.2 build-type: Simple license: BSD3@@ -25,18 +25,21 @@ base >=3 && <5, cmdargs >=0.10.5 && <0.11, haskell-src-exts >=1.14.0 && <1.16,- lens >=3.9.2 && <4.3,+ lens >=3.9.2 && <4.4, mtl >=2.1.2 && <2.3, text >=0.11.3.1 && <1.2, split >=0.2.2 && <0.3, attoparsec >=0.10.4.0 && <0.13,- directory >=1.2.0.1 && <1.3+ directory >=1.2.0.1 && <1.3,+ dyre ==0.8.* exposed-modules: HsImport+ HsImport.Main+ HsImport.Config HsImport.Args HsImport.ImportSpec HsImport.Symbol- HsImport.Main+ HsImport.ImportPos exposed: True buildable: True cpp-options: -DCABAL@@ -44,6 +47,8 @@ other-modules: HsImport.ImportChange HsImport.Parse+ HsImport.PrettyPrint+ HsImport.Utils Paths_hsimport ghc-options: -W @@ -55,6 +60,7 @@ buildable: True cpp-options: -DCABAL hs-source-dirs: exe+ ghc-prof-options: -prof -fprof-auto -rtsopts ghc-options: -W test-suite hsimport-tests@@ -63,6 +69,7 @@ tasty >=0.6 && <0.9, tasty-golden >=2.2.0.1 && <2.3, filepath >=1.3.0.1 && <1.4,+ haskell-src-exts >=1.14.0 && <1.16, hsimport -any type: exitcode-stdio-1.0 main-is: Main.hs
lib/HsImport.hs view
@@ -1,13 +1,10 @@ module HsImport- ( HsImportArgs- , hsImportArgs- , module HsImport.ImportSpec- , module HsImport.Symbol- , module HsImport.Main+ ( module HsImport.Main+ , module HsImport.Config+ , ImportPos(..) ) where -import HsImport.Args-import HsImport.ImportSpec-import HsImport.Symbol import HsImport.Main+import HsImport.Config+import HsImport.ImportPos (ImportPos(..))
+ lib/HsImport/Config.hs view
@@ -0,0 +1,27 @@++module HsImport.Config+ ( Config(..)+ , defaultConfig+ ) where++import qualified Language.Haskell.Exts as HS+import qualified HsImport.PrettyPrint as PP+import qualified HsImport.ImportPos as IP++-- | User definable configuration for hsImport.+data Config = Config+ { -- | function for pretty printing of the import declarations+ prettyPrint :: HS.ImportDecl -> String+ -- | function for finding the position of new import declarations+ , findImportPos :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe IP.ImportPos+ -- | error during configuration of hsimport+ , configError :: Maybe String+ }+++defaultConfig :: Config+defaultConfig = Config+ { prettyPrint = PP.prettyPrint+ , findImportPos = IP.findImportPos+ , configError = Nothing+ }
lib/HsImport/ImportChange.hs view
@@ -7,20 +7,21 @@ import Data.Maybe import Data.List (find, (\\))-import Data.List.Split (splitOn) import Control.Lens import qualified Language.Haskell.Exts as HS import qualified Data.Attoparsec.Text as A-import Data.Monoid (mconcat) import HsImport.Symbol (Symbol(..))+import HsImport.ImportPos (matchingImports)+import HsImport.Utils -type SrcLine = Int-type ImportString = String+type SrcLine = Int -data ImportChange = ReplaceImportAt SrcLine ImportString- | AddImportAfter SrcLine ImportString- | AddImportAtEnd ImportString- | NoImportChange+-- | How the import declarations should be changed+data ImportChange = ReplaceImportAt SrcLine HS.ImportDecl -- ^ replace the import declaration at SrcLine+ | AddImportAfter SrcLine HS.ImportDecl -- ^ add import declaration after SrcLine+ | AddImportAtEnd HS.ImportDecl -- ^ add import declaration at end of source file+ | FindImportPos HS.ImportDecl -- ^ search for an insert position for the import declaration+ | NoImportChange -- ^ no changes of the import declarations deriving (Show) @@ -42,105 +43,58 @@ importModule :: String -> HS.Module -> ImportChange importModule moduleName module_- | matching@(_:_) <- matchingImports moduleName module_ =+ | matching@(_:_) <- matchingImports moduleName (importDecls module_) = if any entireModuleImported matching then NoImportChange- else AddImportAfter (srcLine . last $ matching) (HS.prettyPrint $ importDecl moduleName)+ else FindImportPos $ importDecl moduleName - | Just bestMatch <- bestMatchingImport moduleName module_ =- AddImportAfter (srcLine bestMatch) (HS.prettyPrint $ importDecl moduleName)+ | not $ null (importDecls module_) =+ FindImportPos $ importDecl moduleName | otherwise = case srcLineForNewImport module_ of- Just srcLine -> AddImportAfter srcLine (HS.prettyPrint $ importDecl moduleName)- Nothing -> AddImportAtEnd (HS.prettyPrint $ importDecl moduleName)+ Just srcLine -> AddImportAfter srcLine (importDecl moduleName)+ Nothing -> AddImportAtEnd (importDecl moduleName) importModuleWithSymbol :: String -> Symbol -> HS.Module -> ImportChange importModuleWithSymbol moduleName symbol module_- | matching@(_:_) <- matchingImports moduleName module_ =+ | matching@(_:_) <- matchingImports moduleName (importDecls module_) = if any entireModuleImported matching || any (symbolImported symbol) matching then NoImportChange else case find hasImportedSymbols matching of Just impDecl ->- ReplaceImportAt (srcLine impDecl) (prettyPrint $ addSymbol impDecl symbol)+ ReplaceImportAt (srcLine impDecl) (addSymbol impDecl symbol) Nothing ->- AddImportAfter (srcLine . last $ matching)- (prettyPrint $ importDeclWithSymbol moduleName symbol)+ FindImportPos $ importDeclWithSymbol moduleName symbol - | Just bestMatch <- bestMatchingImport moduleName module_ =- AddImportAfter (srcLine bestMatch) (prettyPrint $ importDeclWithSymbol moduleName symbol)+ | not $ null (importDecls module_) =+ FindImportPos $ importDeclWithSymbol moduleName symbol | otherwise = case srcLineForNewImport module_ of- Just srcLine -> AddImportAfter srcLine (prettyPrint $ importDeclWithSymbol moduleName symbol)- Nothing -> AddImportAtEnd (prettyPrint $ importDeclWithSymbol moduleName symbol)+ Just srcLine -> AddImportAfter srcLine (importDeclWithSymbol moduleName symbol)+ Nothing -> AddImportAtEnd (importDeclWithSymbol moduleName symbol) where addSymbol (id@HS.ImportDecl {HS.importSpecs = specs}) symbol = id {HS.importSpecs = specs & _Just . _2 %~ (++ [importSpec symbol])} - prettyPrint importDecl =- -- remove newlines from pretty printed ImportDecl- case lines $ HS.prettyPrint importDecl of- (fst : [] ) -> fst- (fst : rest) -> mconcat $ fst : (map (' ' :) . map (dropWhile (== ' ')) $ rest)- _ -> "" - importQualifiedModule :: String -> String -> HS.Module -> ImportChange importQualifiedModule moduleName qualifiedName module_- | matching@(_:_) <- matchingImports moduleName module_ =+ | matching@(_:_) <- matchingImports moduleName (importDecls module_) = if any (hasQualifiedImport qualifiedName) matching then NoImportChange- else AddImportAfter (srcLine . last $ matching) (HS.prettyPrint $ qualifiedImportDecl moduleName qualifiedName)+ else FindImportPos $ qualifiedImportDecl moduleName qualifiedName - | Just bestMatch <- bestMatchingImport moduleName module_ =- AddImportAfter (srcLine bestMatch) (HS.prettyPrint $ qualifiedImportDecl moduleName qualifiedName)+ | not $ null (importDecls module_) =+ FindImportPos $ qualifiedImportDecl moduleName qualifiedName | otherwise = case srcLineForNewImport module_ of- Just srcLine -> AddImportAfter srcLine (HS.prettyPrint $ qualifiedImportDecl moduleName qualifiedName)- Nothing -> AddImportAtEnd (HS.prettyPrint $ qualifiedImportDecl moduleName qualifiedName)---matchingImports :: String -> HS.Module -> [HS.ImportDecl]-matchingImports moduleName (HS.Module _ _ _ _ _ imports _) =- [ i - | i@HS.ImportDecl {HS.importModule = HS.ModuleName name} <- imports- , moduleName == name- ] ---bestMatchingImport :: String -> HS.Module -> Maybe HS.ImportDecl-bestMatchingImport moduleName (HS.Module _ _ _ _ _ imports _) =- case ifoldl' computeMatches Nothing splittedMods of- Just (idx, _) -> Just $ imports !! idx- _ -> Nothing- where- computeMatches :: Int -> Maybe (Int, Int) -> [String] -> Maybe (Int, Int)- computeMatches idx matches mod =- let num' = numMatches splittedMod mod- in case matches of- Just (_, num) | num' >= num -> Just (idx, num')- | otherwise -> matches-- Nothing | num' > 0 -> Just (idx, num')- | otherwise -> Nothing- where- numMatches = loop 0- where- loop num (a:as) (b:bs)- | a == b = loop (num + 1) as bs- | otherwise = num-- loop num [] _ = num- loop num _ [] = num-- splittedMod = splitOn "." moduleName- splittedMods = [ splitOn "." name - | HS.ImportDecl {HS.importModule = HS.ModuleName name} <- imports- ] + Just srcLine -> AddImportAfter srcLine (qualifiedImportDecl moduleName qualifiedName)+ Nothing -> AddImportAtEnd (qualifiedImportDecl moduleName qualifiedName) entireModuleImported :: HS.ImportDecl -> Bool@@ -164,7 +118,7 @@ , any (imports symbol) hsSymbols = True - | otherwise + | otherwise = False where imports (Symbol symName) (HS.IVar name) = symName == nameString name@@ -241,40 +195,3 @@ = Just $ max 0 (HS.srcLine sLoc - 1) | otherwise = Nothing---srcLine :: HS.ImportDecl -> SrcLine-srcLine = HS.srcLine . HS.importLoc---declSrcLoc :: HS.Decl -> Maybe HS.SrcLoc-declSrcLoc decl =- case decl of- HS.TypeDecl srcLoc _ _ _ -> Just srcLoc- HS.TypeFamDecl srcLoc _ _ _ -> Just srcLoc- HS.DataDecl srcLoc _ _ _ _ _ _ -> Just srcLoc- HS.GDataDecl srcLoc _ _ _ _ _ _ _ -> Just srcLoc- HS.DataFamDecl srcLoc _ _ _ _ -> Just srcLoc- HS.TypeInsDecl srcLoc _ _ -> Just srcLoc- HS.DataInsDecl srcLoc _ _ _ _ -> Just srcLoc- HS.GDataInsDecl srcLoc _ _ _ _ _ -> Just srcLoc- HS.ClassDecl srcLoc _ _ _ _ _ -> Just srcLoc- HS.InstDecl srcLoc _ _ _ _ -> Just srcLoc- HS.DerivDecl srcLoc _ _ _ -> Just srcLoc- HS.InfixDecl srcLoc _ _ _ -> Just srcLoc- HS.DefaultDecl srcLoc _ -> Just srcLoc- HS.SpliceDecl srcLoc _ -> Just srcLoc- HS.TypeSig srcLoc _ _ -> Just srcLoc- HS.FunBind _ -> Nothing- HS.PatBind srcLoc _ _ _ _ -> Just srcLoc- HS.ForImp srcLoc _ _ _ _ _ -> Just srcLoc- HS.ForExp srcLoc _ _ _ _ -> Just srcLoc- HS.RulePragmaDecl srcLoc _ -> Just srcLoc- HS.DeprPragmaDecl srcLoc _ -> Just srcLoc- HS.WarnPragmaDecl srcLoc _ -> Just srcLoc- HS.InlineSig srcLoc _ _ _ -> Just srcLoc- HS.InlineConlikeSig srcLoc _ _ -> Just srcLoc- HS.SpecSig srcLoc _ _ _ -> Just srcLoc- HS.SpecInlineSig srcLoc _ _ _ _ -> Just srcLoc- HS.InstSig srcLoc _ _ _ -> Just srcLoc- HS.AnnPragma srcLoc _ -> Just srcLoc
+ lib/HsImport/ImportPos.hs view
@@ -0,0 +1,71 @@++module HsImport.ImportPos+ ( findImportPos+ , ImportPos(..)+ , matchingImports+ , bestMatchingImport+ ) where++import qualified Language.Haskell.Exts as HS+import Data.List.Split (splitOn)+import Control.Lens+import Control.Applicative ((<$>))++type ModuleName = String++-- | Where a new import declaration should be added.+data ImportPos = Before HS.ImportDecl -- ^ before the specified import declaration+ | After HS.ImportDecl -- ^ after the specified import declaration+ deriving (Show, Eq)+++-- | Returns the position where the import declaration for the+-- new import should be put into the list of import declarations.+findImportPos :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe ImportPos+findImportPos newImport imports = After <$> bestMatchingImport name imports+ where+ HS.ModuleName name = HS.importModule newImport+++-- | Returns all import declarations having the same module name.+matchingImports :: ModuleName -> [HS.ImportDecl] -> [HS.ImportDecl]+matchingImports moduleName imports =+ [ i+ | i@HS.ImportDecl {HS.importModule = HS.ModuleName name} <- imports+ , moduleName == name+ ]+++-- | Returns the best matching import declaration for the given module name.+-- E.g. if the module name is "Foo.Bar.Boo", then "Foo.Bar" is considered+-- better matching than "Foo".+bestMatchingImport :: ModuleName -> [HS.ImportDecl] -> Maybe HS.ImportDecl+bestMatchingImport _ [] = Nothing+bestMatchingImport moduleName imports =+ case ifoldl' computeMatches Nothing splittedMods of+ Just (idx, _) -> Just $ imports !! idx+ _ -> Nothing+ where+ computeMatches :: Int -> Maybe (Int, Int) -> [String] -> Maybe (Int, Int)+ computeMatches idx matches mod =+ let num' = numMatches splittedMod mod+ in case matches of+ Just (_, num) | num' >= num -> Just (idx, num')+ | otherwise -> matches++ Nothing | num' > 0 -> Just (idx, num')+ | otherwise -> Nothing+ where+ numMatches = loop 0+ where+ loop num (a:as) (b:bs)+ | a == b = loop (num + 1) as bs+ | otherwise = num++ loop num [] _ = num+ loop num _ [] = num++ splittedMod = splitOn "." moduleName+ splittedMods = [ splitOn "." name+ | HS.ImportDecl {HS.importModule = HS.ModuleName name} <- imports+ ]
lib/HsImport/Main.hs view
@@ -1,22 +1,50 @@ {-# Language PatternGuards #-} module HsImport.Main- ( hsImport+ ( hsimport+ , hsimport_ ) where import Control.Lens import Control.Applicative ((<$>)) import Control.Monad (when)+import System.Exit (exitFailure, exitSuccess)+import System.IO (hPutStrLn, stderr) import Data.Maybe (isJust) import Data.List (foldl') import qualified Data.Text as T import qualified Data.Text.IO as TIO+import qualified Config.Dyre as Dyre import HsImport.ImportChange import HsImport.ImportSpec+import HsImport.ImportPos (ImportPos(..))+import qualified HsImport.Args as Args+import HsImport.Config+import HsImport.Utils+import qualified HsImport.Parse as P -hsImport :: ImportSpec -> IO ()-hsImport spec = do+hsimport = Dyre.wrapMain $ Dyre.defaultParams+ { Dyre.projectName = "hsimport"+ , Dyre.realMain = realMain+ , Dyre.showError = \config err -> config { configError = Just err }+ }+ where+ realMain :: Config -> IO ()+ realMain config = do+ case configError config of+ Just error -> hPutStrLn stderr ("hsimport: " ++ error) >> exitFailure+ _ -> return ()++ args <- Args.hsImportArgs+ maybeSpec <- hsImportSpec args+ case maybeSpec of+ Left error -> hPutStrLn stderr ("hsimport: " ++ error) >> exitFailure+ Right spec -> hsimport_ config spec >> exitSuccess+++hsimport_ :: Config -> ImportSpec -> IO ()+hsimport_ Config { prettyPrint = prettyPrint, findImportPos = findImportPos } spec = do let impChanges = importChanges (spec ^. moduleToImport) (spec ^. symbolToImport) (spec ^. qualifiedName)@@ -30,21 +58,36 @@ where applyChanges = foldl' applyChange - applyChange srcLines (ReplaceImportAt srcLine importStr) =- let numDrops = srcLine- numTakes = max 0 (numDrops - 1)- in take numTakes srcLines ++ [importStr] ++ drop numDrops srcLines+ applyChange srcLines (ReplaceImportAt srcLine importDecl) =+ let numTakes = max 0 (srcLine - 1)+ numDrops = lastImportSrcLine srcLine srcLines+ in take numTakes srcLines ++ [prettyPrint importDecl] ++ drop numDrops srcLines - applyChange srcLines (AddImportAfter srcLine importStr) =- let numTakes = srcLine- numDrops = numTakes- in take numTakes srcLines ++ [importStr] ++ drop numDrops srcLines+ applyChange srcLines (AddImportAfter srcLine importDecl) =+ let numTakes = lastImportSrcLine srcLine srcLines+ numDrops = numTakes+ in take numTakes srcLines ++ [prettyPrint importDecl] ++ drop numDrops srcLines - applyChange srcLines (AddImportAtEnd importStr) =- srcLines ++ [importStr]+ applyChange srcLines (AddImportAtEnd importDecl) =+ srcLines ++ [prettyPrint importDecl] + applyChange srcLines (FindImportPos importDecl) =+ case findImportPos importDecl allImportDecls of+ Just (After impDecl) -> applyChange srcLines (AddImportAfter (srcLine impDecl) importDecl)+ Just (Before impDecl) -> applyChange srcLines (AddImportAfter (max 0 (srcLine impDecl - 1)) importDecl)+ _ -> applyChange srcLines (AddImportAfter (srcLine . last $ allImportDecls) importDecl)+ applyChange srcLines NoImportChange = srcLines outputFile spec | Just file <- spec ^. saveToFile = file | otherwise = spec ^. sourceFile++ lastImportSrcLine fstLine srcLines+ | Just lastLine <- P.lastImportSrcLine $ drop (max 0 (fstLine - 1)) srcLines+ = fstLine + (lastLine - 1)++ | otherwise+ = fstLine++ allImportDecls = importDecls $ spec ^. parsedSrcFile
lib/HsImport/Parse.hs view
@@ -2,6 +2,7 @@ module HsImport.Parse ( parseFile+ , lastImportSrcLine ) where import qualified Data.Text.IO as TIO@@ -22,12 +23,12 @@ HS.ParseOk _ -> return $ Right result HS.ParseFailed srcLoc _ -> do- srcResult <- parseInvalidSource (lines srcFile) 0 (HS.srcLine srcLoc)+ srcResult <- parseInvalidSource (lines srcFile) (HS.srcLine srcLoc) 0 (HS.srcLine srcLoc) return $ Right $ fromMaybe result srcResult) (\(e :: SomeException) -> do let srcLines = lines srcFile- srcResult <- parseInvalidSource srcLines 0 (length srcLines)+ srcResult <- parseInvalidSource srcLines (length srcLines) 0 (length srcLines) return $ maybe (Left $ show e) Right srcResult) where -- | replace CPP directives by a fake comment@@ -37,25 +38,61 @@ else line +type SrcLine = Int++-- | Expects that '[String]' starts with an import declaration and returns+-- the last source line of the import declaration, so this function+-- is for the handling of multine line import declarations.+lastImportSrcLine :: [String] -> Maybe SrcLine+lastImportSrcLine srcLines+ | null srcLines+ = Nothing++ | "import" `isPrefixOf` head srcLines+ = parseImport 1++ | otherwise+ = Nothing++ where+ parseImport lastLine+ | lastLine <= numSrcLines+ = case parseFileContents source of+ HS.ParseOk _ -> Just lastLine+ HS.ParseFailed _ _ -> parseImport (lastLine + 1)++ | otherwise+ = Nothing++ where+ source = unlines $ take lastLine srcLines++ numSrcLines = length srcLines++ -- | tries to find the maximal part of the source file (from the beginning) that contains -- valid/complete Haskell code-parseInvalidSource :: [String] -> Int -> Int -> IO (Maybe (HS.ParseResult HS.Module))-parseInvalidSource srcLines lastValidLine firstInvalidLine- | null srcLines || lastValidLine >= firstInvalidLine = return Nothing+parseInvalidSource :: [String] -> Int -> Int -> Int -> IO (Maybe (HS.ParseResult HS.Module))+parseInvalidSource srcLines firstInvalidLine lastValidLine currLastLine+ | null srcLines || lastValidLine >= currLastLine = return Nothing | otherwise = catch (case parseFileContents source of result@(HS.ParseOk _)- | (nextLine + 1) == firstInvalidLine ->+ | (nextLine + 1) == currLastLine -> return $ Just result- | otherwise ->- parseInvalidSource srcLines nextLine firstInvalidLine+ | otherwise ->+ parseInvalidSource srcLines firstInvalidLine nextLine currLastLine - HS.ParseFailed _ _ -> parseInvalidSource srcLines lastValidLine nextLine)+ HS.ParseFailed srcLoc _+ | HS.srcLine srcLoc == firstInvalidLine ->+ parseInvalidSource srcLines firstInvalidLine lastValidLine nextLine+ | otherwise ->+ parseInvalidSource srcLines firstInvalidLine nextLine currLastLine) - (\(_ :: SomeException) -> parseInvalidSource srcLines lastValidLine nextLine)+ (\(_ :: SomeException) -> parseInvalidSource srcLines firstInvalidLine lastValidLine nextLine) where source = unlines $ take (nextLine + 1) srcLines- nextLine = lastValidLine + (floor ((realToFrac (firstInvalidLine - lastValidLine) / 2) :: Double) :: Int)+ nextLine = lastValidLine + (floor ((realToFrac (currLastLine - lastValidLine) / 2) :: Double) :: Int) parseFileContents :: String -> HS.ParseResult HS.Module
+ lib/HsImport/PrettyPrint.hs view
@@ -0,0 +1,16 @@++module HsImport.PrettyPrint+ ( prettyPrint+ ) where++import Data.Monoid (mconcat)+import qualified Language.Haskell.Exts as HS+++prettyPrint :: HS.ImportDecl -> String+prettyPrint importDecl =+ -- remove newlines from pretty printed ImportDecl+ case lines $ HS.prettyPrint importDecl of+ (fst : [] ) -> fst+ (fst : rest) -> mconcat $ fst : (map (' ' :) . map (dropWhile (== ' ')) $ rest)+ _ -> ""
+ lib/HsImport/Utils.hs view
@@ -0,0 +1,50 @@++module HsImport.Utils+ ( srcLine+ , declSrcLoc+ , importDecls+ ) where++import qualified Language.Haskell.Exts as HS++type SrcLine = Int++srcLine :: HS.ImportDecl -> SrcLine+srcLine = HS.srcLine . HS.importLoc+++declSrcLoc :: HS.Decl -> Maybe HS.SrcLoc+declSrcLoc decl =+ case decl of+ HS.TypeDecl srcLoc _ _ _ -> Just srcLoc+ HS.TypeFamDecl srcLoc _ _ _ -> Just srcLoc+ HS.DataDecl srcLoc _ _ _ _ _ _ -> Just srcLoc+ HS.GDataDecl srcLoc _ _ _ _ _ _ _ -> Just srcLoc+ HS.DataFamDecl srcLoc _ _ _ _ -> Just srcLoc+ HS.TypeInsDecl srcLoc _ _ -> Just srcLoc+ HS.DataInsDecl srcLoc _ _ _ _ -> Just srcLoc+ HS.GDataInsDecl srcLoc _ _ _ _ _ -> Just srcLoc+ HS.ClassDecl srcLoc _ _ _ _ _ -> Just srcLoc+ HS.InstDecl srcLoc _ _ _ _ -> Just srcLoc+ HS.DerivDecl srcLoc _ _ _ -> Just srcLoc+ HS.InfixDecl srcLoc _ _ _ -> Just srcLoc+ HS.DefaultDecl srcLoc _ -> Just srcLoc+ HS.SpliceDecl srcLoc _ -> Just srcLoc+ HS.TypeSig srcLoc _ _ -> Just srcLoc+ HS.FunBind _ -> Nothing+ HS.PatBind srcLoc _ _ _ _ -> Just srcLoc+ HS.ForImp srcLoc _ _ _ _ _ -> Just srcLoc+ HS.ForExp srcLoc _ _ _ _ -> Just srcLoc+ HS.RulePragmaDecl srcLoc _ -> Just srcLoc+ HS.DeprPragmaDecl srcLoc _ -> Just srcLoc+ HS.WarnPragmaDecl srcLoc _ -> Just srcLoc+ HS.InlineSig srcLoc _ _ _ -> Just srcLoc+ HS.InlineConlikeSig srcLoc _ _ -> Just srcLoc+ HS.SpecSig srcLoc _ _ _ -> Just srcLoc+ HS.SpecInlineSig srcLoc _ _ _ _ -> Just srcLoc+ HS.InstSig srcLoc _ _ _ -> Just srcLoc+ HS.AnnPragma srcLoc _ -> Just srcLoc+++importDecls :: HS.Module -> [HS.ImportDecl]+importDecls (HS.Module _ _ _ _ _ imports _) = imports
tests/Main.hs view
@@ -5,8 +5,13 @@ import Test.Tasty.Golden import System.FilePath import System.IO (hPutStrLn, stderr)-import HsImport (hsImport, hsImportSpec)+import Data.List (intercalate)+import qualified Language.Haskell.Exts as HS+import HsImport.Main (hsimport_)+import HsImport.ImportSpec (hsImportSpec)+import qualified HsImport.Config as C import qualified HsImport.Args as A+import qualified HsImport.ImportPos as P main = defaultMain tests @@ -42,6 +47,10 @@ , test "ModuleTest24" $ A.defaultArgs { A.moduleName = "Control.Monad", A.qualifiedName = "CM" } , test "ModuleTest25" $ A.defaultArgs { A.moduleName = "Control.Monad", A.qualifiedName = "Control.Monad" } , test "ModuleTest26" $ A.defaultArgs { A.moduleName = "Control.Monad" }+ , test_ "ModuleTest27" (C.defaultConfig { C.findImportPos = importPosBeforeFirst }) (A.defaultArgs { A.moduleName = "Control.Monad" })+ , test_ "ModuleTest28" (C.defaultConfig { C.findImportPos = importPosAfterFirst }) (A.defaultArgs { A.moduleName = "Control.Monad" })+ , test_ "ModuleTest29" (C.defaultConfig { C.findImportPos = importPosBeforeLast }) (A.defaultArgs { A.moduleName = "Control.Monad" })+ , test_ "ModuleTest30" (C.defaultConfig { C.findImportPos = importPosAfterLast }) (A.defaultArgs { A.moduleName = "Control.Monad" }) ] @@ -73,21 +82,62 @@ , test "SymbolTest23" $ A.defaultArgs { A.moduleName = "Data.Maybe", A.symbolName = "Maybe", A.all = True, A.with = ["Just"] } , test "SymbolTest24" $ A.defaultArgs { A.moduleName = "Data.Maybe", A.symbolName = "Maybe", A.with = ["Just"] } , test "SymbolTest25" $ A.defaultArgs { A.moduleName = "Data.Maybe", A.symbolName = "Maybe", A.with = ["Nothing", "Just"] }+ , test "SymbolTest26" $ A.defaultArgs { A.moduleName = "Foo", A.symbolName = "bar" }+ , test "SymbolTest27" $ A.defaultArgs { A.moduleName = "Ugah.Blub", A.symbolName = "g" }+ , test "SymbolTest28" $ A.defaultArgs { A.moduleName = "Ugah.Blub", A.symbolName = "d" }+ , test_ "SymbolTest29" (C.defaultConfig { C.prettyPrint = prettyPrint }) (A.defaultArgs { A.moduleName = "X.Y", A.symbolName = "x" }) ] test :: String -> A.HsImportArgs -> TestTree-test testName args =+test testName args = test_ testName C.defaultConfig args+++test_ :: String -> C.Config -> A.HsImportArgs -> TestTree+test_ testName config args = goldenVsFileDiff testName diff goldenFile outputFile command where command = do spec <- hsImportSpec (args { A.inputSrcFile = inputFile, A.outputSrcFile = outputFile }) case spec of Left error -> hPutStrLn stderr ("hsimport: " ++ error)- Right spec_ -> hsImport spec_+ Right spec_ -> hsimport_ config spec_ diff ref new = ["diff", "-u", ref, new] goldenFile = "tests" </> "goldenFiles" </> testName <.> "hs" outputFile = "tests" </> "outputFiles" </> testName <.> "hs" inputFile = "tests" </> "inputFiles" </> testName <.> "hs"+++prettyPrint :: HS.ImportDecl -> String+prettyPrint HS.ImportDecl { HS.importModule = HS.ModuleName modName, HS.importSpecs = Just (False, syms) } =+ "import " ++ modName ++ " ( " ++ ppSyms ++ " )"+ where+ ppSyms = intercalate " , " symNames+ symNames = map symName syms++ symName (HS.IVar (HS.Ident name)) = name+ symName _ = ""++prettyPrint _ = "Uupps"+++importPosAfterLast :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe P.ImportPos+importPosAfterLast _ [] = Nothing+importPosAfterLast _ imports = Just . P.After . last $ imports+++importPosBeforeLast :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe P.ImportPos+importPosBeforeLast _ [] = Nothing+importPosBeforeLast _ imports = Just . P.Before . last $ imports+++importPosAfterFirst :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe P.ImportPos+importPosAfterFirst _ [] = Nothing+importPosAfterFirst _ imports = Just . P.After . head $ imports+++importPosBeforeFirst :: HS.ImportDecl -> [HS.ImportDecl] -> Maybe P.ImportPos+importPosBeforeFirst _ [] = Nothing+importPosBeforeFirst _ imports = Just . P.Before . head $ imports
+ tests/goldenFiles/ModuleTest27.hs view
@@ -0,0 +1,18 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Control.Monad+import Ugah.Foo ( a+ , b+ )+import Control.Applicative+import Ugah.Blub+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/goldenFiles/ModuleTest28.hs view
@@ -0,0 +1,18 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo ( a+ , b+ )+import Control.Monad+import Control.Applicative+import Ugah.Blub+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/goldenFiles/ModuleTest29.hs view
@@ -0,0 +1,18 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo+import Control.Applicative ( a+ , b+ )+import Control.Monad+import Ugah.Blub+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/goldenFiles/ModuleTest30.hs view
@@ -0,0 +1,18 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo+import Control.Applicative+import Ugah.Blub ( a+ , b+ )+import Control.Monad+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/goldenFiles/SymbolTest26.hs view
@@ -0,0 +1,3 @@+import Foo (foo, bar)+boo :: Int+boo = 3
+ tests/goldenFiles/SymbolTest27.hs view
@@ -0,0 +1,15 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo+import Control.Applicative+import Ugah.Blub (a, b, c, d, e, f, g)+f :: Int -> Int+f = (+ 3)++g :: Int+g =+ where
+ tests/goldenFiles/SymbolTest28.hs view
@@ -0,0 +1,13 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Control.Applicative+import Ugah.Blub (a, b, c, d)+f :: Int -> Int+f = (+ 3)++r :: Int -> Int+r =
+ tests/goldenFiles/SymbolTest29.hs view
@@ -0,0 +1,17 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Control.Applicative+import Ugah.Blub ( a+ , b+ , c+ )+import X.Y ( x )+f :: Int -> Int+f = (+ 3)++r :: Int -> Int+r =
+ tests/inputFiles/ModuleTest27.hs view
@@ -0,0 +1,17 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo ( a+ , b+ )+import Control.Applicative+import Ugah.Blub+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/inputFiles/ModuleTest28.hs view
@@ -0,0 +1,17 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo ( a+ , b+ )+import Control.Applicative+import Ugah.Blub+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/inputFiles/ModuleTest29.hs view
@@ -0,0 +1,17 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo+import Control.Applicative ( a+ , b+ )+import Ugah.Blub+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/inputFiles/ModuleTest30.hs view
@@ -0,0 +1,17 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo+import Control.Applicative+import Ugah.Blub ( a+ , b+ )+f :: Int -> Int+f = (+ 3)++g :: Int -> Int+g =+ where
+ tests/inputFiles/SymbolTest26.hs view
@@ -0,0 +1,3 @@+import Foo (foo)+boo :: Int+boo = 3
+ tests/inputFiles/SymbolTest27.hs view
@@ -0,0 +1,16 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Ugah.Foo+import Control.Applicative+import Ugah.Blub (a, b, c,+ d, e, f)+f :: Int -> Int+f = (+ 3)++g :: Int+g =+ where
+ tests/inputFiles/SymbolTest28.hs view
@@ -0,0 +1,16 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Control.Applicative+import Ugah.Blub ( a+ , b+ , c+ )+f :: Int -> Int+f = (+ 3)++r :: Int -> Int+r =
+ tests/inputFiles/SymbolTest29.hs view
@@ -0,0 +1,16 @@+{-# Language PatternGuards #-}+module Blub+ ( blub+ , foo+ , bar+ ) where+import Control.Applicative+import Ugah.Blub ( a+ , b+ , c+ )+f :: Int -> Int+f = (+ 3)++r :: Int -> Int+r =