@@ -12,9 +12,6 @@ import Data.Default
12
12
import qualified Data.HashMap.Strict as HM
13
13
import Data.List
14
14
import Data.Maybe
15
- #if __GLASGOW_HASKELL__ < 808
16
- import Data.Monoid ((<>) )
17
- #endif
18
15
import qualified Data.Text as T
19
16
import Ide.Plugin.Config
20
17
import Language.Haskell.LSP.Test as Test
@@ -75,12 +72,13 @@ hlintTests = testGroup "hlint suggestions" [
75
72
76
73
_ <- waitForDiagnosticsSource " hlint"
77
74
78
- (CACommand cmd: _) <- getAllCodeActions doc
75
+ cars <- getAllCodeActions doc
76
+ etaReduce <- liftIO $ inspectCommand cars [" Apply hint: Eta reduce" ]
79
77
80
- executeCommand cmd
78
+ executeCommand etaReduce
81
79
82
80
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 "
84
82
85
83
, testCase " changing configuration enables or disables hlint diagnostics" $ runSession hlsCommand fullCaps " test/testdata/hlint" $ do
86
84
let config = def { hlintOn = True }
@@ -127,24 +125,26 @@ hlintTests = testGroup "hlint suggestions" [
127
125
128
126
renameTests :: TestTree
129
127
renameTests = testGroup " rename suggestions" [
130
- ignoreTestBecause " Broken " $ testCase " works" $ runSession hlsCommand noLiteralCaps " test/testdata" $ do
128
+ testCase " works" $ runSession hlsCommand noLiteralCaps " test/testdata" $ do
131
129
doc <- openDoc " CodeActionRename.hs" " haskell"
132
130
133
- _ <- waitForDiagnosticsSource " bios "
131
+ _ <- waitForDiagnosticsSource " typecheck "
134
132
135
- CACommand cmd: _ <- getAllCodeActions doc
136
- executeCommand cmd
133
+ cars <- getAllCodeActions doc
134
+ replaceButStrLn <- liftIO $ inspectCommand cars [" Replace with" , " putStrLn" ]
135
+ executeCommand replaceButStrLn
137
136
138
137
x: _ <- T. lines <$> documentContents doc
139
138
liftIO $ x @?= " main = putStrLn \" hello\" "
140
139
141
- , ignoreTestBecause " Broken " $ testCase " doesn't give both documentChanges and changes"
140
+ , testCase " doesn't give both documentChanges and changes"
142
141
$ runSession hlsCommand noLiteralCaps " test/testdata" $ do
143
142
doc <- openDoc " CodeActionRename.hs" " haskell"
144
143
145
- _ <- waitForDiagnosticsSource " bios "
144
+ _ <- waitForDiagnosticsSource " typecheck "
146
145
147
- CACommand cmd <- (!! 2 ) <$> getAllCodeActions doc
146
+ cars <- getAllCodeActions doc
147
+ cmd <- liftIO $ inspectCommand cars [" Replace with" , " putStrLn" ]
148
148
let Just (List [Object args]) = cmd ^. L. arguments
149
149
Object editParams = args HM. ! " fallbackWorkspaceEdit"
150
150
liftIO $ do
@@ -153,46 +153,43 @@ renameTests = testGroup "rename suggestions" [
153
153
154
154
executeCommand cmd
155
155
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"
158
161
]
159
162
160
163
importTests :: TestTree
161
164
importTests = 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
163
166
doc <- openDoc " CodeActionImport.hs" " haskell"
164
167
-- No Formatting:
165
168
let config = def { formattingProvider = " none" }
166
169
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
167
170
168
- -- ignore the first empty hlint diagnostic publish
169
- [_,diag: _] <- count 2 waitForDiagnostics
171
+ diag: _ <- waitForDiagnostics
170
172
liftIO $ diag ^. L. message @?= " Variable not in scope: when :: Bool -> IO () -> IO ()"
171
173
172
174
actionsOrCommands <- getAllCodeActions doc
173
175
let actns = map fromAction actionsOrCommands
174
176
177
+ importControlMonad <- liftIO $ inspectCodeAction actionsOrCommands [" import Control.Monad" ]
175
178
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)" ]
178
180
forM_ actns $ \ a -> do
179
181
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"
186
183
187
- executeCodeAction ( head actns)
184
+ executeCodeAction importControlMonad
188
185
189
- contents <- getDocumentEdit doc
186
+ contents <- documentContents doc
190
187
liftIO $ contents @?= " import Control.Monad\n main :: IO ()\n main = when True $ putStrLn \" hello\" "
191
188
]
192
189
193
190
packageTests :: TestTree
194
191
packageTests = 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
196
193
flushStackEnvironment
197
194
runSession hlsCommand fullCaps " test/testdata/addPackageTest/cabal-exe" $ do
198
195
doc <- openDoc " AddPackage.hs" " haskell"
@@ -221,7 +218,7 @@ packageTests = testGroup "add package suggestions" [
221
218
liftIO $
222
219
any (\ l -> " text -any" `T.isSuffixOf` l || " text : {} -any" `T.isSuffixOf` l) (T. lines contents) @? " Contains text package"
223
220
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" $
225
222
runSession hlsCommand fullCaps " test/testdata/addPackageTest/hpack-exe" $ do
226
223
doc <- openDoc " app/Asdf.hs" " haskell"
227
224
@@ -254,25 +251,21 @@ packageTests = testGroup "add package suggestions" [
254
251
255
252
redundantImportTests :: TestTree
256
253
redundantImportTests = testGroup " redundant import code actions" [
257
- ignoreTestBecause " Broken " $ testCase " remove solitary redundant imports" $
254
+ testCase " remove solitary redundant imports" $
258
255
runSession hlsCommand fullCaps " test/testdata/redundantImportTest/" $ do
259
256
doc <- openDoc " src/CodeActionRedundant.hs" " haskell"
260
257
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" ]
268
260
269
261
mActions <- getAllCodeActions doc
270
262
271
- let allActions@ [removeAction, changeAction ] = map fromAction mActions
263
+ let allActions@ [removeAction, removeAllAction, makeAllExplicitAction ] = map fromAction mActions
272
264
273
265
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"
276
269
forM_ allActions $ \ a -> a ^. L. kind @?= Just CodeActionQuickFix
277
270
forM_ allActions $ \ a -> a ^. L. command @?= Nothing
278
271
forM_ allActions $ \ a -> isJust (a ^. L. edit) @? " Has edit"
@@ -285,10 +278,10 @@ redundantImportTests = testGroup "redundant import code actions" [
285
278
contents <- documentContents doc
286
279
liftIO $ contents @?= " module CodeActionRedundant where\n main :: IO ()\n main = putStrLn \" hello\" "
287
280
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
289
282
doc <- openDoc " src/MultipleImports.hs" " haskell"
290
- _ <- count 2 waitForDiagnostics
291
- [ CACommand cmd, _] <- getAllCodeActions doc
283
+ _ <- waitForDiagnostics
284
+ CACommand cmd : _ <- getAllCodeActions doc
292
285
executeCommand cmd
293
286
contents <- documentContents doc
294
287
liftIO $ (T. lines contents) @?=
@@ -301,112 +294,61 @@ redundantImportTests = testGroup "redundant import code actions" [
301
294
302
295
typedHoleTests :: TestTree
303
296
typedHoleTests = testGroup " typed hole code actions" [
304
- ignoreTestBecause " Broken " $ testCase " works" $
297
+ testCase " works" $
305
298
runSession hlsCommand fullCaps " test/testdata" $ do
306
299
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" ]
335
306
336
- executeCodeAction $ head cas
307
+ executeCodeAction replaceWithMaxBound
337
308
338
309
contents <- documentContents doc
339
310
340
311
liftIO $ contents @?= T. concat
341
312
[ " module TypedHoles where\n "
342
313
, " foo :: [Int] -> Int\n "
343
- , " foo x = " <> suggestion
314
+ , " foo x = maxBound "
344
315
]
345
316
346
- , ignoreTestBecause " Broken " $ testCase " shows more suggestions" $
317
+ , testCase " shows more suggestions" $
347
318
runSession hlsCommand fullCaps " test/testdata" $ do
348
319
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
372
322
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 _" ]
376
327
377
- executeCodeAction $ head cas
328
+ executeCodeAction replaceWithStuff
378
329
379
330
contents <- documentContents doc
380
331
381
332
liftIO $ (T. lines contents) @?=
382
333
[ " module TypedHoles2 (foo2) where"
383
334
, " newtype A = A Int"
384
335
, " foo2 :: [A] -> A"
385
- , " foo2 x = " <> suggestion <> " "
336
+ , " foo2 x = (stuff _) "
386
337
, " where"
387
338
, " stuff (A a) = A (a + 1)"
388
339
]
389
340
]
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
399
341
400
342
signatureTests :: TestTree
401
343
signatureTests = testGroup " missing top level signature code actions" [
402
- ignoreTestBecause " Broken " $ testCase " Adds top level signature" $
344
+ testCase " Adds top level signature" $
403
345
runSession hlsCommand fullCaps " test/testdata/" $ do
404
346
doc <- openDoc " TopLevelSignature.hs" " haskell"
405
347
406
- _ <- waitForDiagnosticsSource " bios "
348
+ _ <- waitForDiagnosticsSource " typecheck "
407
349
cas <- map fromAction <$> getAllCodeActions doc
408
350
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"
410
352
411
353
executeCodeAction $ head cas
412
354
@@ -425,19 +367,19 @@ signatureTests = testGroup "missing top level signature code actions" [
425
367
426
368
missingPragmaTests :: TestTree
427
369
missingPragmaTests = testGroup " missing pragma warning code actions" [
428
- ignoreTestBecause " Broken " $ testCase " Adds TypeSynonymInstances pragma" $
370
+ testCase " Adds TypeSynonymInstances pragma" $ do
429
371
runSession hlsCommand fullCaps " test/testdata/addPragmas" $ do
430
372
doc <- openDoc " NeedsPragmas.hs" " haskell"
431
373
432
- _ <- waitForDiagnosticsSource " bios "
374
+ _ <- waitForDiagnosticsSource " typecheck "
433
375
cas <- map fromAction <$> getAllCodeActions doc
434
376
435
377
liftIO $ " Add \" TypeSynonymInstances\" " `elem` map (^. L. title) cas @? " Contains TypeSynonymInstances code action"
436
378
liftIO $ " Add \" FlexibleInstances\" " `elem` map (^. L. title) cas @? " Contains FlexibleInstances code action"
437
379
438
380
executeCodeAction $ head cas
439
381
440
- contents <- getDocumentEdit doc
382
+ contents <- documentContents doc
441
383
442
384
let expected = [ " {-# LANGUAGE TypeSynonymInstances #-}"
443
385
, " "
@@ -466,7 +408,7 @@ unusedTermTests = testGroup "unused term code actions" [
466
408
-- runSession hlsCommand fullCaps "test/testdata/" $ do
467
409
-- doc <- openDoc "UnusedTerm.hs" "haskell"
468
410
--
469
- -- _ <- waitForDiagnosticsSource "bios "
411
+ -- _ <- waitForDiagnosticsSource "typecheck "
470
412
-- cas <- map fromAction <$> getAllCodeActions doc
471
413
--
472
414
-- liftIO $ map (^. L.title) cas `shouldContain` [ "Prefix imUnused with _"]
@@ -487,9 +429,9 @@ unusedTermTests = testGroup "unused term code actions" [
487
429
488
430
-- See https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/#textDocument_codeAction
489
431
-- `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
491
433
doc <- openDoc " CodeActionOnly.hs" " haskell"
492
- _ <- count 2 waitForDiagnostics -- need to wait for both hlint and ghcmod
434
+ _ <- waitForDiagnostics
493
435
diags <- getCurrentDiagnostics doc
494
436
let params = CodeActionParams doc (Range (Position 2 10 ) (Position 4 0 )) caContext Nothing
495
437
caContext = CodeActionContext (List diags) (Just (List [CodeActionRefactorInline ]))
@@ -507,8 +449,37 @@ fromAction :: CAResult -> CodeAction
507
449
fromAction (CACodeAction action) = action
508
450
fromAction _ = error " Not a code action"
509
451
452
+ fromCommand :: CAResult -> Command
453
+ fromCommand (CACommand command) = command
454
+ fromCommand _ = error " Not a command"
455
+
510
456
noLiteralCaps :: C. ClientCapabilities
511
457
noLiteralCaps = def { C. _textDocument = Just textDocumentCaps }
512
458
where
513
459
textDocumentCaps = def { C. _codeAction = Just codeActionCaps }
514
460
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