@@ -13,7 +13,7 @@ import Data.Data (Data)
13
13
import Data.Foldable (for_ )
14
14
import Data.List (isPrefixOf )
15
15
import qualified Data.Map.Strict as Map
16
- import Data.Maybe (fromMaybe )
16
+ import Data.Maybe (fromMaybe , isJust )
17
17
import Development.IDE (realSpan )
18
18
import Development.IDE.GHC.Compat
19
19
import Development.IDE.GHC.ExactPrint
@@ -35,53 +35,103 @@ updateDataAnns decl@(L declLoc (TyClD _ DataDecl {tcdDataDefn = HsDataDefn { dd_
35
35
getAnnsT >>= (\ anns -> unless (missingSomeHaddock anns cons) (lift Nothing ))
36
36
37
37
-- visit each constructor and field
38
- addHaddockCommentsToList True declLoc cons
38
+ addHaddockCommentsToList True declLoc ( G AnnVbar ) cons
39
39
for_ cons $ \ case
40
- L conLoc ConDeclH98 { con_args = RecCon (L _ fields) } -> addHaddockCommentsToList False conLoc fields
40
+ L conLoc ConDeclH98 { con_args = RecCon (L _ fields) } -> addHaddockCommentsToList False conLoc ( G AnnComma ) fields
41
41
_ -> pure ()
42
42
modifyAnnsT $ Map. adjust (\ ann -> ann {annPriorComments = [] }) (mkAnnKey decl)
43
- updateDataAnns _ = pure ()
43
+ updateDataAnns _ = lift Nothing
44
44
45
- -- TODO Add explaination to this complex function.
46
- addHaddockCommentsToList :: (Data a , Monad m ) => Bool -> SrcSpan -> [Located a ] -> TransformT m ()
47
- addHaddockCommentsToList usePrevNodeAsAnchor outerLoc nodes =
45
+ -- | Add haddock comments to a list of nodes
46
+ addHaddockCommentsToList
47
+ :: (Data a , Monad m )
48
+ => Bool -- ^ If true, for each node, use previous node in the list as the anchor. Otherwise, use the outer node
49
+ -> SrcSpan -- ^ The outer node
50
+ -> KeywordId -- ^ The seperator between adjacent nodes
51
+ -> [Located a ] -- ^ The list of nodes. Haddock comments will be added to each of them
52
+ -> TransformT m ()
53
+ addHaddockCommentsToList usePrevNodeAsAnchor outerLoc seperator nodes =
54
+ -- If you want to understand this function, please first read this page carefully:
55
+ -- https://hackage.haskell.org/package/ghc-exactprint-0.6.4/docs/Language-Haskell-GHC-ExactPrint-Delta.html
56
+ -- The important part is that for DP(r,c), if r is zero, c is the offset start from the end of the previous node.
57
+ -- However, if r is greater than zero, c is the offset start from the 'anchor'.
58
+ -- Generally speaking, the 'anchor' is the node that "enclose"
48
59
for_ (zip nodes (Nothing : fmap Just nodes)) $ \ (node, prevNode) -> do
49
60
addHaddockCommentToCurrentNode <- fmap (not . fromMaybe True . flip hasHaddock node) getAnnsT
61
+ -- We don't add new haddock comments to nodes with existing ones.
50
62
when addHaddockCommentToCurrentNode $ do
51
- let sameLineAsPrev = maybe False (\ prevNode' -> notSeperatedByLineEnding prevNode' node) prevNode
52
- when sameLineAsPrev $ modifyAnnsT $ \ anns ->
53
- let updateSepAnn :: Annotation -> Annotation
63
+ -- 'sameLineAsPrev' is a flag to determine the inline case, for example:
64
+ -- data T = A { a :: Int, b :: String } | B { b :: Double }
65
+ -- Note that it's a 'Maybe (Located a)', containing the previous node if the current node
66
+ -- and the previous node are on the same line.
67
+ --
68
+ -- For the multiline case (which is the most common), we keep the original indentation of each constructor
69
+ -- and field.
70
+ --
71
+ -- For the inline case, we use the first construcotr/field as the base, and align all following items
72
+ -- to them.
73
+ let sameLineAsPrev = prevNode >>= (
74
+ \ prevNode' -> if notSeperatedByLineEnding prevNode' node
75
+ then pure prevNode'
76
+ else Nothing
77
+ )
78
+ -- For the inline case, we need to move the seperator to the next line.
79
+ -- For constructors, it's vertical bar; for fields, it's comma.
80
+ -- The seperator is passed in as function argument.
81
+ when (isJust sameLineAsPrev) $ modifyAnnsT $ \ anns ->
82
+ let newSepCol :: Annotation -> Int
83
+ newSepCol ann =
84
+ if usePrevNodeAsAnchor then 0 else deltaColumn (annEntryDelta ann)
85
+ updateSepAnn :: Annotation -> Annotation
54
86
updateSepAnn ann = ann {annsDP =
55
- Map. toList . Map. adjust (const ( DP (1 ,0 ))) ( G AnnVbar ) . Map. fromList $ annsDP ann}
87
+ Map. toList . Map. adjust (const $ DP (1 , newSepCol ann)) seperator . Map. fromList $ annsDP ann}
56
88
in flip (maybe anns) prevNode $ \ prevNode' -> Map. adjust updateSepAnn (mkAnnKey prevNode') anns
89
+ -- Calculate the real column of the anchor
57
90
let anchorCol = maybe 0 srcSpanStartCol . realSpan . maybe outerLoc getLoc $
58
91
if usePrevNodeAsAnchor then prevNode else Nothing
92
+ -- 'dpCol' is what we will use for the current node's entry delta's column
93
+ dpCol <- flip fmap getAnnsT $ \ anns ->
94
+ case sameLineAsPrev of
95
+ Just prevNode' ->
96
+ -- If the previous node is the anchor, using 0 as column will make current code align with
97
+ -- the previous one.
98
+ -- Otherwise, use the column of entry delta of the previous node.
99
+ -- The map lookup should not fail. '2' is used as a fallback value to make sure the syntax
100
+ -- is correct after the changes.
101
+ if usePrevNodeAsAnchor then 0 else maybe 2 (deltaColumn . annEntryDelta)
102
+ $ anns Map. !? mkAnnKey prevNode'
103
+ -- We subtract the real column to get dp column.
104
+ Nothing -> (maybe 2 srcSpanStartCol . realSpan $ getLoc node) - anchorCol
105
+ -- Modify the current node
59
106
modifyAnnsT $
60
107
let updateCurrent :: Annotation -> Annotation
61
108
updateCurrent ann = ann {
62
- annPriorComments =
63
- case annPriorComments ann of
64
- (c, dp) : rem -> (emptyPriorHaddockComment, dp) : (c, DP (2 ,0 )) : rem
65
- _ -> [(emptyPriorHaddockComment, annEntryDelta ann)],
109
+ -- If there exists non-haddock comments, we simply inherit it's delta pos, and move existing
110
+ -- comments two lines below (to seperate them from our newly added haddock comments)
111
+ -- Otherwise, inherit the node's entry delta pos.
112
+ annPriorComments = case annPriorComments ann of
113
+ (c, dp) : rem -> (emptyPriorHaddockComment, dp) : (c, DP (2 ,0 )) : rem
114
+ _ -> [(emptyPriorHaddockComment, annEntryDelta ann)],
66
115
annEntryDelta = DP (1 , dpCol)
67
116
}
68
- dpCol = if sameLineAsPrev then 0
69
- else (maybe 2 srcSpanStartCol . realSpan $ getLoc node) - anchorCol
70
117
in Map. adjust updateCurrent (mkAnnKey node)
71
118
119
+ -- | Determine if a list of constructor declarations is missing some haddock comments.
72
120
missingSomeHaddock :: Anns -> [LConDecl GhcPs ] -> Bool
73
121
missingSomeHaddock anns = any $ \ lcon@ (L _ conDecl) -> case conDecl of
74
122
ConDeclH98 { con_args = RecCon (L _ fields) } ->
75
123
elem (Just False ) $ hasHaddock anns lcon : fmap (hasHaddock anns) fields
76
124
_ -> False -- GADT is not supported yet
77
125
126
+ -- | Returns 'True' if the end of the first node and the start of the second node are on the same line.
78
127
notSeperatedByLineEnding :: Located a
79
128
-> Located a
80
129
-> Bool
81
130
notSeperatedByLineEnding (L (RealSrcSpan x _) _) (L (RealSrcSpan y _) _) =
82
131
srcLocLine (realSrcSpanEnd x) == srcLocLine (realSrcSpanStart y)
83
132
notSeperatedByLineEnding _ _ = False
84
133
134
+ -- | Empty haddock, suitable for being added to 'annPriorComments'
85
135
emptyPriorHaddockComment :: Comment
86
136
emptyPriorHaddockComment = mkComment " -- |"
87
137
#if MIN_VERSION_ghc(9,0,0)
@@ -90,6 +140,7 @@ emptyPriorHaddockComment = mkComment "-- |"
90
140
noSrcSpan
91
141
#endif
92
142
143
+ -- | Determines the given node has haddock comments attached to it.
93
144
hasHaddock :: Data a => Anns -> Located a -> Maybe Bool
94
145
hasHaddock anns node = fmap annHasHaddock (anns Map. !? key)
95
146
where
@@ -99,6 +150,7 @@ hasHaddock anns node = fmap annHasHaddock (anns Map.!? key)
99
150
|| any (matchCommentPrefix followingCommentPrefix . fst ) (annFollowingComments ann)
100
151
|| any (keywordIdIsHaddockComment . fst ) (annsDP ann)
101
152
153
+ -- | Checks if the given 'KeywordId' is a comment, and specifically, a haddock comment.
102
154
keywordIdIsHaddockComment :: KeywordId -> Bool
103
155
keywordIdIsHaddockComment (AnnComment comment) = any (`isPrefixOf` commentContents comment) (priorCommentPrefix ++ followingCommentPrefix)
104
156
keywordIdIsHaddockComment _ = False
0 commit comments