Skip to content

Commit 079ed08

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 079ed08

File tree

2 files changed

+43
-35
lines changed

2 files changed

+43
-35
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: 42 additions & 35 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 #-}
@@ -20,19 +21,21 @@ import Control.Monad.IO.Class (MonadIO (liftIO))
2021
import qualified Data.Aeson as JSON
2122
import Data.Char (isAlphaNum)
2223
import qualified Data.Foldable as Foldable
23-
import Data.List.Extra (nubOrdOn)
2424
import qualified Data.Map as M
2525
import Data.Maybe (mapMaybe)
2626
import qualified Data.Text as T
2727
import Development.IDE hiding (line)
28-
import Development.IDE.Core.Compile (sourceParser,
29-
sourceTypecheck)
28+
import Development.IDE.Core.FileStore (getVersionedTextDoc)
3029
import Development.IDE.Core.PluginUtils
3130
import Development.IDE.GHC.Compat
31+
import Development.IDE.GHC.Compat.Error (msgEnvelopeErrorL)
3232
import Development.IDE.Plugin.Completions (ghcideCompletionsPluginPriority)
3333
import Development.IDE.Plugin.Completions.Logic (getCompletionPrefixFromRope)
3434
import Development.IDE.Plugin.Completions.Types (PosPrefixInfo (..))
3535
import qualified Development.IDE.Spans.Pragmas as Pragmas
36+
import GHC.Types.Error (GhcHint (SuggestExtension),
37+
LanguageExtensionHint (..),
38+
diagnosticHints)
3639
import Ide.Plugin.Error
3740
import Ide.Types
3841
import qualified Language.LSP.Protocol.Lens as L
@@ -74,19 +77,27 @@ suggestPragmaProvider = mkCodeActionProvider suggest
7477
suggestDisableWarningProvider :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
7578
suggestDisableWarningProvider = mkCodeActionProvider $ const suggestDisableWarning
7679

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

91102

92103

@@ -115,15 +126,15 @@ pragmaEditToAction uri Pragmas.NextPragmaInfo{ nextPragmaLine, lineSplitTextEdit
115126
Nothing
116127
Nothing
117128

118-
suggest :: Maybe DynFlags -> Diagnostic -> [PragmaEdit]
129+
suggest :: Maybe DynFlags -> FileDiagnostic -> [PragmaEdit]
119130
suggest dflags diag =
120131
suggestAddPragma dflags diag
121132

122133
-- ---------------------------------------------------------------------
123134

124-
suggestDisableWarning :: Diagnostic -> [PragmaEdit]
135+
suggestDisableWarning :: FileDiagnostic -> [PragmaEdit]
125136
suggestDisableWarning diagnostic
126-
| Just (Just (JSON.Array attachedReasons)) <- diagnostic ^? attachedReason
137+
| Just (Just (JSON.Array attachedReasons)) <- diagnostic ^? fdLspDiagnosticL . attachedReason
127138
=
128139
[ ("Disable \"" <> w <> "\" warnings", OptGHC w)
129140
| JSON.String attachedReason <- Foldable.toList attachedReasons
@@ -143,13 +154,12 @@ warningBlacklist =
143154
-- ---------------------------------------------------------------------
144155

145156
-- | 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
157+
-- Pragmas are defined by a cuNewrated list of known pragmas, see 'possiblePragmas'.
158+
suggestAddPragma :: Maybe DynFlags -> FileDiagnostic -> [PragmaEdit]
159+
suggestAddPragma mDynflags fd= filterPragma fd
150160
where
151-
genPragma target =
152-
[("Add \"" <> r <> "\"", LangExt r) | r <- findPragma target, r `notElem` disabled]
161+
filterPragma fd =
162+
[("Add \"" <> r <> "\"", LangExt r) | r <- map (T.pack . show) $ suggestsExtension fd, r `notElem` disabled] --NOCOMMIT
153163
disabled
154164
| Just dynFlags <- mDynflags =
155165
-- GHC does not export 'OnOff', so we have to view it as string
@@ -158,25 +168,22 @@ suggestAddPragma mDynflags Diagnostic {_message, _source}
158168
-- When the module failed to parse, we don't have access to its
159169
-- dynFlags. In that case, simply don't disable any pragmas.
160170
[]
161-
suggestAddPragma _ _ = []
162171

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-
]
172+
suggestsExtension :: FileDiagnostic -> [Extension]
173+
suggestsExtension message = case message ^? fdStructuredMessageL . _SomeStructuredMessage . msgEnvelopeErrorL of
174+
Just s -> concat $ mapMaybe (\case
175+
SuggestExtension s -> Just $ ghcHintSuggestsExtension s
176+
_ -> Nothing) (diagnosticHints s)
177+
_ -> []
178+
179+
ghcHintSuggestsExtension :: LanguageExtensionHint -> [Extension]
180+
ghcHintSuggestsExtension (SuggestSingleExtension _ ext) = [ext]
181+
ghcHintSuggestsExtension (SuggestAnyExtension _ (ext:_)) = [ext] -- ghc suggests any of those, we pick first
182+
ghcHintSuggestsExtension (SuggestAnyExtension _ []) = []
183+
ghcHintSuggestsExtension (SuggestExtensions _ ext) = ext
184+
ghcHintSuggestsExtension (SuggestExtensionInOrderTo _ ext) = [ext]
185+
186+
180187

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

0 commit comments

Comments
 (0)