@@ -12,9 +12,6 @@ import Data.Default
1212import qualified Data.HashMap.Strict as HM
1313import Data.List
1414import Data.Maybe
15- #if __GLASGOW_HASKELL__ < 808
16- import Data.Monoid ((<>) )
17- #endif
1815import qualified Data.Text as T
1916import Ide.Plugin.Config
2017import Language.Haskell.LSP.Test as Test
@@ -75,12 +72,13 @@ hlintTests = testGroup "hlint suggestions" [
7572
7673 _ <- waitForDiagnosticsSource " hlint"
7774
78- (CACommand cmd: _) <- getAllCodeActions doc
75+ cars <- getAllCodeActions doc
76+ etaReduce <- liftIO $ inspectCommand cars [" Apply hint: Eta reduce" ]
7977
80- executeCommand cmd
78+ executeCommand etaReduce
8179
8280 contents <- skipManyTill publishDiagnosticsNotification $ getDocumentEdit doc
83- liftIO $ contents `elem` [ " main = undefined\n foo = id\n " , " main = undefined \n foo x = x \n " ] @? " Command is applied "
81+ liftIO $ contents @?= " main = undefined\n foo = id\n "
8482
8583 , testCase " changing configuration enables or disables hlint diagnostics" $ runSession hlsCommand fullCaps " test/testdata/hlint" $ do
8684 let config = def { hlintOn = True }
@@ -127,24 +125,26 @@ hlintTests = testGroup "hlint suggestions" [
127125
128126renameTests :: TestTree
129127renameTests = testGroup " rename suggestions" [
130- ignoreTestBecause " Broken " $ testCase " works" $ runSession hlsCommand noLiteralCaps " test/testdata" $ do
128+ testCase " works" $ runSession hlsCommand noLiteralCaps " test/testdata" $ do
131129 doc <- openDoc " CodeActionRename.hs" " haskell"
132130
133- _ <- waitForDiagnosticsSource " bios "
131+ _ <- waitForDiagnosticsSource " typecheck "
134132
135- CACommand cmd: _ <- getAllCodeActions doc
136- executeCommand cmd
133+ cars <- getAllCodeActions doc
134+ replaceButStrLn <- liftIO $ inspectCommand cars [" Replace with" , " putStrLn" ]
135+ executeCommand replaceButStrLn
137136
138137 x: _ <- T. lines <$> documentContents doc
139138 liftIO $ x @?= " main = putStrLn \" hello\" "
140139
141- , ignoreTestBecause " Broken " $ testCase " doesn't give both documentChanges and changes"
140+ , testCase " doesn't give both documentChanges and changes"
142141 $ runSession hlsCommand noLiteralCaps " test/testdata" $ do
143142 doc <- openDoc " CodeActionRename.hs" " haskell"
144143
145- _ <- waitForDiagnosticsSource " bios "
144+ _ <- waitForDiagnosticsSource " typecheck "
146145
147- CACommand cmd <- (!! 2 ) <$> getAllCodeActions doc
146+ cars <- getAllCodeActions doc
147+ cmd <- liftIO $ inspectCommand cars [" Replace with" , " putStrLn" ]
148148 let Just (List [Object args]) = cmd ^. L. arguments
149149 Object editParams = args HM. ! " fallbackWorkspaceEdit"
150150 liftIO $ do
@@ -153,46 +153,43 @@ renameTests = testGroup "rename suggestions" [
153153
154154 executeCommand cmd
155155
156- _: x: _ <- T. lines <$> documentContents doc
157- liftIO $ x @?= " foo = putStrLn \" world\" "
156+ x1: x2: _ <- T. lines <$> documentContents doc
157+ liftIO $
158+ x1 == " main = putStrLn \" hello\" "
159+ || x2 == " foo = putStrLn \" world\" "
160+ @? " One of the typos got fixed"
158161 ]
159162
160163importTests :: TestTree
161164importTests = testGroup " import suggestions" [
162- ignoreTestBecause " Broken " $ testCase " works with 3.8 code action kinds" $ runSession hlsCommand fullCaps " test/testdata" $ do
165+ testCase " works with 3.8 code action kinds" $ runSession hlsCommand fullCaps " test/testdata" $ do
163166 doc <- openDoc " CodeActionImport.hs" " haskell"
164167 -- No Formatting:
165168 let config = def { formattingProvider = " none" }
166169 sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
167170
168- -- ignore the first empty hlint diagnostic publish
169- [_,diag: _] <- count 2 waitForDiagnostics
171+ diag: _ <- waitForDiagnostics
170172 liftIO $ diag ^. L. message @?= " Variable not in scope: when :: Bool -> IO () -> IO ()"
171173
172174 actionsOrCommands <- getAllCodeActions doc
173175 let actns = map fromAction actionsOrCommands
174176
177+ importControlMonad <- liftIO $ inspectCodeAction actionsOrCommands [" import Control.Monad" ]
175178 liftIO $ do
176- head actns ^. L. title @?= " Import module Control.Monad"
177- head (tail actns) ^. L. title @?= " Import module Control.Monad (when)"
179+ expectCodeAction actionsOrCommands [" import Control.Monad (when)" ]
178180 forM_ actns $ \ a -> do
179181 a ^. L. kind @?= Just CodeActionQuickFix
180- isJust (a ^. L. command) @? " Contains command"
181- a ^. L. edit @?= Nothing
182- let hasOneDiag (Just (List [_])) = True
183- hasOneDiag _ = False
184- hasOneDiag (a ^. L. diagnostics) @? " Has one diagnostic"
185- length actns @?= 10
182+ length actns >= 10 @? " There are some actions"
186183
187- executeCodeAction ( head actns)
184+ executeCodeAction importControlMonad
188185
189- contents <- getDocumentEdit doc
186+ contents <- documentContents doc
190187 liftIO $ contents @?= " import Control.Monad\n main :: IO ()\n main = when True $ putStrLn \" hello\" "
191188 ]
192189
193190packageTests :: TestTree
194191packageTests = testGroup " add package suggestions" [
195- ignoreTestBecause " Broken " $ testCase " adds to .cabal files" $ do
192+ ignoreTestBecause " no support for adding dependent packages via code action " $ testCase " adds to .cabal files" $ do
196193 flushStackEnvironment
197194 runSession hlsCommand fullCaps " test/testdata/addPackageTest/cabal-exe" $ do
198195 doc <- openDoc " AddPackage.hs" " haskell"
@@ -221,7 +218,7 @@ packageTests = testGroup "add package suggestions" [
221218 liftIO $
222219 any (\ l -> " text -any" `T.isSuffixOf` l || " text : {} -any" `T.isSuffixOf` l) (T. lines contents) @? " Contains text package"
223220
224- , ignoreTestBecause " Broken " $ testCase " adds to hpack package.yaml files" $
221+ , ignoreTestBecause " no support for adding dependent packages via code action " $ testCase " adds to hpack package.yaml files" $
225222 runSession hlsCommand fullCaps " test/testdata/addPackageTest/hpack-exe" $ do
226223 doc <- openDoc " app/Asdf.hs" " haskell"
227224
@@ -254,25 +251,21 @@ packageTests = testGroup "add package suggestions" [
254251
255252redundantImportTests :: TestTree
256253redundantImportTests = testGroup " redundant import code actions" [
257- ignoreTestBecause " Broken " $ testCase " remove solitary redundant imports" $
254+ testCase " remove solitary redundant imports" $
258255 runSession hlsCommand fullCaps " test/testdata/redundantImportTest/" $ do
259256 doc <- openDoc " src/CodeActionRedundant.hs" " haskell"
260257
261- -- ignore the first empty hlint diagnostic publish
262- [_,diag: _] <- count 2 waitForDiagnostics
263-
264- let prefixes = [ " The import of `Data.List' is redundant" -- Windows
265- , " The import of ‘Data.List’ is redundant"
266- ]
267- in liftIO $ any (`T.isPrefixOf` (diag ^. L. message)) prefixes @? " Contains message"
258+ diags <- waitForDiagnostics
259+ liftIO $ expectDiagnostic diags [" The import of" , " Data.List" , " is redundant" ]
268260
269261 mActions <- getAllCodeActions doc
270262
271- let allActions@ [removeAction, changeAction ] = map fromAction mActions
263+ let allActions@ [removeAction, removeAllAction, makeAllExplicitAction ] = map fromAction mActions
272264
273265 liftIO $ do
274- removeAction ^. L. title @?= " Remove redundant import"
275- changeAction ^. L. title @?= " Import instances"
266+ removeAction ^. L. title @?= " Remove import"
267+ removeAllAction ^. L. title @?= " Remove all redundant imports"
268+ makeAllExplicitAction ^. L. title @?= " Make all imports explicit"
276269 forM_ allActions $ \ a -> a ^. L. kind @?= Just CodeActionQuickFix
277270 forM_ allActions $ \ a -> a ^. L. command @?= Nothing
278271 forM_ allActions $ \ a -> isJust (a ^. L. edit) @? " Has edit"
@@ -285,10 +278,10 @@ redundantImportTests = testGroup "redundant import code actions" [
285278 contents <- documentContents doc
286279 liftIO $ contents @?= " module CodeActionRedundant where\n main :: IO ()\n main = putStrLn \" hello\" "
287280
288- , ignoreTestBecause " Broken " $ testCase " doesn't touch other imports" $ runSession hlsCommand noLiteralCaps " test/testdata/redundantImportTest/" $ do
281+ , testCase " doesn't touch other imports" $ runSession hlsCommand noLiteralCaps " test/testdata/redundantImportTest/" $ do
289282 doc <- openDoc " src/MultipleImports.hs" " haskell"
290- _ <- count 2 waitForDiagnostics
291- [ CACommand cmd, _] <- getAllCodeActions doc
283+ _ <- waitForDiagnostics
284+ CACommand cmd : _ <- getAllCodeActions doc
292285 executeCommand cmd
293286 contents <- documentContents doc
294287 liftIO $ (T. lines contents) @?=
@@ -301,112 +294,61 @@ redundantImportTests = testGroup "redundant import code actions" [
301294
302295typedHoleTests :: TestTree
303296typedHoleTests = testGroup " typed hole code actions" [
304- ignoreTestBecause " Broken " $ testCase " works" $
297+ testCase " works" $
305298 runSession hlsCommand fullCaps " test/testdata" $ do
306299 doc <- openDoc " TypedHoles.hs" " haskell"
307- _ <- waitForDiagnosticsSource " bios"
308- cas <- map (\ (CACodeAction x)-> x) <$> getAllCodeActions doc
309-
310- let substitutions GHC810 = substitutions GHC88
311- substitutions GHC88 =
312- [ " Substitute hole (Int) with x ([Int])"
313- , " Substitute hole (Int) with foo ([Int] -> Int Valid hole fits include)"
314- , " Substitute hole (Int) with maxBound (forall a. Bounded a => a with maxBound @Int)"
315- , " Substitute hole (Int) with minBound (forall a. Bounded a => a with minBound @Int)"
316- ]
317- substitutions GHC86 =
318- [ " Substitute hole (Int) with x ([Int])"
319- , " Substitute hole (Int) with foo ([Int] -> Int Valid hole fits include)"
320- , " Substitute hole (Int) with maxBound (forall a. Bounded a => a with maxBound @Int)"
321- , " Substitute hole (Int) with minBound (forall a. Bounded a => a with minBound @Int)"
322- ]
323- substitutions GHC84 =
324- [ " Substitute hole (Int) with maxBound (forall a. Bounded a => a)"
325- , " Substitute hole (Int) with minBound (forall a. Bounded a => a)"
326- , " Substitute hole (Int) with undefined (forall (a :: TYPE r). GHC.Stack.Types.HasCallStack => a)"
327- ]
328-
329- liftIO $ map (^. L. title) cas `matchList`
330- substitutions ghcVersion @? " Contains substitutions"
331-
332- let suggestion = case ghcVersion of
333- GHC84 -> " maxBound"
334- _ -> " x"
300+ _ <- waitForDiagnosticsSource " typecheck"
301+ cas <- getAllCodeActions doc
302+ liftIO $ do
303+ expectCodeAction cas [" replace _ with minBound" ]
304+ expectCodeAction cas [" replace _ with foo _" ]
305+ replaceWithMaxBound <- liftIO $ inspectCodeAction cas [" replace _ with maxBound" ]
335306
336- executeCodeAction $ head cas
307+ executeCodeAction replaceWithMaxBound
337308
338309 contents <- documentContents doc
339310
340311 liftIO $ contents @?= T. concat
341312 [ " module TypedHoles where\n "
342313 , " foo :: [Int] -> Int\n "
343- , " foo x = " <> suggestion
314+ , " foo x = maxBound "
344315 ]
345316
346- , ignoreTestBecause " Broken " $ testCase " shows more suggestions" $
317+ , testCase " shows more suggestions" $
347318 runSession hlsCommand fullCaps " test/testdata" $ do
348319 doc <- openDoc " TypedHoles2.hs" " haskell"
349- _ <- waitForDiagnosticsSource " bios"
350- cas <- map fromAction <$> getAllCodeActions doc
351-
352- let substitutions GHC810 = substitutions GHC88
353- substitutions GHC88 =
354- [ " Substitute hole (A) with stuff (A -> A)"
355- , " Substitute hole (A) with x ([A])"
356- , " Substitute hole (A) with foo2 ([A] -> A)"
357- ]
358- substitutions GHC86 =
359- [ " Substitute hole (A) with stuff (A -> A)"
360- , " Substitute hole (A) with x ([A])"
361- , " Substitute hole (A) with foo2 ([A] -> A)"
362- ]
363- substitutions GHC84 =
364- [ " Substitute hole (A) with undefined (forall (a :: TYPE r). GHC.Stack.Types.HasCallStack => a)"
365- , " Substitute hole (A) with stuff (A -> A)"
366- , " Substitute hole (A) with x ([A])"
367- , " Substitute hole (A) with foo2 ([A] -> A)"
368- ]
369-
370- liftIO $ map (^. L. title) cas `matchList`
371- substitutions ghcVersion @? " Contains substitutions"
320+ _ <- waitForDiagnosticsSource " typecheck"
321+ cas <- getAllCodeActions doc
372322
373- let suggestion = case ghcVersion of
374- GHC84 -> " undefined"
375- _ -> " stuff"
323+ liftIO $ do
324+ expectCodeAction cas [" replace _ with foo2 _" ]
325+ expectCodeAction cas [" replace _ with A _" ]
326+ replaceWithStuff <- liftIO $ inspectCodeAction cas [" replace _ with stuff _" ]
376327
377- executeCodeAction $ head cas
328+ executeCodeAction replaceWithStuff
378329
379330 contents <- documentContents doc
380331
381332 liftIO $ (T. lines contents) @?=
382333 [ " module TypedHoles2 (foo2) where"
383334 , " newtype A = A Int"
384335 , " foo2 :: [A] -> A"
385- , " foo2 x = " <> suggestion <> " "
336+ , " foo2 x = (stuff _) "
386337 , " where"
387338 , " stuff (A a) = A (a + 1)"
388339 ]
389340 ]
390- where
391- -- | 'True' if @xs@ contains all of @ys@, possibly in a different order.
392- matchList :: (Eq a ) => [a ] -> [a ] -> Bool
393- xs `matchList` ys
394- | null extra && null missing = True
395- | otherwise = False
396- where
397- extra = xs \\ ys
398- missing = ys \\ xs
399341
400342signatureTests :: TestTree
401343signatureTests = testGroup " missing top level signature code actions" [
402- ignoreTestBecause " Broken " $ testCase " Adds top level signature" $
344+ testCase " Adds top level signature" $
403345 runSession hlsCommand fullCaps " test/testdata/" $ do
404346 doc <- openDoc " TopLevelSignature.hs" " haskell"
405347
406- _ <- waitForDiagnosticsSource " bios "
348+ _ <- waitForDiagnosticsSource " typecheck "
407349 cas <- map fromAction <$> getAllCodeActions doc
408350
409- liftIO $ " Add signature: main :: IO ()" `elem` (map (^. L. title) cas) @? " Contains code action"
351+ liftIO $ " add signature: main :: IO ()" `elem` (map (^. L. title) cas) @? " Contains code action"
410352
411353 executeCodeAction $ head cas
412354
@@ -425,19 +367,19 @@ signatureTests = testGroup "missing top level signature code actions" [
425367
426368missingPragmaTests :: TestTree
427369missingPragmaTests = testGroup " missing pragma warning code actions" [
428- ignoreTestBecause " Broken " $ testCase " Adds TypeSynonymInstances pragma" $
370+ testCase " Adds TypeSynonymInstances pragma" $ do
429371 runSession hlsCommand fullCaps " test/testdata/addPragmas" $ do
430372 doc <- openDoc " NeedsPragmas.hs" " haskell"
431373
432- _ <- waitForDiagnosticsSource " bios "
374+ _ <- waitForDiagnosticsSource " typecheck "
433375 cas <- map fromAction <$> getAllCodeActions doc
434376
435377 liftIO $ " Add \" TypeSynonymInstances\" " `elem` map (^. L. title) cas @? " Contains TypeSynonymInstances code action"
436378 liftIO $ " Add \" FlexibleInstances\" " `elem` map (^. L. title) cas @? " Contains FlexibleInstances code action"
437379
438380 executeCodeAction $ head cas
439381
440- contents <- getDocumentEdit doc
382+ contents <- documentContents doc
441383
442384 let expected = [ " {-# LANGUAGE TypeSynonymInstances #-}"
443385 , " "
@@ -466,7 +408,7 @@ unusedTermTests = testGroup "unused term code actions" [
466408 -- runSession hlsCommand fullCaps "test/testdata/" $ do
467409 -- doc <- openDoc "UnusedTerm.hs" "haskell"
468410 --
469- -- _ <- waitForDiagnosticsSource "bios "
411+ -- _ <- waitForDiagnosticsSource "typecheck "
470412 -- cas <- map fromAction <$> getAllCodeActions doc
471413 --
472414 -- liftIO $ map (^. L.title) cas `shouldContain` [ "Prefix imUnused with _"]
@@ -487,9 +429,9 @@ unusedTermTests = testGroup "unused term code actions" [
487429
488430 -- See https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/#textDocument_codeAction
489431 -- `CodeActionContext`
490- ignoreTestBecause " Broken " $ testCase " respect 'only' parameter" $ runSession hlsCommand fullCaps " test/testdata" $ do
432+ testCase " respect 'only' parameter" $ runSession hlsCommand fullCaps " test/testdata" $ do
491433 doc <- openDoc " CodeActionOnly.hs" " haskell"
492- _ <- count 2 waitForDiagnostics -- need to wait for both hlint and ghcmod
434+ _ <- waitForDiagnostics
493435 diags <- getCurrentDiagnostics doc
494436 let params = CodeActionParams doc (Range (Position 2 10 ) (Position 4 0 )) caContext Nothing
495437 caContext = CodeActionContext (List diags) (Just (List [CodeActionRefactorInline ]))
@@ -507,8 +449,37 @@ fromAction :: CAResult -> CodeAction
507449fromAction (CACodeAction action) = action
508450fromAction _ = error " Not a code action"
509451
452+ fromCommand :: CAResult -> Command
453+ fromCommand (CACommand command) = command
454+ fromCommand _ = error " Not a command"
455+
510456noLiteralCaps :: C. ClientCapabilities
511457noLiteralCaps = def { C. _textDocument = Just textDocumentCaps }
512458 where
513459 textDocumentCaps = def { C. _codeAction = Just codeActionCaps }
514460 codeActionCaps = C. CodeActionClientCapabilities (Just True ) Nothing
461+
462+ onMatch :: [a ] -> (a -> Bool ) -> String -> IO a
463+ onMatch as pred err = maybe (fail err) return (find pred as)
464+
465+ inspectDiagnostic :: [Diagnostic ] -> [T. Text ] -> IO Diagnostic
466+ inspectDiagnostic diags s = onMatch diags (\ ca -> all (`T.isInfixOf` (ca ^. L. message)) s) err
467+ where err = " expected diagnostic matching '" ++ show s ++ " ' but did not find one"
468+
469+ expectDiagnostic :: [Diagnostic ] -> [T. Text ] -> IO ()
470+ expectDiagnostic diags s = void $ inspectDiagnostic diags s
471+
472+ inspectCodeAction :: [CAResult ] -> [T. Text ] -> IO CodeAction
473+ inspectCodeAction cars s = fromAction <$> onMatch cars pred err
474+ where pred (CACodeAction ca) = all (`T.isInfixOf` (ca ^. L. title)) s
475+ pred _ = False
476+ err = " expected code action matching '" ++ show s ++ " ' but did not find one"
477+
478+ expectCodeAction :: [CAResult ] -> [T. Text ] -> IO ()
479+ expectCodeAction cars s = void $ inspectCodeAction cars s
480+
481+ inspectCommand :: [CAResult ] -> [T. Text ] -> IO Command
482+ inspectCommand cars s = fromCommand <$> onMatch cars pred err
483+ where pred (CACommand command) = all (`T.isInfixOf` (command ^. L. title)) s
484+ pred _ = False
485+ err = " expected code action matching '" ++ show s ++ " ' but did not find one"
0 commit comments