serokell/importify

View on GitHub
src/Importify/Pretty.hs

Summary

Maintainability
Test Coverage
-- | This module contains functions to print imports
-- preserving original formatting.

module Importify.Pretty
       ( printLovelyImports
       ) where

import Universum

import Control.Arrow ((&&&))
import Data.Text (strip)
import Language.Haskell.Exts (Comment (..), ImportDecl (..), SrcSpan (..), SrcSpanInfo (..),
                              exactPrint, startLine)
import Language.Haskell.Exts.Syntax (Annotated (..))

import Importify.Syntax (stripEndLineComment)

import qualified Data.IntMap.Strict as IM

-- | This function takes range of origin text for import and
-- AST of imports without unused entinties and then converts
-- import declarations into list of lines that should be printed.
printLovelyImports :: Int
                   -> Int
                   -> [Comment]
                   -> [Text]
                   -> [ImportDecl SrcSpanInfo]
                   -> [Text]
printLovelyImports start end comments importsText importDecls =
    let -- map ImportDecl into (Int, [Text]) -- index of starting line to lines of text
        indexedImportLines = map (importStartLine &&& exactPrintImport comments) importDecls
        importsMap         = IM.fromList indexedImportLines

        -- find empty and single comment lines
        indexedTextLines    = zip [start..end] importsText
        emptyOrCommentLines = filter (null . strip . stripEndLineComment . snd) indexedTextLines

        -- add empty and single comment lines to result map
        importsMapWithExtraLines = foldl' (\dict (i,l) -> IM.insert i [l] dict)
                                          importsMap
                                          emptyOrCommentLines

        -- collect all values and concat them; order is guaranteed by IntMap
        resultLines = concat $ IM.elems importsMapWithExtraLines
    in resultLines

importStartLine :: ImportDecl SrcSpanInfo -> Int
importStartLine = srcSpanStartLine . srcInfoSpan . importAnn

exactPrintImport :: [Comment] -> ImportDecl SrcSpanInfo -> [Text]
exactPrintImport allComments importDecl = dropWhile null
                            $ lines
                            $ toText
                            $ importText ++ endComments
  where
    importText = exactPrint importDecl (filter commentOnLine allComments)

    -- exactPrint stops printing comments after the import statement is finished, so print those manually.
    -- This would be much easier if Language.Haskell.Exts.ExactPrint exported more stuff
    endComments = printComments
                $ filter (\(Comment _ (SrcSpan _ sl _ _ ec) _) -> ec > importEndPos && sl == curLine)
                  allComments
    printComments = intercalate " " . map ((" " ++) . printComment)
    printComment (Comment multi _ s) = if multi
                                       then "{-" ++ s ++ "-}"
                                       else "--" ++ s

    curLine = (startLine . ann) importDecl
    importEndPos = (srcSpanEndColumn . srcInfoSpan . ann) importDecl
    commentSpan (Comment _ srcSpan _) = srcSpan
    commentOnLine = (== curLine) . srcSpanStartLine . commentSpan