Skip to content

Commit dd3dbd7

Browse files
authored
fix(refactor): do not offer 'Add Argument' for qualified names (#4852)
1 parent 2ed023c commit dd3dbd7

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Development.IDE.Plugin.Plugins.AddArgument (plugin) where
44
import Control.Monad (join)
55
import Control.Monad.Trans.Class (lift)
66
import Data.Bifunctor (Bifunctor (..))
7+
import Data.Char (isUpper)
78
import Data.Either.Extra (maybeToEither)
89
import qualified Data.Text as T
910
import Development.IDE.GHC.Compat
@@ -68,11 +69,20 @@ type HsArrow pass = HsMultAnn pass
6869
-- In this case a new argument would have to add its type between b and c in the signature.
6970
plugin :: ParsedModule -> Diagnostic -> Either PluginError [(T.Text, [TextEdit])]
7071
plugin parsedModule Diagnostic {_message, _range}
71-
| Just (name, typ) <- matchVariableNotInScope message = addArgumentAction parsedModule _range name typ
72+
| Just (name, typ) <- matchVariableNotInScope message
73+
, not (isQualifiedName name) = addArgumentAction parsedModule _range name typ
7274
| Just (name, typ) <- matchFoundHoleIncludeUnderscore message = addArgumentAction parsedModule _range name (Just typ)
7375
| otherwise = pure []
7476
where
7577
message = unifySpaces _message
78+
-- Qualified names (e.g. NE.toList) always start with an uppercase module
79+
-- qualifier. Since "Variable not in scope" only reports variables and
80+
-- operators, an unqualified name will never start with an uppercase letter.
81+
-- Therefore, checking for an uppercase first character reliably identifies
82+
-- qualified names, which can never be valid function argument patterns.
83+
isQualifiedName name = case T.uncons name of
84+
Just (c, _) -> isUpper c
85+
Nothing -> False
7686

7787
-- Given a name for the new binding, add a new pattern to the match in the last position,
7888
-- returning how many patterns there were in this match prior to the transformation:

plugins/hls-refactor-plugin/test/Test/AddArgument.hs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
{-# LANGUAGE DataKinds #-}
44
{-# LANGUAGE DuplicateRecordFields #-}
55
{-# LANGUAGE GADTs #-}
6+
{-# LANGUAGE LambdaCase #-}
67
{-# LANGUAGE OverloadedStrings #-}
78

89
module Test.AddArgument (tests) where
910

11+
import Data.List (find)
12+
import Data.Maybe (isJust)
1013
import qualified Data.Text as T
1114
import Development.IDE.Types.Location
1215
import Language.LSP.Protocol.Types hiding
@@ -42,7 +45,8 @@ tests =
4245
mkGoldenAddArgTest "AddArgWithLambda" (r 1 0 1 50),
4346
mkGoldenAddArgTest "MultiSigFirst" (r 2 0 2 50),
4447
mkGoldenAddArgTest "MultiSigLast" (r 2 0 2 50),
45-
mkGoldenAddArgTest "MultiSigMiddle" (r 2 0 2 50)
48+
mkGoldenAddArgTest "MultiSigMiddle" (r 2 0 2 50),
49+
mkNoAddArgForQualifiedNameTest
4650
]
4751
where
4852
r x y x' y' = Range (Position x y) (Position x' y')
@@ -71,3 +75,30 @@ mkGoldenAddArgTest' testFileName range varName = do
7175
"expected"
7276
"hs"
7377
action
78+
79+
-- | Verify that the "Add argument" code action is NOT offered for qualified names (e.g. NE.toList).
80+
-- We also verify that an import suggestion IS offered, to confirm we're testing the right diagnostic.
81+
mkNoAddArgForQualifiedNameTest :: TestTree
82+
mkNoAddArgForQualifiedNameTest =
83+
testCase "No add argument for qualified names" $ runSessionWithServerInTmpDir def
84+
( mkPluginTestDescriptor Refactor.iePluginDescriptor "ghcide-code-actions-imports-exports"
85+
<> mkPluginTestDescriptor Refactor.bindingsPluginDescriptor "ghcide-code-actions-bindings"
86+
)
87+
(FS.mkVirtualFileTree "plugins/hls-refactor-plugin/test/data/add-arg" (FS.directProject "QualifiedName.hs"))
88+
$ do
89+
doc <- openDoc "QualifiedName.hs" "haskell"
90+
_ <- waitForDiagnostics
91+
actions <- getCodeActions doc (Range (Position 5 0) (Position 5 50))
92+
-- Verify that an import suggestion exists, confirming the right diagnostic.
93+
let importAction = find (\case
94+
InR CodeAction {_title = t} -> "Data.List.NonEmpty" `T.isInfixOf` t
95+
_ -> False) actions
96+
liftIO $ assertBool
97+
"Expected an import suggestion code action for Data.List.NonEmpty"
98+
(isJust importAction)
99+
-- Verify that the "Add argument" code action is NOT offered
100+
let addArgAction = find (\case
101+
InR CodeAction {_title = t} -> "Add argument" `T.isPrefixOf` t
102+
_ -> False) actions
103+
liftIO $ addArgAction @?= Nothing
104+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module QualifiedName where
2+
3+
import Data.List.NonEmpty (NonEmpty(..))
4+
5+
foo :: NonEmpty a -> [a]
6+
foo xs = NE.toList xs

0 commit comments

Comments
 (0)