Skip to content

Commit 414c845

Browse files
committed
Set CodeDescription from HaskellErrorIndex when available
1 parent 8d310fb commit 414c845

File tree

4 files changed

+119
-13
lines changed

4 files changed

+119
-13
lines changed

ghcide/ghcide.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ library
8383
, hiedb ^>= 0.6.0.0
8484
, hls-graph == 2.9.0.0
8585
, hls-plugin-api == 2.9.0.0
86+
, http-conduit
8687
, implicit-hie >= 0.1.4.0 && < 0.1.5
8788
, lens
8889
, lens-aeson
@@ -135,6 +136,7 @@ library
135136
Development.IDE.Core.Debouncer
136137
Development.IDE.Core.FileStore
137138
Development.IDE.Core.FileUtils
139+
Development.IDE.Core.HaskellErrorIndex
138140
Development.IDE.Core.IdeConfiguration
139141
Development.IDE.Core.OfInterest
140142
Development.IDE.Core.PluginUtils
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
-- Retrieve the list of errors from the HaskellErrorIndex via its API
2+
module Development.IDE.Core.HaskellErrorIndex where
3+
4+
import Control.Exception (tryJust)
5+
import Data.Aeson (FromJSON (..), (.:), withObject)
6+
import qualified Data.Map as M
7+
import qualified Data.Text as T
8+
import Development.IDE.Types.Diagnostics
9+
import GHC.Types.Error (DiagnosticCode)
10+
import Ide.Logger (Recorder, Pretty (..), WithPriority, logWith, Priority (..), vcat)
11+
import Language.LSP.Protocol.Types (Uri (..), CodeDescription (..))
12+
import Network.HTTP.Simple (HttpException, JSONException, getResponseBody, httpJSON)
13+
14+
data Log
15+
= LogHaskellErrorIndexInitialized
16+
| LogHaskellErrorIndexJSONError JSONException
17+
| LogHaskellErrorIndexHTTPError HttpException
18+
deriving (Show)
19+
20+
instance Pretty Log where
21+
pretty = \case
22+
LogHaskellErrorIndexInitialized -> "Initialized Haskell Error Index from internet"
23+
LogHaskellErrorIndexJSONError err ->
24+
vcat
25+
[ "Failed to initialize Haskell Error Index due to a JSON error:"
26+
, pretty (show err)
27+
]
28+
LogHaskellErrorIndexHTTPError err ->
29+
vcat
30+
[ "Failed to initialize Haskell Error Index due to an HTTP error:"
31+
, pretty (show err)
32+
]
33+
34+
newtype HaskellErrorIndex = HaskellErrorIndex (M.Map T.Text HEIError)
35+
deriving (Show, Eq, Ord)
36+
37+
data HEIError = HEIError
38+
{ code :: T.Text
39+
, route :: T.Text
40+
}
41+
deriving (Show, Eq, Ord)
42+
43+
errorsToIndex :: [HEIError] -> HaskellErrorIndex
44+
errorsToIndex errs = HaskellErrorIndex $ M.fromList $ map (\err -> (code err, err)) errs
45+
46+
instance FromJSON HEIError where
47+
parseJSON =
48+
withObject "HEIError" $ \v ->
49+
HEIError
50+
<$> v .: "code"
51+
<*> v .: "route"
52+
53+
instance FromJSON HaskellErrorIndex where
54+
parseJSON = fmap errorsToIndex <$> parseJSON
55+
56+
initHaskellErrorIndex :: Recorder (WithPriority Log) -> IO (Maybe HaskellErrorIndex)
57+
initHaskellErrorIndex recorder = do
58+
res <- tryJust handleJSONError $ tryJust handleHttpError $ httpJSON "https://errors.haskell.org/api/errors.json"
59+
case res of
60+
Left jsonErr -> do
61+
logWith recorder Info (LogHaskellErrorIndexJSONError jsonErr)
62+
pure Nothing
63+
Right (Left httpErr) -> do
64+
logWith recorder Info (LogHaskellErrorIndexHTTPError httpErr)
65+
pure Nothing
66+
Right (Right res) -> pure $ Just (getResponseBody res)
67+
where
68+
handleJSONError :: JSONException -> Maybe JSONException
69+
handleJSONError = Just
70+
handleHttpError :: HttpException -> Maybe HttpException
71+
handleHttpError = Just
72+
73+
heiGetError :: HaskellErrorIndex -> DiagnosticCode -> Maybe HEIError
74+
heiGetError (HaskellErrorIndex index) code = showGhcCode code `M.lookup` index
75+
76+
attachHeiErrorCodeDescription :: HEIError -> Diagnostic -> Diagnostic
77+
attachHeiErrorCodeDescription heiError diag =
78+
diag
79+
{ _codeDescription = Just $ CodeDescription $ Uri $ "https://errors.haskell.org/" <> route heiError
80+
}

