fourmolu-0.20.0.0: tests/Ormolu/Fixity/ParserSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Ormolu.Fixity.ParserSpec (spec) where
import Data.List.NonEmpty (NonEmpty (..))
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Data.Text qualified as T
import Ormolu.Fixity
import Ormolu.Fixity.Parser
import Test.Hspec
import Test.Hspec.Megaparsec
import Text.Megaparsec.Error (ErrorFancy (..))
spec :: Spec
spec = do
describe "parseDotOrmolu" $ do
it "parses the empty input without choking" $
parseDotOrmolu "" ""
`shouldParse` (FixityOverrides Map.empty, ModuleReexports Map.empty)
it "parses a collection of fixity declarations" $
-- The example is taken from base.
parseDotOrmolu
""
( T.unlines
[ "infixr 9 .",
"infixr 5 ++",
"infixl 4 <$",
"infixl 1 >>, >>=",
"infixr 1 =<<",
"infixr 0 $, $!",
"infixl 4 <*>, <*, *>, <**>"
]
)
`shouldParse` ( exampleFixityOverrides,
ModuleReexports Map.empty
)
it "accepts fractional operator precedences" $
parseDotOrmolu
""
( T.unlines
[ "infixr 3 >~<",
"infixr 3.3 |~|",
"infixr 3.7 <~>"
]
)
`shouldParse` ( fractionalFixityOverrides,
ModuleReexports Map.empty
)
it "combines conflicting fixity declarations correctly" $
parseDotOrmolu
""
( T.unlines
[ "infixr 9 ., ^",
"infixr 7 ., $",
"infixr 9 ^ ",
"infixl 7 $"
]
)
`shouldParse` ( FixityOverrides
( Map.fromList
[ ("$", FixityInfo InfixL 7),
(".", FixityInfo InfixR 7),
("^", FixityInfo InfixR 9)
]
),
ModuleReexports Map.empty
)
it "handles CRLF line endings correctly" $
parseDotOrmolu ""
`shouldSucceedOn` unlinesCrlf
[ "infixr 9 .",
"infixr 5 ++"
]
it "parses inputs without a trailing newline" $
parseDotOrmolu ""
`shouldSucceedOn` T.intercalate
"\n"
[ "infixr 9 .",
"infixr 5 ++"
]
it "fails with correct parse error (keyword wrong second line)" $
parseDotOrmolu "" "infixr 5 .\nfoobar 5 $"
`shouldFailWith` err
11
( mconcat
[ utok 'f',
etoks "infix",
etoks "infixl",
etoks "infixr",
etoks "module",
eeof
]
)
it "parses module re-exports and combines them correctly" $
parseDotOrmolu
""
( T.unlines
[ "module Control.Lens exports Control.Lens.Lens",
"module Control.Lens exports \"lens\" Control.Lens.At",
"module Text.Megaparsec exports Control.Monad.Combinators"
]
)
`shouldParse` (FixityOverrides Map.empty, exampleModuleReexports)
it "parses fixity declarations + module re-export declarations with blanks" $
parseDotOrmolu
""
( T.unlines
[ "module Control.Lens exports Control.Lens.Lens",
"",
"infixr 5 ++",
"infixl 4 <$",
"",
"",
"module Control.Lens exports \"lens\" Control.Lens.At",
"infixr 9 .",
"module Text.Megaparsec exports Control.Monad.Combinators",
"infixl 1 >>, >>=",
"infixr 1 =<<",
"",
"infixr 0 $, $!",
"infixl 4 <*>, <*, *>, <**>"
]
)
`shouldParse` (exampleFixityOverrides, exampleModuleReexports)
describe "parseFixtiyDeclaration" $ do
it "parses a simple infixr declaration" $
parseFixityDeclaration "infixr 5 $"
`shouldParse` [("$", FixityInfo InfixR 5)]
it "parses a simple infixl declaration" $
parseFixityDeclaration "infixl 5 $"
`shouldParse` [("$", FixityInfo InfixL 5)]
it "parses a simple infix declaration" $
parseFixityDeclaration "infix 5 $"
`shouldParse` [("$", FixityInfo InfixN 5)]
it "parses a declaration for a ticked identifier" $
parseFixityDeclaration "infixl 5 `foo`"
`shouldParse` [("foo", FixityInfo InfixL 5)]
it "parses a declaration for a ticked identifier (constructor case)" $
parseFixityDeclaration "infixl 5 `Foo`"
`shouldParse` [("Foo", FixityInfo InfixL 5)]
it "parses a multi-operator declaration" $
parseFixityDeclaration "infixl 5 $, ., `Foo`, `bar`"
`shouldParse` [ ("$", FixityInfo InfixL 5),
(".", FixityInfo InfixL 5),
("Foo", FixityInfo InfixL 5),
("bar", FixityInfo InfixL 5)
]
it "parses a declaration with a unicode operator" $
parseFixityDeclaration "infixr 5 ×"
`shouldParse` [("×", FixityInfo InfixR 5)]
it "fails with correct parse error (keyword wrong)" $
parseFixityDeclaration "foobar 5 $"
`shouldFailWith` err
0
( mconcat
[ utoks "foobar",
etoks "infix",
etoks "infixl",
etoks "infixr"
]
)
it "fails with correct parse error (missing operator)" $
parseFixityDeclaration "infixr 5 "
`shouldFailWith` err
9
( mconcat
[ ueof,
etok '`',
elabel "operator character"
]
)
it "fails with correct parse error (trailing comma)" $
parseFixityDeclaration "infixr 5 ., "
`shouldFailWith` err
12
( mconcat
[ ueof,
etok '`',
elabel "operator character"
]
)
it "fails with correct parse error (precedence greater than 9)" $
parseFixityDeclaration "infixl 10 $"
`shouldFailWith` errFancy
7
(fancy (ErrorFail "precedence should not be greater than 9"))
describe "parseModuleReexportDeclaration" $ do
it "parses a re-export declaration" $
parseModuleReexportDeclaration "module Control.Lens exports Control.Lens.Lens"
`shouldParse` ( "Control.Lens",
(Nothing, "Control.Lens.Lens") :| []
)
it "parses a re-export declaration (explicit package)" $
parseModuleReexportDeclaration "module Control.Lens exports \"lens\" Control.Lens.Lens"
`shouldParse` ( "Control.Lens",
(Just "lens", "Control.Lens.Lens") :| []
)
it "fails with correct parse error (keyword wrong)" $
parseModuleReexportDeclaration "foo Control.Lens exports Control.Lens.Lens"
`shouldFailWith` err
0
( mconcat
[ utoks "foo Co",
etoks "module"
]
)
it "fails with correct parse error (module syntax)" $
parseModuleReexportDeclaration "module control.Lens exports Control.Lens.Lens"
`shouldFailWith` err
7
( mconcat
[ utok 'c',
elabel "module name"
]
)
it "fails with correct parse error (typo: export instead exports)" $
parseModuleReexportDeclaration "module Control.Lens export Control.Lens.Lens"
`shouldFailWith` err
20
( mconcat
[ utoks "export ",
etoks "exports"
]
)
exampleFixityOverrides :: FixityOverrides
exampleFixityOverrides =
FixityOverrides
( Map.fromList
[ ("$", FixityInfo InfixR 0),
("$!", FixityInfo InfixR 0),
("*>", FixityInfo InfixL 4),
("++", FixityInfo InfixR 5),
(".", FixityInfo InfixR 9),
("<$", FixityInfo InfixL 4),
("<*", FixityInfo InfixL 4),
("<**>", FixityInfo InfixL 4),
("<*>", FixityInfo InfixL 4),
("=<<", FixityInfo InfixR 1),
(">>", FixityInfo InfixL 1),
(">>=", FixityInfo InfixL 1)
]
)
fractionalFixityOverrides :: FixityOverrides
fractionalFixityOverrides =
FixityOverrides
( Map.fromList
[ (">~<", FixityInfo InfixR 3),
("|~|", FixityInfo InfixR 3.3),
("<~>", FixityInfo InfixR 3.7)
]
)
exampleModuleReexports :: ModuleReexports
exampleModuleReexports =
ModuleReexports . Map.fromList $
[ ( "Control.Lens",
(Nothing, "Control.Lens.Lens") :| [(Just "lens", "Control.Lens.At")]
),
( "Text.Megaparsec",
(Nothing, "Control.Monad.Combinators") :| []
)
]
unlinesCrlf :: [Text] -> Text
unlinesCrlf = T.concat . fmap (<> "\r\n")