packages feed

tadka-2.0.0.0: test/props/Labels.hs

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

-- | Primary/secondary labels (post-v1 hardening): the report location anchors
-- on the first primary label, JSON exposes an explicit @primary@ flag, and the
-- derive path (with @specSecondaryLabelFields@) renders identically to a manual
-- @buildContextWith@ instance — keeping the two doors symmetric for kinds too.
module Labels (group) where

import qualified Data.Aeson                       as A
import           Data.Text                        (Text)
import qualified Data.Text                        as T
import qualified Data.Text.Lazy                   as TL
import qualified Data.Text.Lazy.Encoding          as TLE
import           Prettyprinter                    (LayoutOptions (..),
                                                   PageWidth (Unbounded), layoutPretty,
                                                   pretty)
import           Prettyprinter.Render.Text        (renderStrict)

import           Hedgehog                         (Group (..), Property, property,
                                                   withTests, (===), assert)

import           Tadka
import           Tadka.Internal                   (buildContextWith)
import           Tadka.Internal.Related           (walkRelated)
import           Tadka.Internal.Renderer.Json     (LabelDTO (..), dtoLabels, toDTO)

rightOrErr :: Show a => Either a b -> b
rightOrErr = either (error . show) id

-- A source with two labels on different lines: secondary on line 1, primary on 3.
src3 :: NamedSource
src3 = rightOrErr (mkNamedSource "f.hs" "aaaa\nbbbb\ncccc\n")

kindFix :: Context
kindFix = buildContextWith src3
  [ (rightOrErr (mkSpan 0 2), Secondary, Just "context here")   -- line 1
  , (rightOrErr (mkSpan 10 2), Primary,  Just "the error")      -- line 3 (offset 10 = "aaaa\nbbbb\n" + 0)
  ]

data KindDiag = KindDiag
instance Diagnostic KindDiag where
  message _ = "boom"
  context _ = kindFix

gfx :: Diagnostic e => e -> Text
gfx e = case selectRenderer (withColorMode ColorNever (withUnicodeMode UnicodeAlways (withTarget TGraphical defaultConfig))) of
  SomeRenderer r@(Graphical _) -> renderStrict (layoutPretty (LayoutOptions Unbounded) (render r e))
  _                            -> ""

prop_anchorPrimary :: Property
prop_anchorPrimary = withTests 1 . property $ do
  let out = gfx KindDiag
  -- the location line must point at the primary label's line 3, not line 1
  assert (T.isInfixOf "f.hs:3:" out)

prop_glyphs :: Property
prop_glyphs = withTests 1 . property $ do
  let out = gfx KindDiag
  assert (T.isInfixOf "-- context here" out)   -- secondary uses '-'
  assert (T.isInfixOf "^^ the error"   out)    -- primary uses '^'

-- Two Secondary labels on the same line (both on line 1, "aaaa"): must be
-- distinguishable under ColorNever even though they share a LabelKind.
kindFix3 :: Context
kindFix3 = buildContextWith src3
  [ (rightOrErr (mkSpan 0 2), Secondary, Just "first")   -- "aa"
  , (rightOrErr (mkSpan 2 2), Secondary, Just "second")  -- "aa"
  ]

data KindDiag3 = KindDiag3
instance Diagnostic KindDiag3 where
  message _ = "boom"
  context _ = kindFix3

prop_glyphsSameKindDistinct :: Property
prop_glyphsSameKindDistinct = withTests 1 . property $ do
  let out = gfx KindDiag3
  -- rank 0 keeps the Secondary anchor '-', rank 1 cycles to '~'
  assert (T.isInfixOf "-- first"  out)
  assert (T.isInfixOf "~~ second" out)

prop_jsonFlag :: Property
prop_jsonFlag = withTests 1 . property $ do
  let labels = dtoLabels (toDTO 8 (walkRelated 8 (SomeDiagnostic KindDiag)))
  map ldPrimary labels === [False, True]        -- secondary first, primary second

-- === derive (with secondary) == manual ====================================

data DErr2 = DErr2 { d2src :: NamedSource, d2prim :: Span, d2sec :: Span, d2got :: Text }
  deriving (Show)

deriveDiagnostic defaultSpec
  { specSourceField          = Just 'd2src
  , specLabelFields          = [('d2prim, "here")]
  , specSecondaryLabelFields = [('d2sec,  "context")]
  , specMessage              = Just [| \e -> pretty ("undefined " <> d2got e) |]
  }
  ''DErr2

data MErr2 = MErr2 { m2src :: NamedSource, m2prim :: Span, m2sec :: Span, m2got :: Text }

instance Diagnostic MErr2 where
  message e = pretty ("undefined " <> m2got e)
  context e = buildContextWith (m2src e)
    [ (m2prim e, Primary,   Just "here")
    , (m2sec e,  Secondary, Just "context")
    ]

srcV :: NamedSource
srcV = rightOrErr (mkNamedSource "f.hs" "let a = bb")

dVal :: DErr2
dVal = DErr2 srcV (rightOrErr (mkSpan 4 1)) (rightOrErr (mkSpan 8 2)) "x"

mVal :: MErr2
mVal = MErr2 srcV (rightOrErr (mkSpan 4 1)) (rightOrErr (mkSpan 8 2)) "x"

nar :: Diagnostic e => e -> Text
nar e = case selectRenderer (withTarget TNarratable defaultConfig) of
  SomeRenderer r@(Narratable _) -> render r e
  _                             -> ""

jsn :: Diagnostic e => e -> Text
jsn e = case selectRenderer (withTarget TJson defaultConfig) of
  SomeRenderer r@(Json _) -> TL.toStrict (TLE.decodeUtf8 (A.encode (render r e)))
  _                       -> ""

prop_deriveEqualsManual :: Property
prop_deriveEqualsManual = withTests 1 . property $ do
  gfx dVal === gfx mVal
  nar dVal === nar mVal
  jsn dVal === jsn mVal

group :: Group
group = Group "Primary/secondary labels"
  [ ("graphical location anchors on the primary label", prop_anchorPrimary)
  , ("JSON marks primary vs secondary explicitly",      prop_jsonFlag)
  , ("graphical uses ^ for primary, - for secondary",   prop_glyphs)
  , ("same-kind labels on one line get distinct glyphs", prop_glyphsSameKindDistinct)
  , ("derived (with secondary) == manual, all handlers",prop_deriveEqualsManual)
  ]