packages feed

hls-hlint-plugin 2.0.0.1 → 2.1.0.0

raw patch · 3 files changed

+201/−177 lines, 3 filesdep +mtldep +row-typesdep ~ghcidedep ~hlintdep ~hls-plugin-apiPVP ok

version bump matches the API change (PVP)

Dependencies added: mtl, row-types

Dependency ranges changed: ghcide, hlint, hls-plugin-api, hls-test-utils

API changes (from Hackage documentation)

- Ide.Plugin.Hlint: instance Data.Aeson.Types.FromJSON.FromJSON Ide.Plugin.Hlint.ApplyOneParams
- Ide.Plugin.Hlint: instance Data.Aeson.Types.ToJSON.ToJSON Ide.Plugin.Hlint.ApplyOneParams
- Ide.Plugin.Hlint: instance GHC.Classes.Eq Ide.Plugin.Hlint.ApplyOneParams
- Ide.Plugin.Hlint: instance GHC.Generics.Generic Ide.Plugin.Hlint.ApplyOneParams
- Ide.Plugin.Hlint: instance GHC.Show.Show Ide.Plugin.Hlint.ApplyOneParams
- Ide.Plugin.Hlint: instance GHC.Show.Show Ide.Plugin.Hlint.Log
+ Ide.Plugin.Hlint: LogResolve :: a -> Log
+ Ide.Plugin.Hlint: instance Data.Aeson.Types.FromJSON.FromJSON Ide.Plugin.Hlint.HlintResolveCommands
+ Ide.Plugin.Hlint: instance Data.Aeson.Types.FromJSON.FromJSON Ide.Plugin.Hlint.OneHint
+ Ide.Plugin.Hlint: instance Data.Aeson.Types.ToJSON.ToJSON Ide.Plugin.Hlint.HlintResolveCommands
+ Ide.Plugin.Hlint: instance Data.Aeson.Types.ToJSON.ToJSON Ide.Plugin.Hlint.OneHint
+ Ide.Plugin.Hlint: instance GHC.Generics.Generic Ide.Plugin.Hlint.HlintResolveCommands
+ Ide.Plugin.Hlint: instance GHC.Generics.Generic Ide.Plugin.Hlint.OneHint

Files

