packages feed

moonlight-pale-0.1.0.0: test/ghc-surface/ModuleSurfaceSpec.hs

module ModuleSurfaceSpec
  ( tests,
  )
where

import Data.Bifunctor (first)
import Moonlight.Pale.Ghc.ModuleSurface
  ( ExportChildSpec (..),
    ExportItem (..),
    ExportSpec (..),
    ModuleSurface (..),
    moduleSurfaceFromGhcPs,
    parseHsModule,
    renderGhcParseFailure,
    unParsedModuleName,
    unParsedName,
  )
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase, (@?=))

tests :: TestTree
tests =
  testGroup
    "pale.module-surface"
    [ testCase "GHC2024 keeps pattern available as a type-variable name" $
        assertParses
          "PatternTypeVariable.hs"
          [ "{-# LANGUAGE GHC2024 #-}",
            "module PatternTypeVariable where",
            "",
            "foo :: host pattern var -> ()",
            "foo _ = ()"
          ],
      testCase "PatternSynonyms is enabled only when requested by LANGUAGE pragma" $
        assertParses
          "PatternSynonymFixture.hs"
          [ "{-# LANGUAGE PatternSynonyms #-}",
            "module PatternSynonymFixture where",
            "",
            "pattern Unit = ()",
            "value = Unit"
          ],
      testCase "multiline LANGUAGE pragmas are parsed by GHC's header parser" $
        assertParses
          "MultilinePatternSynonymFixture.hs"
          [ "{-# LANGUAGE",
            "      PatternSynonyms",
            "  #-}",
            "module MultilinePatternSynonymFixture where",
            "pattern Unit = ()"
          ],
      testCase "OPTIONS_GHC extension flags are parsed by GHC's header parser" $
        assertParses
          "OptionsPatternSynonymFixture.hs"
          [ "{-# OPTIONS_GHC -XPatternSynonyms #-}",
            "module OptionsPatternSynonymFixture where",
            "pattern Unit = ()"
          ],
      testCase "a missing export list remains implicit rather than becoming empty" $ do
        moduleSurface <-
          parseSurface
            "Implicit.hs"
            ["module Implicit where", "value = ()"]
        surfaceExports moduleSurface @?= ImplicitExports,
      testCase "explicit exports preserve namespace, children, and module re-exports" $ do
        moduleSurface <-
          parseSurface
            "Explicit.hs"
            [ "{-# LANGUAGE ExplicitNamespaces #-}",
              "{-# LANGUAGE PatternSynonyms #-}",
              "module Explicit (value, Type(..), pattern Unit, module Data.List) where",
              "import Data.List",
              "data Type = Constructor",
              "pattern Unit = ()",
              "value = ()"
            ]
        case surfaceExports moduleSurface of
          ExplicitExports
            [ ExportValue valueName,
              ExportType typeName AllExportedChildren,
              ExportPattern patternName,
              ExportModule moduleName
              ] -> do
                unParsedName valueName @?= "value"
                unParsedName typeName @?= "Type"
                unParsedName patternName @?= "Unit"
                unParsedModuleName moduleName @?= "Data.List"
          otherExports ->
            assertFailure ("unexpected explicit export structure: " <> show otherExports)
    ]

assertParses :: FilePath -> [String] -> IO ()
assertParses sourcePath sourceLines =
  case parseHsModule sourcePath (unlines sourceLines) of
    Right _ ->
      pure ()
    Left parserError ->
      assertFailure
        ("expected parser success for " <> sourcePath <> ":\n" <> renderGhcParseFailure parserError)

parseSurface :: FilePath -> [String] -> IO ModuleSurface
parseSurface sourcePath sourceLines =
  case first renderGhcParseFailure (parseHsModule sourcePath (unlines sourceLines))
    >>= first show . moduleSurfaceFromGhcPs of
    Right moduleSurface ->
      pure moduleSurface
    Left surfaceError ->
      assertFailure ("expected module-surface success for " <> sourcePath <> ":\n" <> surfaceError)