ghcide/src/Development/IDE/Core/Shake.hs

+27-5
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ import Control.Concurrent.STM.Stats (atomicallyNamed)
8282
import Control.Concurrent.Strict
8383
import Control.DeepSeq
8484
import Control.Exception.Extra hiding (bracket_)
85-
import Control.Lens ((&), (?~), (%~))
85+
import Control.Lens ((&), (?~), (%~), over)
8686
import Control.Monad.Extra
8787
import Control.Monad.IO.Class
8888
import Control.Monad.Reader
@@ -120,6 +120,8 @@ import Data.Vector (Vector)
120120
import qualified Data.Vector as Vector
121121
import Development.IDE.Core.Debouncer
122122
import Development.IDE.Core.FileUtils (getModTime)
123+
import Development.IDE.Core.HaskellErrorIndex hiding (Log)
124+
import qualified Development.IDE.Core.HaskellErrorIndex as HaskellErrorIndex
123125
import Development.IDE.Core.PositionMapping
124126
import Development.IDE.Core.ProgressReporting
125127
import Development.IDE.Core.RuleTypes
@@ -151,6 +153,7 @@ import Development.IDE.Types.Shake
151153
import qualified Focus
152154
import GHC.Fingerprint
153155
import GHC.Stack (HasCallStack)
156+
import GHC.Types.Error (diagnosticCode, errMsgDiagnostic)
154157
import HieDb.Types
155158
import Ide.Logger hiding (Priority)
156159
import qualified Ide.Logger as Logger
@@ -194,6 +197,7 @@ data Log
194197
| LogShakeGarbageCollection !T.Text !Int !Seconds
195198
-- * OfInterest Log messages
196199
| LogSetFilesOfInterest ![(NormalizedFilePath, FileOfInterestStatus)]
200+
| LogInitializeHaskellErrorIndex !HaskellErrorIndex.Log
197201
deriving Show
198202

199203
instance Pretty Log where
@@ -237,6 +241,8 @@ instance Pretty Log where
237241
LogSetFilesOfInterest ofInterest ->
238242
"Set files of interst to" <> Pretty.line
239243
<> indent 4 (pretty $ fmap (first fromNormalizedFilePath) ofInterest)
244+
LogInitializeHaskellErrorIndex hei ->
245+
"Haskell Error Index:" <+> pretty hei
240246

241247
-- | We need to serialize writes to the database, so we send any function that
242248
-- needs to write to the database over the channel, where it will be picked up by
@@ -332,6 +338,8 @@ data ShakeExtras = ShakeExtras
332338
-- ^ Queue of restart actions to be run.
333339
, loaderQueue :: TQueue (IO ())
334340
-- ^ Queue of loader actions to be run.
341+
, haskellErrorIndex :: Maybe HaskellErrorIndex
342+
-- ^ List of errors in the Haskell Error Index (errors.haskell.org)
335343
}
336344

337345
type WithProgressFunc = forall a.
@@ -702,6 +710,7 @@ shakeOpen recorder lspEnv defaultConfig idePlugins debouncer
702710
dirtyKeys <- newTVarIO mempty
703711
-- Take one VFS snapshot at the start
704712
vfsVar <- newTVarIO =<< vfsSnapshot lspEnv
713+
haskellErrorIndex <- initHaskellErrorIndex (cmapWithPrio LogInitializeHaskellErrorIndex recorder)
705714
pure ShakeExtras{shakeRecorder = recorder, ..}
706715
shakeDb <-
707716
shakeNewDatabase
@@ -1321,24 +1330,25 @@ traceA (A Failed{}) = "Failed"
13211330
traceA (A Stale{}) = "Stale"
13221331
traceA (A Succeeded{}) = "Success"
13231332