hls-hlint-plugin.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name:          hls-hlint-plugin-version:            2.0.0.1+version:       2.1.0.0 synopsis:      Hlint integration plugin with Haskell Language Server description:   Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>@@ -30,8 +30,6 @@   manual:      True  library-  if impl(ghc >= 9.5)-    buildable: False   exposed-modules:    Ide.Plugin.Hlint   hs-source-dirs:     src   build-depends:@@ -47,12 +45,13 @@     , extra     , filepath     , ghc-exactprint        >=0.6.3.4-    , ghcide                == 2.0.0.1+    , ghcide                == 2.1.0.0     , hashable-    , hlint                 < 3.6-    , hls-plugin-api        == 2.0.0.1+    , hlint                 < 3.7+    , hls-plugin-api        == 2.1.0.0     , lens     , lsp+    , mtl     , refact     , regex-tdfa     , stm@@ -78,8 +77,6 @@     TypeOperators  test-suite tests-  if impl(ghc >= 9.5)-    buildable: False   type:             exitcode-stdio-1.0   default-language: Haskell2010   hs-source-dirs:   test@@ -92,7 +89,8 @@     , filepath     , hls-hlint-plugin     , hls-plugin-api-    , hls-test-utils      == 2.0.0.1+    , hls-test-utils      == 2.1.0.0     , lens     , lsp-types+    , row-types     , text
src/Ide/Plugin/Hlint.hs view
@@ -1,23 +1,24 @@-{-# LANGUAGE CPP                   #-}-{-# LANGUAGE DeriveAnyClass        #-}-{-# LANGUAGE DeriveGeneric         #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts      #-}-{-# LANGUAGE FlexibleInstances     #-}-{-# LANGUAGE LambdaCase            #-}-{-# LANGUAGE MultiWayIf            #-}-{-# LANGUAGE NamedFieldPuns        #-}-{-# LANGUAGE OverloadedLabels      #-}-{-# LANGUAGE OverloadedStrings     #-}-{-# LANGUAGE PackageImports        #-}-{-# LANGUAGE PatternSynonyms       #-}-{-# LANGUAGE RecordWildCards       #-}-{-# LANGUAGE ScopedTypeVariables   #-}-{-# LANGUAGE StrictData            #-}-{-# LANGUAGE TupleSections         #-}-{-# LANGUAGE TypeFamilies          #-}-{-# LANGUAGE ViewPatterns          #-}-+{-# LANGUAGE CPP                       #-}+{-# LANGUAGE DeriveAnyClass            #-}+{-# LANGUAGE DeriveGeneric             #-}+{-# LANGUAGE DuplicateRecordFields     #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts          #-}+{-# LANGUAGE FlexibleInstances         #-}+{-# LANGUAGE LambdaCase                #-}+{-# LANGUAGE MultiWayIf                #-}+{-# LANGUAGE NamedFieldPuns            #-}+{-# LANGUAGE OverloadedLabels          #-}+{-# LANGUAGE OverloadedStrings         #-}+{-# LANGUAGE PackageImports            #-}+{-# LANGUAGE PatternSynonyms           #-}+{-# LANGUAGE RecordWildCards           #-}+{-# LANGUAGE ScopedTypeVariables       #-}+{-# LANGUAGE StrictData                #-}+{-# LANGUAGE TupleSections             #-}+{-# LANGUAGE TypeApplications          #-}+{-# LANGUAGE TypeFamilies              #-}+{-# LANGUAGE ViewPatterns              #-} {-# OPTIONS_GHC -Wno-orphans   #-}  -- On 9.4 we get a new redundant constraint warning, but deleting the@@ -40,16 +41,20 @@ import           Control.Concurrent.STM import           Control.DeepSeq import           Control.Exception-import           Control.Lens                                       ((^.))+import           Control.Lens                                       ((?~), (^.)) import           Control.Monad-import           Control.Monad.IO.Class-import           Control.Monad.Trans.Except+import           Control.Monad.Error.Class                          (MonadError (throwError))+import           Control.Monad.IO.Class                             (MonadIO (liftIO))+import           Control.Monad.Trans.Class                          (MonadTrans (lift))+import           Control.Monad.Trans.Except                         (ExceptT (..),+                                                                     runExceptT) import           Data.Aeson.Types                                   (FromJSON (..),                                                                      ToJSON (..),                                                                      Value (..)) import qualified Data.ByteString                                    as BS import           Data.Hashable import qualified Data.HashMap.Strict                                as Map+import qualified Data.Map                                           as M import           Data.Maybe import qualified Data.Text                                          as T import qualified Data.Text.Encoding                                 as T@@ -57,6 +62,7 @@ import           Development.IDE                                    hiding                                                                     (Error,                                                                      getExtensions)+import           Development.IDE.Core.Compile                       (sourceParser) import           Development.IDE.Core.Rules                         (defineNoFile,                                                                      getParsedModuleWithComments) import           Development.IDE.Core.Shake                         (getDiagnostics)@@ -111,24 +117,22 @@ import           Language.Haskell.GhclibParserEx.Fixity             as GhclibParserEx (applyFixities) import qualified Refact.Fixity                                      as Refact #endif- import           Ide.Plugin.Config                                  hiding                                                                     (Config)+import           Ide.Plugin.Error import           Ide.Plugin.Properties+import           Ide.Plugin.Resolve import           Ide.PluginUtils import           Ide.Types                                          hiding                                                                     (Config) import           Language.Haskell.HLint                             as Hlint hiding                                                                              (Error)-import           Language.LSP.Server                                (ProgressCancellable (Cancellable),-                                                                     sendRequest,-                                                                     withIndefiniteProgress)-import           Language.LSP.Types                                 hiding-                                                                    (SemanticTokenAbsolute (length, line),-                                                                     SemanticTokenRelative (length),-                                                                     SemanticTokensEdit (_start))-import qualified Language.LSP.Types                                 as LSP-import qualified Language.LSP.Types.Lens                            as LSP+import qualified Language.LSP.Protocol.Lens                         as LSP+import           Language.LSP.Protocol.Message+import           Language.LSP.Protocol.Types                        hiding+                                                                    (Null)+import qualified Language.LSP.Protocol.Types                        as LSP+import           Language.LSP.Server                                (getVersionedTextDoc)  import qualified Development.IDE.Core.Shake                         as Shake import           Development.IDE.Spans.Pragmas                      (LineSplitTextEdits (LineSplitTextEdits),@@ -144,6 +148,7 @@ import           System.Environment                                 (setEnv,                                                                      unsetEnv) #endif+import           Development.IDE.Core.PluginUtils                   as PluginUtils import           Text.Regex.TDFA.Text                               () -- --------------------------------------------------------------------- @@ -153,7 +158,7 @@   | LogGeneratedIdeas NormalizedFilePath [[Refact.Refactoring Refact.SrcSpan]]   | LogGetIdeas NormalizedFilePath   | LogUsingExtensions NormalizedFilePath [String] -- Extension is only imported conditionally, so we just stringify them-  deriving Show+  | forall a. (Pretty a) => LogResolve a  instance Pretty Log where   pretty = \case@@ -162,6 +167,7 @@     LogGeneratedIdeas fp ideas -> "Generated hlint ideas for for" <+> viaShow fp <> ":" <+> viaShow ideas     LogUsingExtensions fp exts -> "Using extensions for " <+> viaShow fp <> ":" <+> pretty exts     LogGetIdeas fp -> "Getting hlint ideas for " <+> viaShow fp+    LogResolve msg -> pretty msg  #ifdef HLINT_ON_GHC_LIB -- Reimplementing this, since the one in Development.IDE.GHC.Compat isn't for ghc-lib@@ -186,13 +192,13 @@ #endif  descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState-descriptor recorder plId = (defaultPluginDescriptor plId)+descriptor recorder plId =+  let resolveRecorder = cmapWithPrio LogResolve recorder+      (pluginCommands, pluginHandlers) = mkCodeActionWithResolveAndCommand resolveRecorder plId codeActionProvider (resolveProvider recorder)+  in (defaultPluginDescriptor plId)   { pluginRules = rules recorder plId-  , pluginCommands =-      [ PluginCommand "applyOne" "Apply a single hint" (applyOneCmd recorder)-      , PluginCommand "applyAll" "Apply all hints to the file" (applyAllCmd recorder)-      ]-  , pluginHandlers = mkPluginHandler STextDocumentCodeAction codeActionProvider+  , pluginCommands = pluginCommands+  , pluginHandlers = pluginHandlers   , pluginConfigDescriptor = defaultConfigDescriptor       { configHasDiagnostics = True       , configCustomConfig = mkCustomConfig properties@@ -243,13 +249,15 @@       ideaToDiagnostic idea =         LSP.Diagnostic {             _range    = srcSpanToRange $ ideaSpan idea-          , _severity = Just LSP.DsInfo+          , _severity = Just LSP.DiagnosticSeverity_Information           -- we are encoding the fact that idea has refactorings in diagnostic code           , _code     = Just (InR $ T.pack $ codePre ++ ideaHint idea)           , _source   = Just "hlint"           , _message  = idea2Message idea           , _relatedInformation = Nothing           , _tags     = Nothing+          , _codeDescription = Nothing+          , _data_ = Nothing         }         where codePre = if null $ ideaRefactoring idea then "" else "refact:" @@ -267,12 +275,14 @@       parseErrorToDiagnostic (Hlint.ParseError l msg contents) =         LSP.Diagnostic {             _range    = srcSpanToRange l-          , _severity = Just LSP.DsInfo-          , _code     = Just (InR "parser")+          , _severity = Just LSP.DiagnosticSeverity_Information+          , _code     = Just (InR sourceParser)           , _source   = Just "hlint"           , _message  = T.unlines [T.pack msg,T.pack contents]           , _relatedInformation = Nothing           , _tags     = Nothing+          , _codeDescription = Nothing+          , _data_ = Nothing         }        -- This one is defined in Development.IDE.GHC.Error but here@@ -390,25 +400,16 @@   Config     <$> usePropertyAction #flags pId properties -runHlintAction- :: (Eq k, Hashable k, Show k, Show (RuleResult k), Typeable k, Typeable (RuleResult k), NFData k, NFData (RuleResult k))- => IdeState- -> NormalizedFilePath -> String -> k -> IO (Maybe (RuleResult k))-runHlintAction ideState normalizedFilePath desc rule = runAction desc ideState $ use rule normalizedFilePath--runGetFileContentsAction :: IdeState -> NormalizedFilePath -> IO (Maybe (FileVersion, Maybe T.Text))-runGetFileContentsAction ideState normalizedFilePath = runHlintAction ideState normalizedFilePath "Hlint.GetFileContents" GetFileContents--runGetModSummaryAction :: IdeState -> NormalizedFilePath -> IO (Maybe ModSummaryResult)-runGetModSummaryAction ideState normalizedFilePath = runHlintAction ideState normalizedFilePath "Hlint.GetModSummary" GetModSummary- -- ----------------------------------------------------------------------codeActionProvider :: PluginMethodHandler IdeState TextDocumentCodeAction-codeActionProvider ideState pluginId (CodeActionParams _ _ documentId _ context)+codeActionProvider :: PluginMethodHandler IdeState Method_TextDocumentCodeAction+codeActionProvider ideState _pluginId (CodeActionParams _ _ documentId _ context)   | let TextDocumentIdentifier uri = documentId   , Just docNormalizedFilePath <- uriToNormalizedFilePath (toNormalizedUri uri)-  = liftIO $ fmap (Right . LSP.List . map LSP.InR) $ do+  = do+    verTxtDocId <- lift $ getVersionedTextDoc documentId+    liftIO $ fmap (InL . map LSP.InR) $ do       allDiagnostics <- atomically $ getDiagnostics ideState+       let numHintsInDoc = length             [diagnostic | (diagnosticNormalizedFilePath, _, diagnostic) <- allDiagnostics                         , validCommand diagnostic@@ -418,76 +419,70 @@             [diagnostic | diagnostic <- diags                         , validCommand diagnostic             ]-      file <- runGetFileContentsAction ideState docNormalizedFilePath-      singleHintCodeActions <--        if | Just (_, source) <- file -> do-               modSummaryResult <- runGetModSummaryAction ideState docNormalizedFilePath-               pure if | Just modSummaryResult <- modSummaryResult-                       , Just source <- source-                       , let dynFlags = ms_hspp_opts $ msrModSummary modSummaryResult ->-                           diags >>= diagnosticToCodeActions dynFlags source pluginId documentId-                       | otherwise -> []-           | otherwise -> pure []+      let singleHintCodeActions = diags >>= diagnosticToCodeActions verTxtDocId       if numHintsInDoc > 1 && numHintsInContext > 0 then do-        pure $ singleHintCodeActions ++ [applyAllAction]+        pure $ singleHintCodeActions ++ [applyAllAction verTxtDocId]       else         pure singleHintCodeActions   | otherwise-  = pure $ Right $ LSP.List []+  = pure $ InL []    where-    applyAllAction =-      let args = Just [toJSON (documentId ^. LSP.uri)]-          cmd = mkLspCommand pluginId "applyAll" "Apply all hints" args-        in LSP.CodeAction "Apply all hints" (Just LSP.CodeActionQuickFix) Nothing Nothing Nothing Nothing (Just cmd) Nothing+    applyAllAction verTxtDocId =+      let args = Just $ toJSON (ApplyHint verTxtDocId Nothing)+        in LSP.CodeAction "Apply all hints" (Just LSP.CodeActionKind_QuickFix) Nothing Nothing Nothing Nothing Nothing args      -- |Some hints do not have an associated refactoring-    validCommand (LSP.Diagnostic _ _ (Just (InR code)) (Just "hlint") _ _ _) =+    validCommand (LSP.Diagnostic _ _ (Just (InR code)) _ (Just "hlint") _ _ _ _) =         "refact:" `T.isPrefixOf` code     validCommand _ =         False -    LSP.List diags = context ^. LSP.diagnostics+    diags = context ^. LSP.diagnostics +resolveProvider :: Recorder (WithPriority Log) -> ResolveFunction IdeState HlintResolveCommands Method_CodeActionResolve+resolveProvider recorder ideState _plId ca uri resolveValue = do+  file <-  getNormalizedFilePathE uri+  case resolveValue of+    (ApplyHint verTxtDocId oneHint) -> do+        edit <- ExceptT $ liftIO $ applyHint recorder ideState file oneHint verTxtDocId+        pure $ ca & LSP.edit ?~ edit+    (IgnoreHint verTxtDocId hintTitle ) -> do+        edit <- ExceptT $ liftIO $ ignoreHint recorder ideState file verTxtDocId hintTitle+        pure $ ca & LSP.edit ?~ edit+ -- | Convert a hlint diagnostic into an apply and an ignore code action -- if applicable-diagnosticToCodeActions :: DynFlags -> T.Text -> PluginId -> TextDocumentIdentifier -> LSP.Diagnostic -> [LSP.CodeAction]-diagnosticToCodeActions dynFlags fileContents pluginId documentId diagnostic+diagnosticToCodeActions :: VersionedTextDocumentIdentifier -> LSP.Diagnostic -> [LSP.CodeAction]+diagnosticToCodeActions verTxtDocId diagnostic   | LSP.Diagnostic{ _source = Just "hlint", _code = Just (InR code), _range = LSP.Range start _ } <- diagnostic-  , let TextDocumentIdentifier uri = documentId   , let isHintApplicable = "refact:" `T.isPrefixOf` code   , let hint = T.replace "refact:" "" code   , let suppressHintTitle = "Ignore hint \"" <> hint <> "\" in this module"-  , let suppressHintTextEdits = mkSuppressHintTextEdits dynFlags fileContents hint-  , let suppressHintWorkspaceEdit =-          LSP.WorkspaceEdit-            (Just (Map.singleton uri (List suppressHintTextEdits)))-            Nothing-            Nothing+  , let suppressHintArguments = IgnoreHint verTxtDocId hint   = catMaybes       -- Applying the hint is marked preferred because it addresses the underlying error.       -- Disabling the rule isn't, because less often used and configuration can be adapted.       [ if | isHintApplicable            , let applyHintTitle = "Apply hint \"" <> hint <> "\""-                 applyHintArguments = [toJSON (AOP (documentId ^. LSP.uri) start hint)]-                 applyHintCommand = mkLspCommand pluginId "applyOne" applyHintTitle (Just applyHintArguments) ->-               Just (mkCodeAction applyHintTitle diagnostic Nothing (Just applyHintCommand) True)+                 applyHintArguments = ApplyHint verTxtDocId (Just $ OneHint start hint) ->+               Just (mkCodeAction applyHintTitle diagnostic (Just (toJSON applyHintArguments)) True)            | otherwise -> Nothing-      , Just (mkCodeAction suppressHintTitle diagnostic (Just suppressHintWorkspaceEdit) Nothing False)+      , Just (mkCodeAction suppressHintTitle diagnostic (Just (toJSON suppressHintArguments)) False)       ]   | otherwise = [] -mkCodeAction :: T.Text -> LSP.Diagnostic -> Maybe LSP.WorkspaceEdit -> Maybe LSP.Command -> Bool -> LSP.CodeAction-mkCodeAction title diagnostic workspaceEdit command isPreferred =+mkCodeAction :: T.Text -> LSP.Diagnostic -> Maybe Value -> Bool -> LSP.CodeAction+mkCodeAction title diagnostic data_  isPreferred =   LSP.CodeAction     { _title = title-    , _kind = Just LSP.CodeActionQuickFix-    , _diagnostics = Just (LSP.List [diagnostic])+    , _kind = Just LSP.CodeActionKind_QuickFix+    , _diagnostics = Just [diagnostic]     , _isPreferred = Just isPreferred     , _disabled = Nothing-    , _edit = workspaceEdit-    , _command = command-    , _xdata = Nothing+    , _edit = Nothing+    , _command = Nothing+    , _data_ = data_     }  mkSuppressHintTextEdits :: DynFlags -> T.Text -> T.Text -> [LSP.TextEdit]@@ -511,60 +506,52 @@     combinedTextEdit : lineSplitTextEditList -- --------------------------------------------------------------------- -applyAllCmd :: Recorder (WithPriority Log) -> CommandFunction IdeState Uri-applyAllCmd recorder ide uri = do-  let file = maybe (error $ show uri ++ " is not a file.")-                    toNormalizedFilePath'-                   (uriToFilePath' uri)-  withIndefiniteProgress "Applying all hints" Cancellable $ do-    res <- liftIO $ applyHint recorder ide file Nothing-    logWith recorder Debug $ LogApplying file res-    case res of-      Left err -> pure $ Left (responseError (T.pack $ "hlint:applyAll: " ++ show err))-      Right fs -> do-        _ <- sendRequest SWorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing fs) (\_ -> pure ())-        pure $ Right Null+ignoreHint :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> VersionedTextDocumentIdentifier -> HintTitle -> IO (Either PluginError WorkspaceEdit)+ignoreHint _recorder ideState nfp verTxtDocId ignoreHintTitle = runExceptT $ do+  (_, fileContents) <- runActionE "Hlint.GetFileContents" ideState $ useE GetFileContents nfp+  (msr, _) <- runActionE "Hlint.GetModSummaryWithoutTimestamps" ideState $ useWithStaleE GetModSummaryWithoutTimestamps nfp+  case fileContents of+    Just contents -> do+        let dynFlags = ms_hspp_opts $ msrModSummary msr+            textEdits = mkSuppressHintTextEdits dynFlags contents ignoreHintTitle+            workspaceEdit =+                LSP.WorkspaceEdit+                  (Just (M.singleton (verTxtDocId ^. LSP.uri) textEdits))+                  Nothing+                  Nothing+        pure workspaceEdit+    Nothing -> throwError $ PluginInternalError "Unable to get fileContents"  -- -----------------------------------------------------------------------data ApplyOneParams = AOP-  { file      :: Uri-  , start_pos :: Position-  -- | There can be more than one hint suggested at the same position, so HintTitle is used to distinguish between them.-  , hintTitle :: HintTitle-  } deriving (Eq,Show,Generic,FromJSON,ToJSON)+data HlintResolveCommands =+    ApplyHint+      { verTxtDocId :: VersionedTextDocumentIdentifier+      -- |If Nothing, apply all hints, otherise only apply+      -- the given hint+      , oneHint     :: Maybe OneHint+      }+  | IgnoreHint+      { verTxtDocId     :: VersionedTextDocumentIdentifier+      , ignoreHintTitle :: HintTitle+      } deriving (Generic, ToJSON, FromJSON)  type HintTitle = T.Text -data OneHint = OneHint-  { oneHintPos   :: Position-  , oneHintTitle :: HintTitle-  } deriving (Eq, Show)--applyOneCmd :: Recorder (WithPriority Log) -> CommandFunction IdeState ApplyOneParams-applyOneCmd recorder ide (AOP uri pos title) = do-  let oneHint = OneHint pos title-  let file = maybe (error $ show uri ++ " is not a file.") toNormalizedFilePath'-                   (uriToFilePath' uri)-  let progTitle = "Applying hint: " <> title-  withIndefiniteProgress progTitle Cancellable $ do-    res <- liftIO $ applyHint recorder ide file (Just oneHint)-    logWith recorder Debug $ LogApplying file res-    case res of-      Left err -> pure $ Left (responseError (T.pack $ "hlint:applyOne: " ++ show err))-      Right fs -> do-        _ <- sendRequest SWorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing fs) (\_ -> pure ())-        pure $ Right Null+data OneHint =+  OneHint+    { oneHintPos   :: Position+    , oneHintTitle :: HintTitle+    } deriving (Generic, Eq, Show, ToJSON, FromJSON) -applyHint :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> Maybe OneHint -> IO (Either String WorkspaceEdit)-applyHint recorder ide nfp mhint =+applyHint :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> Maybe OneHint -> VersionedTextDocumentIdentifier -> IO (Either PluginError WorkspaceEdit)+applyHint recorder ide nfp mhint verTxtDocId =   runExceptT $ do     let runAction' :: Action a -> IO a         runAction' = runAction "applyHint" ide     let errorHandlers = [ Handler $ \e -> return (Left (show (e :: IOException)))                         , Handler $ \e -> return (Left (show (e :: ErrorCall)))                         ]-    ideas <- bimapExceptT showParseError id $ ExceptT $ runAction' $ getIdeas recorder nfp+    ideas <- bimapExceptT (PluginInternalError . T.pack . showParseError) id $ ExceptT $ runAction' $ getIdeas recorder nfp     let ideas' = maybe ideas (`filterIdeas` ideas) mhint     let commands = map ideaRefactoring ideas'     logWith recorder Debug $ LogGeneratedIdeas nfp commands@@ -600,7 +587,7 @@     mbParsedModule <- liftIO $ runAction' $ getParsedModuleWithComments nfp     res <-         case mbParsedModule of-            Nothing -> throwE "Apply hint: error parsing the module"+            Nothing -> throwError "Apply hint: error parsing the module"             Just pm -> do                 let anns = pm_annotations pm                 let modu = pm_parsed_source pm@@ -614,11 +601,10 @@ #endif     case res of       Right appliedFile -> do-        let uri = fromNormalizedUri (filePathToUri' nfp)-        let wsEdit = diffText' True (uri, oldContent) (T.pack appliedFile) IncludeDeletions+        let wsEdit = diffText' True (verTxtDocId, oldContent) (T.pack appliedFile) IncludeDeletions         ExceptT $ return (Right wsEdit)       Left err ->-        throwE err+        throwError $ PluginInternalError $ T.pack err     where           -- | If we are only interested in applying a particular hint then           -- let's filter out all the irrelevant ideas
test/Main.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE NamedFieldPuns    #-}+{-# LANGUAGE OverloadedLabels  #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE TypeOperators     #-}@@ -6,20 +7,21 @@   ( main   ) where -import           Control.Lens            ((^.))-import           Control.Monad           (when)-import           Data.Aeson              (Value (..), object, toJSON, (.=))-import           Data.Functor            (void)-import           Data.List               (find)-import qualified Data.Map                as Map-import           Data.Maybe              (fromJust, isJust)-import qualified Data.Text               as T-import           Ide.Plugin.Config       (Config (..), PluginConfig (..))-import qualified Ide.Plugin.Config       as Plugin-import qualified Ide.Plugin.Hlint        as HLint-import           Ide.Types               (PluginId)-import qualified Language.LSP.Types.Lens as L-import           System.FilePath         ((</>))+import           Control.Lens               ((^.))+import           Control.Monad              (when)+import           Data.Aeson                 (Value (..), object, toJSON, (.=))+import           Data.Functor               (void)+import           Data.List                  (find)+import qualified Data.Map                   as Map+import           Data.Maybe                 (fromJust, isJust)+import           Data.Row                   ((.+), (.==))+import qualified Data.Text                  as T+import           Ide.Plugin.Config          (Config (..), PluginConfig (..))+import qualified Ide.Plugin.Config          as Plugin+import qualified Ide.Plugin.Hlint           as HLint+import           Ide.Types                  (PluginId)+import qualified Language.LSP.Protocol.Lens as L+import           System.FilePath            ((</>)) import           Test.Hls  main :: IO ()@@ -34,6 +36,7 @@     , configTests     , ignoreHintTests     , applyHintTests+    , resolveTests     ]  getIgnoreHintText :: T.Text -> T.Text@@ -42,6 +45,22 @@ getApplyHintText :: T.Text -> T.Text getApplyHintText name = "Apply hint \"" <> name <> "\"" +resolveTests :: TestTree+resolveTests = testGroup "hlint resolve tests"+  [+    ignoreHintGoldenResolveTest+      "Resolve version of: Ignore hint in this module inserts -Wno-unrecognised-pragmas and hlint ignore pragma if warn unrecognized pragmas is off"+      "UnrecognizedPragmasOff"+      (Point 3 8)+      "Eta reduce"+  , applyHintGoldenResolveTest+      "Resolve version of: [#2612] Apply hint works when operator fixities go right-to-left"+      "RightToLeftFixities"+      (Point 6 13)+      "Avoid reverse"+  ]++ ignoreHintTests :: TestTree ignoreHintTests = testGroup "hlint ignore hint tests"   [@@ -77,7 +96,7 @@         liftIO $ do             length diags @?= 2 -- "Eta Reduce" and "Redundant Id"             reduceDiag ^. L.range @?= Range (Position 1 0) (Position 1 12)-            reduceDiag ^. L.severity @?= Just DsInfo+            reduceDiag ^. L.severity @?= Just DiagnosticSeverity_Information             reduceDiag ^. L.code @?= Just (InR "refact:Eta reduce")             reduceDiag ^. L.source @?= Just "hlint" @@ -123,15 +142,16 @@         doc <- openDoc "Base.hs" "haskell"         testHlintDiagnostics doc -        let change = TextDocumentContentChangeEvent-                        (Just (Range (Position 1 8) (Position 1 12)))-                         Nothing "x"+        let change = TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 1 8) (Position 1 12)+                                                         .+ #rangeLength .== Nothing+                                                         .+ #text .== "x"         changeDoc doc [change]         expectNoMoreDiagnostics 3 doc "hlint" -        let change' = TextDocumentContentChangeEvent-                        (Just (Range (Position 1 8) (Position 1 12)))-                         Nothing "id x"+        let change' = TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 1 8) (Position 1 12)+                                                          .+ #rangeLength .== Nothing+                                                          .+ #text .== "id x"+         changeDoc doc [change']         testHlintDiagnostics doc @@ -182,11 +202,11 @@         doc <- openDoc "IgnoreAnnHlint.hs" "haskell"         expectNoMoreDiagnostics 3 doc "hlint" -    , knownBrokenForGhcVersions [GHC92, GHC94] "apply-refact has different behavior on v0.10" $+    , knownBrokenForGhcVersions [GHC92, GHC94, GHC96] "apply-refact has different behavior on v0.10" $       testCase "apply-refact preserve regular comments" $ runHlintSession "" $ do         testRefactor "Comments.hs" "Redundant bracket" expectedComments -    , onlyRunForGhcVersions [GHC92, GHC94] "only run test for apply-refact-0.10" $+    , onlyRunForGhcVersions [GHC92, GHC94, GHC96] "only run test for apply-refact-0.10" $       testCase "apply-refact preserve regular comments" $ runHlintSession "" $ do         testRefactor "Comments.hs" "Redundant bracket" expectedComments' @@ -323,7 +343,7 @@         liftIO $ do             length diags' @?= 1             d ^. L.range @?= Range (Position 1 10) (Position 1 21)-            d ^. L.severity @?= Just DsInfo+            d ^. L.severity @?= Just DiagnosticSeverity_Information     ]  testDir :: FilePath@@ -331,7 +351,7 @@  runHlintSession :: FilePath -> Session a -> IO a runHlintSession subdir  =-    failIfSessionTimeout . runSessionWithServer hlintPlugin (testDir </> subdir)+    failIfSessionTimeout . runSessionWithServerAndCaps hlintPlugin codeActionNoResolveCaps (testDir </> subdir)  noHlintDiagnostics :: [Diagnostic] -> Assertion noHlintDiagnostics diags =@@ -419,5 +439,25 @@  setupGoldenHlintTest :: TestName -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree setupGoldenHlintTest testName path =-  goldenWithHaskellDoc hlintPlugin testName testDir path "expected" "hs"+  goldenWithHaskellAndCaps codeActionNoResolveCaps hlintPlugin testName testDir path "expected" "hs" +ignoreHintGoldenResolveTest :: TestName -> FilePath -> Point -> T.Text -> TestTree+ignoreHintGoldenResolveTest testCaseName goldenFilename point hintName =+  goldenResolveTest testCaseName goldenFilename point (getIgnoreHintText hintName)++applyHintGoldenResolveTest :: TestName -> FilePath -> Point -> T.Text -> TestTree+applyHintGoldenResolveTest testCaseName goldenFilename point hintName = do+  goldenResolveTest testCaseName goldenFilename point (getApplyHintText hintName)++goldenResolveTest :: TestName -> FilePath -> Point -> T.Text -> TestTree+goldenResolveTest testCaseName goldenFilename point hintText =+  setupGoldenHlintResolveTest testCaseName goldenFilename $ \document -> do+    waitForDiagnosticsFromSource document "hlint"+    actions <- getAndResolveCodeActions document $ pointToRange point+    case find ((== Just hintText) . getCodeActionTitle) actions of+      Just (InR codeAction) -> executeCodeAction codeAction+      _ -> liftIO $ assertFailure $ makeCodeActionNotFoundAtString point++setupGoldenHlintResolveTest :: TestName -> FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree+setupGoldenHlintResolveTest testName path =+  goldenWithHaskellAndCaps codeActionResolveCaps hlintPlugin testName testDir path "expected" "hs"