packages feed

fix-imports 0.1.1 → 0.1.2

raw patch · 3 files changed

+71/−71 lines, 3 files

Files

fix-imports.cabal view
@@ -1,5 +1,5 @@ name: fix-imports-version: 0.1.1+version: 0.1.2 cabal-version: >= 1.6 build-type: Simple synopsis: Program to manage the imports of a haskell module
src/FixImports.hs view
@@ -34,9 +34,6 @@         imports, and groups them by their toplevel module name (before the         first dot).  Small groups are combined.  They go in alphabetical order         by default, but a per-project order may be defined.--    - If there are no imports to be added or removed, the file is returned-    unchanged.  This means it won't sort the imports in this case. -} module FixImports where import Prelude hiding (mod)@@ -44,18 +41,18 @@ import qualified Control.Monad as Monad import qualified Data.Either as Either import qualified Data.Generics.Uniplate.Data as Uniplate+import qualified Data.List as List import qualified Data.Map as Map import qualified Data.Maybe as Maybe import qualified Data.Set as Set -import qualified Language.Preprocessor.Cpphs as Cpphs import qualified Language.Haskell.Exts.Annotated as Haskell-+import qualified Language.Preprocessor.Cpphs as Cpphs import qualified System.Directory as Directory import qualified System.Environment import qualified System.Exit import qualified System.FilePath as FilePath-import System.FilePath ( (</>) )+import System.FilePath ((</>)) import qualified System.IO as IO import qualified System.Process as Process @@ -150,39 +147,35 @@         , Cpphs.warnings = False         } --- | Take a parsed module along with its unparsed text.  If the imports should--- change, generate a new import block with proper spacing, formatting, and--- comments.  Then snip out the import block on the import file, and replace--- it.  Otherwise, the unparsed input module is returned unchanged.+-- | Take a parsed module along with its unparsed text.  Generate a new import+-- block with proper spacing, formatting, and comments.  Then snip out the+-- import block on the import file, and replace it. fixImports :: Config.Config -> FilePath -> Types.Module -> [Haskell.Comment]     -> String -> IO (Either String Result)-fixImports config modulePath mod cmts text-    | Set.null newImports && Set.null unusedImports =-        return $ Right $ Result text Set.empty Set.empty-    | otherwise = do-        -- Don't bother loading the index if I'm not going to use it.-        -- TODO actually, only load it if I don't find local imports-        -- I guess Data.Binary's laziness will serve me there-        index <- if Set.null newImports-            then return Index.empty-            else Index.loadIndex (Config.configIndex config)-        mbNew <- mapM (mkImportLine modulePath index) (Set.toList newImports)-        mbExisting <- mapM findImport imports-        let existing = map (Types.importDeclModule . fst) imports-        let (notFound, importLines) = Either.partitionEithers $-                zipWith mkError-                    (map toModule (Set.toList newImports) ++ existing)-                    (mbNew ++ mbExisting)-            mkError _ (Just imp) = Right imp-            mkError mod Nothing = Left mod-        return $ case notFound of-            _ : _ -> Left $ "modules not found: "-                ++ Util.join ", " (map Types.moduleName notFound)-            [] -> Right $ Result-                (substituteImports (showImports importLines) range text)-                (Set.fromList (map (Types.importDeclModule . Types.importDecl)-                    (Maybe.catMaybes mbNew)))-                unusedImports+fixImports config modulePath mod cmts text = do+    -- Don't bother loading the index if I'm not going to use it.+    -- TODO actually, only load it if I don't find local imports+    -- I guess Data.Binary's laziness will serve me there+    index <- if Set.null newImports+        then return Index.empty+        else Index.loadIndex (Config.configIndex config)+    mbNew <- mapM (mkImportLine modulePath index) (Set.toList newImports)+    mbExisting <- mapM findImport imports+    let existing = map (Types.importDeclModule . fst) imports+    let (notFound, importLines) = Either.partitionEithers $+            zipWith mkError+                (map toModule (Set.toList newImports) ++ existing)+                (mbNew ++ mbExisting)+        mkError _ (Just imp) = Right imp+        mkError mod Nothing = Left mod+    return $ case notFound of+        _ : _ -> Left $ "modules not found: "+            ++ Util.join ", " (map Types.moduleName notFound)+        [] -> Right $ Result+            (substituteImports (showImports importLines) range text)+            (Set.fromList (map (Types.importDeclModule . Types.importDecl)+                (Maybe.catMaybes mbNew)))+            unusedImports     where     (newImports, unusedImports, imports, range) = importInfo mod cmts     toModule (Types.Qualification name) = Types.ModuleName name@@ -321,30 +314,31 @@   -- | Pair ImportDecls up with the comments that apply to them.  Comments--- below the last import are dropped.  Comments that are separated from an--- import by a blank line will also be lost.  It could be fixed but I don't--- think I write those comments.+-- below the last import are dropped, but there shouldn't be any of those+-- because they should have been omitted from the comment block. ----- Also misses cmts that have leading whitespace.  Don't do that.--- To do it right I'd have to sort imports and cmts and pull cmts off with--- a foldl.+-- Spaces between comments above an import will be lost, and multiple comments+-- to the right of an import (e.g. commenting a complicated import list) will+-- probably be messed up.  TODO Fix it if it becomes a problem. associateComments :: [Types.ImportDecl] -> [Haskell.Comment]     -> [(Types.ImportDecl, [Types.Comment])]-associateComments imports cmts = [(imp, cmtsFor imp) | imp <- imports]+associateComments imports cmts = snd $ List.mapAccumL associate cmts imports     where-    cmtsFor imp = Util.mapMaybe (associate src) cmts-        where src = Haskell.srcInfoSpan (Haskell.importAnn imp)-    associate src (Haskell.Comment block csrc text)-        | start csrc == end csrc && start csrc `within` src =-            Just $ Types.Comment Types.CmtRight cmt-        | Haskell.srcSpanStartColumn csrc == 1 && end csrc + 1 == start src =-            Just $ Types.Comment Types.CmtAbove cmt-        | otherwise = Nothing+    associate cmts imp = (after, (imp, associated))         where-        cmt = if block then "{-" ++ text ++ "-}" else "--" ++ text+        associated = map (Types.Comment Types.CmtAbove . cmtText) above+            ++ map (Types.Comment Types.CmtRight . cmtText) right+        -- cmts that end before the import beginning are above it+        (above, rest) = List.span ((< start impSpan) . end . cmtSpan) cmts+        -- remaining cmts that start before or at the import's end are right+        -- of it+        (right, after) = List.span ((<= end impSpan) . start . cmtSpan) rest+        impSpan = Haskell.srcInfoSpan (Haskell.importAnn imp)+    cmtSpan (Haskell.Comment _ span _) = span+    cmtText (Haskell.Comment True _ s) = "{-" ++ s ++ "-}"+    cmtText (Haskell.Comment False _ s) = "--" ++ s     start = Haskell.srcSpanStartLine     end = Haskell.srcSpanEndLine-    within line src = line >= start src && line <= end src  parse :: String -> (Types.Module -> [Haskell.Comment] -> a) -> Either String a parse text f = case Haskell.parseFileContentsWithComments mode text of@@ -377,9 +371,9 @@     | Haskell.Qual _ m _ <- Uniplate.universeBi mod]  -{- -- * test +{- test = do     res <- fixModule (Config.defaultConfig ["base"])  "TestMod.hs" tmod     case res of@@ -403,6 +397,7 @@ \) where\n\ \import qualified Data.List as C\n\ \-- I want this comment\n\+\-- And this one\n\ \import qualified Util -- cmt right\n\ \import qualified Extra as Biz {- block cmt -}\n\ \import Data.Map (a,\n\
vimrc view
@@ -15,21 +15,26 @@     execute 'silent !FixImports -v' expand('%') '<' tmp '>' out '2>' err     let errs = readfile(err)     if v:shell_error == 0-        " Is there an easier way to replace the buffer with a file?-        let old_line = line('.')-        let old_col = col('.')-        let old_total = line('$')-        %d-        execute 'silent :read' out-        0d-        let new_total = line('$')-        " If the import fix added or removed lines I need to take that into-        " account.  This will be wrong if the cursor was above the import-        " block.-        call cursor(old_line + (new_total - old_total), old_col)-        " The reload will forget fold state.  It was open, right?-        if foldclosed('.') != -1-            execute 'normal zO'+        " Don't replace the buffer if there's no change, this way I won't+        " mess up fold and undo state.+        call system('cmp -s ' . tmp . ' ' . out)+        if v:shell_error != 0+            " Is there an easier way to replace the buffer with a file?+            let old_line = line('.')+            let old_col = col('.')+            let old_total = line('$')+            %d+            execute 'silent :read' out+            0d+            let new_total = line('$')+            " If the import fix added or removed lines I need to take that+            " into account.  This will be wrong if the cursor was above the+            " import block.+            call cursor(old_line + (new_total - old_total), old_col)+            " The reload will forget fold state.  It was open, right?+            if foldclosed('.') != -1+                execute 'normal zO'+            endif         endif     endif     call delete(out)