Skip to content

Commit 9e2e931

Browse files
authored
Adds a code action to delete all unused bindings. (#4958)
The action finds the suggested bindings to delete, then groups them together based on whether the ranges subsumes each other, and finally deletes the groups. Closes #4909
1 parent e03324f commit 9e2e931

2 files changed

Lines changed: 77 additions & 3 deletions

File tree

plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import Development.IDE.Core.Service
4949
import Development.IDE.Core.Shake hiding (Log)
5050
import Development.IDE.GHC.Compat hiding
5151
(ImplicitPrelude)
52+
import qualified Language.LSP.Protocol.Types as TE (TextEdit (..))
5253
#if !MIN_VERSION_ghc(9,11,0)
5354
import Development.IDE.GHC.Compat.Util
5455
#endif
@@ -144,6 +145,7 @@ codeAction state _ (CodeActionParams _ _ (TextDocumentIdentifier uri) range _) =
144145
textContents = fmap Rope.toText contents
145146
actions = caRemoveRedundantImports parsedModule textContents allDiags range uri
146147
<> caRemoveInvalidExports parsedModule textContents allDiags range uri
148+
<> caDeleteUnusedBindings parsedModule textContents allDiags range uri
147149
pure $ InL actions
148150

149151
-------------------------------------------------------------------------------------------------
@@ -185,7 +187,6 @@ bindingsPluginDescriptor recorder plId = mkExactprintPluginDescriptor recorder $
185187
, wrap suggestImplicitParameter
186188
, wrap suggestNewDefinition
187189
, wrap Development.IDE.Plugin.Plugins.AddArgument.plugin
188-
, wrap suggestDeleteUnusedBinding
189190
]
190191
plId
191192
"Provides various quick fixes for bindings"
@@ -710,6 +711,47 @@ suggestDeleteUnusedBinding
710711
isSameName :: IdP GhcPs -> String -> Bool
711712
isSameName x name = T.unpack (printOutputable x) == name
712713

714+
caDeleteUnusedBindings :: Maybe ParsedModule -> Maybe T.Text -> [Diagnostic] -> Range -> Uri -> [Command |? CodeAction]
715+
caDeleteUnusedBindings m contents allDiags contextRange uri
716+
| Just pm <- m,
717+
r <- join $ map (\d -> repeat d `zip` suggestDeleteUnusedBinding pm contents d) allDiags,
718+
-- `allEdits` contains the representative edits to make.
719+
-- Representative means the edit with a range that subsumes the others,
720+
-- because GHC emits diagnostics for the top level binding *and* the where clause.
721+
allEdits <-
722+
-- take the representative and drop the others
723+
concatMap headToList $
724+
-- deduplicate by creating groups of ranges (same group if any subsumes the other)
725+
groupBy subsumesEither
726+
-- Sort to put related ranges next to each other
727+
(sortOn TE._range [ e | (_, (_, edits)) <- r, e <- edits]),
728+
caRemoveAll <- removeAll allEdits,
729+
ctxEdits <- [ x | x@(d, _) <- r, d `diagInRange` contextRange],
730+
not $ null ctxEdits,
731+
caRemoveCtx <- map (\(d, (title, tedit)) -> removeSingle title tedit d) ctxEdits
732+
= caRemoveCtx ++ [caRemoveAll]
733+
| otherwise = []
734+
where
735+
removeSingle title tedit diagnostic = mkCA title (Just CodeActionKind_QuickFix) Nothing [diagnostic] WorkspaceEdit{..} where
736+
_changes = Just $ M.singleton uri tedit
737+
_documentChanges = Nothing
738+
_changeAnnotations = Nothing
739+
removeAll tedit = InR $ CodeAction{..} where
740+
_changes = Just $ M.singleton uri tedit
741+
_title = "Delete all unused bindings"
742+
_kind = Just CodeActionKind_QuickFix
743+
_diagnostics = Nothing
744+
_documentChanges = Nothing
745+
_edit = Just WorkspaceEdit{..}
746+
_isPreferred = Just False
747+
_command = Nothing
748+
_disabled = Nothing
749+
_data_ = Nothing
750+
_changeAnnotations = Nothing
751+
headToList [] = []
752+
headToList (x : _) = [x]
753+
subsumesEither (TE._range -> range1) (TE._range -> range2) = subRange range1 range2 || subRange range1 range2
754+
713755
data ExportsAs = ExportName | ExportPattern | ExportFamily | ExportAll
714756
deriving (Eq)
715757

plugins/hls-refactor-plugin/test/Main.hs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,7 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
25082508
, "some = ()"
25092509
]
25102510
(4, 0)
2511+
1
25112512
"Delete ‘f’"
25122513
[ "{-# OPTIONS_GHC -Wunused-top-binds #-}"
25132514
, "module A (some) where"
@@ -2525,6 +2526,7 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
25252526
, "some = ()"
25262527
]
25272528
(4, 2)
2529+
1
25282530
"Delete ‘myPlus’"
25292531
[ "{-# OPTIONS_GHC -Wunused-top-binds #-}"
25302532
, "module A (some) where"
@@ -2547,6 +2549,7 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
25472549
, ""
25482550
]
25492551
(10, 4)
2552+
1
25502553
"Delete ‘h’"
25512554
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
25522555
, "module A (h, g) where"
@@ -2570,6 +2573,7 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
25702573
, "c = 5"
25712574
]
25722575
(4, 0)
2576+
1
25732577
"Delete ‘a’"
25742578
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
25752579
, "module A (b, c) where"
@@ -2589,6 +2593,7 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
25892593
, "c = 5"
25902594
]
25912595
(5, 0)
2596+
1
25922597
"Delete ‘b’"
25932598
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
25942599
, "module A (a, c) where"
@@ -2608,19 +2613,46 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
26082613
, "c = 5"
26092614
]
26102615
(6, 0)
2616+
1
26112617
"Delete ‘c’"
26122618
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
26132619
, "module A (a, b) where"
26142620
, ""
26152621
, "a, b :: Int"
26162622
, "a = 3"
26172623
, "b = 4"
2624+
],
2625+
testSession "delete all unused level bindings" $
2626+
testFor
2627+
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
2628+
, "module A (some) where"
2629+
, ""
2630+
, "f :: Int -> Int"
2631+
, "f 1 = let a = 1"
2632+
, " in a"
2633+
, "f 2 = 2"
2634+
, ""
2635+
, "some = ()"
2636+
, " where"
2637+
, " a = 2"
2638+
, ""
2639+
, "unusedSome :: ()"
2640+
, "unusedSome = ()"
2641+
]
2642+
(4, 0)
2643+
3
2644+
"Delete all unused bindings"
2645+
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
2646+
, "module A (some) where"
2647+
, ""
2648+
, "some = ()"
2649+
, " where"
26182650
]
26192651
]
26202652
where
2621-
testFor sourceLines pos@(l,c) expectedTitle expectedLines = do
2653+
testFor sourceLines pos@(l,c) expectedNbrWarnings expectedTitle expectedLines = do
26222654
docId <- createDoc "A.hs" "haskell" $ T.unlines sourceLines
2623-
expectDiagnostics [ ("A.hs", [(DiagnosticSeverity_Warning, pos, "not used", Nothing)]) ]
2655+
expectDiagnostics [ ("A.hs", replicate expectedNbrWarnings (DiagnosticSeverity_Warning, pos, "not used", Nothing)) ]
26242656
action <- pickActionWithTitle expectedTitle =<< getCodeActions docId (R l c l c)
26252657
executeCodeAction action
26262658
contentAfterAction <- documentContents docId

0 commit comments

Comments
 (0)