@@ -38,11 +38,11 @@ import Data.Aeson.Types (FromJSON (.
38
38
import qualified Data.ByteString as BS
39
39
import Data.Hashable
40
40
import qualified Data.HashMap.Strict as Map
41
+ import Data.List (find )
41
42
import qualified Data.Map as M
42
43
import Data.Maybe
43
44
import qualified Data.Text as T
44
45
import qualified Data.Text.Encoding as T
45
- import Data.Text.Utf16.Rope.Mixed (Rope )
46
46
import qualified Data.Text.Utf16.Rope.Mixed as Rope
47
47
import Data.Typeable
48
48
import Development.IDE hiding
@@ -65,16 +65,29 @@ import System.Environment (setEnv,
65
65
66
66
import Development.IDE.GHC.Compat (DynFlags ,
67
67
extensionFlags ,
68
+ getLoc ,
69
+ hsmodDecls ,
68
70
ms_hspp_opts ,
69
- topDir )
71
+ pattern RealSrcLoc ,
72
+ pattern UnhelpfulLoc ,
73
+ pm_parsed_source ,
74
+ srcLocLine ,
75
+ srcSpanStart ,
76
+ topDir ,
77
+ unLoc )
70
78
import qualified Development.IDE.GHC.Compat.Util as EnumSet
71
79
72
80
#if MIN_GHC_API_VERSION(9,4,0)
73
81
import qualified GHC.Data.Strict as Strict
74
82
#endif
75
83
#if MIN_GHC_API_VERSION(9,0,0)
76
84
import GHC.Types.SrcLoc hiding
77
- (RealSrcSpan )
85
+ (RealSrcSpan ,
86
+ SrcLoc (.. ),
87
+ getLoc ,
88
+ srcLocLine ,
89
+ srcSpanStart ,
90
+ unLoc )
78
91
import qualified GHC.Types.SrcLoc as GHC
79
92
#else
80
93
import qualified SrcLoc as GHC
@@ -111,6 +124,7 @@ import qualified Language.LSP.Protocol.Types as LSP
111
124
112
125
import Development.IDE.Core.PluginUtils as PluginUtils
113
126
import qualified Development.IDE.Core.Shake as Shake
127
+ import Development.IDE.LSP.Outline (documentSymbolForDecl )
114
128
import Development.IDE.Spans.Pragmas (LineSplitTextEdits (LineSplitTextEdits ),
115
129
NextPragmaInfo (NextPragmaInfo ),
116
130
getNextPragmaInfo ,
@@ -413,8 +427,8 @@ resolveProvider recorder ideState _plId ca uri resolveValue = do
413
427
(ApplyHint verTxtDocId oneHint) -> do
414
428
edit <- ExceptT $ liftIO $ applyHint recorder ideState file oneHint verTxtDocId
415
429
pure $ ca & LSP. edit ?~ edit
416
- (IgnoreHint verTxtDocId hintTitle ) -> do
417
- edit <- ExceptT $ liftIO $ ignoreHint recorder ideState file verTxtDocId hintTitle
430
+ (IgnoreHint verTxtDocId hintTitle scope ) -> do
431
+ edit <- ExceptT $ liftIO $ ignoreHint scope recorder ideState file verTxtDocId hintTitle
418
432
pure $ ca & LSP. edit ?~ edit
419
433
420
434
applyRefactAvailable :: Bool
@@ -431,7 +445,7 @@ diagnosticToCodeActions verTxtDocId diagnostic
431
445
| LSP. Diagnostic { _source = Just " hlint" , _code = Just (InR code), _range = LSP. Range start _ } <- diagnostic
432
446
, let isHintApplicable = " refact:" `T.isPrefixOf` code && applyRefactAvailable
433
447
, let hint = T. replace " refact:" " " code
434
- , let suppressHintTitle = " Ignore hint \" " <> hint <> " \" in this module "
448
+ , let suppressHintTitle s = " Ignore hint \" " <> hint <> " \" in this " <> s
435
449
, let suppressHintArguments = IgnoreHint verTxtDocId hint
436
450
= catMaybes
437
451
-- Applying the hint is marked preferred because it addresses the underlying error.
@@ -441,7 +455,8 @@ diagnosticToCodeActions verTxtDocId diagnostic
441
455
applyHintArguments = ApplyHint verTxtDocId (Just $ OneHint start hint) ->
442
456
Just (mkCodeAction applyHintTitle diagnostic (Just (toJSON applyHintArguments)) True )
443
457
| otherwise -> Nothing
444
- , Just (mkCodeAction suppressHintTitle diagnostic (Just (toJSON suppressHintArguments)) False )
458
+ , Just (mkCodeAction (suppressHintTitle " module" ) diagnostic (Just (toJSON $ suppressHintArguments IgnoreInModule )) False )
459
+ , Just (mkCodeAction (suppressHintTitle " definition" ) diagnostic (Just (toJSON $ suppressHintArguments $ IgnoreInDefinition start)) False )
445
460
]
446
461
| otherwise = []
447
462
@@ -458,27 +473,45 @@ mkCodeAction title diagnostic data_ isPreferred =
458
473
, _data_ = data_
459
474
}
460
475
461
- mkSuppressHintTextEdits :: DynFlags -> Rope -> T. Text -> [LSP. TextEdit ]
462
- mkSuppressHintTextEdits dynFlags fileContents hint =
476
+ mkSuppressHintTextEdits :: Int -> T. Text -> Maybe LineSplitTextEdits -> Maybe T. Text -> [LSP. TextEdit ]
477
+ mkSuppressHintTextEdits line hint lineSplitTextEdits defName =
463
478
let
464
- NextPragmaInfo { nextPragmaLine, lineSplitTextEdits } = getNextPragmaInfo dynFlags (Just fileContents)
465
- nextPragmaLinePosition = Position (fromIntegral nextPragmaLine) 0
466
- nextPragmaRange = Range nextPragmaLinePosition nextPragmaLinePosition
467
- textEdit = LSP. TextEdit nextPragmaRange $ " {- HLINT ignore \" " <> hint <> " \" -}\n "
479
+ pos = Position (fromIntegral line) 0
480
+ range = Range pos pos
481
+ textEdit = LSP. TextEdit range $ " {- HLINT ignore " <> foldMap (<> " " ) defName <> " \" " <> hint <> " \" -}\n "
468
482
lineSplitTextEditList = maybe [] (\ LineSplitTextEdits {.. } -> [lineSplitInsertTextEdit, lineSplitDeleteTextEdit]) lineSplitTextEdits
469
483
in
470
484
textEdit : lineSplitTextEditList
471
485
-- ---------------------------------------------------------------------
472
486
473
- ignoreHint :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> VersionedTextDocumentIdentifier -> HintTitle -> IO (Either PluginError WorkspaceEdit )
474
- ignoreHint _recorder ideState nfp verTxtDocId ignoreHintTitle = runExceptT $ do
487
+ ignoreHint :: IgnoreHintScope -> Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> VersionedTextDocumentIdentifier -> HintTitle -> IO (Either PluginError WorkspaceEdit )
488
+ ignoreHint scope _recorder ideState nfp verTxtDocId ignoreHintTitle = runExceptT $ do
475
489
(_, fileContents) <- runActionE " Hlint.GetFileContents" ideState $ useE GetFileContents nfp
476
490
(msr, _) <- runActionE " Hlint.GetModSummaryWithoutTimestamps" ideState $ useWithStaleE GetModSummaryWithoutTimestamps nfp
477
491
case fileContents of
478
492
Just contents -> do
479
493
let dynFlags = ms_hspp_opts $ msrModSummary msr
480
- textEdits = mkSuppressHintTextEdits dynFlags contents ignoreHintTitle
481
- workspaceEdit =
494
+ textEdits <- case scope of
495
+ IgnoreInModule ->
496
+ let NextPragmaInfo {nextPragmaLine, lineSplitTextEdits} = getNextPragmaInfo dynFlags (Just contents)
497
+ in pure $ mkSuppressHintTextEdits nextPragmaLine ignoreHintTitle lineSplitTextEdits Nothing
498
+ IgnoreInDefinition pos -> do
499
+ (pm, _) <- runActionE " Hlint.GetParsedModule" ideState $ useWithStaleE GetParsedModule nfp
500
+ let defInfo = do
501
+ containingDecl <- find (maybe False (positionInRange pos) . srcSpanToRange . getLoc)
502
+ $ hsmodDecls $ unLoc $ pm_parsed_source pm
503
+ defStartLine <- case srcSpanStart $ getLoc containingDecl of
504
+ -- TODO `srcLocLine` can apparently raise an error, but it's not clear what the safe version is
505
+ RealSrcLoc sl _ -> Just (srcLocLine sl - 1 )
506
+ UnhelpfulLoc _ -> Nothing
507
+ -- TODO `documentSymbolForDecl` wasn't intended to be exported, and computes more than we need
508
+ -- (although laziness should save us there)
509
+ defName <- (^. LSP. name) <$> documentSymbolForDecl containingDecl
510
+ pure (defStartLine, defName)
511
+ case defInfo of
512
+ Nothing -> throwError $ PluginInternalError " bad things happened" -- TODO better error handling
513
+ Just (defStartLine, defName) -> pure $ mkSuppressHintTextEdits defStartLine ignoreHintTitle Nothing (Just defName)
514
+ let workspaceEdit =
482
515
LSP. WorkspaceEdit
483
516
(Just (M. singleton (verTxtDocId ^. LSP. uri) textEdits))
484
517
Nothing
@@ -497,6 +530,7 @@ data HlintResolveCommands =
497
530
| IgnoreHint
498
531
{ verTxtDocId :: VersionedTextDocumentIdentifier
499
532
, ignoreHintTitle :: HintTitle
533
+ , startPosition :: IgnoreHintScope
500
534
} deriving (Generic , ToJSON , FromJSON )
501
535
502
536
type HintTitle = T. Text
@@ -507,6 +541,11 @@ data OneHint =
507
541
, oneHintTitle :: HintTitle
508
542
} deriving (Generic , Eq , Show , ToJSON , FromJSON )
509
543
544
+ data IgnoreHintScope
545
+ = IgnoreInModule
546
+ | IgnoreInDefinition Position
547
+ deriving (Generic , ToJSON , FromJSON )
548
+
510
549
applyHint :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> Maybe OneHint -> VersionedTextDocumentIdentifier -> IO (Either PluginError WorkspaceEdit )
511
550
#if !APPLY_REFACT
512
551
applyHint _ _ _ _ _ =
0 commit comments