content: {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE QualifiedDo #-}
  
  -- | 'OrmoluException' type and surrounding definitions.
  module Ormolu.Exception
    ( OrmoluException (..),
      printOrmoluException,
      withPrettyOrmoluExceptions,
    )
  where
  
  import Control.Exception
  import Data.Foldable (for_)
  import Data.List.NonEmpty (NonEmpty (..))
  import Data.List.NonEmpty qualified as NE
  import Data.Text (Text)
  import Data.Text qualified as T
  import Data.Void (Void)
  import Distribution.Parsec.Error (PError, showPError)
  import GHC.Types.SrcLoc
  import Ormolu.Diff.Text (TextDiff, printTextDiff)
  import Ormolu.Terminal
  import Ormolu.Terminal.QualifiedDo qualified as Term
  import System.Exit (ExitCode (..))
  import System.IO
  import Text.Megaparsec (ParseErrorBundle, errorBundlePretty)
  
  -- | Ormolu exception representing all cases when Ormolu can fail.
  data OrmoluException
    = -- | Parsing of original source code failed
      OrmoluParsingFailed SrcSpan String
    | -- | Parsing of formatted source code failed
      OrmoluOutputParsingFailed SrcSpan String
    | -- | Original and resulting ASTs differ
      OrmoluASTDiffers TextDiff [RealSrcSpan]
    | -- | Formatted source code is not idempotent
      OrmoluNonIdempotentOutput TextDiff
    | -- | Some GHC options were not recognized
      OrmoluUnrecognizedOpts (NonEmpty String)
    | -- | Cabal file parsing failed
      OrmoluCabalFileParsingFailed FilePath (NonEmpty PError)
    | -- | Missing input file path when using stdin input and
      -- accounting for .cabal files
      OrmoluMissingStdinInputFile
    | -- | A parse error in a fixity overrides file
      OrmoluFixityOverridesParseError (ParseErrorBundle Text Void)
    deriving (Show)
  
  instance Exception OrmoluException where
    displayException = T.unpack . runTermPure . printOrmoluException
  
  -- | Print an 'OrmoluException'.
  printOrmoluException ::
    OrmoluException ->
    Term
  printOrmoluException = \case
    OrmoluParsingFailed s e -> Term.do
      bold (putOutputable s)
      newline
      put "  The GHC parser (in Haddock mode) failed:"
      newline
      put "  "
      put (T.pack e)
      newline
    OrmoluOutputParsingFailed s e -> Term.do
      bold (putOutputable s)
      newline
      put "  Parsing of formatted code failed:"
      newline
      put "  "
      put (T.pack e)
      newline
    OrmoluASTDiffers diff ss -> Term.do
      printTextDiff diff
      newline
      put "  AST of input and AST of formatted code differ."
      newline
      for_ ss $ \s -> Term.do
        put "    at "
        putOutputable s
        newline
      put "  Please, consider reporting the bug."
      newline
      put "  To format anyway, use --unsafe."
      newline
    OrmoluNonIdempotentOutput diff -> Term.do
      printTextDiff diff
      newline
      put "  Formatting is not idempotent."
      newline
      put "  Please, consider reporting the bug."
      newline
    OrmoluUnrecognizedOpts opts -> Term.do
      put "The following GHC options were not recognized:"
      newline
      put "  "
      (put . T.unwords . map T.pack . NE.toList) opts
      newline
    OrmoluCabalFileParsingFailed cabalFile parseErrors -> Term.do
      put "Parsing this .cabal file failed:"
      newline
      for_ parseErrors $ \e -> Term.do
        put . T.pack $ "  " <> showPError cabalFile e
        newline
    OrmoluMissingStdinInputFile -> Term.do
      put "The --stdin-input-file option is necessary when using input"
      newline
      put "from stdin and accounting for .cabal files"
      newline
    OrmoluFixityOverridesParseError errorBundle -> Term.do
      put . T.pack . errorBundlePretty $ errorBundle
      newline
  
  -- | Inside this wrapper 'OrmoluException' will be caught and displayed
  -- nicely.
  withPrettyOrmoluExceptions ::
    -- | Color mode
    ColorMode ->
    -- | Action that may throw an exception
    IO ExitCode ->
    IO ExitCode
  withPrettyOrmoluExceptions colorMode m = m `catch` h
    where
      h e = do
        runTerm (printOrmoluException e) colorMode stderr
        return . ExitFailure $
          case e of
            -- Error code 1 is for 'error' or 'notImplemented'
            -- 2 used to be for erroring out on CPP
            OrmoluParsingFailed {} -> 3
            OrmoluOutputParsingFailed {} -> 4
            OrmoluASTDiffers {} -> 5
            OrmoluNonIdempotentOutput {} -> 6
            OrmoluUnrecognizedOpts {} -> 7
            OrmoluCabalFileParsingFailed {} -> 8
            OrmoluMissingStdinInputFile {} -> 9
            OrmoluFixityOverridesParseError {} -> 10
  