on master help sign in
Fix for #45 - remove redundant symbols from imports (#290)

* Test for #45 * Remove redundant symbols from imports Fixes #45 * Update src/Development/IDE/LSP/CodeAction.hs Co-Authored-By: Andreas Herrmann <42969706+aherrmann-da@users.noreply.github.com> * Apply suggestions from code review Co-Authored-By: Andreas Herrmann <42969706+aherrmann-da@users.noreply.github.com> * Add regex-tdfa extra deps to ghc-lib build * Fix for GHC 8.4 (error message prints qualified binding) GHC ticket #14881 changed this to print identifiers unqualified * dropBindingsFromImportLine: make total Co-authored-by: Andreas Herrmann <42969706+aherrmann-da@users.noreply.github.com>

commit0bcdc6a22699c93d32598ee1cac28ff09f54b884 authorPepe Iborra <pepeiborra@gmail.com> dateMon 30 Dec 2019, 10:40 (7 years ago) parent359cdf5
7 files changed+123−3
ghcide.cabal+1 src/Development/IDE/LSP/CodeAction.hs+54−3 stack-ghc-lib.yaml+2 stack.yaml+2 stack84.yaml+2 stack88.yaml+2 test/exe/Main.hs+60
ghcide.cabal+1
@@ -49,6 +49,7 @@ library
4949 prettyprinter-ansi-terminal,
5050 prettyprinter-ansi-terminal,
5151 prettyprinter,
52+ regex-tdfa >= 1.3.1.0,
5253 rope-utf16-splay,
5354 safe-exceptions,
5455 shake >= 0.17.5,
src/Development/IDE/LSP/CodeAction.hs+54−3
@@ -30,6 +30,8 @@ import Data.Char
3030import Data.Maybe
3131import Data.List.Extra
3232import qualified Data.Text as T
33+import Text.Regex.TDFA ((=~), (=~~))
34+import Text.Regex.TDFA.Text()
3335
3436-- | Generate code actions.
3537codeAction
@@ -85,14 +87,18 @@ executeAddSignatureCommand _lsp _ideState ExecuteCommandParams{..}
8587
8688suggestAction :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
8789suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
90+-- The qualified import of ‘many’ from module ‘Control.Applicative’ is redundant
91+ | Just [_, bindings] <- matchRegex _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"
92+ , Just c <- contents
93+ , importLine <- textInRange _range c
94+ = [( "Remove " <> bindings <> " from import"
95+ , [TextEdit _range (dropBindingsFromImportLine (T.splitOn "," bindings) importLine)])]
8896
8997-- File.hs:16:1: warning:
9098-- The import of `Data.List' is redundant
9199-- except perhaps to import instances from `Data.List'
92100-- To import instances alone, use: import Data.List()
93− | "The import of " `T.isInfixOf` _message
94− || "The qualified import of " `T.isInfixOf` _message
95− , " is redundant" `T.isInfixOf` _message
101+ | _message =~ ("The( qualified)? import of [^ ]* is redundant" :: String)
96102 = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]
97103
98104-- File.hs:52:41: error:
@@ -293,6 +299,51 @@ textInRange (Range (Position startRow startCol) (Position endRow endCol)) text =
293299 where
294300 linesBeginningWithStartLine = drop startRow (T.splitOn "\n" text)
295301
302+-- | Drop all occurrences of a binding in an import line.
303+-- Preserves well-formedness but not whitespace between bindings.
304+--
305+-- >>> dropBindingsFromImportLine ["bA", "bC"] "import A(bA, bB,bC ,bA)"
306+-- "import A(bB)"
307+--
308+-- >>> dropBindingsFromImportLine ["+"] "import "P" qualified A as B ((+))"
309+-- "import "P" qualified A() as B hiding (bB)"
310+dropBindingsFromImportLine :: [T.Text] -> T.Text -> T.Text
311+dropBindingsFromImportLine bindings_ importLine =
312+ importPre <> "(" <> importRest'
313+ where
314+ bindings = map (wrapOperatorInParens . removeQualified) bindings_
315+
316+ (importPre, importRest) = T.breakOn "(" importLine
317+
318+ wrapOperatorInParens x = if isAlpha (T.head x) then x else "(" <> x <> ")"
319+
320+ removeQualified x = case T.breakOn "." x of
321+ (_qualifier, T.uncons -> Just (_, unqualified)) -> unqualified
322+ _ -> x
323+
324+ importRest' = case T.uncons importRest of
325+ Just (_, x) ->
326+ T.intercalate ","
327+ $ joinCloseParens
328+ $ mapMaybe (filtering . T.strip)
329+ $ T.splitOn "," x
330+ Nothing -> importRest
331+
332+ filtering x = case () of
333+ () | x `elem` bindings -> Nothing
334+ () | x `elem` map (<> ")") bindings -> Just ")"
335+ _ -> Just x
336+
337+ joinCloseParens (x : ")" : rest) = (x <> ")") : joinCloseParens rest
338+ joinCloseParens (x : rest) = x : joinCloseParens rest
339+ joinCloseParens [] = []
340+
341+-- | Returns Just (the submatches) for the first capture, or Nothing.
342+matchRegex :: T.Text -> T.Text -> Maybe [T.Text]
343+matchRegex message regex = case message =~~ regex of
344+ Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings
345+ Nothing -> Nothing
346+
296347setHandlersCodeAction :: PartialHandlers
297348setHandlersCodeAction = PartialHandlers $ \WithMessage{..} x -> return x{
298349 LSP.codeActionHandler = withResponse RspCodeAction codeAction
stack-ghc-lib.yaml+2
@@ -9,6 +9,8 @@ extra-deps:
99- ghc-lib-parser-8.8.1
1010- ghc-lib-8.8.1
1111- fuzzy-0.1.0.0
12+- regex-base-0.94.0.0
13+- regex-tdfa-1.3.1.0
1214nix:
1315 packages: [zlib]
1416flags:
stack.yaml+2
@@ -7,5 +7,7 @@ extra-deps:
77- lsp-test-0.9.0.0
88- hie-bios-0.3.0
99- fuzzy-0.1.0.0
10+- regex-base-0.94.0.0
11+- regex-tdfa-1.3.1.0
1012nix:
1113 packages: [zlib]
stack84.yaml+2
@@ -12,6 +12,8 @@ extra-deps:
1212- js-dgtable-0.5.2
1313- hie-bios-0.3.0
1414- fuzzy-0.1.0.0
15+- regex-base-0.94.0.0
16+- regex-tdfa-1.3.1.0
1517nix:
1618 packages: [zlib]
1719allow-newer: true
stack88.yaml+2
@@ -7,6 +7,8 @@ extra-deps:
77- lsp-test-0.9.0.0
88- hie-bios-0.3.0
99- fuzzy-0.1.0.0
10+- regex-base-0.94.0.0
11+- regex-tdfa-1.3.1.0
1012allow-newer: true
1113nix:
1214 packages: [zlib]
test/exe/Main.hs+60
@@ -594,6 +594,66 @@ removeImportTests = testGroup "remove import actions"
594594 , "stuffB = 123"
595595 ]
596596 liftIO $ expectedContentAfterAction @=? contentAfterAction
597+ , testSession "redundant binding" $ do
598+ let contentA = T.unlines
599+ [ "module ModuleA where"
600+ , "stuffA = False"
601+ , "stuffB :: Integer"
602+ , "stuffB = 123"
603+ ]
604+ _docA <- openDoc' "ModuleA.hs" "haskell" contentA
605+ let contentB = T.unlines
606+ [ "{-# OPTIONS_GHC -Wunused-imports #-}"
607+ , "module ModuleB where"
608+ , "import ModuleA (stuffA, stuffB)"
609+ , "main = print stuffB"
610+ ]
611+ docB <- openDoc' "ModuleB.hs" "haskell" contentB
612+ _ <- waitForDiagnostics
613+ [CACodeAction action@CodeAction { _title = actionTitle }]
614+ <- getCodeActions docB (Range (Position 2 0) (Position 2 5))
615+ liftIO $ "Remove stuffA from import" @=? actionTitle
616+ executeCodeAction action
617+ contentAfterAction <- documentContents docB
618+ let expectedContentAfterAction = T.unlines
619+ [ "{-# OPTIONS_GHC -Wunused-imports #-}"
620+ , "module ModuleB where"
621+ , "import ModuleA (stuffB)"
622+ , "main = print stuffB"
623+ ]
624+ liftIO $ expectedContentAfterAction @=? contentAfterAction
625+ , testSession "redundant symbol binding" $ do
626+ let contentA = T.unlines
627+ [ "module ModuleA where"
628+ , "a !! b = a"
629+ , "stuffB :: Integer"
630+ , "stuffB = 123"
631+ ]
632+ _docA <- openDoc' "ModuleA.hs" "haskell" contentA
633+ let contentB = T.unlines
634+ [ "{-# OPTIONS_GHC -Wunused-imports #-}"
635+ , "module ModuleB where"
636+ , "import qualified ModuleA as A ((!!), stuffB, (!!))"
637+ , "main = print A.stuffB"
638+ ]
639+ docB <- openDoc' "ModuleB.hs" "haskell" contentB
640+ _ <- waitForDiagnostics
641+ [CACodeAction action@CodeAction { _title = actionTitle }]
642+ <- getCodeActions docB (Range (Position 2 0) (Position 2 5))
643+#if MIN_GHC_API_VERSION(8,6,0)
644+ liftIO $ "Remove !! from import" @=? actionTitle
645+#else
646+ liftIO $ "Remove A.!! from import" @=? actionTitle
647+#endif
648+ executeCodeAction action
649+ contentAfterAction <- documentContents docB
650+ let expectedContentAfterAction = T.unlines
651+ [ "{-# OPTIONS_GHC -Wunused-imports #-}"
652+ , "module ModuleB where"
653+ , "import qualified ModuleA as A (stuffB)"
654+ , "main = print A.stuffB"
655+ ]
656+ liftIO $ expectedContentAfterAction @=? contentAfterAction
597657 ]
598658
599659importRenameActionTests :: TestTree