2019-08-13 19:23:03 +03:00
|
|
|
|
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
|
2019-07-22 16:42:04 +03:00
|
|
|
|
-- SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
2019-07-25 15:50:07 +03:00
|
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
2019-09-16 17:43:50 +03:00
|
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
#include "ghc-api-version.h"
|
2019-07-22 16:42:04 +03:00
|
|
|
|
|
|
|
|
|
module Main (main) where
|
|
|
|
|
|
2019-10-04 10:37:47 +03:00
|
|
|
|
import Control.Applicative.Combinators
|
|
|
|
|
import Control.Monad
|
2019-09-11 23:48:09 +03:00
|
|
|
|
import Control.Monad.IO.Class (liftIO)
|
2019-10-04 10:37:47 +03:00
|
|
|
|
import Data.Char (toLower)
|
|
|
|
|
import Data.Foldable
|
2019-07-22 16:42:04 +03:00
|
|
|
|
import qualified Data.Text as T
|
|
|
|
|
import Development.IDE.Test
|
2019-07-25 15:50:07 +03:00
|
|
|
|
import Development.IDE.Test.Runfiles
|
2019-07-22 16:42:04 +03:00
|
|
|
|
import Language.Haskell.LSP.Test
|
|
|
|
|
import Language.Haskell.LSP.Types
|
2019-08-13 21:00:21 +03:00
|
|
|
|
import Language.Haskell.LSP.Types.Capabilities
|
2019-07-22 16:42:04 +03:00
|
|
|
|
import System.Environment.Blank (setEnv)
|
2019-10-04 10:37:47 +03:00
|
|
|
|
import System.FilePath
|
2019-07-22 16:42:04 +03:00
|
|
|
|
import System.IO.Extra
|
2019-09-25 14:01:41 +03:00
|
|
|
|
import System.Directory
|
2019-07-22 16:42:04 +03:00
|
|
|
|
import Test.Tasty
|
|
|
|
|
import Test.Tasty.HUnit
|
2019-09-29 13:03:16 +03:00
|
|
|
|
import Test.Tasty.ExpectedFailure
|
2019-10-01 10:24:33 +03:00
|
|
|
|
import Data.Maybe
|
2019-07-22 16:42:04 +03:00
|
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = defaultMain $ testGroup "HIE"
|
|
|
|
|
[ testSession "open close" $ do
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" ""
|
|
|
|
|
void (message :: Session ProgressStartNotification)
|
|
|
|
|
closeDoc doc
|
|
|
|
|
void (message :: Session ProgressDoneNotification)
|
2019-10-01 14:03:06 +03:00
|
|
|
|
, initializeResponseTests
|
2019-08-15 12:35:52 +03:00
|
|
|
|
, diagnosticTests
|
2019-09-11 23:48:09 +03:00
|
|
|
|
, codeActionTests
|
2019-09-29 13:03:16 +03:00
|
|
|
|
, findDefinitionTests
|
2019-08-15 12:35:52 +03:00
|
|
|
|
]
|
|
|
|
|
|
2019-10-01 14:03:06 +03:00
|
|
|
|
initializeResponseTests :: TestTree
|
|
|
|
|
initializeResponseTests = withResource acquire release tests where
|
|
|
|
|
|
|
|
|
|
-- these tests document and monitor the evolution of the
|
|
|
|
|
-- capabilities announced by the server in the initialize
|
|
|
|
|
-- response. Currently the server advertises almost no capabilities
|
|
|
|
|
-- at all, in some cases failing to announce capabilities that it
|
|
|
|
|
-- actually does provide! Hopefully this will change ...
|
|
|
|
|
tests :: IO InitializeResponse -> TestTree
|
|
|
|
|
tests getInitializeResponse =
|
|
|
|
|
testGroup "initialize response capabilities"
|
|
|
|
|
[ chk " text doc sync" _textDocumentSync tds
|
|
|
|
|
, chk " hover" _hoverProvider (Just True)
|
|
|
|
|
, chk "NO completion" _completionProvider Nothing
|
|
|
|
|
, chk "NO signature help" _signatureHelpProvider Nothing
|
|
|
|
|
, chk " goto definition" _definitionProvider (Just True)
|
|
|
|
|
, chk "NO goto type definition" _typeDefinitionProvider Nothing
|
|
|
|
|
, chk "NO goto implementation" _implementationProvider Nothing
|
|
|
|
|
, chk "NO find references" _referencesProvider Nothing
|
|
|
|
|
, chk "NO doc highlight" _documentHighlightProvider Nothing
|
|
|
|
|
, chk "NO doc symbol" _documentSymbolProvider Nothing
|
|
|
|
|
, chk "NO workspace symbol" _workspaceSymbolProvider Nothing
|
2019-10-04 09:57:23 +03:00
|
|
|
|
, chk " code action" _codeActionProvider $ Just $ CodeActionOptionsStatic True
|
2019-10-01 14:03:06 +03:00
|
|
|
|
, chk "NO code lens" _codeLensProvider Nothing
|
|
|
|
|
, chk "NO doc formatting" _documentFormattingProvider Nothing
|
|
|
|
|
, chk "NO doc range formatting"
|
|
|
|
|
_documentRangeFormattingProvider Nothing
|
|
|
|
|
, chk "NO doc formatting on typing"
|
|
|
|
|
_documentOnTypeFormattingProvider Nothing
|
|
|
|
|
, chk "NO renaming" _renameProvider Nothing
|
|
|
|
|
, chk "NO doc link" _documentLinkProvider Nothing
|
|
|
|
|
, chk "NO color" _colorProvider Nothing
|
|
|
|
|
, chk "NO folding range" _foldingRangeProvider Nothing
|
|
|
|
|
, chk "NO execute command" _executeCommandProvider Nothing
|
|
|
|
|
, chk "NO workspace" _workspace nothingWorkspace
|
|
|
|
|
, chk "NO experimental" _experimental Nothing
|
|
|
|
|
] where
|
|
|
|
|
|
|
|
|
|
tds = Just (TDSOptions (TextDocumentSyncOptions
|
|
|
|
|
{ _openClose = Just True
|
|
|
|
|
, _change = Just TdSyncIncremental
|
|
|
|
|
, _willSave = Nothing
|
|
|
|
|
, _willSaveWaitUntil = Nothing
|
|
|
|
|
, _save = Just (SaveOptions {_includeText = Nothing})}))
|
|
|
|
|
|
|
|
|
|
nothingWorkspace = Just (WorkspaceOptions {_workspaceFolders = Nothing})
|
|
|
|
|
|
|
|
|
|
chk :: (Eq a, Show a) => TestName -> (InitializeResponseCapabilitiesInner -> a) -> a -> TestTree
|
|
|
|
|
chk title getActual expected =
|
|
|
|
|
testCase title $ getInitializeResponse >>= \ir -> expected @=? (getActual . innerCaps) ir
|
|
|
|
|
|
|
|
|
|
innerCaps :: InitializeResponse -> InitializeResponseCapabilitiesInner
|
|
|
|
|
innerCaps (ResponseMessage _ _ (Just (InitializeResponseCapabilities c)) _) = c
|
|
|
|
|
innerCaps _ = error "this test only expects inner capabilities"
|
|
|
|
|
|
|
|
|
|
acquire :: IO InitializeResponse
|
|
|
|
|
acquire = run initializeResponse
|
|
|
|
|
|
|
|
|
|
release :: InitializeResponse -> IO ()
|
|
|
|
|
release = const $ pure ()
|
|
|
|
|
|
|
|
|
|
|
2019-08-15 12:35:52 +03:00
|
|
|
|
diagnosticTests :: TestTree
|
|
|
|
|
diagnosticTests = testGroup "diagnostics"
|
2019-10-01 15:52:07 +03:00
|
|
|
|
[ testSessionWait "fix syntax error" $ do
|
2019-07-22 16:42:04 +03:00
|
|
|
|
let content = T.unlines [ "module Testing wher" ]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
expectDiagnostics [("Testing.hs", [(DsError, (0, 15), "parse error")])]
|
|
|
|
|
let change = TextDocumentContentChangeEvent
|
|
|
|
|
{ _range = Just (Range (Position 0 15) (Position 0 19))
|
|
|
|
|
, _rangeLength = Nothing
|
|
|
|
|
, _text = "where"
|
|
|
|
|
}
|
|
|
|
|
changeDoc doc [change]
|
|
|
|
|
expectDiagnostics [("Testing.hs", [])]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "introduce syntax error" $ do
|
2019-07-22 16:42:04 +03:00
|
|
|
|
let content = T.unlines [ "module Testing where" ]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
void (message :: Session ProgressStartNotification)
|
|
|
|
|
let change = TextDocumentContentChangeEvent
|
|
|
|
|
{ _range = Just (Range (Position 0 15) (Position 0 18))
|
|
|
|
|
, _rangeLength = Nothing
|
|
|
|
|
, _text = "wher"
|
|
|
|
|
}
|
|
|
|
|
changeDoc doc [change]
|
|
|
|
|
expectDiagnostics [("Testing.hs", [(DsError, (0, 15), "parse error")])]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "variable not in scope" $ do
|
2019-08-15 12:35:52 +03:00
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "foo :: Int -> Int -> Int"
|
|
|
|
|
, "foo a b = a + ab"
|
|
|
|
|
, "bar :: Int -> Int -> Int"
|
|
|
|
|
, "bar a b = cd + b"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "Testing.hs"
|
|
|
|
|
, [ (DsError, (2, 14), "Variable not in scope: ab")
|
|
|
|
|
, (DsError, (4, 10), "Variable not in scope: cd")
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "type error" $ do
|
2019-08-15 12:35:52 +03:00
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "foo :: Int -> String -> Int"
|
|
|
|
|
, "foo a b = a + b"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "Testing.hs"
|
|
|
|
|
, [(DsError, (2, 14), "Couldn't match type '[Char]' with 'Int'")]
|
|
|
|
|
)
|
|
|
|
|
]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "typed hole" $ do
|
Defer type errors (#47)
* TEST: Degrade type error to warning
It will be upgraded again later, but for the time being we want to see
whether the proposed mechanism for deferring type errors works at
all. As it turns out the first, most obvious approach, does not
work: this is documented in the next commit.
A second approach was found that does work, and appears in the commit
after the next.
This test is failing until the second approach is implemented.
* Defer type errors (first approach: FAILED)
The idea is to set the `-fdefer-type-errors` and
`-fwarn-deferred-type-errors` flags, by setting options
programatically inside the `Ghc` monad.
Deferral of type errors was not observed with this approach. The
(less obvious) approach used in the next commit seems to be more
successful.
* Defer type errors (second approach: SUCCESS)
This approach modifies the `ParsedModule` which is passed to
`GHC.typecheckedModule` by hie-core's `typecheckModule`.
Type warning deferral is now observed at run time, and the tests pass.
* TEST: Reinstate severity of type errors
So far, type errors have been deferred and reported as warnings.
The next step is to ensure that the deferred type errors are reported
as errors rather than warnings, once again. This test fails until the
implementation arrives in the next commit.
* Upgrade severity of deferred Type Errors after typecheck
... and make the test pass again.
* Hide helper functions in local scopes
* Stop setting Opt_WarnDeferredTypeErrors
... and the tests still pass, thereby confirming @hsenag's hypothesis
that this flag is not needed.
* TEST: Check that typed holes are reported as errors
* TEST: Downgrade severity of typed holes Error -> Warning
This test fails, thereby falsifying the hypothesis that
`Opt_DeferTypeErrors` implies `Opt_DeferTypedHoles`.
* Defer typed holes
... and pass the failing test.
* TEST: Reinstate severity of typed holes
... failing the test until the implementation catches up in the next
commit.
* Upgrade severity of deferred Typed Holes after typecheck
... and pass the test once again.
* TEST: Degrade variable out of scope from Error to Warning
... test fails until next commit.
* Defer out of scope variables
... passing the test which was changed in the last commit.
* TEST: Reinstate severity of out of scope variables
... failing the test, and forcing the implementation to catch up.
* Upgrade severity of deferred out of scope vars after typecheck
... passing the test once again.
* Add explicit tests for deferrals
* Add IdeOption for deferral switching
* Improve documentation of optDefer
* Add IdeDefer newtype
2019-09-17 15:28:20 +03:00
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "foo :: Int -> String"
|
|
|
|
|
, "foo a = _ a"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "Testing.hs"
|
|
|
|
|
, [(DsError, (2, 8), "Found hole: _ :: Int -> String")]
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
, testGroup "deferral" $
|
|
|
|
|
let sourceA a = T.unlines
|
|
|
|
|
[ "module A where"
|
|
|
|
|
, "a :: Int"
|
|
|
|
|
, "a = " <> a]
|
|
|
|
|
sourceB = T.unlines
|
|
|
|
|
[ "module B where"
|
|
|
|
|
, "import A"
|
|
|
|
|
, "b :: Float"
|
|
|
|
|
, "b = True"]
|
|
|
|
|
bMessage = "Couldn't match expected type 'Float' with actual type 'Bool'"
|
|
|
|
|
expectedDs aMessage =
|
|
|
|
|
[ ("A.hs", [(DsError, (2,4), aMessage)])
|
|
|
|
|
, ("B.hs", [(DsError, (3,4), bMessage)])]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
deferralTest title binding msg = testSessionWait title $ do
|
Defer type errors (#47)
* TEST: Degrade type error to warning
It will be upgraded again later, but for the time being we want to see
whether the proposed mechanism for deferring type errors works at
all. As it turns out the first, most obvious approach, does not
work: this is documented in the next commit.
A second approach was found that does work, and appears in the commit
after the next.
This test is failing until the second approach is implemented.
* Defer type errors (first approach: FAILED)
The idea is to set the `-fdefer-type-errors` and
`-fwarn-deferred-type-errors` flags, by setting options
programatically inside the `Ghc` monad.
Deferral of type errors was not observed with this approach. The
(less obvious) approach used in the next commit seems to be more
successful.
* Defer type errors (second approach: SUCCESS)
This approach modifies the `ParsedModule` which is passed to
`GHC.typecheckedModule` by hie-core's `typecheckModule`.
Type warning deferral is now observed at run time, and the tests pass.
* TEST: Reinstate severity of type errors
So far, type errors have been deferred and reported as warnings.
The next step is to ensure that the deferred type errors are reported
as errors rather than warnings, once again. This test fails until the
implementation arrives in the next commit.
* Upgrade severity of deferred Type Errors after typecheck
... and make the test pass again.
* Hide helper functions in local scopes
* Stop setting Opt_WarnDeferredTypeErrors
... and the tests still pass, thereby confirming @hsenag's hypothesis
that this flag is not needed.
* TEST: Check that typed holes are reported as errors
* TEST: Downgrade severity of typed holes Error -> Warning
This test fails, thereby falsifying the hypothesis that
`Opt_DeferTypeErrors` implies `Opt_DeferTypedHoles`.
* Defer typed holes
... and pass the failing test.
* TEST: Reinstate severity of typed holes
... failing the test until the implementation catches up in the next
commit.
* Upgrade severity of deferred Typed Holes after typecheck
... and pass the test once again.
* TEST: Degrade variable out of scope from Error to Warning
... test fails until next commit.
* Defer out of scope variables
... passing the test which was changed in the last commit.
* TEST: Reinstate severity of out of scope variables
... failing the test, and forcing the implementation to catch up.
* Upgrade severity of deferred out of scope vars after typecheck
... passing the test once again.
* Add explicit tests for deferrals
* Add IdeOption for deferral switching
* Improve documentation of optDefer
* Add IdeDefer newtype
2019-09-17 15:28:20 +03:00
|
|
|
|
_ <- openDoc' "A.hs" "haskell" $ sourceA binding
|
|
|
|
|
_ <- openDoc' "B.hs" "haskell" sourceB
|
2019-09-29 13:03:16 +03:00
|
|
|
|
expectDiagnostics $ expectedDs msg
|
Defer type errors (#47)
* TEST: Degrade type error to warning
It will be upgraded again later, but for the time being we want to see
whether the proposed mechanism for deferring type errors works at
all. As it turns out the first, most obvious approach, does not
work: this is documented in the next commit.
A second approach was found that does work, and appears in the commit
after the next.
This test is failing until the second approach is implemented.
* Defer type errors (first approach: FAILED)
The idea is to set the `-fdefer-type-errors` and
`-fwarn-deferred-type-errors` flags, by setting options
programatically inside the `Ghc` monad.
Deferral of type errors was not observed with this approach. The
(less obvious) approach used in the next commit seems to be more
successful.
* Defer type errors (second approach: SUCCESS)
This approach modifies the `ParsedModule` which is passed to
`GHC.typecheckedModule` by hie-core's `typecheckModule`.
Type warning deferral is now observed at run time, and the tests pass.
* TEST: Reinstate severity of type errors
So far, type errors have been deferred and reported as warnings.
The next step is to ensure that the deferred type errors are reported
as errors rather than warnings, once again. This test fails until the
implementation arrives in the next commit.
* Upgrade severity of deferred Type Errors after typecheck
... and make the test pass again.
* Hide helper functions in local scopes
* Stop setting Opt_WarnDeferredTypeErrors
... and the tests still pass, thereby confirming @hsenag's hypothesis
that this flag is not needed.
* TEST: Check that typed holes are reported as errors
* TEST: Downgrade severity of typed holes Error -> Warning
This test fails, thereby falsifying the hypothesis that
`Opt_DeferTypeErrors` implies `Opt_DeferTypedHoles`.
* Defer typed holes
... and pass the failing test.
* TEST: Reinstate severity of typed holes
... failing the test until the implementation catches up in the next
commit.
* Upgrade severity of deferred Typed Holes after typecheck
... and pass the test once again.
* TEST: Degrade variable out of scope from Error to Warning
... test fails until next commit.
* Defer out of scope variables
... passing the test which was changed in the last commit.
* TEST: Reinstate severity of out of scope variables
... failing the test, and forcing the implementation to catch up.
* Upgrade severity of deferred out of scope vars after typecheck
... passing the test once again.
* Add explicit tests for deferrals
* Add IdeOption for deferral switching
* Improve documentation of optDefer
* Add IdeDefer newtype
2019-09-17 15:28:20 +03:00
|
|
|
|
in
|
2019-09-24 21:41:38 +03:00
|
|
|
|
[ deferralTest "type error" "True" "Couldn't match expected type"
|
|
|
|
|
, deferralTest "typed hole" "_" "Found hole"
|
|
|
|
|
, deferralTest "out of scope var" "unbound" "Variable not in scope"
|
|
|
|
|
, deferralTest "message shows error" "True" "A.hs:3:5: error:"
|
Defer type errors (#47)
* TEST: Degrade type error to warning
It will be upgraded again later, but for the time being we want to see
whether the proposed mechanism for deferring type errors works at
all. As it turns out the first, most obvious approach, does not
work: this is documented in the next commit.
A second approach was found that does work, and appears in the commit
after the next.
This test is failing until the second approach is implemented.
* Defer type errors (first approach: FAILED)
The idea is to set the `-fdefer-type-errors` and
`-fwarn-deferred-type-errors` flags, by setting options
programatically inside the `Ghc` monad.
Deferral of type errors was not observed with this approach. The
(less obvious) approach used in the next commit seems to be more
successful.
* Defer type errors (second approach: SUCCESS)
This approach modifies the `ParsedModule` which is passed to
`GHC.typecheckedModule` by hie-core's `typecheckModule`.
Type warning deferral is now observed at run time, and the tests pass.
* TEST: Reinstate severity of type errors
So far, type errors have been deferred and reported as warnings.
The next step is to ensure that the deferred type errors are reported
as errors rather than warnings, once again. This test fails until the
implementation arrives in the next commit.
* Upgrade severity of deferred Type Errors after typecheck
... and make the test pass again.
* Hide helper functions in local scopes
* Stop setting Opt_WarnDeferredTypeErrors
... and the tests still pass, thereby confirming @hsenag's hypothesis
that this flag is not needed.
* TEST: Check that typed holes are reported as errors
* TEST: Downgrade severity of typed holes Error -> Warning
This test fails, thereby falsifying the hypothesis that
`Opt_DeferTypeErrors` implies `Opt_DeferTypedHoles`.
* Defer typed holes
... and pass the failing test.
* TEST: Reinstate severity of typed holes
... failing the test until the implementation catches up in the next
commit.
* Upgrade severity of deferred Typed Holes after typecheck
... and pass the test once again.
* TEST: Degrade variable out of scope from Error to Warning
... test fails until next commit.
* Defer out of scope variables
... passing the test which was changed in the last commit.
* TEST: Reinstate severity of out of scope variables
... failing the test, and forcing the implementation to catch up.
* Upgrade severity of deferred out of scope vars after typecheck
... passing the test once again.
* Add explicit tests for deferrals
* Add IdeOption for deferral switching
* Improve documentation of optDefer
* Add IdeDefer newtype
2019-09-17 15:28:20 +03:00
|
|
|
|
]
|
|
|
|
|
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "remove required module" $ do
|
2019-08-15 12:35:52 +03:00
|
|
|
|
let contentA = T.unlines [ "module ModuleA where" ]
|
|
|
|
|
docA <- openDoc' "ModuleA.hs" "haskell" contentA
|
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "module ModuleB where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
let change = TextDocumentContentChangeEvent
|
|
|
|
|
{ _range = Just (Range (Position 0 0) (Position 0 20))
|
|
|
|
|
, _rangeLength = Nothing
|
|
|
|
|
, _text = ""
|
|
|
|
|
}
|
|
|
|
|
changeDoc docA [change]
|
|
|
|
|
expectDiagnostics [("ModuleB.hs", [(DsError, (1, 0), "Could not find module")])]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "add missing module" $ do
|
2019-08-15 12:35:52 +03:00
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "module ModuleB where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
expectDiagnostics [("ModuleB.hs", [(DsError, (1, 7), "Could not find module")])]
|
|
|
|
|
let contentA = T.unlines [ "module ModuleA where" ]
|
|
|
|
|
_ <- openDoc' "ModuleA.hs" "haskell" contentA
|
|
|
|
|
expectDiagnostics [("ModuleB.hs", [])]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "cyclic module dependency" $ do
|
2019-08-15 12:35:52 +03:00
|
|
|
|
let contentA = T.unlines
|
|
|
|
|
[ "module ModuleA where"
|
|
|
|
|
, "import ModuleB"
|
|
|
|
|
]
|
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "module ModuleB where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "ModuleA.hs" "haskell" contentA
|
|
|
|
|
_ <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "ModuleA.hs"
|
|
|
|
|
, [(DsError, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")]
|
|
|
|
|
)
|
|
|
|
|
, ( "ModuleB.hs"
|
|
|
|
|
, [(DsError, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")]
|
|
|
|
|
)
|
|
|
|
|
]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "cyclic module dependency with hs-boot" $ do
|
2019-09-10 12:35:52 +03:00
|
|
|
|
let contentA = T.unlines
|
|
|
|
|
[ "module ModuleA where"
|
|
|
|
|
, "import {-# SOURCE #-} ModuleB"
|
|
|
|
|
]
|
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "module ModuleB where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
]
|
|
|
|
|
let contentBboot = T.unlines
|
|
|
|
|
[ "module ModuleB where"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "ModuleA.hs" "haskell" contentA
|
|
|
|
|
_ <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
_ <- openDoc' "ModuleB.hs-boot" "haskell" contentBboot
|
|
|
|
|
expectDiagnostics []
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "correct reference used with hs-boot" $ do
|
2019-09-10 12:35:52 +03:00
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "module ModuleB where"
|
|
|
|
|
, "import {-# SOURCE #-} ModuleA"
|
|
|
|
|
]
|
|
|
|
|
let contentA = T.unlines
|
|
|
|
|
[ "module ModuleA where"
|
|
|
|
|
, "import ModuleB"
|
|
|
|
|
, "x = 5"
|
|
|
|
|
]
|
|
|
|
|
let contentAboot = T.unlines
|
|
|
|
|
[ "module ModuleA where"
|
|
|
|
|
]
|
|
|
|
|
let contentC = T.unlines
|
|
|
|
|
[ "module ModuleC where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
-- this reference will fail if it gets incorrectly
|
|
|
|
|
-- resolved to the hs-boot file
|
|
|
|
|
, "y = x"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
_ <- openDoc' "ModuleA.hs" "haskell" contentA
|
|
|
|
|
_ <- openDoc' "ModuleA.hs-boot" "haskell" contentAboot
|
|
|
|
|
_ <- openDoc' "ModuleC.hs" "haskell" contentC
|
|
|
|
|
expectDiagnostics []
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "redundant import" $ do
|
2019-08-15 12:35:52 +03:00
|
|
|
|
let contentA = T.unlines ["module ModuleA where"]
|
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
|
|
|
|
|
, "module ModuleB where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "ModuleA.hs" "haskell" contentA
|
|
|
|
|
_ <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "ModuleB.hs"
|
|
|
|
|
, [(DsWarning, (2, 0), "The import of 'ModuleA' is redundant")]
|
|
|
|
|
)
|
|
|
|
|
]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "package imports" $ do
|
2019-09-12 10:39:13 +03:00
|
|
|
|
let thisDataListContent = T.unlines
|
|
|
|
|
[ "module Data.List where"
|
|
|
|
|
, "x = 123"
|
|
|
|
|
]
|
|
|
|
|
let mainContent = T.unlines
|
|
|
|
|
[ "{-# LANGUAGE PackageImports #-}"
|
|
|
|
|
, "module Main where"
|
|
|
|
|
, "import qualified \"this\" Data.List as ThisList"
|
|
|
|
|
, "import qualified \"base\" Data.List as BaseList"
|
|
|
|
|
, "useThis = ThisList.x"
|
|
|
|
|
, "useBase = BaseList.map"
|
|
|
|
|
, "wrong1 = ThisList.map"
|
|
|
|
|
, "wrong2 = BaseList.x"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "Data/List.hs" "haskell" thisDataListContent
|
|
|
|
|
_ <- openDoc' "Main.hs" "haskell" mainContent
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "Main.hs"
|
|
|
|
|
, [(DsError, (6, 9), "Not in scope: \8216ThisList.map\8217")
|
|
|
|
|
,(DsError, (7, 9), "Not in scope: \8216BaseList.x\8217")
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
2019-10-01 15:52:07 +03:00
|
|
|
|
, testSessionWait "unqualified warnings" $ do
|
2019-09-13 15:20:10 +03:00
|
|
|
|
let fooContent = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wredundant-constraints #-}"
|
|
|
|
|
, "module Foo where"
|
|
|
|
|
, "foo :: Ord a => a -> Int"
|
|
|
|
|
, "foo a = 1"
|
|
|
|
|
]
|
|
|
|
|
_ <- openDoc' "Foo.hs" "haskell" fooContent
|
|
|
|
|
expectDiagnostics
|
|
|
|
|
[ ( "Foo.hs"
|
|
|
|
|
-- The test is to make sure that warnings contain unqualified names
|
|
|
|
|
-- where appropriate. The warning should use an unqualified name 'Ord', not
|
|
|
|
|
-- sometihng like 'GHC.Classes.Ord'. The choice of redundant-constraints to
|
|
|
|
|
-- test this is fairly arbitrary.
|
|
|
|
|
, [(DsWarning, (2, 0), "Redundant constraint: Ord a")
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
2019-10-04 10:37:47 +03:00
|
|
|
|
, testSessionWait "lower-case drive" $ do
|
|
|
|
|
let aContent = T.unlines
|
|
|
|
|
[ "module A.A where"
|
|
|
|
|
, "import A.B ()"
|
|
|
|
|
]
|
|
|
|
|
bContent = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wall #-}"
|
|
|
|
|
, "module A.B where"
|
|
|
|
|
, "import Data.List"
|
|
|
|
|
]
|
|
|
|
|
uriB <- getDocUri "A/B.hs"
|
|
|
|
|
Just pathB <- pure $ uriToFilePath uriB
|
|
|
|
|
uriB <- pure $
|
|
|
|
|
let (drive, suffix) = splitDrive pathB
|
|
|
|
|
in filePathToUri (joinDrive (map toLower drive ) suffix)
|
|
|
|
|
liftIO $ createDirectoryIfMissing True (takeDirectory pathB)
|
|
|
|
|
liftIO $ writeFileUTF8 pathB $ T.unpack bContent
|
|
|
|
|
uriA <- getDocUri "A/A.hs"
|
|
|
|
|
Just pathA <- pure $ uriToFilePath uriA
|
|
|
|
|
uriA <- pure $
|
|
|
|
|
let (drive, suffix) = splitDrive pathA
|
|
|
|
|
in filePathToUri (joinDrive (map toLower drive ) suffix)
|
|
|
|
|
let itemA = TextDocumentItem uriA "haskell" 0 aContent
|
|
|
|
|
let a = TextDocumentIdentifier uriA
|
|
|
|
|
sendNotification TextDocumentDidOpen (DidOpenTextDocumentParams itemA)
|
|
|
|
|
diagsNot <- skipManyTill anyMessage message :: Session PublishDiagnosticsNotification
|
|
|
|
|
let PublishDiagnosticsParams fileUri diags = _params (diagsNot :: PublishDiagnosticsNotification)
|
|
|
|
|
-- Check that if we put a lower-case drive in for A.A
|
|
|
|
|
-- the diagnostics for A.B will also be lower-case.
|
|
|
|
|
liftIO $ fileUri @?= uriB
|
|
|
|
|
let msg = _message (head (toList diags) :: Diagnostic)
|
|
|
|
|
liftIO $ unless ("redundant" `T.isInfixOf` msg) $
|
|
|
|
|
assertFailure ("Expected redundant import but got " <> T.unpack msg)
|
|
|
|
|
closeDoc a
|
2019-07-22 16:42:04 +03:00
|
|
|
|
]
|
|
|
|
|
|
2019-09-11 23:48:09 +03:00
|
|
|
|
codeActionTests :: TestTree
|
|
|
|
|
codeActionTests = testGroup "code actions"
|
|
|
|
|
[ renameActionTests
|
2019-09-11 11:28:31 +03:00
|
|
|
|
, typeWildCardActionTests
|
2019-09-12 23:47:50 +03:00
|
|
|
|
, removeImportTests
|
2019-09-13 02:08:57 +03:00
|
|
|
|
, importRenameActionTests
|
2019-09-16 17:43:50 +03:00
|
|
|
|
, fillTypedHoleTests
|
2019-09-19 20:40:52 +03:00
|
|
|
|
, addSigActionTests
|
2019-09-11 23:48:09 +03:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
renameActionTests :: TestTree
|
|
|
|
|
renameActionTests = testGroup "rename actions"
|
|
|
|
|
[ testSession "change to local variable name" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "foo :: Int -> Int"
|
|
|
|
|
, "foo argName = argNme"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
[CACodeAction action@CodeAction { _title = actionTitle }]
|
|
|
|
|
<- getCodeActions doc (Range (Position 2 14) (Position 2 20))
|
|
|
|
|
liftIO $ "Replace with ‘argName’" @=? actionTitle
|
|
|
|
|
executeCodeAction action
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "foo :: Int -> Int"
|
|
|
|
|
, "foo argName = argName"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
, testSession "change to name of imported function" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "import Data.Maybe (maybeToList)"
|
|
|
|
|
, "foo :: Maybe a -> [a]"
|
|
|
|
|
, "foo = maybToList"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
[CACodeAction action@CodeAction { _title = actionTitle }]
|
|
|
|
|
<- getCodeActions doc (Range (Position 3 6) (Position 3 16))
|
|
|
|
|
liftIO $ "Replace with ‘maybeToList’" @=? actionTitle
|
|
|
|
|
executeCodeAction action
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "import Data.Maybe (maybeToList)"
|
|
|
|
|
, "foo :: Maybe a -> [a]"
|
|
|
|
|
, "foo = maybeToList"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
, testSession "suggest multiple local variable names" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "foo :: Char -> Char -> Char -> Char"
|
|
|
|
|
, "foo argument1 argument2 argument3 = argumentX"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 2 36) (Position 2 45))
|
|
|
|
|
let actionTitles = [ actionTitle | CACodeAction CodeAction{ _title = actionTitle } <- actionsOrCommands ]
|
|
|
|
|
expectedActionTitles = ["Replace with ‘argument1’", "Replace with ‘argument2’", "Replace with ‘argument3’"]
|
|
|
|
|
liftIO $ expectedActionTitles @=? actionTitles
|
|
|
|
|
, testSession "change infix function" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "monus :: Int -> Int"
|
|
|
|
|
, "monus x y = max 0 (x - y)"
|
|
|
|
|
, "foo x y = x `monnus` y"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 3 12) (Position 3 20))
|
|
|
|
|
[fixTypo] <- pure [action | CACodeAction action@CodeAction{ _title = actionTitle } <- actionsOrCommands, "monus" `T.isInfixOf` actionTitle ]
|
|
|
|
|
executeCodeAction fixTypo
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "monus :: Int -> Int"
|
|
|
|
|
, "monus x y = max 0 (x - y)"
|
|
|
|
|
, "foo x y = x `monus` y"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
]
|
2019-07-22 16:42:04 +03:00
|
|
|
|
|
2019-09-11 11:28:31 +03:00
|
|
|
|
typeWildCardActionTests :: TestTree
|
|
|
|
|
typeWildCardActionTests = testGroup "type wildcard actions"
|
|
|
|
|
[ testSession "global signature" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "func :: _"
|
|
|
|
|
, "func x = x"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 2 1) (Position 2 10))
|
|
|
|
|
let [addSignature] = [action | CACodeAction action@CodeAction { _title = actionTitle } <- actionsOrCommands
|
|
|
|
|
, "Use type signature" `T.isInfixOf` actionTitle
|
|
|
|
|
]
|
|
|
|
|
executeCodeAction addSignature
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "func :: (p -> p)"
|
|
|
|
|
, "func x = x"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
, testSession "multi-line message" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "func :: _"
|
|
|
|
|
, "func x y = x + y"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 2 1) (Position 2 10))
|
|
|
|
|
let [addSignature] = [action | CACodeAction action@CodeAction { _title = actionTitle } <- actionsOrCommands
|
|
|
|
|
, "Use type signature" `T.isInfixOf` actionTitle
|
|
|
|
|
]
|
|
|
|
|
executeCodeAction addSignature
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "func :: (Integer -> Integer -> Integer)"
|
|
|
|
|
, "func x y = x + y"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
, testSession "local signature" $ do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "func :: Int -> Int"
|
|
|
|
|
, "func x ="
|
|
|
|
|
, " let y :: _"
|
|
|
|
|
, " y = x * 2"
|
|
|
|
|
, " in y"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 4 1) (Position 4 10))
|
|
|
|
|
let [addSignature] = [action | CACodeAction action@CodeAction { _title = actionTitle } <- actionsOrCommands
|
|
|
|
|
, "Use type signature" `T.isInfixOf` actionTitle
|
|
|
|
|
]
|
|
|
|
|
executeCodeAction addSignature
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "func :: Int -> Int"
|
|
|
|
|
, "func x ="
|
|
|
|
|
, " let y :: (Int)"
|
|
|
|
|
, " y = x * 2"
|
|
|
|
|
, " in y"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
]
|
|
|
|
|
|
2019-09-12 23:47:50 +03:00
|
|
|
|
removeImportTests :: TestTree
|
|
|
|
|
removeImportTests = testGroup "remove import actions"
|
|
|
|
|
[ testSession "redundant" $ do
|
|
|
|
|
let contentA = T.unlines
|
|
|
|
|
[ "module ModuleA where"
|
|
|
|
|
]
|
2019-09-17 09:50:20 +03:00
|
|
|
|
_docA <- openDoc' "ModuleA.hs" "haskell" contentA
|
2019-09-12 23:47:50 +03:00
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
|
|
|
|
|
, "module ModuleB where"
|
|
|
|
|
, "import ModuleA"
|
|
|
|
|
, "stuffB = 123"
|
|
|
|
|
]
|
|
|
|
|
docB <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
[CACodeAction action@CodeAction { _title = actionTitle }]
|
|
|
|
|
<- getCodeActions docB (Range (Position 2 0) (Position 2 5))
|
|
|
|
|
liftIO $ "Remove import" @=? actionTitle
|
|
|
|
|
executeCodeAction action
|
|
|
|
|
contentAfterAction <- documentContents docB
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
|
|
|
|
|
, "module ModuleB where"
|
|
|
|
|
, "stuffB = 123"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
2019-09-12 23:51:46 +03:00
|
|
|
|
, testSession "qualified redundant" $ do
|
|
|
|
|
let contentA = T.unlines
|
|
|
|
|
[ "module ModuleA where"
|
|
|
|
|
]
|
2019-09-17 09:50:20 +03:00
|
|
|
|
_docA <- openDoc' "ModuleA.hs" "haskell" contentA
|
2019-09-12 23:51:46 +03:00
|
|
|
|
let contentB = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
|
|
|
|
|
, "module ModuleB where"
|
|
|
|
|
, "import qualified ModuleA"
|
|
|
|
|
, "stuffB = 123"
|
|
|
|
|
]
|
|
|
|
|
docB <- openDoc' "ModuleB.hs" "haskell" contentB
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
[CACodeAction action@CodeAction { _title = actionTitle }]
|
|
|
|
|
<- getCodeActions docB (Range (Position 2 0) (Position 2 5))
|
|
|
|
|
liftIO $ "Remove import" @=? actionTitle
|
|
|
|
|
executeCodeAction action
|
|
|
|
|
contentAfterAction <- documentContents docB
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wunused-imports #-}"
|
|
|
|
|
, "module ModuleB where"
|
|
|
|
|
, "stuffB = 123"
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
2019-09-12 23:47:50 +03:00
|
|
|
|
]
|
|
|
|
|
|
2019-09-13 02:08:57 +03:00
|
|
|
|
importRenameActionTests :: TestTree
|
|
|
|
|
importRenameActionTests = testGroup "import rename actions"
|
|
|
|
|
[ testSession "Data.Mape -> Data.Map" $ check "Map"
|
|
|
|
|
, testSession "Data.Mape -> Data.Maybe" $ check "Maybe" ] where
|
|
|
|
|
check modname = do
|
|
|
|
|
let content = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "import Data.Mape"
|
|
|
|
|
]
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" content
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 2 8) (Position 2 16))
|
|
|
|
|
let [changeToMap] = [action | CACodeAction action@CodeAction{ _title = actionTitle } <- actionsOrCommands, ("Data." <> modname) `T.isInfixOf` actionTitle ]
|
|
|
|
|
executeCodeAction changeToMap
|
|
|
|
|
contentAfterAction <- documentContents doc
|
|
|
|
|
let expectedContentAfterAction = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, "import Data." <> modname
|
|
|
|
|
]
|
|
|
|
|
liftIO $ expectedContentAfterAction @=? contentAfterAction
|
|
|
|
|
|
2019-09-16 17:43:50 +03:00
|
|
|
|
fillTypedHoleTests :: TestTree
|
|
|
|
|
fillTypedHoleTests = let
|
|
|
|
|
|
|
|
|
|
sourceCode :: T.Text -> T.Text -> T.Text -> T.Text
|
|
|
|
|
sourceCode a b c = T.unlines
|
|
|
|
|
[ "module Testing where"
|
|
|
|
|
, ""
|
|
|
|
|
, "globalConvert :: Int -> String"
|
|
|
|
|
, "globalConvert = undefined"
|
|
|
|
|
, ""
|
|
|
|
|
, "globalInt :: Int"
|
|
|
|
|
, "globalInt = 3"
|
|
|
|
|
, ""
|
|
|
|
|
, "bar :: Int -> Int -> String"
|
|
|
|
|
, "bar n parameterInt = " <> a <> " (n + " <> b <> " + " <> c <> ") where"
|
|
|
|
|
, " localConvert = (flip replicate) 'x'"
|
|
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
check :: T.Text -> T.Text -> T.Text -> T.Text -> T.Text -> T.Text -> T.Text -> TestTree
|
|
|
|
|
check actionTitle
|
|
|
|
|
oldA oldB oldC
|
2019-09-19 20:40:52 +03:00
|
|
|
|
newA newB newC = testSession (T.unpack actionTitle) $ do
|
2019-09-16 17:43:50 +03:00
|
|
|
|
let originalCode = sourceCode oldA oldB oldC
|
|
|
|
|
let expectedCode = sourceCode newA newB newC
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" originalCode
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 9 0) (Position 9 maxBound))
|
|
|
|
|
let chosenAction = pickActionWithTitle actionTitle actionsOrCommands
|
|
|
|
|
executeCodeAction chosenAction
|
|
|
|
|
modifiedCode <- documentContents doc
|
|
|
|
|
liftIO $ expectedCode @=? modifiedCode
|
|
|
|
|
in
|
|
|
|
|
testGroup "fill typed holes"
|
|
|
|
|
[ check "replace hole `_` with show"
|
|
|
|
|
"_" "n" "n"
|
|
|
|
|
"show" "n" "n"
|
|
|
|
|
|
|
|
|
|
, check "replace hole `_` with globalConvert"
|
|
|
|
|
"_" "n" "n"
|
|
|
|
|
"globalConvert" "n" "n"
|
|
|
|
|
|
|
|
|
|
#if MIN_GHC_API_VERSION(8,6,0)
|
|
|
|
|
, check "replace hole `_convertme` with localConvert"
|
|
|
|
|
"_convertme" "n" "n"
|
|
|
|
|
"localConvert" "n" "n"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
, check "replace hole `_b` with globalInt"
|
|
|
|
|
"_a" "_b" "_c"
|
|
|
|
|
"_a" "globalInt" "_c"
|
|
|
|
|
|
|
|
|
|
, check "replace hole `_c` with globalInt"
|
|
|
|
|
"_a" "_b" "_c"
|
|
|
|
|
"_a" "_b" "globalInt"
|
|
|
|
|
|
|
|
|
|
#if MIN_GHC_API_VERSION(8,6,0)
|
|
|
|
|
, check "replace hole `_c` with parameterInt"
|
|
|
|
|
"_a" "_b" "_c"
|
|
|
|
|
"_a" "_b" "parameterInt"
|
|
|
|
|
#endif
|
|
|
|
|
]
|
|
|
|
|
|
2019-09-19 20:40:52 +03:00
|
|
|
|
addSigActionTests :: TestTree
|
|
|
|
|
addSigActionTests = let
|
2019-09-29 13:03:16 +03:00
|
|
|
|
header = T.unlines [ "{-# OPTIONS_GHC -Wmissing-signatures #-}"
|
2019-09-19 20:40:52 +03:00
|
|
|
|
, "module Sigs where"]
|
2019-09-29 13:03:16 +03:00
|
|
|
|
before def = T.unlines [header, def]
|
|
|
|
|
after' def sig = T.unlines [header, sig, def]
|
2019-09-19 20:40:52 +03:00
|
|
|
|
|
|
|
|
|
def >:: sig = testSession (T.unpack def) $ do
|
|
|
|
|
let originalCode = before def
|
2019-09-29 13:03:16 +03:00
|
|
|
|
let expectedCode = after' def sig
|
2019-09-19 20:40:52 +03:00
|
|
|
|
doc <- openDoc' "Sigs.hs" "haskell" originalCode
|
|
|
|
|
_ <- waitForDiagnostics
|
|
|
|
|
actionsOrCommands <- getCodeActions doc (Range (Position 3 1) (Position 3 maxBound))
|
|
|
|
|
let chosenAction = pickActionWithTitle ("add signature: " <> sig) actionsOrCommands
|
|
|
|
|
executeCodeAction chosenAction
|
|
|
|
|
modifiedCode <- documentContents doc
|
|
|
|
|
liftIO $ expectedCode @=? modifiedCode
|
|
|
|
|
in
|
|
|
|
|
testGroup "add signature"
|
|
|
|
|
[ "abc = True" >:: "abc :: Bool"
|
|
|
|
|
, "foo a b = a + b" >:: "foo :: Num a => a -> a -> a"
|
|
|
|
|
, "bar a b = show $ a + b" >:: "bar :: (Show a, Num a) => a -> a -> String"
|
|
|
|
|
, "(!!!) a b = a > b" >:: "(!!!) :: Ord a => a -> a -> Bool"
|
|
|
|
|
, "a >>>> b = a + b" >:: "(>>>>) :: Num a => a -> a -> a"
|
|
|
|
|
, "a `haha` b = a b" >:: "haha :: (t1 -> t2) -> t1 -> t2"
|
|
|
|
|
]
|
|
|
|
|
|
2019-09-29 13:03:16 +03:00
|
|
|
|
findDefinitionTests :: TestTree
|
|
|
|
|
findDefinitionTests = let
|
|
|
|
|
|
|
|
|
|
tst (get, check) pos targetRange title = testSession title $ do
|
|
|
|
|
doc <- openDoc' "Testing.hs" "haskell" source
|
|
|
|
|
found <- get doc pos
|
|
|
|
|
check found targetRange
|
|
|
|
|
|
|
|
|
|
checkDefs defs expected = do
|
|
|
|
|
|
|
|
|
|
let ndef = length defs
|
|
|
|
|
if ndef /= 1
|
|
|
|
|
then let dfound n = "definitions found: " <> show n in
|
|
|
|
|
liftIO $ dfound (1 :: Int) @=? dfound (length defs)
|
|
|
|
|
else do
|
|
|
|
|
let [Location{_range = foundRange}] = defs
|
|
|
|
|
liftIO $ expected @=? foundRange
|
|
|
|
|
|
2019-10-01 10:24:33 +03:00
|
|
|
|
checkHover hover expected =
|
2019-09-29 13:03:16 +03:00
|
|
|
|
case hover of
|
|
|
|
|
Nothing -> liftIO $ "hover found" @=? ("no hover found" :: T.Text)
|
2019-10-01 10:24:33 +03:00
|
|
|
|
Just Hover{_contents = (HoverContents MarkupContent{_value = msg})
|
|
|
|
|
,_range = mRange } ->
|
|
|
|
|
let
|
|
|
|
|
extractLineColFromMsg =
|
|
|
|
|
T.splitOn ":" . head . T.splitOn "**" . last . T.splitOn "Testing.hs:"
|
|
|
|
|
lineCol = extractLineColFromMsg msg
|
|
|
|
|
|
2019-09-29 13:03:16 +03:00
|
|
|
|
-- looks like hovers use 1-based numbering while definitions use 0-based
|
2019-10-01 10:24:33 +03:00
|
|
|
|
-- turns out that they are stored 1-based in RealSrcLoc by GHC itself.
|
|
|
|
|
adjust Position{_line = l, _character = c} =
|
2019-09-29 13:03:16 +03:00
|
|
|
|
Position{_line = l + 1, _character = c + 1}
|
2019-10-01 10:24:33 +03:00
|
|
|
|
in
|
|
|
|
|
case lineCol of
|
|
|
|
|
[_,_] -> liftIO $ (adjust $ _start expected) @=? Position l c where [l,c] = map (read . T.unpack) lineCol
|
|
|
|
|
_ -> liftIO $ ("[...]Testing.hs:<LINE>:<COL>**[...]", mRange) @=? (msg, Just expected)
|
2019-09-29 13:03:16 +03:00
|
|
|
|
_ -> error "test not expecting this kind of hover info"
|
|
|
|
|
|
2019-10-01 10:24:33 +03:00
|
|
|
|
mkFindTests tests = testGroup "get"
|
|
|
|
|
[ testGroup "definition" $ mapMaybe fst tests
|
|
|
|
|
, testGroup "hover" $ mapMaybe snd tests ]
|
|
|
|
|
|
|
|
|
|
test runDef runHover look bind title =
|
|
|
|
|
( runDef $ tst def look bind title
|
|
|
|
|
, runHover $ tst hover look bind title ) where
|
|
|
|
|
def = (getDefinitions, checkDefs)
|
|
|
|
|
hover = (getHover , checkHover)
|
|
|
|
|
--type_ = (getTypeDefinitions, checkTDefs) -- getTypeDefinitions always times out
|
|
|
|
|
-- test run control
|
|
|
|
|
yes, broken :: (TestTree -> Maybe TestTree)
|
|
|
|
|
yes = Just -- test should run and pass
|
|
|
|
|
broken = Just . (`xfail` "known broken")
|
|
|
|
|
-- no = const Nothing -- don't run this test at all
|
|
|
|
|
|
2019-09-29 13:03:16 +03:00
|
|
|
|
source = T.unlines
|
|
|
|
|
-- 0123456789 123456789 123456789 123456789
|
|
|
|
|
[ "{-# OPTIONS_GHC -Wmissing-signatures #-}" -- 0
|
|
|
|
|
, "module Testing where" -- 1
|
|
|
|
|
, "data TypeConstructor = DataConstructor" -- 2
|
|
|
|
|
, " { fff :: String" -- 3
|
|
|
|
|
, " , ggg :: Int }" -- 4
|
|
|
|
|
, "aaa :: TypeConstructor" -- 5
|
|
|
|
|
, "aaa = DataConstructor" -- 6
|
|
|
|
|
, " { fff = \"\"" -- 7
|
|
|
|
|
, " , ggg = 0" -- 8
|
|
|
|
|
, " }" -- 9
|
|
|
|
|
-- 0123456789 123456789 123456789 123456789
|
|
|
|
|
, "bbb :: TypeConstructor" -- 10
|
|
|
|
|
, "bbb = DataConstructor \"\" 0" -- 11
|
|
|
|
|
, "ccc :: (String, Int)" -- 12
|
|
|
|
|
, "ccc = (fff bbb, ggg aaa)" -- 13
|
|
|
|
|
, "ddd :: Num a => a -> a -> a" -- 14
|
|
|
|
|
, "ddd vv ww = vv +! ww" -- 15
|
|
|
|
|
, "a +! b = a - b" -- 16
|
|
|
|
|
, "hhh (Just a) (><) = a >< a" -- 17
|
|
|
|
|
, "iii a b = a `b` a" -- 18
|
|
|
|
|
-- 0123456789 123456789 123456789 123456789
|
|
|
|
|
]
|
|
|
|
|
|
2019-10-01 10:24:33 +03:00
|
|
|
|
-- search locations definition locations
|
|
|
|
|
fffL3 = _start fff ; fff = mkRange 3 4 3 7
|
|
|
|
|
fffL7 = Position 7 4 ;
|
|
|
|
|
fffL13 = Position 13 7 ;
|
|
|
|
|
aaaL13 = Position 13 20 ; aaa = mkRange 6 0 6 3
|
|
|
|
|
dcL6 = Position 6 11 ; tcDC = mkRange 2 23 4 16
|
|
|
|
|
dcL11 = Position 11 11 ;
|
|
|
|
|
tcL5 = Position 5 11 ; tcData = mkRange 2 0 4 16
|
|
|
|
|
vvL15 = Position 15 12 ; vv = mkRange 15 4 15 6
|
|
|
|
|
opL15 = Position 15 15 ; op = mkRange 16 2 16 4
|
|
|
|
|
opL17 = Position 17 22 ; opp = mkRange 17 13 17 17
|
|
|
|
|
aL17 = Position 17 20 ; apmp = mkRange 17 10 17 11
|
|
|
|
|
b'L18 = Position 18 13 ; bp = mkRange 18 6 18 7
|
|
|
|
|
|
2019-09-29 13:03:16 +03:00
|
|
|
|
in
|
2019-10-01 10:24:33 +03:00
|
|
|
|
mkFindTests
|
|
|
|
|
-- def hover look bind
|
|
|
|
|
[ test yes yes fffL3 fff "field in record definition"
|
|
|
|
|
, test broken broken fffL7 fff "field in record construction"
|
|
|
|
|
, test yes yes fffL13 fff "field name used as accessor" -- 120 in Calculate.hs
|
|
|
|
|
, test yes yes aaaL13 aaa "top-level name" -- 120
|
|
|
|
|
, test broken broken dcL6 tcDC "record data constructor"
|
|
|
|
|
, test yes yes dcL11 tcDC "plain data constructor" -- 121
|
|
|
|
|
, test yes broken tcL5 tcData "type constructor" -- 147
|
|
|
|
|
, test yes yes vvL15 vv "plain parameter"
|
|
|
|
|
, test yes yes aL17 apmp "pattern match name"
|
|
|
|
|
, test yes yes opL15 op "top-level operator" -- 123
|
|
|
|
|
, test yes yes opL17 opp "parameter operator"
|
|
|
|
|
, test yes yes b'L18 bp "name in backticks"
|
2019-09-29 13:03:16 +03:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
xfail :: TestTree -> String -> TestTree
|
|
|
|
|
xfail = flip expectFailBecause
|
|
|
|
|
|
2019-07-22 16:42:04 +03:00
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
-- Utils
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
testSession :: String -> Session () -> TestTree
|
2019-10-01 15:52:07 +03:00
|
|
|
|
testSession name = testCase name . run
|
|
|
|
|
|
|
|
|
|
testSessionWait :: String -> Session () -> TestTree
|
|
|
|
|
testSessionWait name = testSession name .
|
2019-09-09 21:24:50 +03:00
|
|
|
|
-- Check that any diagnostics produced were already consumed by the test case.
|
|
|
|
|
--
|
|
|
|
|
-- If in future we add test cases where we don't care about checking the diagnostics,
|
|
|
|
|
-- this could move elsewhere.
|
|
|
|
|
--
|
|
|
|
|
-- Experimentally, 0.5s seems to be long enough to wait for any final diagnostics to appear.
|
|
|
|
|
( >> expectNoMoreDiagnostics 0.5)
|
2019-07-22 16:42:04 +03:00
|
|
|
|
|
2019-09-19 20:40:52 +03:00
|
|
|
|
pickActionWithTitle :: T.Text -> [CAResult] -> CodeAction
|
|
|
|
|
pickActionWithTitle title actions = head
|
|
|
|
|
[ action
|
|
|
|
|
| CACodeAction action@CodeAction{ _title = actionTitle } <- actions
|
|
|
|
|
, title == actionTitle ]
|
2019-07-22 16:42:04 +03:00
|
|
|
|
|
2019-09-29 13:03:16 +03:00
|
|
|
|
mkRange :: Int -> Int -> Int -> Int -> Range
|
|
|
|
|
mkRange a b c d = Range (Position a b) (Position c d)
|
|
|
|
|
|
2019-07-22 16:42:04 +03:00
|
|
|
|
run :: Session a -> IO a
|
|
|
|
|
run s = withTempDir $ \dir -> do
|
2019-09-09 16:55:16 +03:00
|
|
|
|
ghcideExe <- locateGhcideExecutable
|
2019-09-25 14:01:41 +03:00
|
|
|
|
|
|
|
|
|
-- Temporarily hack around https://github.com/mpickering/hie-bios/pull/56
|
|
|
|
|
-- since the package import test creates "Data/List.hs", which otherwise has no physical home
|
|
|
|
|
createDirectoryIfMissing True $ dir ++ "/Data"
|
|
|
|
|
|
2019-09-09 16:55:16 +03:00
|
|
|
|
let cmd = unwords [ghcideExe, "--lsp", "--cwd", dir]
|
2019-07-22 16:42:04 +03:00
|
|
|
|
-- HIE calls getXgdDirectory which assumes that HOME is set.
|
|
|
|
|
-- Only sets HOME if it wasn't already set.
|
|
|
|
|
setEnv "HOME" "/homeless-shelter" False
|
2019-08-13 21:00:21 +03:00
|
|
|
|
runSessionWithConfig conf cmd fullCaps { _window = Just $ WindowClientCapabilities $ Just True } dir s
|
2019-07-22 16:42:04 +03:00
|
|
|
|
where
|
|
|
|
|
conf = defaultConfig
|
|
|
|
|
-- If you uncomment this you can see all messages
|
|
|
|
|
-- which can be quite useful for debugging.
|
|
|
|
|
-- { logMessages = True, logColor = False, logStdErr = True }
|