Skip to content

Commit 597da9d

Browse files
authored
Code action to remove redundant record field import (fixes #4220) (#4308)
1 parent cebd641 commit 597da9d

File tree

2 files changed

+80
-1
lines changed

2 files changed

+80
-1
lines changed

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ suggestRemoveRedundantImport ParsedModule{pm_parsed_source = L _ HsModule{hsmod
422422
| Just [_, bindings] <- matchRegexUnifySpaces _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"
423423
, Just (L _ impDecl) <- find (\(L (locA -> l) _) -> _start _range `isInsideSrcSpan` l && _end _range `isInsideSrcSpan` l ) hsmodImports
424424
, Just c <- contents
425-
, ranges <- map (rangesForBindingImport impDecl . T.unpack) (T.splitOn ", " bindings)
425+
, ranges <- map (rangesForBindingImport impDecl . T.unpack) (T.splitOn ", " bindings >>= trySplitIntoOriginalAndRecordField)
426426
, ranges' <- extendAllToIncludeCommaIfPossible False (indexedByPosition $ T.unpack c) (concat ranges)
427427
, not (null ranges')
428428
= [( "Remove " <> bindings <> " from import" , [ TextEdit r "" | r <- ranges' ] )]
@@ -434,6 +434,15 @@ suggestRemoveRedundantImport ParsedModule{pm_parsed_source = L _ HsModule{hsmod
434434
| _message =~ ("The( qualified)? import of [^ ]* is redundant" :: String)
435435
= [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]
436436
| otherwise = []
437+
where
438+
-- In case of an unused record field import, the binding from the message will not match any import directly
439+
-- In this case, we try if we can additionally extract a record field name
440+
-- Example: The import of ‘B(b2)’ from module ‘ModuleB’ is redundant
441+
trySplitIntoOriginalAndRecordField :: T.Text -> [T.Text]
442+
trySplitIntoOriginalAndRecordField binding =
443+
case matchRegexUnifySpaces binding "([^ ]+)\\(([^)]+)\\)" of
444+
Just [_, fields] -> [binding, fields]
445+
_ -> [binding]
437446

438447
diagInRange :: Diagnostic -> Range -> Bool
439448
diagInRange Diagnostic {_range = dr} r = dr `subRange` extendedRange

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

+70
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,76 @@ removeImportTests = testGroup "remove import actions"
10041004
, "x = a -- Must use something from module A, but not (@.)"
10051005
]
10061006
liftIO $ expectedContentAfterAction @=? contentAfterAction
1007+
, testSession "remove redundant record field import" $ do
1008+
let contentA = T.unlines
1009+
[ "module ModuleA where"
1010+
, "data A = A {"
1011+
, " a1 :: String,"
1012+
, " a2 :: Int"
1013+
, "}"
1014+
, "newA = A \"foo\" 42"
1015+
]
1016+
_docA <- createDoc "ModuleA.hs" "haskell" contentA
1017+
let contentB = T.unlines
1018+
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
1019+
, "module ModuleB where"
1020+
, "import ModuleA"
1021+
, " ( A (a1, a2),"
1022+
, " newA"
1023+
, " )"
1024+
, "x = a1 newA"
1025+
]
1026+
docB <- createDoc "ModuleB.hs" "haskell" contentB
1027+
_ <- waitForDiagnostics
1028+
action <- pickActionWithTitle "Remove A(a2) from import" =<< getCodeActions docB (R 2 0 5 3)
1029+
executeCodeAction action
1030+
contentAfterAction <- documentContents docB
1031+
let expectedContentAfterAction = T.unlines
1032+
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
1033+
, "module ModuleB where"
1034+
, "import ModuleA"
1035+
, " ( A (a1),"
1036+
, " newA"
1037+
, " )"
1038+
, "x = a1 newA"
1039+
]
1040+
liftIO $ expectedContentAfterAction @=? contentAfterAction
1041+
, testSession "remove multiple redundant record field imports" $ do
1042+
let contentA = T.unlines
1043+
[ "module ModuleA where"
1044+
, "data A = A {"
1045+
, " a1 :: String,"
1046+
, " a2 :: Int,"
1047+
, " a3 :: Int,"
1048+
, " a4 :: Int"
1049+
, "}"
1050+
, "newA = A \"foo\" 2 3 4"
1051+
]
1052+
_docA <- createDoc "ModuleA.hs" "haskell" contentA
1053+
let contentB = T.unlines
1054+
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
1055+
, "module ModuleB where"
1056+
, "import ModuleA"
1057+
, " ( A (a1, a2, a3, a4),"
1058+
, " newA"
1059+
, " )"
1060+
, "x = a2 newA"
1061+
]
1062+
docB <- createDoc "ModuleB.hs" "haskell" contentB
1063+
_ <- waitForDiagnostics
1064+
action <- pickActionWithTitle "Remove A(a1), A(a3), A(a4) from import" =<< getCodeActions docB (R 2 0 5 3)
1065+
executeCodeAction action
1066+
contentAfterAction <- documentContents docB
1067+
let expectedContentAfterAction = T.unlines
1068+
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
1069+
, "module ModuleB where"
1070+
, "import ModuleA"
1071+
, " ( A (a2),"
1072+
, " newA"
1073+
, " )"
1074+
, "x = a2 newA"
1075+
]
1076+
liftIO $ expectedContentAfterAction @=? contentAfterAction
10071077
]
10081078

10091079
extendImportTests :: TestTree

0 commit comments

Comments
 (0)