Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix comment in empty export list #933

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased

* Fix comment in empty export list being moved out [Issue 906](https://github.com/tweag/ormolu/issues/906)

## Ormolu 0.5.3.0

* Stop making empty `let`s move comments. [Issue
Expand Down
4 changes: 4 additions & 0 deletions data/examples/module-header/multiline-empty-comment-out.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Foo
( -- test
)
where
2 changes: 2 additions & 0 deletions data/examples/module-header/multiline-empty-comment.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Foo ( -- test
) where
15 changes: 9 additions & 6 deletions src/Ormolu/Parser/CommentStream.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Ormolu.Parser.CommentStream
showCommentStream,

-- * Comment
LComment,
Comment (..),
unComment,
hasAtomsBefore,
Expand Down Expand Up @@ -48,7 +49,7 @@ import Ormolu.Utils (onTheSameLine, showOutputable)

-- | A stream of 'RealLocated' 'Comment's in ascending order with respect to
-- beginning of corresponding spans.
newtype CommentStream = CommentStream [RealLocated Comment]
newtype CommentStream = CommentStream [LComment]
deriving (Eq, Data, Semigroup, Monoid)

-- | Create 'CommentStream' from 'HsModule'. The pragmas are
Expand All @@ -59,8 +60,8 @@ mkCommentStream ::
-- | Module to use for comment extraction
HsModule ->
-- | Stack header, pragmas, and comment stream
( Maybe (RealLocated Comment),
[([RealLocated Comment], Pragma)],
( Maybe LComment,
[([LComment], Pragma)],
CommentStream
)
mkCommentStream input hsModule =
Expand Down Expand Up @@ -119,6 +120,8 @@ showCommentStream (CommentStream xs) =
----------------------------------------------------------------------------
-- Comment

type LComment = RealLocated Comment

-- | A wrapper for a single comment. The 'Bool' indicates whether there were
-- atoms before beginning of the comment in the original input. The
-- 'NonEmpty' list inside contains lines of multiline comment @{- … -}@ or
Expand All @@ -135,7 +138,7 @@ mkComment ::
-- | Raw comment string
RealLocated Text ->
-- | Remaining lines of original input and the constructed 'Comment'
([(Int, Text)], RealLocated Comment)
([(Int, Text)], LComment)
mkComment ls (L l s) = (ls', comment)
where
comment =
Expand Down Expand Up @@ -189,7 +192,7 @@ isMultilineComment (Comment _ (x :| _)) = "{-" `T.isPrefixOf` x
extractStackHeader ::
-- | Comment stream to analyze
[RealLocated Text] ->
([RealLocated Text], Maybe (RealLocated Comment))
([RealLocated Text], Maybe LComment)
extractStackHeader = \case
[] -> ([], Nothing)
(x : xs) ->
Expand All @@ -207,7 +210,7 @@ extractPragmas ::
Text ->
-- | Comment stream to analyze
[RealLocated Text] ->
([RealLocated Comment], [([RealLocated Comment], Pragma)])
([LComment], [([LComment], Pragma)])
extractPragmas input = go initialLs id id
where
initialLs = zip [1 ..] (T.lines input)
Expand Down
5 changes: 2 additions & 3 deletions src/Ormolu/Parser/Result.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import Data.Text (Text)
import GHC.Data.EnumSet (EnumSet)
import GHC.Hs
import GHC.LanguageExtensions.Type
import GHC.Types.SrcLoc
import Ormolu.Config (SourceType)
import Ormolu.Fixity (FixityMap, LazyFixityMap)
import Ormolu.Parser.CommentStream
Expand All @@ -27,9 +26,9 @@ data ParseResult = ParseResult
-- | Either regular module or signature file
prSourceType :: SourceType,
-- | Stack header
prStackHeader :: Maybe (RealLocated Comment),
prStackHeader :: Maybe LComment,
-- | Pragmas and the associated comments
prPragmas :: [([RealLocated Comment], Pragma)],
prPragmas :: [([LComment], Pragma)],
-- | Comment stream
prCommentStream :: CommentStream,
-- | Enabled extensions
Expand Down
3 changes: 2 additions & 1 deletion src/Ormolu/Printer/Combinators.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Ormolu.Printer.Combinators
R,
runR,
getEnclosingSpan,
getEnclosingSpanWhere,
isExtensionEnabled,

-- * Combinators
Expand Down Expand Up @@ -108,7 +109,7 @@ located ::
located (L l' a) f = case loc' l' of
UnhelpfulSpan _ -> f a
RealSrcSpan l _ -> do
spitPrecedingComments l
spitPrecedingComments True l
withEnclosingSpan l $
switchLayout [RealSrcSpan l Strict.Nothing] (f a)
spitFollowingComments l
Expand Down
74 changes: 42 additions & 32 deletions src/Ormolu/Printer/Comments.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,23 @@ import Ormolu.Printer.Internal

-- | Output all preceding comments for an element at given location.
spitPrecedingComments ::
-- | Whether to output a newline after the last comment
Bool ->
-- | Span of the element to attach comments to
RealSrcSpan ->
R ()
spitPrecedingComments ref = do
gotSome <- handleCommentSeries (spitPrecedingComment ref)
when gotSome $ do
spitPrecedingComments newlineAfter ref = do
comments <- handleCommentSeries (spitPrecedingComment ref)

whenNonEmpty comments $ \(lastComment NE.:| _) -> do
when newlineAfter $ if theSameLinePre (getLoc lastComment) ref then space else newline

lastMark <- getSpanMark
-- Insert a blank line between the preceding comments and the thing
-- after them if there was a blank line in the input.
when (needsNewlineBefore ref lastMark) newline
where
whenNonEmpty xs f = maybe (return ()) f (NE.nonEmpty xs)

-- | Output all comments following an element at given location.
spitFollowingComments ::
Expand All @@ -42,15 +49,15 @@ spitFollowingComments ::
R ()
spitFollowingComments ref = do
trimSpanStream ref
void $ handleCommentSeries (spitFollowingComment ref)
void $ handleCommentSeries (\_ -> spitFollowingComment ref)

-- | Output all remaining comments in the comment stream.
spitRemainingComments :: R ()
spitRemainingComments = do
-- Make sure we have a blank a line between the last definition and the
-- trailing comments.
newline
void $ handleCommentSeries spitRemainingComment
void $ handleCommentSeries (\_ -> spitRemainingComment)

----------------------------------------------------------------------------
-- Single-comment functions
Expand All @@ -59,12 +66,18 @@ spitRemainingComments = do
spitPrecedingComment ::
-- | Span of the element to attach comments to
RealSrcSpan ->
-- | Are we done?
R Bool
spitPrecedingComment ref = do
-- | The last comment output, if any
Maybe LComment ->
-- | The comment that was output, if any
R (Maybe LComment)
spitPrecedingComment ref mLastComment = do
mlastMark <- getSpanMark
let p (L l _) = realSrcSpanEnd l <= realSrcSpanStart ref
withPoppedComment p $ \l comment -> do
case mLastComment of
Just lastComment -> if theSameLinePre (getLoc lastComment) ref then space else newline
Nothing -> return ()

lineSpans <- thisLineSpans
let thisCommentLine = srcLocLine (realSrcSpanStart l)
needsNewline =
Expand All @@ -73,23 +86,20 @@ spitPrecedingComment ref = do
Just spn -> srcLocLine (realSrcSpanEnd spn) /= thisCommentLine
when (needsNewline || needsNewlineBefore l mlastMark) newline
spitCommentNow l comment
if theSameLinePre l ref
then space
else newline

-- | Output a comment that follows element at given location immediately on
-- the same line, if there is any.
spitFollowingComment ::
-- | AST element to attach comments to
RealSrcSpan ->
-- | Are we done?
R Bool
-- | The comment that was output, if any
R (Maybe LComment)
spitFollowingComment ref = do
mlastMark <- getSpanMark
mnSpn <- nextEltSpan
-- Get first enclosing span that is not equal to reference span, i.e. it's
-- truly something enclosing the AST element.
meSpn <- getEnclosingSpan (/= ref)
meSpn <- getEnclosingSpanWhere (/= ref)
withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastMark) $ \l comment ->
if theSameLinePost l ref
then
Expand All @@ -103,8 +113,8 @@ spitFollowingComment ref = do

-- | Output a single remaining comment from the comment stream.
spitRemainingComment ::
-- | Are we done?
R Bool
-- | The comment that was output, if any
R (Maybe LComment)
spitRemainingComment = do
mlastMark <- getSpanMark
withPoppedComment (const True) $ \l comment -> do
Expand All @@ -117,33 +127,33 @@ spitRemainingComment = do

-- | Output series of comments.
handleCommentSeries ::
-- | Given location of previous comment, output the next comment
-- returning 'True' if we're done
R Bool ->
-- | Whether we printed any comments
R Bool
handleCommentSeries f = go False
-- | Given the last output comment, output and return the next comment, if any
(Maybe LComment -> R (Maybe LComment)) ->
-- | The comments outputted
R [LComment]
handleCommentSeries f = go Nothing
where
go gotSome = do
done <- f
if done
then return gotSome
else go True
go lastComment = do
mComment <- f lastComment
case mComment of
Nothing -> return []
Just comment -> (comment:) <$> go (Just comment)

-- | Try to pop a comment using given predicate and if there is a comment
-- matching the predicate, print it out.
withPoppedComment ::
-- | Comment predicate
(RealLocated Comment -> Bool) ->
(LComment -> Bool) ->
-- | Printing function
(RealSrcSpan -> Comment -> R ()) ->
-- | Are we done?
R Bool
R (Maybe LComment)
withPoppedComment p f = do
r <- popComment p
case r of
Nothing -> return True
Just (L l comment) -> False <$ f l comment
Nothing -> return ()
Just (L l comment) -> f l comment
return r

-- | Determine if we need to insert a newline between current comment and
-- last printed comment.
Expand Down Expand Up @@ -191,7 +201,7 @@ commentFollowsElt ::
-- | Location of last comment in the series
Maybe SpanMark ->
-- | Comment to test
RealLocated Comment ->
LComment ->
Bool
commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) =
-- A comment follows a AST element if all 4 conditions are satisfied:
Expand Down
29 changes: 13 additions & 16 deletions src/Ormolu/Printer/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module Ormolu.Printer.Internal
nextEltSpan,
popComment,
getEnclosingSpan,
getEnclosingSpanWhere,
withEnclosingSpan,
thisLineSpans,

Expand Down Expand Up @@ -498,30 +499,26 @@ nextEltSpan = listToMaybe . coerce <$> R (gets scSpanStream)
-- | Pop a 'Comment' from the 'CommentStream' if given predicate is
-- satisfied and there are comments in the stream.
popComment ::
(RealLocated Comment -> Bool) ->
R (Maybe (RealLocated Comment))
(LComment -> Bool) ->
R (Maybe LComment)
popComment f = R $ do
CommentStream cstream <- gets scCommentStream
case cstream of
[] -> return Nothing
(x : xs) ->
if f x
then
Just x
<$ modify
( \sc ->
sc
{ scCommentStream = CommentStream xs
}
)
else return Nothing
(x : xs) | f x -> do
modify $ \sc -> sc{scCommentStream = CommentStream xs}
return $ Just x
_ -> return Nothing

-- | Get the immediately enclosing 'RealSrcSpan'.
getEnclosingSpan :: R (Maybe RealSrcSpan)
getEnclosingSpan = getEnclosingSpanWhere (const True)

-- | Get the first enclosing 'RealSrcSpan' that satisfies given predicate.
getEnclosingSpan ::
getEnclosingSpanWhere ::
-- | Predicate to use
(RealSrcSpan -> Bool) ->
R (Maybe RealSrcSpan)
getEnclosingSpan f =
getEnclosingSpanWhere f =
listToMaybe . filter f <$> R (asks rcEnclosingSpans)

-- | Set 'RealSrcSpan' of enclosing span for the given computation.
Expand Down
2 changes: 1 addition & 1 deletion src/Ormolu/Printer/Meat/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ p_hsDoc hstyle needsNewline (L l str) = do
-- It's often the case that the comment itself doesn't have a span
-- attached to it and instead its location can be obtained from
-- nearest enclosing span.
getEnclosingSpan (const True) >>= mapM_ (setSpanMark . HaddockSpan hstyle)
getEnclosingSpan >>= mapM_ (setSpanMark . HaddockSpan hstyle)
RealSrcSpan spn _ -> setSpanMark (HaddockSpan hstyle spn)

-- | Print anchor of named doc section.
Expand Down
2 changes: 1 addition & 1 deletion src/Ormolu/Printer/Meat/Declaration/Value.hs
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ p_hsExpr' s = \case
Unboxed -> parensHash
enclSpan <-
fmap (flip RealSrcSpan Strict.Nothing) . maybeToList
<$> getEnclosingSpan (const True)
<$> getEnclosingSpan
if isSection
then
switchLayout [] . parens' s $
Expand Down
29 changes: 17 additions & 12 deletions src/Ormolu/Printer/Meat/ImportExport.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@ import GHC.Types.PkgQual
import GHC.Types.SrcLoc
import GHC.Unit.Types
import Ormolu.Printer.Combinators
import Ormolu.Printer.Comments (spitPrecedingComments)
import Ormolu.Printer.Meat.Common
import Ormolu.Utils (RelativePos (..), attachRelativePos)

p_hsmodExports :: [LIE GhcPs] -> R ()
p_hsmodExports [] = do
txt "("
breakpoint'
txt ")"
p_hsmodExports xs =
parens N $ do
layout <- getLayout
sep
breakpoint
(\(p, l) -> sitcc (located l (p_lie layout p)))
(attachRelativePos xs)
p_hsmodExports :: LocatedL [LIE GhcPs] -> R ()
p_hsmodExports lexports =
located lexports $ \exports ->
inci . parens N $ do
layout <- getLayout
sep
breakpoint
(\(p, l) -> sitcc (located l (p_lie layout p)))
(attachRelativePos exports)

-- if there are any more comments before the close parens,
-- output them now
case al_close . anns . ann . getLoc $ lexports of
Nothing -> return ()
Just (AddEpAnn _ closeParenLoc) -> do
spitPrecedingComments False $ epaLocationRealSrcSpan closeParenLoc

p_hsmodImport :: ImportDecl GhcPs -> R ()
p_hsmodImport ImportDecl {..} = do
Expand Down
Loading