packages feed

ormolu-0.9.0.0: src/Ormolu/Printer.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

-- | Pretty-printer for Haskell AST.
--
-- Each snippet is rendered twice. Comments are attached to the elements the
-- printer enters, and the only way to know which elements those are is to
-- render once and see; the first pass therefore runs with no comments at
-- all and is kept only for the spans it visited. See 'render'.
module Ormolu.Printer
  ( printSnippets,
    printSnippetsWithPlacements,
  )
where

import Data.Choice (Choice)
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Types.SrcLoc (RealSrcSpan)
import Ormolu.Comments.Anchor
import Ormolu.Parser.CommentStream (CommentStream (..))
import Ormolu.Parser.Result
import Ormolu.Printer.Combinators
import Ormolu.Printer.CommentPlacement
import Ormolu.Printer.Meat.Module
import Ormolu.Processing.Common

-- | Render several source snippets.
printSnippets ::
  -- | Whether to print out debug information during printing
  Choice "debug" ->
  -- | Result of parsing
  [SourceSnippet] ->
  -- | Resulting rendition
  Text
printSnippets debug = T.concat . fmap fst . printSnippetsWithPlacements debug

-- | Like 'printSnippets', but also return, for each snippet, the placement
-- of every comment it emitted.
--
-- Snippets are rendered separately and their spans are relative to
-- themselves, so the placements stay grouped by snippet: anything that
-- compares them against the input has to work one snippet at a time.
printSnippetsWithPlacements ::
  -- | Whether to print out debug information during printing
  Choice "debug" ->
  -- | Result of parsing
  [SourceSnippet] ->
  -- | For each snippet, its rendition and the comments it emitted
  [(Text, [CommentPlacement])]
printSnippetsWithPlacements debug = fmap (renderSnippet debug)

-- | Render one snippet. A snippet that could not be parsed is passed
-- through as it was.
renderSnippet ::
  Choice "debug" ->
  SourceSnippet ->
  (Text, [CommentPlacement])
renderSnippet debug = \case
  ParsedSnippet r -> render debug r
  RawSnippet r -> (r, [])

-- | Render one parsed snippet, along with the placement of every comment it
-- emitted.
--
-- This renders twice. Anchoring a comment to an element the printer never
-- enters would leave the comment stranded, and there is no way to know
-- which elements those are but to render once and see. The first pass is
-- given an empty 'AnchorMap', so it emits no comments and its output is
-- thrown away; what it is for is the spans it visited, which is what the
-- second pass attaches the comments to.
render ::
  Choice "debug" ->
  ParseResult ->
  (Text, [CommentPlacement])
render debug r@ParseResult {..} =
  let (_, _, visited) = renderWith noComments
      (rendered, placements, _) = renderWith (anchorMapFor r visited)
   in (rendered, placements)
  where
    renderWith anchorMap =
      let (rendered, placements, visited) =
            runR
              ( p_hsModule
                  prStackHeader
                  prPragmas
                  prParsedSource
              )
              anchorMap
              prSourceType
              prExtensions
              prModuleFixityMap
              debug
              prHaddockText
       in (reindent prIndent rendered, placements, visited)

-- | Attach the comments of a snippet to the elements the printer enters.
anchorMapFor :: ParseResult -> [RealSrcSpan] -> AnchorMap
anchorMapFor ParseResult {..} visited =
  mkAnchorMap (attachComments comments visited)
  where
    CommentStream comments = prCommentStream