1324-
updateFileDiagnostics :: MonadIO m
1325-
=> Recorder (WithPriority Log)
1333+
updateFileDiagnostics
1334+
:: Recorder (WithPriority Log)
13261335
-> NormalizedFilePath
13271336
-> Maybe Int32
13281337
-> Key
13291338
-> ShakeExtras
13301339
-> [FileDiagnostic] -- ^ current results
1331-
-> m ()
1340+
-> Action ()
13321341
updateFileDiagnostics recorder fp ver k ShakeExtras{diagnostics, hiddenDiagnostics, publishedDiagnostics, debouncer, lspEnv, ideTesting} current0 = do
1342+
hei <- haskellErrorIndex <$> getShakeExtras
13331343
liftIO $ withTrace ("update diagnostics " <> fromString(fromNormalizedFilePath fp)) $ \ addTag -> do
13341344
addTag "key" (show k)
1345+
current <- traverse (attachHEI hei) $ map (over fdLspDiagnosticL diagsFromRule) current0
13351346
let (currentShown, currentHidden) = partition ((== ShowDiag) . fdShouldShowDiagnostic) current
13361347
uri = filePathToUri' fp
13371348
addTagUnsafe :: String -> String -> String -> a -> a
13381349
addTagUnsafe msg t x v = unsafePerformIO(addTag (msg <> t) x) `seq` v
13391350
update :: (forall a. String -> String -> a -> a) -> [FileDiagnostic] -> STMDiagnosticStore -> STM [FileDiagnostic]
13401351
update addTagUnsafeMethod new store = addTagUnsafeMethod "count" (show $ Prelude.length new) $ setStageDiagnostics addTagUnsafeMethod uri ver (renderKey k) new store
1341-
current = map (fdLspDiagnosticL %~ diagsFromRule) current0
13421352
addTag "version" (show ver)
13431353
mask_ $ do
13441354
-- Mask async exceptions to ensure that updated diagnostics are always
@@ -1362,6 +1372,18 @@ updateFileDiagnostics recorder fp ver k ShakeExtras{diagnostics, hiddenDiagnosti
13621372
LSP.PublishDiagnosticsParams (fromNormalizedUri uri') (fmap fromIntegral ver) (map fdLspDiagnostic newDiags)
13631373
return action
13641374
where
1375+
attachHEI :: Maybe HaskellErrorIndex -> FileDiagnostic -> IO FileDiagnostic
1376+
attachHEI mbHei diag
1377+
| Just hei <- mbHei
1378+
, SomeStructuredMessage msg <- fdStructuredMessage diag
1379+
, Just code <- diagnosticCode (errMsgDiagnostic msg)
1380+
, Just heiError <- hei `heiGetError` code
1381+
= pure $ diag & fdLspDiagnosticL %~ attachHeiErrorCodeDescription heiError
1382+
| otherwise
1383+
= do
1384+
writeFile "/home/dylan/attachHEI" (show mbHei <> "\n" <> show diag)
1385+
pure diag
1386+
13651387
diagsFromRule :: Diagnostic -> Diagnostic
13661388
diagsFromRule c@Diagnostic{_range}
13671389
| coerce ideTesting = c & L.relatedInformation ?~

ghcide/src/Development/IDE/Types/Diagnostics.hs

+10-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Development.IDE.Types.Diagnostics (
1919
ideErrorFromLspDiag,
2020
showDiagnostics,
2121
showDiagnosticsColored,
22+
showGhcCode,
2223
IdeResultNoDiagnosticsEarlyCutoff,
2324
attachReason,
2425
attachedReason) where
@@ -77,19 +78,20 @@ ideErrorFromLspDiag lspDiag fdFilePath origMsg =
7778
Just msg -> SomeStructuredMessage msg
7879
fdLspDiagnostic = (attachReason (fmap (diagnosticReason . errMsgDiagnostic) origMsg) lspDiag)
7980
#if MIN_VERSION_ghc(9,6,1)
80-
{ _code = fmap ghcCodeToLspCode . diagnosticCode . errMsgDiagnostic =<< origMsg
81+
{ _code = fmap (InR . showGhcCode) . diagnosticCode . errMsgDiagnostic =<< origMsg
8182
}
8283
#endif
84+
in
85+
FileDiagnostic {..}
86+
8387
#if MIN_VERSION_ghc(9,8,1)
84-
ghcCodeToLspCode :: DiagnosticCode -> Int32 LSP.|? T.Text
85-
ghcCodeToLspCode = InR . T.pack . show
88+
showGhcCode :: DiagnosticCode -> T.Text
89+
showGhcCode = T.pack . show
8690
#elif MIN_VERSION_ghc(9,6,1)
87-
-- DiagnosticCode only got a show instance in 9.8.1
88-
ghcCodeToLspCode :: DiagnosticCode -> Int32 LSP.|? T.Text
89-
ghcCodeToLspCode (DiagnosticCode prefix c) = InR $ T.pack $ prefix ++ "-" ++ printf "%05d" c
91+
-- DiagnosticCode only got a show instance in 9.8.1
92+
showGhcCode :: DiagnosticCode -> T.Text
93+
showGhcCode (DiagnosticCode prefix c) = T.pack $ prefix ++ "-" ++ printf "%05d" c
9094
#endif
91-
in
92-
FileDiagnostic {..}
9395

9496
attachedReason :: Traversal' Diagnostic (Maybe JSON.Value)
9597
attachedReason = data_ . non (JSON.object []) . JSON.atKey "attachedReason"

0 commit comments

Comments
 (0)