hindent-6.1.0: src/HIndent/ModulePreprocessing.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | Module preprocessing before pretty-printing.
module HIndent.ModulePreprocessing
( modifyASTForPrettyPrinting
) where
import Control.Applicative
import Data.Function
import Data.List
import Data.Maybe
import GHC.Hs
import GHC.Types.SrcLoc
import Generics.SYB hiding (GT, typeOf, typeRep)
import HIndent.Fixity
import HIndent.GhcLibParserWrapper.GHC.Hs
import HIndent.ModulePreprocessing.CommentRelocation
import Language.Haskell.GhclibParserEx.Fixity
import Type.Reflection
-- | This function modifies the given module AST for pretty-printing.
--
-- Pretty-printing a module without calling this function for it before may
-- raise an error or not print it correctly.
modifyASTForPrettyPrinting :: HsModule' -> HsModule'
modifyASTForPrettyPrinting m = relocateComments (beforeRelocation m) allComments
where
beforeRelocation =
resetLGRHSEndPositionInModule
. removeAllDocDs
. closeEpAnnOfHsFunTy
. closeEpAnnOfMatchMExt
. closePlaceHolderEpAnns
. closeEpAnnOfFunBindFunId
. resetModuleNameColumn
. replaceAllNotUsedAnns
. removeComments
. sortExprLStmt
. fixFixities
allComments = listify (not . isEofComment . ac_tok . unLoc) m
isEofComment EpaEofComment = True
isEofComment _ = False
-- | This function modifies the given module AST to apply fixities of infix
-- operators defined in the 'base' package.
fixFixities :: HsModule' -> HsModule'
fixFixities = applyFixities fixities
-- | This function sets an 'LGRHS's end position to the end position of the
-- last RHS in the 'grhssGRHSs'.
--
-- The source span of an 'L?GRHS' contains the 'where' keyword, which
-- locates comments in the wrong position in the process of comment
-- relocation. This function prevents it by fixing the 'L?GRHS''s source
-- span.
resetLGRHSEndPositionInModule :: HsModule' -> HsModule'
resetLGRHSEndPositionInModule = everywhere (mkT resetLGRHSEndPosition)
-- | This function sorts lists of statements in order their positions.
--
-- For example, the last element of 'HsDo' of 'HsExpr' is the element
-- before a bar, and the elements are not sorted by their locations. This
-- function fixes the orderings.
sortExprLStmt :: HsModule' -> HsModule'
sortExprLStmt m@HsModule {hsmodDecls = xs} = m {hsmodDecls = sorted}
where
sorted = everywhere (mkT sortByLoc) xs
sortByLoc :: [ExprLStmt GhcPs] -> [ExprLStmt GhcPs]
sortByLoc = sortBy (compare `on` srcSpanToRealSrcSpan . locA . getLoc)
-- | This function removes all comments from the given module not to
-- duplicate them on comment relocation.
removeComments :: HsModule' -> HsModule'
removeComments = everywhere (mkT $ const emptyComments)
-- | This function replaces all 'EpAnnNotUsed's in 'SrcSpanAnn''s with
-- 'EpAnn's to make it possible to locate comments on them.
replaceAllNotUsedAnns :: HsModule' -> HsModule'
replaceAllNotUsedAnns = everywhere app
where
app ::
forall a. Data a
=> (a -> a)
app sp
| App g (App y z) <- typeRep @a
, Just HRefl <- eqTypeRep g (typeRep @SrcSpanAnn')
, Just HRefl <- eqTypeRep y (typeRep @EpAnn) =
fromMaybe sp $ do
let try :: Typeable b => b -> Maybe a
try ann = do
HRefl <- eqTypeRep (typeOf ann) z
pure sp {ann = EpAnn (spanAsAnchor $ locA sp) ann emptyComments}
try emptyListItem
<|> try emptyList
<|> try emptyPragma
<|> try emptyContext
<|> try emptyNameAnn
<|> try NoEpAnns
app x = x
emptyListItem = AnnListItem []
emptyList = AnnList Nothing Nothing Nothing [] []
emptyPragma = AnnPragma emptyAddEpAnn emptyAddEpAnn []
emptyContext = AnnContext Nothing [] []
emptyNameAnn = NameAnnTrailing []
emptyAddEpAnn = AddEpAnn AnnAnyclass emptyEpaLocation
emptyEpaLocation = EpaDelta (SameLine 0) []
-- | This function sets the start column of 'hsmodName' of the given
-- 'HsModule' to 1 to correctly locate comments above the module name.
resetModuleNameColumn :: HsModule' -> HsModule'
resetModuleNameColumn m@HsModule {hsmodName = Just (L (SrcSpanAnn epa@EpAnn {..} sp) name)} =
m {hsmodName = Just (L (SrcSpanAnn newAnn sp) name)}
where
newAnn = epa {entry = realSpanAsAnchor newSpan}
newSpan =
mkRealSrcSpan
(mkRealSrcLoc (srcSpanFile anc) (srcSpanStartLine anc) 1)
(realSrcSpanEnd anc)
anc = anchor entry
resetModuleNameColumn m = m
-- | This function replaces the 'EpAnn' of 'fun_id' in 'FunBind' with
-- 'EpAnnNotUsed'.
--
-- The 'fun_id' contains the function's name. However, 'FunRhs' of 'Match'
-- also contains the name, and we use the latter one. This function
-- prevents comments from being located in 'fun_id'.
closeEpAnnOfFunBindFunId :: HsModule' -> HsModule'
closeEpAnnOfFunBindFunId = everywhere (mkT closeEpAnn)
where
closeEpAnn :: HsBind GhcPs -> HsBind GhcPs
closeEpAnn bind@FunBind {fun_id = (L (SrcSpanAnn _ l) name)} =
bind {fun_id = L (SrcSpanAnn EpAnnNotUsed l) name}
closeEpAnn x = x
-- | This function replaces the 'EpAnn' of 'm_ext' in 'Match' with
-- 'EpAnnNotUsed.
--
-- The field contains the annotation of the match LHS. However, the same
-- information is also stored inside the 'Match'. This function removes the
-- duplication not to locate comments on a wrong point.
closeEpAnnOfMatchMExt :: HsModule' -> HsModule'
closeEpAnnOfMatchMExt = everywhere closeEpAnn
where
closeEpAnn ::
forall a. Typeable a
=> a
-> a
closeEpAnn x
| App (App g h) _ <- typeRep @a
, Just HRefl <- eqTypeRep g (typeRep @Match)
, Just HRefl <- eqTypeRep h (typeRep @GhcPs) = x {m_ext = EpAnnNotUsed}
| otherwise = x
-- | This function replaces the 'EpAnn' of the first argument of 'HsFunTy'
-- of 'HsType'.
--
-- 'HsFunTy' should not have any comments. Instead, its LHS and RHS should
-- have them.
closeEpAnnOfHsFunTy :: HsModule' -> HsModule'
closeEpAnnOfHsFunTy = everywhere (mkT closeEpAnn)
where
closeEpAnn :: HsType GhcPs -> HsType GhcPs
closeEpAnn (HsFunTy _ p l r) = HsFunTy EpAnnNotUsed p l r
closeEpAnn x = x
-- | This function replaces all 'EpAnn's that contain placeholder anchors
-- to locate comments correctly. A placeholder anchor is an anchor pointing
-- on (-1, -1).
closePlaceHolderEpAnns :: HsModule' -> HsModule'
closePlaceHolderEpAnns = everywhere closeEpAnn
where
closeEpAnn ::
forall a. Typeable a
=> a
-> a
closeEpAnn x
| App g _ <- typeRep @a
, Just HRefl <- eqTypeRep g (typeRep @EpAnn)
, (EpAnn (Anchor sp _) _ _) <- x
, srcSpanEndLine sp == -1 && srcSpanEndCol sp == -1 = EpAnnNotUsed
| otherwise = x
-- | This function removes all 'DocD's from the given module. They have
-- haddocks, but the same information is stored in 'EpaCommentTok's. Thus,
-- we need to remove the duplication.
removeAllDocDs :: HsModule' -> HsModule'
removeAllDocDs x@HsModule {hsmodDecls = decls} =
x {hsmodDecls = filter (not . isDocD . unLoc) decls}
where
isDocD DocD {} = True
isDocD _ = False
-- | This function sets the position of the given 'LGRHS' to the end
-- position of the last RHS in it.
--
-- See the documentation of 'resetLGRHSEndPositionInModule' for the reason.
resetLGRHSEndPosition ::
LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs)
#if MIN_VERSION_ghc_lib_parser(9,4,1)
resetLGRHSEndPosition (L (SrcSpanAnn locAnn@EpAnn {} sp) (GRHS ext@EpAnn {..} stmt body)) =
let lastPosition =
maximum $ realSrcSpanEnd . anchor <$> listify collectAnchor body
newSpan = mkRealSrcSpan (realSrcSpanStart $ anchor entry) lastPosition
newLocAnn = locAnn {entry = realSpanAsAnchor newSpan}
newAnn = ext {entry = realSpanAsAnchor newSpan}
in L (SrcSpanAnn newLocAnn sp) (GRHS newAnn stmt body)
where
collectAnchor :: Anchor -> Bool
collectAnchor _ = True
#else
resetLGRHSEndPosition (L _ (GRHS ext@EpAnn {..} stmt body)) =
let lastPosition =
maximum $ realSrcSpanEnd . anchor <$> listify collectAnchor body
newSpan = mkRealSrcSpan (realSrcSpanStart $ anchor entry) lastPosition
newLoc = RealSrcSpan newSpan Nothing
newAnn = ext {entry = realSpanAsAnchor newSpan}
in L newLoc (GRHS newAnn stmt body)
where
collectAnchor :: Anchor -> Bool
collectAnchor _ = True
#endif
resetLGRHSEndPosition x = x