Skip to content

Commit 305eaab

Browse files
committed
Use structured diagnostics in pragmas plugin
Changes suggestion provider in pragmas plugin to use structured diagnostics and ghc machinery to generate hints
1 parent 349ff6e commit 305eaab

File tree

2 files changed

+39
-34
lines changed

2 files changed

+39
-34
lines changed

haskell-language-server.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ library hls-pragmas-plugin
905905
, text
906906
, transformers
907907
, containers
908+
, ghc
908909

909910
test-suite hls-pragmas-plugin-tests
910911
import: defaults, pedantic, test-defaults, warnings

plugins/hls-pragmas-plugin/src/Ide/Plugin/Pragmas.hs

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE DataKinds #-}
33
{-# LANGUAGE DuplicateRecordFields #-}
4+
{-# LANGUAGE LambdaCase #-}
45
{-# LANGUAGE MultiWayIf #-}
56
{-# LANGUAGE OverloadedStrings #-}
67
{-# LANGUAGE ViewPatterns #-}
@@ -25,14 +26,17 @@ import qualified Data.Map as M
2526
import Data.Maybe (mapMaybe)
2627
import qualified Data.Text as T
2728
import Development.IDE hiding (line)
28-
import Development.IDE.Core.Compile (sourceParser,
29-
sourceTypecheck)
29+
import Development.IDE.Core.FileStore (getVersionedTextDoc)
3030
import Development.IDE.Core.PluginUtils
3131
import Development.IDE.GHC.Compat
32+
import Development.IDE.GHC.Compat.Error (msgEnvelopeErrorL)
3233
import Development.IDE.Plugin.Completions (ghcideCompletionsPluginPriority)
3334
import Development.IDE.Plugin.Completions.Logic (getCompletionPrefixFromRope)
3435
import Development.IDE.Plugin.Completions.Types (PosPrefixInfo (..))
3536
import qualified Development.IDE.Spans.Pragmas as Pragmas
37+
import GHC.Types.Error (GhcHint (SuggestExtension),
38+
LanguageExtensionHint (..),
39+
diagnosticHints)
3640
import Ide.Plugin.Error
3741
import Ide.Types
3842
import qualified Language.LSP.Protocol.Lens as L
@@ -74,19 +78,25 @@ suggestPragmaProvider = mkCodeActionProvider suggest
7478
suggestDisableWarningProvider :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
7579
suggestDisableWarningProvider = mkCodeActionProvider $ const suggestDisableWarning
7680

77-
mkCodeActionProvider :: (Maybe DynFlags -> Diagnostic -> [PragmaEdit]) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
81+
mkCodeActionProvider :: (Maybe DynFlags -> FileDiagnostic -> [PragmaEdit]) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
7882
mkCodeActionProvider mkSuggest state _plId
79-
(LSP.CodeActionParams _ _ LSP.TextDocumentIdentifier{ _uri = uri } _ (LSP.CodeActionContext diags _monly _)) = do
80-
normalizedFilePath <- getNormalizedFilePathE uri
83+
(LSP.CodeActionParams _ _ docId@LSP.TextDocumentIdentifier{ _uri = uri } caRange _) = do
84+
verTxtDocId <- liftIO $ runAction "classplugin.codeAction.getVersionedTextDoc" state $ getVersionedTextDoc docId
85+
normalizedFilePath <- getNormalizedFilePathE (verTxtDocId ^. L.uri)
8186
-- ghc session to get some dynflags even if module isn't parsed
8287
(hscEnv -> hsc_dflags -> sessionDynFlags, _) <-
8388
runActionE "Pragmas.GhcSession" state $ useWithStaleE GhcSession normalizedFilePath
8489
fileContents <- liftIO $ runAction "Pragmas.GetFileContents" state $ getFileContents normalizedFilePath
8590
parsedModule <- liftIO $ runAction "Pragmas.GetParsedModule" state $ getParsedModule normalizedFilePath
91+
92+
8693
let parsedModuleDynFlags = ms_hspp_opts . pm_mod_summary <$> parsedModule
8794
nextPragmaInfo = Pragmas.getNextPragmaInfo sessionDynFlags fileContents
88-
pedits = nubOrdOn snd $ concatMap (mkSuggest parsedModuleDynFlags) diags
89-
pure $ LSP.InL $ pragmaEditToAction uri nextPragmaInfo <$> pedits
95+
activeDiagnosticsInRange (shakeExtras state) normalizedFilePath caRange >>= \case
96+
Nothing -> pure $ LSP.InL []
97+
Just fileDiags -> do
98+
let actions = concatMap (mkSuggest parsedModuleDynFlags) fileDiags
99+
pure $ LSP.InL $ pragmaEditToAction uri nextPragmaInfo <$> nubOrdOn snd actions
90100

91101

92102

@@ -115,15 +125,15 @@ pragmaEditToAction uri Pragmas.NextPragmaInfo{ nextPragmaLine, lineSplitTextEdit
115125
Nothing
116126
Nothing
117127

118-
suggest :: Maybe DynFlags -> Diagnostic -> [PragmaEdit]
128+
suggest :: Maybe DynFlags -> FileDiagnostic -> [PragmaEdit]
119129
suggest dflags diag =
120130
suggestAddPragma dflags diag
121131

122132
-- ---------------------------------------------------------------------
123133

124-
suggestDisableWarning :: Diagnostic -> [PragmaEdit]
134+
suggestDisableWarning :: FileDiagnostic -> [PragmaEdit]
125135
suggestDisableWarning diagnostic
126-
| Just (Just (JSON.Array attachedReasons)) <- diagnostic ^? attachedReason
136+
| Just (Just (JSON.Array attachedReasons)) <- diagnostic ^? fdLspDiagnosticL . attachedReason
127137
=
128138
[ ("Disable \"" <> w <> "\" warnings", OptGHC w)
129139
| JSON.String attachedReason <- Foldable.toList attachedReasons
@@ -143,13 +153,10 @@ warningBlacklist =
143153
-- ---------------------------------------------------------------------
144154

145155
-- | Offer to add a missing Language Pragma to the top of a file.
146-
-- Pragmas are defined by a curated list of known pragmas, see 'possiblePragmas'.
147-
suggestAddPragma :: Maybe DynFlags -> Diagnostic -> [PragmaEdit]
148-
suggestAddPragma mDynflags Diagnostic {_message, _source}
149-
| _source == Just sourceTypecheck || _source == Just sourceParser = genPragma _message
156+
-- Pragmas are defined by a cuNewrated list of known pragmas, see 'possiblePragmas'.
157+
suggestAddPragma :: Maybe DynFlags -> FileDiagnostic -> [PragmaEdit]
158+
suggestAddPragma mDynflags fd= [("Add \"" <> r <> "\"", LangExt r) | r <- map (T.pack . show) $ suggestsExtension fd, r `notElem` disabled]
150159
where
151-
genPragma target =
152-
[("Add \"" <> r <> "\"", LangExt r) | r <- findPragma target, r `notElem` disabled]
153160
disabled
154161
| Just dynFlags <- mDynflags =
155162
-- GHC does not export 'OnOff', so we have to view it as string
@@ -158,25 +165,22 @@ suggestAddPragma mDynflags Diagnostic {_message, _source}
158165
-- When the module failed to parse, we don't have access to its
159166
-- dynFlags. In that case, simply don't disable any pragmas.
160167
[]
161-
suggestAddPragma _ _ = []
162168

163-
-- | Find all Pragmas are an infix of the search term.
164-
findPragma :: T.Text -> [T.Text]
165-
findPragma str = concatMap check possiblePragmas
166-
where
167-
check p = [p | T.isInfixOf p str]
168-
169-
-- We exclude the Strict extension as it causes many false positives, see
170-
-- the discussion at https://github.com/haskell/ghcide/pull/638
171-
--
172-
-- We don't include the No- variants, as GHC never suggests disabling an
173-
-- extension in an error message.
174-
possiblePragmas :: [T.Text]
175-
possiblePragmas =
176-
[ name
177-
| FlagSpec{flagSpecName = T.pack -> name} <- xFlags
178-
, "Strict" /= name
179-
]
169+
suggestsExtension :: FileDiagnostic -> [Extension]
170+
suggestsExtension message = case message ^? fdStructuredMessageL . _SomeStructuredMessage . msgEnvelopeErrorL of
171+
Just s -> concat $ mapMaybe (\case
172+
SuggestExtension s -> Just $ ghcHintSuggestsExtension s
173+
_ -> Nothing) (diagnosticHints s)
174+
_ -> []
175+
176+
ghcHintSuggestsExtension :: LanguageExtensionHint -> [Extension]
177+
ghcHintSuggestsExtension (SuggestSingleExtension _ ext) = [ext]
178+
ghcHintSuggestsExtension (SuggestAnyExtension _ (ext:_)) = [ext] -- ghc suggests any of those, we pick first
179+
ghcHintSuggestsExtension (SuggestAnyExtension _ []) = []
180+
ghcHintSuggestsExtension (SuggestExtensions _ ext) = ext
181+
ghcHintSuggestsExtension (SuggestExtensionInOrderTo _ ext) = [ext]
182+
183+
180184

181185
-- | All language pragmas, including the No- variants
182186
allPragmas :: [T.Text]

0 commit comments

Comments
 (0)