packages feed

ghcide 2.7.0.0 → 2.8.0.0

raw patch · 63 files changed

+392/−5213 lines, 63 filesdep −QuickCheckdep −ekg-coredep −ekg-waidep ~basedep ~extradep ~hie-biosPVP ok

version bump matches the API change (PVP)

Dependencies removed: QuickCheck, ekg-core, ekg-wai, fuzzy, ghc-typelits-knownnat, monoid-subclasses, network-uri, shake, tasty, tasty-expected-failure, tasty-quickcheck, tasty-rerun

Dependency ranges changed: base, extra, hie-bios, hls-graph, hls-plugin-api, lsp, lsp-test, lsp-types, regex-tdfa, unordered-containers

API changes (from Hackage documentation)

Files

exe/Arguments.hs view
@@ -20,7 +20,6 @@     ,argsVerbose                    :: Bool     ,argsCommand                    :: Command     ,argsConservativeChangeTracking :: Bool-    ,argsMonitoringPort             :: Int     }  getArguments :: IdePlugins IdeState -> IO Arguments@@ -43,7 +42,6 @@       <*> switch (short 'd' <> long "verbose" <> help "Include internal events in logging output")       <*> (commandP plugins <|> lspCommand <|> checkCommand)       <*> switch (long "conservative-change-tracking" <> help "disable reactive change tracking (for testing/debugging)")-      <*> option auto (long "monitoring-port" <> metavar "PORT" <> value 8999 <> showDefault <> help "Port to use for EKG monitoring (if the binary is built with EKG)")       where           checkCommand = Check <$> many (argument str (metavar "FILES/DIRS..."))           lspCommand = LSP <$ flag' True (long "lsp" <> help "Start talking to an LSP client")
exe/Main.hs view
@@ -16,15 +16,12 @@ import           Development.IDE.Core.OfInterest          (kick) import           Development.IDE.Core.Rules               (mainRule) import qualified Development.IDE.Core.Rules               as Rules-import           Development.IDE.Core.Tracing             (withTelemetryLogger)+import           Development.IDE.Core.Tracing             (withTelemetryRecorder) import qualified Development.IDE.Main                     as IDEMain-import qualified Development.IDE.Monitoring.EKG           as EKG import qualified Development.IDE.Monitoring.OpenTelemetry as OpenTelemetry import qualified Development.IDE.Plugin.HLS.GhcIde        as GhcIde import           Development.IDE.Types.Options-import           GHC.Stack                                (emptyCallStack)-import           Ide.Logger                               (Logger (Logger),-                                                           LoggingColumn (DataColumn, PriorityColumn),+import           Ide.Logger                               (LoggingColumn (DataColumn, PriorityColumn),                                                            Pretty (pretty),                                                            Priority (Debug, Error, Info),                                                            WithPriority (WithPriority, priority),@@ -72,7 +69,7 @@              <> gitHashSection  main :: IO ()-main = withTelemetryLogger $ \telemetryLogger -> do+main = withTelemetryRecorder $ \telemetryRecorder -> do     -- stderr recorder just for plugin cli commands     pluginCliRecorder <-       cmapWithPrio pretty@@ -110,23 +107,20 @@           (lspLogRecorder & cmapWithPrio (renderStrict . layoutPretty defaultLayoutOptions)                           & cfilter (\WithPriority{ priority } -> priority >= minPriority)) <>           (lspMessageRecorder & cmapWithPrio (renderStrict . layoutPretty defaultLayoutOptions)-                              & cfilter (\WithPriority{ priority } -> priority >= Error))--    -- exists so old-style logging works. intended to be phased out-    let logger = Logger $ \p m -> Logger.logger_ docWithFilteredPriorityRecorder (WithPriority p emptyCallStack (pretty m))+                              & cfilter (\WithPriority{ priority } -> priority >= Error)) <>+          telemetryRecorder      let recorder = docWithFilteredPriorityRecorder                  & cmapWithPrio pretty      let arguments =           if argsTesting-          then IDEMain.testing (cmapWithPrio LogIDEMain recorder) logger hlsPlugins-          else IDEMain.defaultArguments (cmapWithPrio LogIDEMain recorder) logger hlsPlugins+          then IDEMain.testing (cmapWithPrio LogIDEMain recorder) hlsPlugins+          else IDEMain.defaultArguments (cmapWithPrio LogIDEMain recorder) hlsPlugins      IDEMain.defaultMain (cmapWithPrio LogIDEMain recorder) arguments         { IDEMain.argsProjectRoot = Just argsCwd         , IDEMain.argCommand = argsCommand-        , IDEMain.argsLogger = IDEMain.argsLogger arguments <> pure telemetryLogger         , IDEMain.argsHlsPlugins = IDEMain.argsHlsPlugins arguments <> pluginDescToIdePlugins [lspRecorderPlugin]          , IDEMain.argsRules = do@@ -148,5 +142,5 @@                 , optRunSubset = not argsConservativeChangeTracking                 , optVerifyCoreFile = argsVerifyCoreFile                 }-        , IDEMain.argsMonitoring = OpenTelemetry.monitoring <> EKG.monitoring logger argsMonitoringPort+        , IDEMain.argsMonitoring = OpenTelemetry.monitoring         }
ghcide.cabal view
@@ -2,7 +2,7 @@ build-type:         Simple category:           Development name:               ghcide-version:            2.7.0.0+version:            2.8.0.0 license:            Apache-2.0 license-file:       LICENSE author:             Digital Asset and Ghcide contributors@@ -28,13 +28,6 @@   type:     git   location: https://github.com/haskell/haskell-language-server.git -flag ekg-  description:-    Enable EKG monitoring of the build graph and other metrics on port 8999--  default:     False-  manual:      True- flag pedantic   description: Enable -Werror   default:     False@@ -85,11 +78,11 @@     , Glob     , haddock-library              >=1.8      && <1.12     , hashable-    , hie-bios                     ==0.13.1+    , hie-bios                     ^>=0.14.0     , hie-compat                   ^>=0.3.0.0     , hiedb                        ^>= 0.6.0.0-    , hls-graph                    == 2.7.0.0-    , hls-plugin-api               == 2.7.0.0+    , hls-graph                    == 2.8.0.0+    , hls-plugin-api               == 2.8.0.0     , implicit-hie                 >= 0.1.4.0 && < 0.1.5     , lens     , list-t@@ -178,7 +171,6 @@     Development.IDE.LSP.Server     Development.IDE.Main     Development.IDE.Main.HeapStats-    Development.IDE.Monitoring.EKG     Development.IDE.Monitoring.OpenTelemetry     Development.IDE.Plugin     Development.IDE.Plugin.Completions@@ -218,13 +210,6 @@     ghc-options:       -Werror -  if flag(ekg)-    build-depends:-      , ekg-core-      , ekg-wai--    cpp-options:   -DMONITORING_EKG- flag test-exe   description: Build the ghcide-test-preprocessor executable   default:     True@@ -285,7 +270,7 @@   visibility: public   default-language:   GHC2021 -  hs-source-dirs: test/src test/cabal +  hs-source-dirs: test/src test/cabal   exposed-modules:     Development.IDE.Test     Development.IDE.Test.Runfiles@@ -306,109 +291,9 @@       lsp-test ^>= 0.17,       tasty-hunit >= 0.10,       text,-      row-types,    default-extensions:       LambdaCase       OverloadedStrings       RecordWildCards       ViewPatterns-  -test-suite ghcide-tests-  import: warnings-  type:               exitcode-stdio-1.0-  default-language:   GHC2021-  build-tool-depends:-    , ghcide:ghcide-    , ghcide:ghcide-test-preprocessor-    , implicit-hie:gen-hie--  build-depends:-    , aeson-    , async-    , base-    , containers-    , data-default-    , directory-    , enummapset-    , extra-    , filepath-    , fuzzy-    , ghcide-    , ghcide:ghcide-test-utils-    , hls-plugin-api-    , lens-    , list-t-    , lsp-    , lsp-test                ^>=0.17.0.0-    , lsp-types-    , monoid-subclasses-    , mtl-    , network-uri-    , QuickCheck-    , random-    , regex-tdfa              ^>=1.3.1-    , row-types-    , shake-    , sqlite-simple-    , stm-    , stm-containers-    , tasty-    , tasty-expected-failure-    , tasty-hunit             >=0.10-    , tasty-quickcheck-    , tasty-rerun-    , text-    , text-rope-    , unordered-containers--  if impl(ghc <9.3)-    build-depends: ghc-typelits-knownnat--  hs-source-dirs:     test/exe -  ghc-options:        -threaded -O0--  main-is:            Main.hs-  other-modules:-    AsyncTests-    BootTests-    ClientSettingsTests-    CodeLensTests-    CompletionTests-    CPPTests-    CradleTests-    DependentFileTest-    DiagnosticTests-    ExceptionTests-    FindDefinitionAndHoverTests-    FuzzySearch-    GarbageCollectionTests-    HaddockTests-    HieDbRetry-    HighlightTests-    IfaceTests-    InitializeResponseTests-    LogType-    NonLspCommandLine-    OpenCloseTest-    OutlineTests-    PluginParsedResultTests-    PluginSimpleTests-    PositionMappingTests-    PreprocessorTests-    Progress-    ReferenceTests-    RootUriTests-    SafeTests-    SymlinkTests-    TestUtils-    THTests-    UnitTests-    WatchedFileTests--  -- Tests that have been pulled out of the main file-  default-extensions:-    LambdaCase-    OverloadedStrings-    RecordWildCards-    ViewPatterns
session-loader/Development/IDE/Session.hs view
@@ -25,7 +25,7 @@ import           Control.Concurrent.Strict import           Control.Exception.Safe               as Safe import           Control.Monad-import           Control.Monad.Extra+import           Control.Monad.Extra                  as Extra import           Control.Monad.IO.Class import qualified Crypto.Hash.SHA1                     as H import           Data.Aeson                           hiding (Error)@@ -58,6 +58,7 @@ import qualified Development.IDE.GHC.Compat.Core      as GHC import           Development.IDE.GHC.Compat.Env       hiding (Logger) import           Development.IDE.GHC.Compat.Units     (UnitId)+import qualified Development.IDE.GHC.Compat.Util      as Compat import           Development.IDE.GHC.Util import           Development.IDE.Graph                (Action) import           Development.IDE.Session.VersionCheck@@ -69,6 +70,7 @@ import           Development.IDE.Types.Options import           GHC.Check import qualified HIE.Bios                             as HieBios+import qualified HIE.Bios.Cradle                      as HieBios import           HIE.Bios.Environment                 hiding (getCacheDir) import           HIE.Bios.Types                       hiding (Log) import qualified HIE.Bios.Types                       as HieBios@@ -79,6 +81,8 @@                                                        nest,                                                        toCologActionWithPrio,                                                        vcat, viaShow, (<+>))+import           Ide.Types                            (SessionLoadingPreferenceConfig (..),+                                                       sessionLoading) import           Language.LSP.Protocol.Message import           Language.LSP.Server import           System.Directory@@ -122,10 +126,12 @@ import           GHC.Driver.Env                       (hsc_all_home_unit_ids) import           GHC.Driver.Errors.Types import           GHC.Driver.Make                      (checkHomeUnitsClosed)-import           GHC.Types.Error                      (errMsgDiagnostic)+import           GHC.Types.Error                      (errMsgDiagnostic,+                                                       singleMessage) import           GHC.Unit.State #endif +import           GHC.Data.Graph.Directed import           GHC.ResponseFile  data Log@@ -147,6 +153,7 @@   | LogNoneCradleFound FilePath   | LogNewComponentCache !(([FileDiagnostic], Maybe HscEnvEq), DependencyInfo)   | LogHieBios HieBios.Log+  | LogSessionLoadingChanged deriving instance Show Log  instance Pretty Log where@@ -217,6 +224,8 @@     LogNewComponentCache componentCache ->       "New component cache HscEnvEq:" <+> viaShow componentCache     LogHieBios msg -> pretty msg+    LogSessionLoadingChanged ->+      "Session Loading config changed, reloading the full session."  -- | Bump this version number when making changes to the format of the data stored in hiedb hiedbDataVersion :: String@@ -447,6 +456,7 @@   filesMap <- newVar HM.empty :: IO (Var FilesMap)   -- Version of the mappings above   version <- newVar 0+  biosSessionLoadingVar <- newVar Nothing :: IO (Var (Maybe SessionLoadingPreferenceConfig))   let returnWithVersion fun = IdeGhcSession fun <$> liftIO (readVar version)   -- This caches the mapping from Mod.hs -> hie.yaml   cradleLoc <- liftIO $ memoIO $ \v -> do@@ -461,6 +471,7 @@   runningCradle <- newVar dummyAs :: IO (Var (Async (IdeResult HscEnvEq,[FilePath])))    return $ do+    clientConfig <- getClientConfigAction     extras@ShakeExtras{restartShakeSession, ideNc, knownTargetsVar, lspEnv                       } <- getShakeExtras     let invalidateShakeCache :: IO ()@@ -495,14 +506,14 @@                 -- 'TargetFile Foo.hs' in the 'knownTargetsVar', thus not find 'TargetFile Foo.hs-boot'                 -- and also not find 'TargetModule Foo'.                 fs <- filterM (IO.doesFileExist . fromNormalizedFilePath) targetLocations-                pure $ map (\fp -> (TargetFile fp, [fp])) (nubOrd (f:fs))+                pure $ map (\fp -> (TargetFile fp, Set.singleton fp)) (nubOrd (f:fs))               TargetModule _ -> do                 found <- filterM (IO.doesFileExist . fromNormalizedFilePath) targetLocations-                return [(targetTarget, found)]+                return [(targetTarget, Set.fromList found)]           hasUpdate <- join $ atomically $ do             known <- readTVar knownTargetsVar             let known' = flip mapHashed known $ \k ->-                            HM.unionWith (<>) k $ HM.fromList $ map (second Set.fromList) knownTargets+                            HM.unionWith (<>) k $ HM.fromList knownTargets                 hasUpdate = if known /= known' then Just (unhashed known') else Nothing             writeTVar knownTargetsVar known'             logDirtyKeys <- recordDirtyKeys extras GetKnownTargets [emptyFilePath]@@ -585,9 +596,21 @@           let new_cache = newComponentCache recorder optExtensions hieYaml _cfp hscEnv           all_target_details <- new_cache old_deps new_deps -          let all_targets = concatMap fst all_target_details--          let this_flags_map = HM.fromList (concatMap toFlagsMap all_targets)+          this_dep_info <- getDependencyInfo $ maybeToList hieYaml+          let (all_targets, this_flags_map, this_options)+                = case HM.lookup _cfp flags_map' of+                    Just this -> (all_targets', flags_map', this)+                    Nothing -> (this_target_details : all_targets', HM.insert _cfp this_flags flags_map', this_flags)+                  where all_targets' = concat all_target_details+                        flags_map' = HM.fromList (concatMap toFlagsMap all_targets')+                        this_target_details = TargetDetails (TargetFile _cfp) this_error_env this_dep_info [_cfp]+                        this_flags = (this_error_env, this_dep_info)+                        this_error_env = ([this_error], Nothing)+                        this_error = ideErrorWithSource (Just "cradle") (Just DiagnosticSeverity_Error) _cfp+                                       $ T.unlines+                                       [ "No cradle target found. Is this file listed in the targets of your cradle?"+                                       , "If you are using a .cabal file, please ensure that this module is listed in either the exposed-modules or other-modules section"+                                       ]            void $ modifyVar' fileToFlags $               Map.insert hieYaml this_flags_map@@ -615,7 +638,7 @@                     let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces                     liftIO $ atomically $ modifyTVar' (exportsMap shakeExtras) (exportsMap' <>) -          return $ second Map.keys $ this_flags_map HM.! _cfp+          return $ second Map.keys this_options      let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, [FilePath])         consultCradle hieYaml cfp = do@@ -639,7 +662,7 @@               withTrace "Load cradle" $ \addTag -> do                   addTag "file" lfp                   old_files <- readIORef cradle_files-                  res <- cradleToOptsAndLibDir recorder cradle cfp old_files+                  res <- cradleToOptsAndLibDir recorder (sessionLoading clientConfig) cradle cfp old_files                   addTag "result" (show res)                   return res @@ -667,11 +690,38 @@                void $ modifyVar' filesMap $ HM.insert ncfp hieYaml                return (res, maybe [] pure hieYaml ++ concatMap cradleErrorDependencies err) +    let+        -- | We allow users to specify a loading strategy.+        -- Check whether this config was changed since the last time we have loaded+        -- a session.+        --+        -- If the loading configuration changed, we likely should restart the session+        -- in its entirety.+        didSessionLoadingPreferenceConfigChange :: IO Bool+        didSessionLoadingPreferenceConfigChange = do+          mLoadingConfig <- readVar biosSessionLoadingVar+          case mLoadingConfig of+            Nothing -> do+              writeVar biosSessionLoadingVar (Just (sessionLoading clientConfig))+              pure False+            Just loadingConfig -> do+              writeVar biosSessionLoadingVar (Just (sessionLoading clientConfig))+              pure (loadingConfig /= sessionLoading clientConfig)+     -- This caches the mapping from hie.yaml + Mod.hs -> [String]     -- Returns the Ghc session and the cradle dependencies     let sessionOpts :: (Maybe FilePath, FilePath)                     -> IO (IdeResult HscEnvEq, [FilePath])         sessionOpts (hieYaml, file) = do+          Extra.whenM didSessionLoadingPreferenceConfigChange $ do+            logWith recorder Info LogSessionLoadingChanged+            -- If the dependencies are out of date then clear both caches and start+            -- again.+            modifyVar_ fileToFlags (const (return Map.empty))+            modifyVar_ filesMap (const (return HM.empty))+            -- Don't even keep the name cache, we start from scratch here!+            modifyVar_ hscEnvs (const (return Map.empty))+           v <- Map.findWithDefault HM.empty hieYaml <$> readVar fileToFlags           cfp <- makeAbsolute file           case HM.lookup (toNormalizedFilePath' cfp) v of@@ -682,6 +732,7 @@                   -- If the dependencies are out of date then clear both caches and start                   -- again.                   modifyVar_ fileToFlags (const (return Map.empty))+                  modifyVar_ filesMap (const (return HM.empty))                   -- Keep the same name cache                   modifyVar_ hscEnvs (return . Map.adjust (const []) hieYaml )                   consultCradle hieYaml cfp@@ -701,7 +752,7 @@                 return (([renderPackageSetupException file e], Nothing), maybe [] pure hieYaml)      returnWithVersion $ \file -> do-      opts <- liftIO $ join $ mask_ $ modifyVar runningCradle $ \as -> do+      opts <- join $ mask_ $ modifyVar runningCradle $ \as -> do         -- If the cradle is not finished, then wait for it to finish.         void $ wait as         asyncRes <- async $ getOptions file@@ -711,14 +762,14 @@ -- | Run the specific cradle on a specific FilePath via hie-bios. -- This then builds dependencies or whatever based on the cradle, gets the -- GHC options/dynflags needed for the session and the GHC library directory-cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> Cradle Void -> FilePath -> [FilePath]+cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> SessionLoadingPreferenceConfig -> Cradle Void -> FilePath -> [FilePath]                       -> IO (Either [CradleError] (ComponentOptions, FilePath))-cradleToOptsAndLibDir recorder cradle file old_files = do+cradleToOptsAndLibDir recorder loadConfig cradle file old_fps = do     -- let noneCradleFoundMessage :: FilePath -> T.Text     --     noneCradleFoundMessage f = T.pack $ "none cradle found for " <> f <> ", ignoring the file"     -- Start off by getting the session options     logWith recorder Debug $ LogCradle cradle-    cradleRes <- HieBios.getCompilerOptions file old_files cradle+    cradleRes <- HieBios.getCompilerOptions file loadStyle cradle     case cradleRes of         CradleSuccess r -> do             -- Now get the GHC lib dir@@ -736,6 +787,11 @@             logWith recorder Info $ LogNoneCradleFound file             return (Left []) +    where+        loadStyle = case loadConfig of+            PreferSingleComponentLoading -> LoadFile+            PreferMultiComponentLoading  -> LoadWithContext old_fps+ #if MIN_VERSION_ghc(9,3,0) emptyHscEnv :: NameCache -> FilePath -> IO HscEnv #else@@ -798,6 +854,65 @@ #endif setNameCache nc hsc = hsc { hsc_NC = nc } +#if MIN_VERSION_ghc(9,3,0)+-- This function checks the important property that if both p and q are home units+-- then any dependency of p, which transitively depends on q is also a home unit.+-- GHC had an implementation of this function, but it was horribly inefficient+-- We should move back to the GHC implementation on compilers where+-- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12162 is included+checkHomeUnitsClosed' ::  UnitEnv -> OS.Set UnitId -> [DriverMessages]+checkHomeUnitsClosed' ue home_id_set+    | OS.null bad_unit_ids = []+    | otherwise = [singleMessage $ GHC.mkPlainErrorMsgEnvelope rootLoc $ DriverHomePackagesNotClosed (OS.toList bad_unit_ids)]+  where+    bad_unit_ids = upwards_closure OS.\\ home_id_set+    rootLoc = mkGeneralSrcSpan (Compat.fsLit "<command line>")++    graph :: Graph (Node UnitId UnitId)+    graph = graphFromEdgedVerticesUniq graphNodes++    -- downwards closure of graph+    downwards_closure+      = graphFromEdgedVerticesUniq [ DigraphNode uid uid (OS.toList deps)+                                   | (uid, deps) <- Map.toList (allReachable graph node_key)]++    inverse_closure = transposeG downwards_closure++    upwards_closure = OS.fromList $ map node_key $ reachablesG inverse_closure [DigraphNode uid uid [] | uid <- OS.toList home_id_set]++    all_unit_direct_deps :: UniqMap UnitId (OS.Set UnitId)+    all_unit_direct_deps+      = unitEnv_foldWithKey go emptyUniqMap $ ue_home_unit_graph ue+      where+        go rest this this_uis =+           plusUniqMap_C OS.union+             (addToUniqMap_C OS.union external_depends this (OS.fromList $ this_deps))+             rest+           where+             external_depends = mapUniqMap (OS.fromList . unitDepends)+#if !MIN_VERSION_ghc(9,7,0)+                              $ listToUniqMap $ Map.toList+#endif++                              $ unitInfoMap this_units+             this_units = homeUnitEnv_units this_uis+             this_deps = [ Compat.toUnitId unit | (unit,Just _) <- explicitUnits this_units]++    graphNodes :: [Node UnitId UnitId]+    graphNodes = go OS.empty home_id_set+      where+        go done todo+          = case OS.minView todo of+              Nothing -> []+              Just (uid, todo')+                | OS.member uid done -> go done todo'+                | otherwise -> case lookupUniqMap all_unit_direct_deps uid of+                    Nothing -> pprPanic "uid not found" (Compat.ppr (uid, all_unit_direct_deps))+                    Just depends ->+                      let todo'' = (depends OS.\\ done) `OS.union` todo'+                      in DigraphNode uid uid (OS.toList depends) : go (OS.insert uid done) todo''+#endif+ -- | Create a mapping from FilePaths to HscEnvEqs -- This combines all the components we know about into -- an appropriate session, which is a multi component@@ -810,7 +925,7 @@          -> HscEnv             -- ^ An empty HscEnv          -> [ComponentInfo]    -- ^ New components to be loaded          -> [ComponentInfo]    -- ^ old, already existing components-         -> IO [ ([TargetDetails], (IdeResult HscEnvEq, DependencyInfo))]+         -> IO [ [TargetDetails] ] newComponentCache recorder exts cradlePath _cfp hsc_env old_cis new_cis = do     let cis = Map.unionWith unionCIs (mkMap new_cis) (mkMap old_cis)         -- When we have multiple components with the same uid,@@ -826,11 +941,7 @@               Compat.initUnits dfs hsc_env  #if MIN_VERSION_ghc(9,3,0)-    let closure_errs = checkHomeUnitsClosed (hsc_unit_env hscEnv') (hsc_all_home_unit_ids hscEnv') pkg_deps-        pkg_deps = do-          home_unit_id <- uids-          home_unit_env <- maybeToList $ unitEnv_lookup_maybe home_unit_id $ hsc_HUG hscEnv'-          map (home_unit_id,) (map (Compat.toUnitId . fst) $ explicitUnits $ homeUnitEnv_units home_unit_env)+    let closure_errs = checkHomeUnitsClosed' (hsc_unit_env hscEnv') (hsc_all_home_unit_ids hscEnv')         multi_errs = map (ideErrorWithSource (Just "cradle") (Just DiagnosticSeverity_Warning) _cfp . T.pack . Compat.printWithoutUniques) closure_errs         bad_units = OS.fromList $ concat $ do             x <- bagToList $ mapBag errMsgDiagnostic $ unionManyBags $ map Compat.getMessages closure_errs@@ -882,14 +993,13 @@       henv <- createHscEnvEq thisEnv (zip uids dfs)       let targetEnv = (if isBad ci then multi_errs else [], Just henv)           targetDepends = componentDependencyInfo ci-          res = ( targetEnv, targetDepends)-      logWith recorder Debug $ LogNewComponentCache res+      logWith recorder Debug $ LogNewComponentCache (targetEnv, targetDepends)       evaluate $ liftRnf rwhnf $ componentTargets ci        let mk t = fromTargetId (importPaths df) exts (targetId t) targetEnv targetDepends       ctargets <- concatMapM mk (componentTargets ci) -      return (L.nubOrdOn targetTarget ctargets, res)+      return (L.nubOrdOn targetTarget ctargets)  {- Note [Avoiding bad interface files] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~@@ -1081,15 +1191,20 @@         -- A special target for the file which caused this wonderful         -- component to be created. In case the cradle doesn't list all the targets for         -- the component, in which case things will be horribly broken anyway.-        -- Otherwise, we will immediately attempt to reload this module which-        -- causes an infinite loop and high CPU usage.         --+        -- When we have a singleComponent that is caused to be loaded due to a+        -- file, we assume the file is part of that component. This is useful+        -- for bare GHC sessions, such as many of the ones used in the testsuite+        --         -- We don't do this when we have multiple components, because each         -- component better list all targets or there will be anarchy.         -- It is difficult to know which component to add our file to in         -- that case.         -- Multi unit arguments are likely to come from cabal, which         -- does list all targets.+        --+        -- If we don't end up with a target for the current file in the end, then+        -- we will report it as an error for that file         abs_fp <- liftIO $ makeAbsolute (fromNormalizedFilePath cfp)         let special_target = Compat.mkSimpleTarget df abs_fp         pure $ (df, special_target : targets) :| []
src/Development/IDE/Core/Compile.hs view
@@ -436,6 +436,7 @@   -- Note [Clearing mi_globals after generating an iface]+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- GHC populates the mi_global field in interfaces for GHCi if we are using the bytecode -- interpreter. -- However, this field is expensive in terms of heap usage, and we don't use it in HLS@@ -1366,7 +1367,7 @@   {- Note [Recompilation avoidance in the presence of TH]-+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Most versions of GHC we currently support don't have a working implementation of code unloading for object code, and no version of GHC supports this on certain platforms like Windows. This makes it completely infeasible for interactive use,@@ -1736,6 +1737,7 @@       rep c = c  {- Note [Guidelines For Using CPP In GHCIDE Import Statements]+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   GHCIDE's interface with GHC is extensive, and unfortunately, because we have   to work with multiple versions of GHC, we have several files that need to use   a lot of CPP. In order to simplify the CPP in the import section of every file
src/Development/IDE/Core/FileExists.hs view
@@ -40,6 +40,7 @@ import qualified System.FilePath.Glob                  as Glob  {- Note [File existence cache and LSP file watchers]+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some LSP servers provide the ability to register file watches with the client, which will then notify us of file changes. Some clients can do this more efficiently than us, or generally it's a tricky problem@@ -135,6 +136,7 @@ getFileExists fp = use_ GetFileExists fp  {- Note [Which files should we watch?]+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The watcher system gives us a lot of flexibility: we can set multiple watchers, and they can all watch on glob patterns. @@ -201,6 +203,7 @@             else fileExistsSlow file  {- Note [Invalidating file existence results]+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We have two mechanisms for getting file existence information: - The file existence cache - The VFS lookup
src/Development/IDE/Core/OfInterest.hs view
@@ -42,10 +42,11 @@ import           Development.IDE.Types.Options            (IdeTesting (..)) import           GHC.TypeLits                             (KnownSymbol) import           Ide.Logger                               (Pretty (pretty),+                                                           Priority (..),                                                            Recorder,                                                            WithPriority,                                                            cmapWithPrio,-                                                           logDebug)+                                                           logWith) import qualified Language.LSP.Protocol.Message            as LSP import qualified Language.LSP.Server                      as LSP @@ -110,16 +111,16 @@         pure (new, (prev, new))     when (prev /= Just v) $ do         join $ atomically $ recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]-        logDebug (ideLogger state) $-            "Set files of interest to: " <> T.pack (show files)+        logWith (ideLogger state) Debug $+            LogSetFilesOfInterest (HashMap.toList files)  deleteFileOfInterest :: IdeState -> NormalizedFilePath -> IO () deleteFileOfInterest state f = do     OfInterestVar var <- getIdeGlobalState state     files <- modifyVar' var $ HashMap.delete f     join $ atomically $ recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]-    logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show files)-+    logWith (ideLogger state) Debug $+        LogSetFilesOfInterest (HashMap.toList files) scheduleGarbageCollection :: IdeState -> IO () scheduleGarbageCollection state = do     GarbageCollectVar var <- getIdeGlobalState state
src/Development/IDE/Core/RuleTypes.hs view
@@ -41,6 +41,8 @@ import           Development.IDE.Spans.LocalBindings import           Development.IDE.Types.Diagnostics import           GHC.Serialized                               (Serialized)+import           Ide.Logger                                   (Pretty (..),+                                                               viaShow) import           Language.LSP.Protocol.Types                  (Int32,                                                                NormalizedFilePath) @@ -340,6 +342,9 @@ instance Hashable FileOfInterestStatus instance NFData   FileOfInterestStatus +instance Pretty FileOfInterestStatus where+    pretty = viaShow+ data IsFileOfInterestResult = NotFOI | IsFOI FileOfInterestStatus   deriving (Eq, Show, Typeable, Generic) instance Hashable IsFileOfInterestResult@@ -512,6 +517,7 @@     ''Splices  {- Note [Client configuration in Rules]+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The LSP client configuration is stored by `lsp` for us, and is accesible in handlers through the LspT monad. 
src/Development/IDE/Core/Rules.hs view
@@ -11,11 +11,8 @@ module Development.IDE.Core.Rules(     -- * Types     IdeState, GetParsedModule(..), TransitiveDependencies(..),-    Priority(..), GhcSessionIO(..), GetClientSettings(..),+    GhcSessionIO(..), GetClientSettings(..),     -- * Functions-    priorityTypeCheck,-    priorityGenerateCore,-    priorityFilesOfInterest,     runAction,     toIdeResult,     defineNoFile,@@ -60,14 +57,16 @@     DisplayTHWarning(..),     ) where -import           Prelude                                      hiding (mod) import           Control.Applicative import           Control.Concurrent.Async                     (concurrently)+import           Control.Concurrent.STM.Stats                 (atomically)+import           Control.Concurrent.STM.TVar import           Control.Concurrent.Strict import           Control.DeepSeq-import           Control.Exception.Safe import           Control.Exception                            (evaluate)+import           Control.Exception.Safe import           Control.Monad.Extra                          hiding (msum)+import           Control.Monad.IO.Unlift import           Control.Monad.Reader                         hiding (msum) import           Control.Monad.State                          hiding (msum) import           Control.Monad.Trans.Except                   (ExceptT, except,@@ -78,44 +77,53 @@ import qualified Data.ByteString                              as BS import qualified Data.ByteString.Lazy                         as LBS import           Data.Coerce+import           Data.Default                                 (Default, def) import           Data.Foldable                                hiding (msum)+import           Data.Hashable import qualified Data.HashMap.Strict                          as HM import qualified Data.HashSet                                 as HashSet-import           Data.Hashable-import           Data.IORef-import           Control.Concurrent.STM.TVar import           Data.IntMap.Strict                           (IntMap) import qualified Data.IntMap.Strict                           as IntMap+import           Data.IORef import           Data.List import           Data.List.Extra                              (nubOrdOn) import qualified Data.Map                                     as M import           Data.Maybe import           Data.Proxy-import qualified Data.Text.Utf16.Rope                         as Rope import qualified Data.Set                                     as Set import qualified Data.Text                                    as T import qualified Data.Text.Encoding                           as T+import qualified Data.Text.Utf16.Rope                         as Rope import           Data.Time                                    (UTCTime (..))+import           Data.Time.Clock.POSIX                        (posixSecondsToUTCTime) import           Data.Tuple.Extra import           Data.Typeable                                (cast) import           Development.IDE.Core.Compile-import           Development.IDE.Core.FileExists hiding (LogShake, Log)+import           Development.IDE.Core.FileExists              hiding (Log,+                                                               LogShake) import           Development.IDE.Core.FileStore               (getFileContents,                                                                getModTime) import           Development.IDE.Core.IdeConfiguration-import           Development.IDE.Core.OfInterest hiding (LogShake, Log)+import           Development.IDE.Core.OfInterest              hiding (Log,+                                                               LogShake) import           Development.IDE.Core.PositionMapping import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Service hiding (LogShake, Log)-import           Development.IDE.Core.Shake hiding (Log)-import           Development.IDE.GHC.Compat.Env+import           Development.IDE.Core.Service                 hiding (Log,+                                                               LogShake)+import           Development.IDE.Core.Shake                   hiding (Log)+import qualified Development.IDE.Core.Shake                   as Shake import           Development.IDE.GHC.Compat                   hiding-                                                              (vcat, nest, parseModule,-                                                               TargetId(..),-                                                               loadInterface,+                                                              (TargetId (..),                                                                Var,-                                                               (<+>), settings)-import qualified Development.IDE.GHC.Compat                   as Compat hiding (vcat, nest)+                                                               loadInterface,+                                                               nest,+                                                               parseModule,+                                                               settings, vcat,+                                                               (<+>))+import qualified Development.IDE.GHC.Compat                   as Compat hiding+                                                                        (nest,+                                                                         vcat)+import           Development.IDE.GHC.Compat.Env import qualified Development.IDE.GHC.Compat.Util              as Util import           Development.IDE.GHC.Error import           Development.IDE.GHC.Util                     hiding@@ -130,15 +138,18 @@ import           Development.IDE.Types.HscEnvEq import           Development.IDE.Types.Location import           Development.IDE.Types.Options+import qualified Development.IDE.Types.Shake                  as Shake import qualified GHC.LanguageExtensions                       as LangExt+import           HIE.Bios.Ghc.Gap                             (hostIsDynamic) import qualified HieDb+import           Ide.Logger                                   (Pretty (pretty),+                                                               Recorder,+                                                               WithPriority,+                                                               cmapWithPrio,+                                                               logWith, nest,+                                                               vcat, (<+>))+import qualified Ide.Logger                                   as Logger import           Ide.Plugin.Config-import qualified Language.LSP.Server                          as LSP-import           Language.LSP.Protocol.Types                  (ShowMessageParams (ShowMessageParams), MessageType (MessageType_Info))-import           Language.LSP.Protocol.Message                (SMethod (SMethod_CustomMethod, SMethod_WindowShowMessage))-import           Language.LSP.VFS-import           System.Directory                             (makeAbsolute, doesFileExist)-import           Data.Default                                 (def, Default) import           Ide.Plugin.Properties                        (HasProperty,                                                                KeyNameProxy,                                                                Properties,@@ -146,28 +157,28 @@                                                                useProperty) import           Ide.Types                                    (DynFlagsModifications (dynFlagsModifyGlobal, dynFlagsModifyParser),                                                                PluginId)-import Control.Concurrent.STM.Stats (atomically)-import Language.LSP.Server (LspT)-import System.Info.Extra (isWindows)-import HIE.Bios.Ghc.Gap (hostIsDynamic)-import Ide.Logger (Recorder, logWith, cmapWithPrio, WithPriority, Pretty (pretty), (<+>), nest, vcat)-import qualified Development.IDE.Core.Shake as Shake-import qualified Ide.Logger as Logger-import qualified Development.IDE.Types.Shake as Shake-import           Data.Time.Clock.POSIX             (posixSecondsToUTCTime)-import Control.Monad.IO.Unlift+import           Language.LSP.Protocol.Message                (SMethod (SMethod_CustomMethod, SMethod_WindowShowMessage))+import           Language.LSP.Protocol.Types                  (MessageType (MessageType_Info),+                                                               ShowMessageParams (ShowMessageParams))+import           Language.LSP.Server                          (LspT)+import qualified Language.LSP.Server                          as LSP+import           Language.LSP.VFS+import           Prelude                                      hiding (mod)+import           System.Directory                             (doesFileExist,+                                                               makeAbsolute)+import           System.Info.Extra                            (isWindows)  -import GHC.Fingerprint+import           GHC.Fingerprint  -- See Note [Guidelines For Using CPP In GHCIDE Import Statements]  #if !MIN_VERSION_ghc(9,3,0)-import GHC (mgModSummaries)+import           GHC                                          (mgModSummaries) #endif  #if MIN_VERSION_ghc(9,3,0)-import qualified Data.IntMap as IM+import qualified Data.IntMap                                  as IM #endif  @@ -236,15 +247,6 @@ -- Rules -- These typically go from key to value and are oracles. -priorityTypeCheck :: Priority-priorityTypeCheck = Priority 0--priorityGenerateCore :: Priority-priorityGenerateCore = Priority (-1)--priorityFilesOfInterest :: Priority-priorityFilesOfInterest = Priority (-2)- -- | WARNING: -- We currently parse the module both with and without Opt_Haddock, and -- return the one with Haddocks if it -- succeeds. However, this may not work@@ -266,40 +268,7 @@      -- We still parse with Haddocks whether Opt_Haddock is True or False to collect information     -- but we no longer need to parse with and without Haddocks separately for above GHC90.-    res@(_,pmod) <- if Compat.ghcVersion >= Compat.GHC90 then-      liftIO $ (fmap.fmap.fmap) reset_ms $ getParsedModuleDefinition hsc opt file (withOptHaddock ms)-    else do-        let dflags    = ms_hspp_opts ms-            mainParse = getParsedModuleDefinition hsc opt file ms--        -- Parse again (if necessary) to capture Haddock parse errors-        if gopt Opt_Haddock dflags-            then-                liftIO $ (fmap.fmap.fmap) reset_ms mainParse-            else do-                let haddockParse = getParsedModuleDefinition hsc opt file (withOptHaddock ms)--                -- parse twice, with and without Haddocks, concurrently-                -- we cannot ignore Haddock parse errors because files of-                -- non-interest are always parsed with Haddocks-                -- If we can parse Haddocks, might as well use them-                ((diags,res),(diagsh,resh)) <- liftIO $ (fmap.fmap.fmap.fmap) reset_ms $ concurrently mainParse haddockParse--                -- Merge haddock and regular diagnostics so we can always report haddock-                -- parse errors-                let diagsM = mergeParseErrorsHaddock diags diagsh-                case resh of-                  Just _-                    | HaddockParse <- optHaddockParse opt-                    -> pure (diagsM, resh)-                  -- If we fail to parse haddocks, report the haddock diagnostics as well and-                  -- return the non-haddock parse.-                  -- This seems to be the correct behaviour because the Haddock flag is added-                  -- by us and not the user, so our IDE shouldn't stop working because of it.-                  _ -> pure (diagsM, res)-    -- Add dependencies on included files-    _ <- uses GetModificationTime $ map toNormalizedFilePath' (maybe [] pm_extra_src_files pmod)-    pure res+    liftIO $ (fmap.fmap.fmap) reset_ms $ getParsedModuleDefinition hsc opt file (withOptHaddock ms)  withOptHaddock :: ModSummary -> ModSummary withOptHaddock = withOption Opt_Haddock@@ -310,18 +279,6 @@ withoutOption :: GeneralFlag -> ModSummary -> ModSummary withoutOption opt ms = ms{ms_hspp_opts= gopt_unset (ms_hspp_opts ms) opt} --- | Given some normal parse errors (first) and some from Haddock (second), merge them.---   Ignore Haddock errors that are in both. Demote Haddock-only errors to warnings.-mergeParseErrorsHaddock :: [FileDiagnostic] -> [FileDiagnostic] -> [FileDiagnostic]-mergeParseErrorsHaddock normal haddock = normal ++-    [ (a,b,c{_severity = Just DiagnosticSeverity_Warning, _message = fixMessage $ _message c})-    | (a,b,c) <- haddock, Diag._range c `Set.notMember` locations]-  where-    locations = Set.fromList $ map (Diag._range . thd3) normal--    fixMessage x | "parse error " `T.isPrefixOf` x = "Haddock " <> x-                 | otherwise = "Haddock: " <> x- -- | This rule provides a ParsedModule preserving all annotations, -- including keywords, punctuation and comments. -- So it is suitable for use cases where you need a perfect edit.@@ -713,7 +670,6 @@     -> ParsedModule     -> Action (IdeResult TcModuleResult) typeCheckRuleDefinition hsc pm = do-  setPriority priorityTypeCheck   IdeOptions { optDefer = defer } <- getIdeOptions    unlift <- askUnliftIO@@ -745,9 +701,20 @@     defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GhcSessionIO -> do         alwaysRerun         opts <- getIdeOptions+        config <- getClientConfigAction         res <- optGhcSession opts -        let fingerprint = LBS.toStrict $ B.encode $ hash (sessionVersion res)+        let fingerprint = LBS.toStrict $ LBS.concat+                [ B.encode (hash (sessionVersion res))+                -- When the session version changes, reload all session+                -- hsc env sessions+                , B.encode (show (sessionLoading config))+                -- The loading config affects session loading.+                -- Invalidate all build nodes.+                -- Changing the session loading config will increment+                -- the 'sessionVersion', thus we don't generate the same fingerprint+                -- twice by accident.+                ]         return (fingerprint, res)      defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GhcSession file -> do@@ -780,6 +747,7 @@     }  -- | Note [GhcSessionDeps]+--   ~~~~~~~~~~~~~~~~~~~~~ -- For a file 'Foo', GhcSessionDeps "Foo.hs" results in an HscEnv which includes -- 1. HomeModInfo's (in the HUG/HPT) for all modules in the transitive closure of "Foo", **NOT** including "Foo" itself. -- 2. ModSummary's (in the ModuleGraph) for all modules in the transitive closure of "Foo", including "Foo" itself.@@ -850,7 +818,7 @@       let m_old = case old of             Shake.Succeeded (Just old_version) v -> Just (v, old_version)             Shake.Stale _   (Just old_version) v -> Just (v, old_version)-            _ -> Nothing+            _                                    -> Nothing           recompInfo = RecompilationInfo             { source_version = ver             , old_value = m_old@@ -966,7 +934,6 @@ generateCore runSimplifier file = do     packageState <- hscEnv <$> use_ GhcSessionDeps file     tm <- use_ TypeCheck file-    setPriority priorityGenerateCore     liftIO $ compileModule runSimplifier packageState (tmrModSummary tm) (tmrTypechecked tm)  generateCoreRule :: Recorder (WithPriority Log) -> Rules ()@@ -1023,22 +990,14 @@      -- Embed haddocks in the interface file     (diags, mb_pm) <- liftIO $ getParsedModuleDefinition hsc opt f (withOptHaddock ms)-    (diags', mb_pm') <--        -- We no longer need to parse again if GHC version is above 9.0. https://github.com/haskell/haskell-language-server/issues/1892-        if Compat.ghcVersion >= Compat.GHC90 || isJust mb_pm then do-            return (diags, mb_pm)-        else do-            -- if parsing fails, try parsing again with Haddock turned off-            (diagsNoHaddock, mb_pm') <- liftIO $ getParsedModuleDefinition hsc opt f ms-            return (mergeParseErrorsHaddock diagsNoHaddock diags, mb_pm')-    case mb_pm' of-        Nothing -> return (diags', Nothing)+    case mb_pm of+        Nothing -> return (diags, Nothing)         Just pm -> do             -- Invoke typechecking directly to update it without incurring a dependency             -- on the parsed module and the typecheck rules-            (diags'', mtmr) <- typeCheckRuleDefinition hsc pm+            (diags', mtmr) <- typeCheckRuleDefinition hsc pm             case mtmr of-              Nothing -> pure (diags'', Nothing)+              Nothing -> pure (diags', Nothing)               Just tmr -> do                  let compile = liftIO $ compileModule (RunSimplifier True) hsc (pm_mod_summary pm) $ tmrTypechecked tmr@@ -1046,7 +1005,7 @@                 se <- getShakeExtras                  -- Bang pattern is important to avoid leaking 'tmr'-                (diags''', !res) <- writeCoreFileIfNeeded se hsc compNeeded compile tmr+                (diags'', !res) <- writeCoreFileIfNeeded se hsc compNeeded compile tmr                  -- Write hi file                 hiDiags <- case res of@@ -1070,7 +1029,7 @@                     pure (hiDiags <> gDiags <> concat wDiags)                   Nothing -> pure [] -                return (diags' <> diags'' <> diags''' <> hiDiags, res)+                return (diags <> diags' <> diags'' <> hiDiags, res)   -- | HscEnv should have deps included already@@ -1233,9 +1192,9 @@       -- Disabling this drastically decreases sharing and is likely to       -- increase memory usage if you have multiple files open       -- Disabling this also disables checking for import cycles-      fullModuleGraph :: Bool+      fullModuleGraph        :: Bool     -- | Disable TH for improved performance in large codebases-    , enableTemplateHaskell :: Bool+    , enableTemplateHaskell  :: Bool     -- | Warning to show when TH is not supported by the current HLS binary     , templateHaskellWarning :: LspT Config IO ()     }
src/Development/IDE/Core/Service.hs view
@@ -22,8 +22,7 @@ import           Development.IDE.Core.OfInterest  hiding (Log, LogShake) import           Development.IDE.Graph import           Development.IDE.Types.Options    (IdeOptions (..))-import           Ide.Logger                       as Logger (Logger,-                                                             Pretty (pretty),+import           Ide.Logger                       as Logger (Pretty (pretty),                                                              Priority (Debug),                                                              Recorder,                                                              WithPriority,@@ -63,14 +62,13 @@            -> IdePlugins IdeState            -> Rules ()            -> Maybe (LSP.LanguageContextEnv Config)-           -> Logger            -> Debouncer LSP.NormalizedUri            -> IdeOptions            -> WithHieDb            -> IndexQueue            -> Monitoring            -> IO IdeState-initialise recorder defaultConfig plugins mainRule lspEnv logger debouncer options withHieDb hiedbChan metrics = do+initialise recorder defaultConfig plugins mainRule lspEnv debouncer options withHieDb hiedbChan metrics = do     shakeProfiling <- do         let fromConf = optShakeProfiling options         fromEnv <- lookupEnv "GHCIDE_BUILD_PROFILING"@@ -80,7 +78,6 @@         lspEnv         defaultConfig         plugins-        logger         debouncer         shakeProfiling         (optReportProgress options)
src/Development/IDE/Core/Shake.hs view
@@ -51,12 +51,10 @@     HLS.getClientConfig,     getPluginConfigAction,     knownTargets,-    setPriority,     ideLogger,     actionLogger,     getVirtualFile,     FileVersion(..),-    Priority(..),     updatePositionMapping,     updatePositionMappingHelper,     deleteValue, recordDirtyKeys,@@ -164,17 +162,16 @@ import qualified Language.LSP.Protocol.Lens             as L import           Language.LSP.Protocol.Message import           Language.LSP.Protocol.Types-import           Language.LSP.Protocol.Types            (SemanticTokens) import qualified Language.LSP.Protocol.Types            as LSP import qualified Language.LSP.Server                    as LSP import           Language.LSP.VFS                       hiding (start) import qualified "list-t" ListT import           OpenTelemetry.Eventlog                 hiding (addEvent)+import qualified Prettyprinter                          as Pretty import qualified StmContainers.Map                      as STM import           System.FilePath                        hiding (makeRelative) import           System.IO.Unsafe                       (unsafePerformIO) import           System.Time.Extra- -- See Note [Guidelines For Using CPP In GHCIDE Import Statements]  #if !MIN_VERSION_ghc(9,3,0)@@ -193,6 +190,12 @@   | LogDiagsDiffButNoLspEnv ![FileDiagnostic]   | LogDefineEarlyCutoffRuleNoDiagHasDiag !FileDiagnostic   | LogDefineEarlyCutoffRuleCustomNewnessHasDiag !FileDiagnostic+  | LogCancelledAction !T.Text+  | LogSessionInitialised+  | LogLookupPersistentKey !T.Text+  | LogShakeGarbageCollection !T.Text !Int !Seconds+  -- * OfInterest Log messages+  | LogSetFilesOfInterest ![(NormalizedFilePath, FileOfInterestStatus)]   deriving Show  instance Pretty Log where@@ -226,6 +229,16 @@     LogDefineEarlyCutoffRuleCustomNewnessHasDiag fileDiagnostic ->       "defineEarlyCutoff RuleWithCustomNewnessCheck - file diagnostic:"       <+> pretty (showDiagnosticsColored [fileDiagnostic])+    LogCancelledAction action ->+        pretty action <+> "was cancelled"+    LogSessionInitialised -> "Shake session initialized"+    LogLookupPersistentKey key ->+        "LOOKUP PERSISTENT FOR:" <+> pretty key+    LogShakeGarbageCollection label number duration ->+        pretty label <+> "of" <+> pretty number <+> "keys (took " <+> pretty (showDuration duration) <> ")"+    LogSetFilesOfInterest ofInterest ->+        "Set files of interst to" <> Pretty.line+            <> indent 4 (pretty $ fmap (first fromNormalizedFilePath) ofInterest)  -- | We need to serialize writes to the database, so we send any function that -- needs to write to the database over the channel, where it will be picked up by@@ -256,7 +269,7 @@     { --eventer :: LSP.FromServerMessage -> IO ()      lspEnv :: Maybe (LSP.LanguageContextEnv Config)     ,debouncer :: Debouncer NormalizedUri-    ,logger :: Logger+    ,shakeRecorder :: Recorder (WithPriority Log)     ,idePlugins :: IdePlugins IdeState     ,globals :: TVar (HMap.HashMap TypeRep Dynamic)       -- ^ Registry of global state used by rules.@@ -441,7 +454,7 @@           | otherwise = do           pmap <- readTVarIO persistentKeys           mv <- runMaybeT $ do-            liftIO $ Logger.logDebug (logger s) $ T.pack $ "LOOKUP PERSISTENT FOR: " ++ show k+            liftIO $ logWith (shakeRecorder s) Debug $ LogLookupPersistentKey (T.pack $ show k)             f <- MaybeT $ pure $ lookupKeyMap (newKey k) pmap             (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f file             MaybeT $ pure $ (,del,ver) <$> fromDynamic dv@@ -604,7 +617,6 @@           -> Maybe (LSP.LanguageContextEnv Config)           -> Config           -> IdePlugins IdeState-          -> Logger           -> Debouncer NormalizedUri           -> Maybe FilePath           -> IdeReportProgress@@ -615,7 +627,7 @@           -> Monitoring           -> Rules ()           -> IO IdeState-shakeOpen recorder lspEnv defaultConfig idePlugins logger debouncer+shakeOpen recorder lspEnv defaultConfig idePlugins debouncer   shakeProfileDir (IdeReportProgress reportProgress)   ideTesting@(IdeTesting testing)   withHieDb indexQueue opts monitoring rules = mdo@@ -662,7 +674,7 @@         dirtyKeys <- newTVarIO mempty         -- Take one VFS snapshot at the start         vfsVar <- newTVarIO =<< vfsSnapshot lspEnv-        pure ShakeExtras{..}+        pure ShakeExtras{shakeRecorder = recorder, ..}     shakeDb  <-         shakeNewDatabase             opts { shakeExtra = newShakeExtra shakeExtras }@@ -709,7 +721,7 @@     vfs <- vfsSnapshot (lspEnv shakeExtras)     initSession <- newSession recorder shakeExtras (VFSModified vfs) shakeDb [] "shakeSessionInit"     putMVar shakeSession initSession-    logDebug (ideLogger ide) "Shake session initialized"+    logWith recorder Debug LogSessionInitialised  shakeShut :: IdeState -> IO () shakeShut IdeState{..} = do@@ -777,7 +789,7 @@ -- --   Appropriate for user actions other than edits. shakeEnqueue :: ShakeExtras -> DelayedAction a -> IO (IO a)-shakeEnqueue ShakeExtras{actionQueue, logger} act = do+shakeEnqueue ShakeExtras{actionQueue, shakeRecorder} act = do     (b, dai) <- instantiateDelayedAction act     atomicallyNamed "actionQueue - push" $ pushQueue dai actionQueue     let wait' barrier =@@ -786,7 +798,7 @@                     fail $ "internal bug: forever blocked on MVar for " <>                             actionName act)               , Handler (\e@AsyncCancelled -> do-                  logPriority logger Debug $ T.pack $ actionName act <> " was cancelled"+                  logWith shakeRecorder Debug $ LogCancelledAction (T.pack $ actionName act)                    atomicallyNamed "actionQueue - abort" $ abortQueue dai actionQueue                   throw e)@@ -910,13 +922,12 @@ garbageCollectKeys :: String -> Int -> CheckParents -> [(Key, Int)] -> Action [Key] garbageCollectKeys label maxAge checkParents agedKeys = do     start <- liftIO offsetTime-    ShakeExtras{state, dirtyKeys, lspEnv, logger, ideTesting} <- getShakeExtras+    ShakeExtras{state, dirtyKeys, lspEnv, shakeRecorder, ideTesting} <- getShakeExtras     (n::Int, garbage) <- liftIO $         foldM (removeDirtyKey dirtyKeys state) (0,[]) agedKeys     t <- liftIO start     when (n>0) $ liftIO $ do-        logDebug logger $ T.pack $-            label <> " of " <> show n <> " keys (took " <> showDuration t <> ")"+        logWith shakeRecorder Debug $ LogShakeGarbageCollection (T.pack label) n t     when (coerce ideTesting) $ liftIO $ mRunLspT lspEnv $         LSP.sendNotification (SMethod_CustomMethod (Proxy @"ghcide/GC"))                              (toJSON $ mapMaybe (fmap showKey . fromKeyType) garbage)@@ -1307,18 +1318,11 @@             | otherwise = c  -newtype Priority = Priority Double--setPriority :: Priority -> Action ()-setPriority (Priority p) = reschedule p--ideLogger :: IdeState -> Logger-ideLogger IdeState{shakeExtras=ShakeExtras{logger}} = logger+ideLogger :: IdeState -> Recorder (WithPriority Log)+ideLogger IdeState{shakeExtras=ShakeExtras{shakeRecorder}} = shakeRecorder -actionLogger :: Action Logger-actionLogger = do-    ShakeExtras{logger} <- getShakeExtras-    return logger+actionLogger :: Action (Recorder (WithPriority Log))+actionLogger = shakeRecorder <$> getShakeExtras  -------------------------------------------------------------------------------- type STMDiagnosticStore = STM.Map NormalizedUri StoreItem
src/Development/IDE/Core/Tracing.hs view
@@ -7,7 +7,7 @@     , otTracedGarbageCollection     , withTrace     , withEventTrace-    , withTelemetryLogger+    , withTelemetryRecorder     ) where @@ -26,7 +26,7 @@ import           Development.IDE.Types.Diagnostics (FileDiagnostic,                                                     showDiagnostics) import           Development.IDE.Types.Location    (Uri (..))-import           Ide.Logger                        (Logger (Logger))+import           Ide.Logger import           Ide.Types                         (PluginId (..)) import           Language.LSP.Protocol.Types       (NormalizedFilePath,                                                     fromNormalizedFilePath)@@ -51,16 +51,20 @@   | otherwise = act (\_ -> pure ())  -- | Returns a logger that produces telemetry events in a single span-withTelemetryLogger :: (MonadIO m, MonadMask m) => (Logger -> m a) -> m a-withTelemetryLogger k = withSpan "Logger" $ \sp ->+withTelemetryRecorder :: (MonadIO m, MonadMask m) => (Recorder (WithPriority (Doc a)) -> m c) -> m c+withTelemetryRecorder k = withSpan "Logger" $ \sp ->     -- Tracy doesn't like when we create a new span for every log line.     -- To workaround that, we create a single span for all log events.     -- This is fine since we don't care about the span itself, only about the events-    k $ Logger $ \p m ->-            addEvent sp (fromString $ show p) (encodeUtf8 $ trim m)-    where-        -- eventlog message size is limited by EVENT_PAYLOAD_SIZE_MAX = STG_WORD16_MAX-        trim = T.take (fromIntegral(maxBound :: Word16) - 10)+    k $ telemetryLogRecorder sp++-- | Returns a logger that produces telemetry events in a single span.+telemetryLogRecorder :: SpanInFlight -> Recorder (WithPriority (Doc a))+telemetryLogRecorder sp = Recorder $ \WithPriority {..} ->+  liftIO $ addEvent sp (fromString $ show priority) (encodeUtf8 $ trim $ renderStrict $ layoutCompact $ payload)+  where+    -- eventlog message size is limited by EVENT_PAYLOAD_SIZE_MAX = STG_WORD16_MAX+    trim = T.take (fromIntegral(maxBound :: Word16) - 10)  -- | Trace a handler using OpenTelemetry. Adds various useful info into tags in the OpenTelemetry span. otTracedHandler
src/Development/IDE/GHC/Compat.hs view
@@ -504,9 +504,7 @@ generatedNodeInfo = Map.lookup GeneratedInfo . getSourcedNodeInfo . sourcedNodeInfo  data GhcVersion-  = GHC810-  | GHC90-  | GHC92+  = GHC92   | GHC94   | GHC96   | GHC98
src/Development/IDE/GHC/Compat/Core.hs view
@@ -543,6 +543,7 @@ #endif  #if MIN_VERSION_ghc(9,3,0)+import GHC.Utils.Error (mkPlainErrorMsgEnvelope) import GHC.Driver.Env.KnotVars import GHC.Unit.Module.Graph import GHC.Driver.Errors.Types
src/Development/IDE/GHC/Error.hs view
@@ -91,6 +91,7 @@   Position (fromIntegral $ srcLocLine real - 1) (fromIntegral $ srcLocCol real - 1)  -- Note [Unicode support]+-- ~~~~~~~~~~~~~~~~~~~~~~ -- the current situation is: -- LSP Positions use UTF-16 code units(Unicode may count as variable columns); -- GHC use Unicode code points(Unicode count as one column).
src/Development/IDE/LSP/HoverDefinition.hs view
@@ -4,9 +4,9 @@  -- | Display information on hover. module Development.IDE.LSP.HoverDefinition-    (+    ( Log(..)     -- * For haskell-language-server-    hover+    , hover     , gotoDefinition     , gotoTypeDefinition     , documentHighlight@@ -18,8 +18,9 @@ import           Control.Monad.IO.Class import           Data.Maybe                     (fromMaybe) import           Development.IDE.Core.Actions-import           Development.IDE.Core.Rules-import           Development.IDE.Core.Shake+import qualified Development.IDE.Core.Rules     as Shake+import           Development.IDE.Core.Shake     (IdeAction, IdeState (..),+                                                 ideLogger, runIdeAction) import           Development.IDE.Types.Location import           Ide.Logger import           Ide.Plugin.Error@@ -30,26 +31,37 @@  import qualified Data.Text                      as T -gotoDefinition :: IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) (MessageResult Method_TextDocumentDefinition)-hover          :: IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) (Hover |? Null)-gotoTypeDefinition :: IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) (MessageResult Method_TextDocumentTypeDefinition)-documentHighlight :: IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) ([DocumentHighlight] |? Null)++data Log+  = LogWorkspaceSymbolRequest !T.Text+  | LogRequest !T.Text !Position !NormalizedFilePath+  deriving (Show)++instance Pretty Log where+  pretty = \case+    LogWorkspaceSymbolRequest query -> "Workspace symbols request:" <+> pretty query+    LogRequest label pos nfp ->+      pretty label <+> "request at position" <+> pretty (showPosition pos) <+>+        "in file:" <+> pretty (fromNormalizedFilePath nfp)++gotoDefinition     :: Recorder (WithPriority Log) -> IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) (MessageResult Method_TextDocumentDefinition)+hover              :: Recorder (WithPriority Log) -> IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) (Hover |? Null)+gotoTypeDefinition :: Recorder (WithPriority Log) -> IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) (MessageResult Method_TextDocumentTypeDefinition)+documentHighlight  :: Recorder (WithPriority Log) -> IdeState -> TextDocumentPositionParams -> ExceptT PluginError (LSP.LspM c) ([DocumentHighlight] |? Null) gotoDefinition = request "Definition" getDefinition (InR $ InR Null) (InL . Definition. InR) gotoTypeDefinition = request "TypeDefinition" getTypeDefinition (InR $ InR Null) (InL . Definition. InR) hover          = request "Hover"      getAtPoint     (InR Null)     foundHover documentHighlight = request "DocumentHighlight" highlightAtPoint (InR Null) InL -references :: PluginMethodHandler IdeState Method_TextDocumentReferences-references ide _ (ReferenceParams (TextDocumentIdentifier uri) pos _ _ _) = do+references :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState Method_TextDocumentReferences+references recorder ide _ (ReferenceParams (TextDocumentIdentifier uri) pos _ _ _) = do   nfp <- getNormalizedFilePathE uri-  liftIO $ logDebug (ideLogger ide) $-        "References request at position " <> T.pack (showPosition pos) <>-        " in file: " <> T.pack (show nfp)-  InL <$> (liftIO $ runAction "references" ide $ refsAtPoint nfp pos)+  liftIO $ logWith recorder Debug $ LogRequest "References" pos nfp+  InL <$> (liftIO $ Shake.runAction "references" ide $ refsAtPoint nfp pos) -wsSymbols :: PluginMethodHandler IdeState Method_WorkspaceSymbol-wsSymbols ide _ (WorkspaceSymbolParams _ _ query) = liftIO $ do-  logDebug (ideLogger ide) $ "Workspace symbols request: " <> query+wsSymbols :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState Method_WorkspaceSymbol+wsSymbols recorder ide _ (WorkspaceSymbolParams _ _ query) = liftIO $ do+  logWith recorder Debug $ LogWorkspaceSymbolRequest query   runIdeAction "WorkspaceSymbols" (shakeExtras ide) $ InL . fromMaybe [] <$> workspaceSymbols query  foundHover :: (Maybe Range, [T.Text]) -> Hover |? Null@@ -62,19 +74,18 @@   -> (NormalizedFilePath -> Position -> IdeAction (Maybe a))   -> b   -> (a -> b)+  -> Recorder (WithPriority Log)   -> IdeState   -> TextDocumentPositionParams   -> ExceptT PluginError (LSP.LspM c) b-request label getResults notFound found ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos) = liftIO $ do+request label getResults notFound found recorder ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos) = liftIO $ do     mbResult <- case uriToFilePath' uri of-        Just path -> logAndRunRequest label getResults ide pos path+        Just path -> logAndRunRequest recorder label getResults ide pos path         Nothing   -> pure Nothing     pure $ maybe notFound found mbResult -logAndRunRequest :: T.Text -> (NormalizedFilePath -> Position -> IdeAction b) -> IdeState -> Position -> String -> IO b-logAndRunRequest label getResults ide pos path = do+logAndRunRequest :: Recorder (WithPriority Log) -> T.Text -> (NormalizedFilePath -> Position -> IdeAction b) -> IdeState -> Position -> String -> IO b+logAndRunRequest recorder label getResults ide pos path = do   let filePath = toNormalizedFilePath' path-  logDebug (ideLogger ide) $-    label <> " request at position " <> T.pack (showPosition pos) <>-    " in file: " <> T.pack path+  logWith recorder Debug $ LogRequest label pos filePath   runIdeAction (T.unpack label) (shakeExtras ide) (getResults filePath pos)
src/Development/IDE/LSP/LanguageServer.hs view
@@ -42,7 +42,6 @@ import           Language.LSP.Server                   (LanguageContextEnv,                                                         LspServerLog,                                                         type (<~>))-import           System.IO.Unsafe                      (unsafeInterleaveIO) data Log   = LogRegisteringIdeConfig !IdeConfiguration   | LogReactorThreadException !SomeException@@ -51,6 +50,7 @@   | LogCancelledRequest !SomeLspId   | LogSession Session.Log   | LogLspServer LspServerLog+  | LogServerShutdownMessage   deriving Show  instance Pretty Log where@@ -74,6 +74,7 @@       "Cancelled request" <+> viaShow requestId     LogSession msg -> pretty msg     LogLspServer msg -> pretty msg+    LogServerShutdownMessage -> "Received shutdown message"  -- used to smuggle RankNType WithHieDb through dbMVar newtype WithHieDbShield = WithHieDbShield WithHieDb@@ -155,7 +156,7 @@           -- We want to avoid that the list of cancelled requests           -- keeps growing if we receive cancellations for requests           -- that do not exist or have already been processed.-          when (reqId `elem` queued) $+          when (reqId `Set.member` queued) $               modifyTVar cancelledRequests (Set.insert reqId)   let clearReqId reqId = atomically $ do           modifyTVar pendingRequests (Set.delete reqId)@@ -170,7 +171,7 @@         [ userHandlers         , cancelHandler cancelRequest         , exitHandler exit-        , shutdownHandler stopReactorLoop+        , shutdownHandler recorder stopReactorLoop         ]         -- Cancel requests are special since they need to be handled         -- out of order to be useful. Existing handlers are run afterwards.@@ -197,19 +198,11 @@     let root = LSP.resRootPath env     dir <- maybe getCurrentDirectory return root     dbLoc <- getHieDbLoc dir--    -- The database needs to be open for the duration of the reactor thread, but we need to pass in a reference-    -- to 'getIdeState', so we use this dirty trick-    dbMVar <- newEmptyMVar-    ~(WithHieDbShield withHieDb,hieChan) <- unsafeInterleaveIO $ takeMVar dbMVar--    ide <- getIdeState env root withHieDb hieChan-     let initConfig = parseConfiguration params-     logWith recorder Info $ LogRegisteringIdeConfig initConfig-    registerIdeConfiguration (shakeExtras ide) initConfig+    dbMVar <- newEmptyMVar +     let handleServerException (Left e) = do             logWith recorder Error $ LogReactorThreadException e             exitClientMsg@@ -245,6 +238,10 @@                     ReactorNotification act -> handle exceptionInHandler act                     ReactorRequest _id act k -> void $ async $ checkCancelled _id act k         logWith recorder Info LogReactorThreadStopped++    (WithHieDbShield withHieDb,hieChan) <- takeMVar dbMVar+    ide <- getIdeState env root withHieDb hieChan+    registerIdeConfiguration (shakeExtras ide) initConfig     pure $ Right (env,ide)  @@ -261,10 +258,10 @@         toLspId (InL x) = IdInt x         toLspId (InR y) = IdString y -shutdownHandler :: IO () -> LSP.Handlers (ServerM c)-shutdownHandler stopReactor = LSP.requestHandler SMethod_Shutdown $ \_ resp -> do+shutdownHandler :: Recorder (WithPriority Log) -> IO () -> LSP.Handlers (ServerM c)+shutdownHandler recorder stopReactor = LSP.requestHandler SMethod_Shutdown $ \_ resp -> do     (_, ide) <- ask-    liftIO $ logDebug (ideLogger ide) "Received shutdown message"+    liftIO $ logWith recorder Debug LogServerShutdownMessage     -- stop the reactor to free up the hiedb connection     liftIO stopReactor     -- flush out the Shake session to record a Shake profile if applicable
src/Development/IDE/LSP/Notifications.hs view
@@ -41,12 +41,25 @@ data Log   = LogShake Shake.Log   | LogFileStore FileStore.Log+  | LogOpenTextDocument !Uri+  | LogOpenedTextDocument !Uri+  | LogModifiedTextDocument !Uri+  | LogSavedTextDocument !Uri+  | LogClosedTextDocument !Uri+  | LogWatchedFileEvents !Text.Text+  | LogWarnNoWatchedFilesSupport   deriving Show  instance Pretty Log where   pretty = \case     LogShake msg     -> pretty msg     LogFileStore msg -> pretty msg+    LogOpenedTextDocument uri ->  "Opened text document:" <+> pretty (getUri uri)+    LogModifiedTextDocument uri -> "Modified text document:" <+> pretty (getUri uri)+    LogSavedTextDocument uri -> "Saved text document:" <+> pretty (getUri uri)+    LogClosedTextDocument uri -> "Closed text document:" <+> pretty (getUri uri)+    LogWatchedFileEvents msg -> "Watched file events:" <+> pretty msg+    LogWarnNoWatchedFilesSupport -> "Client does not support watched files. Falling back to OS polling"  whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO () whenUriFile uri act = whenJust (LSP.uriToFilePath uri) $ act . toNormalizedFilePath'@@ -61,7 +74,7 @@           -- For example, vscode restores previously unsaved contents on open           addFileOfInterest ide file Modified{firstOpen=True}           setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide False file-          logDebug (ideLogger ide) $ "Opened text document: " <> getUri _uri+          logWith recorder Debug $ LogOpenedTextDocument _uri    , mkPluginNotificationHandler LSP.SMethod_TextDocumentDidChange $       \ide vfs _ (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> liftIO $ do@@ -69,14 +82,14 @@         whenUriFile _uri $ \file -> do           addFileOfInterest ide file Modified{firstOpen=False}           setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide False file-        logDebug (ideLogger ide) $ "Modified text document: " <> getUri _uri+        logWith recorder Debug $ LogModifiedTextDocument _uri    , mkPluginNotificationHandler LSP.SMethod_TextDocumentDidSave $       \ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier{_uri} _) -> liftIO $ do         whenUriFile _uri $ \file -> do             addFileOfInterest ide file OnDisk             setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide True file-        logDebug (ideLogger ide) $ "Saved text document: " <> getUri _uri+        logWith recorder Debug $ LogSavedTextDocument _uri    , mkPluginNotificationHandler LSP.SMethod_TextDocumentDidClose $         \ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> liftIO $ do@@ -85,7 +98,7 @@               let msg = "Closed text document: " <> getUri _uri               scheduleGarbageCollection ide               setSomethingModified (VFSModified vfs) ide [] $ Text.unpack msg-              logDebug (ideLogger ide) msg+              logWith recorder Debug $ LogClosedTextDocument _uri    , mkPluginNotificationHandler LSP.SMethod_WorkspaceDidChangeWatchedFiles $       \ide vfs _ (DidChangeWatchedFilesParams fileEvents) -> liftIO $ do@@ -102,7 +115,7 @@                 ]         unless (null fileEvents') $ do             let msg = show fileEvents'-            logDebug (ideLogger ide) $ "Watched file events: " <> Text.pack msg+            logWith recorder Debug $ LogWatchedFileEvents (Text.pack msg)             modifyFileExists ide fileEvents'             resetFileStore ide fileEvents'             setSomethingModified (VFSModified vfs) ide [] msg@@ -133,7 +146,7 @@       let globs = watchedGlobs opts       success <- registerFileWatches globs       unless success $-        liftIO $ logDebug (ideLogger ide) "Warning: Client does not support watched files. Falling back to OS polling"+        liftIO $ logWith recorder Warning LogWarnNoWatchedFilesSupport   ],      -- The ghcide descriptors should come last'ish so that the notification handlers
src/Development/IDE/Main.hs view
@@ -65,7 +65,6 @@ import qualified Development.IDE.LSP.LanguageServer       as LanguageServer import           Development.IDE.Main.HeapStats           (withHeapStats) import qualified Development.IDE.Main.HeapStats           as HeapStats-import qualified Development.IDE.Monitoring.EKG           as EKG import qualified Development.IDE.Monitoring.OpenTelemetry as OpenTelemetry import           Development.IDE.Plugin                   (Plugin (pluginHandlers, pluginModifyDynflags, pluginRules)) import           Development.IDE.Plugin.HLS               (asGhcIdePlugin)@@ -95,14 +94,13 @@ import           GHC.IO.Handle                            (hDuplicate) import           HIE.Bios.Cradle                          (findCradle) import qualified HieDb.Run                                as HieDb-import           Ide.Logger                               (Logger,-                                                           Pretty (pretty),+import           Ide.Logger                               (Pretty (pretty),                                                            Priority (Info),                                                            Recorder,                                                            WithPriority,                                                            cmapWithPrio,-                                                           logDebug, logWith,-                                                           nest, vsep, (<+>))+                                                           logWith, nest, vsep,+                                                           (<+>)) import           Ide.Plugin.Config                        (CheckParents (NeverCheck),                                                            Config, checkParents,                                                            checkProject,@@ -140,6 +138,7 @@   | LogLspStartDuration !Seconds   | LogShouldRunSubset !Bool   | LogSetInitialDynFlagsException !SomeException+  | LogConfigurationChange T.Text   | LogService Service.Log   | LogShake Shake.Log   | LogGhcIde GhcIde.Log@@ -164,6 +163,7 @@       "shouldRunSubset:" <+> pretty shouldRunSubset     LogSetInitialDynFlagsException e ->       "setInitialDynFlags:" <+> pretty (displayException e)+    LogConfigurationChange msg -> "Configuration changed:" <+> pretty msg     LogService msg -> pretty msg     LogShake msg -> pretty msg     LogGhcIde msg -> pretty msg@@ -210,7 +210,6 @@ data Arguments = Arguments     { argsProjectRoot           :: Maybe FilePath     , argCommand                :: Command-    , argsLogger                :: IO Logger     , argsRules                 :: Rules ()     , argsHlsPlugins            :: IdePlugins IdeState     , argsGhcidePlugin          :: Plugin Config  -- ^ Deprecated@@ -226,11 +225,10 @@     , argsMonitoring            :: IO Monitoring     } -defaultArguments :: Recorder (WithPriority Log) -> Logger -> IdePlugins IdeState -> Arguments-defaultArguments recorder logger plugins = Arguments+defaultArguments :: Recorder (WithPriority Log) -> IdePlugins IdeState -> Arguments+defaultArguments recorder plugins = Arguments         { argsProjectRoot = Nothing         , argCommand = LSP-        , argsLogger = pure logger         , argsRules = mainRule (cmapWithPrio LogRules recorder) def >> action kick         , argsGhcidePlugin = mempty         , argsHlsPlugins = pluginDescToIdePlugins (GhcIde.descriptors (cmapWithPrio LogGhcIde recorder)) <> plugins@@ -259,15 +257,15 @@                 -- the language server tests without the redirection.                 putStr " " >> hFlush stdout                 return newStdout-        , argsMonitoring = OpenTelemetry.monitoring <> EKG.monitoring logger 8999+        , argsMonitoring = OpenTelemetry.monitoring         }  -testing :: Recorder (WithPriority Log) -> Logger -> IdePlugins IdeState -> Arguments-testing recorder logger plugins =+testing :: Recorder (WithPriority Log) -> IdePlugins IdeState -> Arguments+testing recorder plugins =   let     arguments@Arguments{ argsHlsPlugins, argsIdeOptions } =-        defaultArguments recorder logger plugins+        defaultArguments recorder plugins     hlsPlugins = pluginDescToIdePlugins $       idePluginsToPluginDesc argsHlsPlugins       ++ [Test.blockCommandDescriptor "block-command", Test.plugin]@@ -288,7 +286,6 @@   fun = do     setLocaleEncoding utf8     pid <- T.pack . show <$> getProcessID-    logger <- argsLogger     hSetBuffering stderr LineBuffering      let hlsPlugin = asGhcIdePlugin (cmapWithPrio LogPluginHLS recorder) argsHlsPlugins@@ -347,7 +344,6 @@                       argsHlsPlugins                       rules                       (Just env)-                      logger                       debouncer                       ideOptions                       withHieDb@@ -366,7 +362,7 @@                     Nothing -> pure ()                     Just ide -> liftIO $ do                         let msg = T.pack $ show cfg-                        logDebug (Shake.ideLogger ide) $ "Configuration changed: " <> msg+                        logWith recorder Debug $ LogConfigurationChange msg                         modifyClientSettings ide (const $ Just cfgObj)                         setSomethingModified Shake.VFSUnmodified ide [toKey Rules.GetClientSettings emptyFilePath] "config change" @@ -403,7 +399,7 @@                         , optCheckProject = pure False                         , optModifyDynFlags = optModifyDynFlags def_options <> pluginModifyDynflags plugins                         }-            ide <- initialise (cmapWithPrio LogService recorder) argsDefaultHlsConfig argsHlsPlugins rules Nothing logger debouncer ideOptions hiedb hieChan mempty+            ide <- initialise (cmapWithPrio LogService recorder) argsDefaultHlsConfig argsHlsPlugins rules Nothing debouncer ideOptions hiedb hieChan mempty             shakeSessionInit (cmapWithPrio LogShake recorder) ide             registerIdeConfiguration (shakeExtras ide) $ IdeConfiguration mempty (hashed Nothing) @@ -441,7 +437,7 @@                     , optCheckProject = pure False                     , optModifyDynFlags = optModifyDynFlags def_options <> pluginModifyDynflags plugins                     }-            ide <- initialise (cmapWithPrio LogService recorder) argsDefaultHlsConfig argsHlsPlugins rules Nothing logger debouncer ideOptions hiedb hieChan mempty+            ide <- initialise (cmapWithPrio LogService recorder) argsDefaultHlsConfig argsHlsPlugins rules Nothing debouncer ideOptions hiedb hieChan mempty             shakeSessionInit (cmapWithPrio LogShake recorder) ide             registerIdeConfiguration (shakeExtras ide) $ IdeConfiguration mempty (hashed Nothing)             c ide
− src/Development/IDE/Monitoring/EKG.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE CPP #-}-module Development.IDE.Monitoring.EKG(monitoring) where--import           Development.IDE.Types.Monitoring (Monitoring (..))-import           Ide.Logger                       (Logger)--#ifdef MONITORING_EKG-import           Control.Concurrent               (killThread)-import           Control.Concurrent.Async         (async, waitCatch)-import           Control.Monad                    (forM_)-import           Data.Text                        (pack)-import           Ide.Logger                       (logInfo)-import qualified System.Metrics                   as Monitoring-import qualified System.Remote.Monitoring.Wai     as Monitoring---- | Monitoring using EKG-monitoring :: Logger -> Int -> IO Monitoring-monitoring logger port = do-    store <- Monitoring.newStore-    Monitoring.registerGcMetrics store-    let registerCounter name read = Monitoring.registerCounter name read store-        registerGauge name read = Monitoring.registerGauge name read store-        start = do-            server <- do-                let startServer = Monitoring.forkServerWith store "localhost" port-                -- this can fail if the port is busy, throwing an async exception back to us-                -- to handle that, wrap the server thread in an async-                mb_server <- async startServer >>= waitCatch-                case mb_server of-                    Right s -> do-                        logInfo logger $ pack $-                            "Started monitoring server on port " <> show port-                        return $ Just s-                    Left e -> do-                        logInfo logger $ pack $-                            "Unable to bind monitoring server on port "-                            <> show port <> ":" <> show e-                        return Nothing-            return $ forM_ server $ \s -> do-                logInfo logger "Stopping monitoring server"-                killThread $ Monitoring.serverThreadId s-    return $ Monitoring {..}--#else--monitoring :: Logger -> Int -> IO Monitoring-monitoring _ _ = mempty--#endif
src/Development/IDE/Plugin/HLS.hs view
@@ -359,6 +359,7 @@   mempty = IdeNotificationHandlers mempty  {- Note [Exception handling in plugins]+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Plugins run in LspM, and so have access to IO. This means they are likely to throw exceptions, even if only by accident or through calling libraries that throw exceptions. Ultimately, we're running a bunch of less-trusted IO code,
src/Development/IDE/Plugin/HLS/GhcIde.hs view
@@ -9,7 +9,7 @@   ) where import           Control.Monad.IO.Class import           Development.IDE-import           Development.IDE.LSP.HoverDefinition+import qualified Development.IDE.LSP.HoverDefinition as Hover import qualified Development.IDE.LSP.Notifications   as Notifications import           Development.IDE.LSP.Outline import qualified Development.IDE.Plugin.Completions  as Completions@@ -23,6 +23,7 @@   = LogNotifications Notifications.Log   | LogCompletions Completions.Log   | LogTypeLenses TypeLenses.Log+  | LogHover Hover.Log   deriving Show  instance Pretty Log where@@ -30,10 +31,11 @@     LogNotifications msg -> pretty msg     LogCompletions msg   -> pretty msg     LogTypeLenses msg    -> pretty msg+    LogHover msg         -> pretty msg  descriptors :: Recorder (WithPriority Log) -> [PluginDescriptor IdeState] descriptors recorder =-  [ descriptor "ghcide-hover-and-symbols",+  [ descriptor (cmapWithPrio LogHover recorder) "ghcide-hover-and-symbols",     Completions.descriptor (cmapWithPrio LogCompletions recorder) "ghcide-completions",     TypeLenses.descriptor (cmapWithPrio LogTypeLenses recorder) "ghcide-type-lenses",     Notifications.descriptor (cmapWithPrio LogNotifications recorder) "ghcide-core"@@ -41,18 +43,18 @@  -- --------------------------------------------------------------------- -descriptor :: PluginId -> PluginDescriptor IdeState-descriptor plId = (defaultPluginDescriptor plId desc)-  { pluginHandlers = mkPluginHandler SMethod_TextDocumentHover hover'+descriptor :: Recorder (WithPriority Hover.Log) -> PluginId -> PluginDescriptor IdeState+descriptor recorder plId = (defaultPluginDescriptor plId desc)+  { pluginHandlers = mkPluginHandler SMethod_TextDocumentHover (hover' recorder)                   <> mkPluginHandler SMethod_TextDocumentDocumentSymbol moduleOutline                   <> mkPluginHandler SMethod_TextDocumentDefinition (\ide _ DefinitionParams{..} ->-                      gotoDefinition ide TextDocumentPositionParams{..})+                      Hover.gotoDefinition recorder ide TextDocumentPositionParams{..})                   <> mkPluginHandler SMethod_TextDocumentTypeDefinition (\ide _ TypeDefinitionParams{..} ->-                      gotoTypeDefinition ide TextDocumentPositionParams{..})+                      Hover.gotoTypeDefinition recorder ide TextDocumentPositionParams{..})                   <> mkPluginHandler SMethod_TextDocumentDocumentHighlight (\ide _ DocumentHighlightParams{..} ->-                      documentHighlight ide TextDocumentPositionParams{..})-                  <> mkPluginHandler SMethod_TextDocumentReferences references-                  <> mkPluginHandler SMethod_WorkspaceSymbol wsSymbols,+                      Hover.documentHighlight recorder ide TextDocumentPositionParams{..})+                  <> mkPluginHandler SMethod_TextDocumentReferences (Hover.references recorder)+                  <> mkPluginHandler SMethod_WorkspaceSymbol (Hover.wsSymbols recorder),      pluginConfigDescriptor = defaultConfigDescriptor   }@@ -61,7 +63,6 @@  -- --------------------------------------------------------------------- -hover' :: PluginMethodHandler IdeState Method_TextDocumentHover-hover' ideState _ HoverParams{..} = do-    liftIO $ logDebug (ideLogger ideState) "GhcIde.hover entered (ideLogger)" -- AZ-    hover ideState TextDocumentPositionParams{..}+hover' :: Recorder (WithPriority Hover.Log) -> PluginMethodHandler IdeState Method_TextDocumentHover+hover' recorder ideState _ HoverParams{..} =+    Hover.hover recorder ideState TextDocumentPositionParams{..}
src/Development/IDE/Types/Exports.hs view
@@ -207,7 +207,7 @@  buildModuleExportMap:: [(ModuleName, HashSet IdentInfo)] -> ModuleNameEnv (HashSet IdentInfo) buildModuleExportMap exportsMap = do-  let lst = concatMap (Set.toList. snd) exportsMap+  let lst = concatMap (Set.toList . snd) exportsMap   let lstThree = map identInfoToKeyVal lst   sortAndGroup lstThree @@ -223,4 +223,4 @@   (modName, functionSet)  sortAndGroup :: [(ModuleName, IdentInfo)] -> ModuleNameEnv (HashSet IdentInfo)-sortAndGroup assocs = listToUFM_C (<>) [(k, Set.fromList [v]) | (k, v) <- assocs]+sortAndGroup assocs = listToUFM_C (<>) [(k, Set.singleton v) | (k, v) <- assocs]
− test/data/plugin-recorddot/RecordDot.hs
@@ -1,6 +0,0 @@-{-# LANGUAGE DuplicateRecordFields, TypeApplications, TypeFamilies, UndecidableInstances, FlexibleContexts, DataKinds, MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}-{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}-module RecordDot (Company(..), display) where-data Company = Company {name :: String}-display :: Company -> String-display c = c.name
− test/data/plugin-recorddot/cabal.project
@@ -1,1 +0,0 @@-packages: .
− test/data/plugin-recorddot/plugin.cabal
@@ -1,9 +0,0 @@-cabal-version:  1.18-name: plugin-version: 1.0.0-build-type: Simple--library-  build-depends: base, record-dot-preprocessor, record-hasfield-  exposed-modules: RecordDot-  hs-source-dirs: .
− test/exe/AsyncTests.hs
@@ -1,53 +0,0 @@-{-# LANGUAGE DataKinds #-}--module AsyncTests (tests) where--import           Control.Monad-import           Control.Monad.IO.Class        (liftIO)-import           Data.Aeson                    (toJSON)-import           Data.Proxy-import qualified Data.Text                     as T-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types   hiding-                                               (SemanticTokenAbsolute (..),-                                                SemanticTokenRelative (..),-                                                SemanticTokensEdit (..),-                                                mkRange)-import           Language.LSP.Test--- import Test.QuickCheck.Instances ()-import           Development.IDE.Plugin.Test   (TestRequest (BlockSeconds),-                                                blockCommandId)-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils---- | Test if ghcide asynchronously handles Commands and user Requests-tests :: TestTree-tests = testGroup "async"-    [-      testSession "command" $ do-            -- Execute a command that will block forever-            let req = ExecuteCommandParams Nothing blockCommandId Nothing-            void $ sendRequest SMethod_WorkspaceExecuteCommand req-            -- Load a file and check for code actions. Will only work if the command is run asynchronously-            doc <- createDoc "A.hs" "haskell" $ T.unlines-              [ "{-# OPTIONS -Wmissing-signatures #-}"-              , "foo = id"-              ]-            void waitForDiagnostics-            codeLenses <- getAndResolveCodeLenses doc-            liftIO $ [ _title | CodeLens{_command = Just Command{_title}} <- codeLenses] @=?-              [ "foo :: a -> a" ]-    , testSession "request" $ do-            -- Execute a custom request that will block for 1000 seconds-            void $ sendRequest (SMethod_CustomMethod (Proxy @"test")) $ toJSON $ BlockSeconds 1000-            -- Load a file and check for code actions. Will only work if the request is run asynchronously-            doc <- createDoc "A.hs" "haskell" $ T.unlines-              [ "{-# OPTIONS -Wmissing-signatures #-}"-              , "foo = id"-              ]-            void waitForDiagnostics-            codeLenses <- getAndResolveCodeLenses doc-            liftIO $ [ _title | CodeLens{_command = Just Command{_title}} <- codeLenses] @=?-              [ "foo :: a -> a" ]-    ]
− test/exe/BootTests.hs
@@ -1,55 +0,0 @@-module BootTests (tests) where--import           Control.Applicative.Combinators-import           Control.Monad-import           Control.Monad.IO.Class          (liftIO)-import           Development.IDE.GHC.Util-import           Development.IDE.Test            (expectNoMoreDiagnostics,-                                                  isReferenceReady)-import           Development.IDE.Types.Location-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           System.FilePath-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils---tests :: TestTree-tests = testGroup "boot"-  [ testCase "boot-def-test" $ runWithExtraFiles "boot" $ \dir -> do-        let cPath = dir </> "C.hs"-        cSource <- liftIO $ readFileUtf8 cPath-        -- Dirty the cache-        liftIO $ runInDir dir $ do-            cDoc <- createDoc cPath "haskell" cSource-            -- We send a hover request then wait for either the hover response or-            -- `ghcide/reference/ready` notification.-            -- Once we receive one of the above, we wait for the other that we-            -- haven't received yet.-            -- If we don't wait for the `ready` notification it is possible-            -- that the `getDefinitions` request/response in the outer ghcide-            -- session will find no definitions.-            let hoverParams = HoverParams cDoc (Position 4 3) Nothing-            hoverRequestId <- sendRequest SMethod_TextDocumentHover hoverParams-            let parseReadyMessage = isReferenceReady cPath-            let parseHoverResponse = responseForId SMethod_TextDocumentHover hoverRequestId-            hoverResponseOrReadyMessage <- skipManyTill anyMessage ((Left <$> parseHoverResponse) <|> (Right <$> parseReadyMessage))-            _ <- skipManyTill anyMessage $-              case hoverResponseOrReadyMessage of-                Left _  -> void parseReadyMessage-                Right _ -> void parseHoverResponse-            closeDoc cDoc-        cdoc <- createDoc cPath "haskell" cSource-        locs <- getDefinitions cdoc (Position 7 4)-        let floc = mkR 9 0 9 1-        checkDefs locs (pure [floc])-  , testCase "graph with boot modules" $ runWithExtraFiles "boot2" $ \dir -> do-      _ <- openDoc (dir </> "A.hs") "haskell"-      expectNoMoreDiagnostics 2-  ]
− test/exe/CPPTests.hs
@@ -1,56 +0,0 @@-module CPPTests (tests) where--import           Control.Exception           (catch)-import qualified Data.Text                   as T-import           Development.IDE.Test        (Cursor, expectDiagnostics,-                                              expectNoMoreDiagnostics)-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests =-  testGroup "cpp"-    [ ignoreInWindowsBecause "Throw a lsp session time out in windows for ghc-8.8 and is broken for other versions" $ testCase "cpp-error" $ do-        let content =-              T.unlines-                [ "{-# LANGUAGE CPP #-}",-                  "module Testing where",-                  "#ifdef FOO",-                  "foo = 42"-                ]-        -- The error locations differ depending on which C-preprocessor is used.-        -- Some give the column number and others don't (hence maxBound == -1 unsigned). Assert either-        -- of them.-        (run $ expectError content (2, maxBound))-          `catch` ( \e -> do-                      let _ = e :: HUnitFailure-                      run $ expectError content (2, 1)-                  )-    , testSessionWait "cpp-ghcide" $ do-        _ <- createDoc "A.hs" "haskell" $ T.unlines-          ["{-# LANGUAGE CPP #-}"-          ,"main ="-          ,"#ifdef __GHCIDE__"-          ,"  worked"-          ,"#else"-          ,"  failed"-          ,"#endif"-          ]-        expectDiagnostics [("A.hs", [(DiagnosticSeverity_Error, (3, 2), "Variable not in scope: worked")])]-    ]-  where-    expectError :: T.Text -> Cursor -> Session ()-    expectError content cursor = do-      _ <- createDoc "Testing.hs" "haskell" content-      expectDiagnostics-        [ ( "Testing.hs",-            [(DiagnosticSeverity_Error, cursor, "error: unterminated")]-          )-        ]-      expectNoMoreDiagnostics 0.5
− test/exe/ClientSettingsTests.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE GADTs #-}-module ClientSettingsTests (tests) where--import           Control.Applicative.Combinators-import           Control.Monad-import           Data.Aeson                      (toJSON)-import           Data.Default-import qualified Data.Text                       as T-import           Ide.Types-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           Test.Tasty-import           TestUtils--tests :: TestTree-tests = testGroup "client settings handling"-    [ testSession "ghcide restarts shake session on config changes" $ do-            setIgnoringLogNotifications False-            void $ createDoc "A.hs" "haskell" "module A where"-            waitForProgressDone-            setConfigSection "haskell" $ toJSON (def :: Config)-            skipManyTill anyMessage restartingBuildSession--    ]-  where-    restartingBuildSession :: Session ()-    restartingBuildSession = do-        FromServerMess SMethod_WindowLogMessage TNotificationMessage{_params = LogMessageParams{..}} <- loggingNotification-        guard $ "Restarting build session" `T.isInfixOf` _message
− test/exe/CodeLensTests.hs
@@ -1,121 +0,0 @@-{-# LANGUAGE GADTs #-}--module CodeLensTests (tests) where--import           Control.Applicative.Combinators-import           Control.Lens                    ((^.))-import           Control.Monad                   (void)-import           Control.Monad.IO.Class          (liftIO)-import qualified Data.Aeson                      as A-import           Data.Maybe-import qualified Data.Text                       as T-import           Data.Tuple.Extra-import           Development.IDE.GHC.Compat      (GhcVersion (..), ghcVersion)-import qualified Language.LSP.Protocol.Lens      as L-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = testGroup "code lenses"-  [ addSigLensesTests-  ]---addSigLensesTests :: TestTree-addSigLensesTests =-  let pragmas = "{-# OPTIONS_GHC -Wmissing-signatures -Wmissing-pattern-synonym-signatures #-}"-      moduleH exported =-        T.unlines-          [ "{-# LANGUAGE PatternSynonyms,TypeApplications,DataKinds,RankNTypes,ScopedTypeVariables,TypeOperators,GADTs,BangPatterns #-}"-          , "module Sigs(" <> exported <> ") where"-          , "import qualified Data.Complex as C"-          , "import Data.Data (Proxy (..), type (:~:) (..), mkCharType)"-          , "data T1 a where"-          , "  MkT1 :: (Show b) => a -> b -> T1 a"-          ]-      before enableGHCWarnings exported (def, _) others =-        T.unlines $ [pragmas | enableGHCWarnings] <> [moduleH exported, def] <> others-      after' enableGHCWarnings exported (def, sig) others =-        T.unlines $ [pragmas | enableGHCWarnings] <> [moduleH exported] <> maybe [] pure sig <> [def] <> others-      createConfig mode = A.object ["plugin" A..= A.object ["ghcide-type-lenses" A..= A.object ["config" A..= A.object ["mode" A..= A.String mode]]]]-      sigSession testName enableGHCWarnings waitForDiags mode exported def others = testSession testName $ do-        let originalCode = before enableGHCWarnings exported def others-        let expectedCode = after' enableGHCWarnings exported def others-        setConfigSection "haskell" (createConfig mode)-        doc <- createDoc "Sigs.hs" "haskell" originalCode-        -- Because the diagnostics mode is really relying only on diagnostics now-        -- to generate the code lens we need to make sure we wait till the file-        -- is parsed before asking for codelenses, otherwise we will get nothing.-        if waitForDiags-          then void waitForDiagnostics-          else waitForProgressDone-        codeLenses <- getAndResolveCodeLenses doc-        if not $ null $ snd def-          then do-            liftIO $ length codeLenses == 1 @? "Expected 1 code lens, but got: " <> show codeLenses-            executeCommand $ fromJust $ head codeLenses ^. L.command-            modifiedCode <- skipManyTill anyMessage (getDocumentEdit doc)-            liftIO $ expectedCode @=? modifiedCode-          else liftIO $ null codeLenses @? "Expected no code lens, but got: " <> show codeLenses-      cases =-        [ ("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")-        , ("pattern Some a = Just a", "pattern Some :: a -> Maybe a")-        , ("pattern Some a <- Just a", "pattern Some :: a -> Maybe a")-        , ("pattern Some a <- Just a\n  where Some a = Just a", "pattern Some :: a -> Maybe a")-        , ("pattern Some a <- Just !a\n  where Some !a = Just a", "pattern Some :: a -> Maybe a")-        , ("pattern Point{x, y} = (x, y)", "pattern Point :: a -> b -> (a, b)")-        , ("pattern Point{x, y} <- (x, y)", "pattern Point :: a -> b -> (a, b)")-        , ("pattern Point{x, y} <- (x, y)\n  where Point x y = (x, y)", "pattern Point :: a -> b -> (a, b)")-        , ("pattern MkT1' b = MkT1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a")-        , ("pattern MkT1' b <- MkT1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a")-        , ("pattern MkT1' b <- MkT1 42 b\n  where MkT1' b = MkT1 42 b", "pattern MkT1' :: (Eq a, Num a) => Show b => b -> T1 a")-        , ("qualifiedSigTest= C.realPart", "qualifiedSigTest :: C.Complex a -> a")-        , ("head = 233", "head :: Integer")-        , ("rank2Test (k :: forall a . a -> a) = (k 233 :: Int, k \"QAQ\")", "rank2Test :: (forall a. a -> a) -> (Int, " <> listOfChar <> ")")-        , ("symbolKindTest = Proxy @\"qwq\"", "symbolKindTest :: Proxy \"qwq\"")-        , ("promotedKindTest = Proxy @Nothing", if ghcVersion >= GHC96 then "promotedKindTest :: Proxy Nothing" else "promotedKindTest :: Proxy 'Nothing")-        , ("typeOperatorTest = Refl", if ghcVersion >= GHC92 then "typeOperatorTest :: forall {k} {a :: k}. a :~: a" else "typeOperatorTest :: a :~: a")-        , ("notInScopeTest = mkCharType", "notInScopeTest :: String -> Data.Data.DataType")-        , ("aVeryLongSignature a b c d e f g h i j k l m n = a && b && c && d && e && f && g && h && i && j && k && l && m && n", "aVeryLongSignature :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool")-        ]-   in testGroup-        "add signature"-        [ testGroup "signatures are correct" [sigSession (T.unpack $ T.replace "\n" "\\n" def) False False "always" "" (def, Just sig) [] | (def, sig) <- cases]-        , sigSession "exported mode works" False False "exported" "xyz" ("xyz = True", Just "xyz :: Bool") (fst <$> take 3 cases)-        , testGroup-            "diagnostics mode works"-            [ sigSession "with GHC warnings" True True "diagnostics" "" (second Just $ head cases) []-            , sigSession "without GHC warnings" False False "diagnostics" "" (second (const Nothing) $ head cases) []-            ]-        , testSession "keep stale lens" $ do-            let content = T.unlines-                    [ "module Stale where"-                    , "f = _"-                    ]-            doc <- createDoc "Stale.hs" "haskell" content-            oldLens <- getCodeLenses doc-            liftIO $ length oldLens @?= 1-            let edit = TextEdit (mkRange 0 4 0 5) "" -- Remove the `_`-            _ <- applyEdit doc edit-            newLens <- getCodeLenses doc-            liftIO $ newLens @?= oldLens-        ]---- | Before ghc9, lists of Char is displayed as [Char], but with ghc9 and up, it's displayed as String-listOfChar :: T.Text-listOfChar | ghcVersion >= GHC90 = "String"-           | otherwise = "[Char]"--
− test/exe/CompletionTests.hs
@@ -1,575 +0,0 @@--{-# LANGUAGE GADTs            #-}-{-# LANGUAGE OverloadedLabels #-}--module CompletionTests (tests) where--import           Control.Lens                   ((^.))-import qualified Control.Lens                   as Lens-import           Control.Monad-import           Control.Monad.IO.Class         (liftIO)-import           Data.Default-import           Data.List.Extra-import           Data.Maybe-import           Data.Row-import qualified Data.Text                      as T-import           Development.IDE.GHC.Compat     (GhcVersion (..), ghcVersion)-import           Development.IDE.Test           (waitForTypecheck)-import           Development.IDE.Types.Location-import           Ide.Plugin.Config-import qualified Language.LSP.Protocol.Lens     as L-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types    hiding-                                                (SemanticTokenAbsolute (..),-                                                 SemanticTokenRelative (..),-                                                 SemanticTokensEdit (..),-                                                 mkRange)-import           Language.LSP.Test-import           System.FilePath-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils---tests :: TestTree-tests-  = testGroup "completion"-    [-    testGroup "non local" nonLocalCompletionTests-    , testGroup "topLevel" topLevelCompletionTests-    , testGroup "local" localCompletionTests-    , testGroup "package" packageCompletionTests-    , testGroup "project" projectCompletionTests-    , testGroup "other" otherCompletionTests-    , testGroup "doc" completionDocTests-    ]--completionTest :: HasCallStack => String -> [T.Text] -> Position -> [(T.Text, CompletionItemKind, T.Text, Bool, Bool, Maybe [TextEdit])] -> TestTree-completionTest name src pos expected = testSessionWait name $ do-    docId <- createDoc "A.hs" "haskell" (T.unlines src)-    _ <- waitForDiagnostics-    compls <- getAndResolveCompletions docId pos-    let compls' = [ (_label, _kind, _insertText, _additionalTextEdits) | CompletionItem{..} <- compls]-    let emptyToMaybe x = if T.null x then Nothing else Just x-    liftIO $ sortOn (Lens.view Lens._1) (take (length expected) compls') @?=-        sortOn (Lens.view Lens._1)-          [ (l, Just k, emptyToMaybe t, at) | (l,k,t,_,_,at) <- expected]-    forM_ (zip compls expected) $ \(CompletionItem{..}, (_,_,_,expectedSig, expectedDocs, _)) -> do-        when expectedSig $-            liftIO $ assertBool ("Missing type signature: " <> T.unpack _label) (isJust _detail)-        when expectedDocs $-            liftIO $ assertBool ("Missing docs: " <> T.unpack _label) (isJust _documentation)---topLevelCompletionTests :: [TestTree]-topLevelCompletionTests = [-    completionTest-        "variable"-        ["bar = xx", "-- | haddock", "xxx :: ()", "xxx = ()", "-- | haddock", "data Xxx = XxxCon"]-        (Position 0 8)-        [("xxx", CompletionItemKind_Function, "xxx", True, True, Nothing)-        ],-    completionTest-        "constructor"-        ["bar = xx", "-- | haddock", "xxx :: ()", "xxx = ()", "-- | haddock", "data Xxx = XxxCon"]-        (Position 0 8)-        [("xxx", CompletionItemKind_Function, "xxx", True, True, Nothing)-        ],-    completionTest-        "class method"-        ["bar = xx", "class Xxx a where", "-- | haddock", "xxx :: ()", "xxx = ()"]-        (Position 0 8)-        [("xxx", CompletionItemKind_Function, "xxx", True, True, Nothing)],-    completionTest-        "type"-        ["bar :: Xz", "zzz = ()", "-- | haddock", "data Xzz = XzzCon"]-        (Position 0 9)-        [("Xzz", CompletionItemKind_Struct, "Xzz", False, True, Nothing)],-    completionTest-        "class"-        ["bar :: Xz", "zzz = ()", "-- | haddock", "class Xzz a"]-        (Position 0 9)-        [("Xzz", CompletionItemKind_Interface, "Xzz", False, True, Nothing)],-    completionTest-        "records"-        ["data Person = Person { _personName:: String, _personAge:: Int}", "bar = Person { _pers }" ]-        (Position 1 19)-        [("_personName", CompletionItemKind_Function, "_personName", False, True, Nothing),-         ("_personAge", CompletionItemKind_Function, "_personAge", False, True, Nothing)],-    completionTest-        "recordsConstructor"-        ["data XxRecord = XyRecord { x:: String, y:: Int}", "bar = Xy" ]-        (Position 1 19)-        [("XyRecord", CompletionItemKind_Constructor, "XyRecord", False, True, Nothing),-         ("XyRecord", CompletionItemKind_Snippet, "XyRecord {x=${1:_x}, y=${2:_y}}", False, True, Nothing)]-    ]--localCompletionTests :: [TestTree]-localCompletionTests = [-    completionTest-        "argument"-        ["bar (Just abcdef) abcdefg = abcd"]-        (Position 0 32)-        [("abcdef", CompletionItemKind_Function, "abcdef", True, False, Nothing),-         ("abcdefg", CompletionItemKind_Function , "abcdefg", True, False, Nothing)-        ],-    completionTest-        "let"-        ["bar = let (Just abcdef) = undefined"-        ,"          abcdefg = let abcd = undefined in undefined"-        ,"        in abcd"-        ]-        (Position 2 15)-        [("abcdef", CompletionItemKind_Function, "abcdef", True, False, Nothing),-         ("abcdefg", CompletionItemKind_Function , "abcdefg", True, False, Nothing)-        ],-    completionTest-        "where"-        ["bar = abcd"-        ,"  where (Just abcdef) = undefined"-        ,"        abcdefg = let abcd = undefined in undefined"-        ]-        (Position 0 10)-        [("abcdef", CompletionItemKind_Function, "abcdef", True, False, Nothing),-         ("abcdefg", CompletionItemKind_Function , "abcdefg", True, False, Nothing)-        ],-    completionTest-        "do/1"-        ["bar = do"-        ,"  Just abcdef <- undefined"-        ,"  abcd"-        ,"  abcdefg <- undefined"-        ,"  pure ()"-        ]-        (Position 2 6)-        [("abcdef", CompletionItemKind_Function, "abcdef", True, False, Nothing)-        ],-    completionTest-        "do/2"-        ["bar abcde = do"-        ,"    Just [(abcdef,_)] <- undefined"-        ,"    abcdefg <- undefined"-        ,"    let abcdefgh = undefined"-        ,"        (Just [abcdefghi]) = undefined"-        ,"    abcd"-        ,"  where"-        ,"    abcdefghij = undefined"-        ]-        (Position 5 8)-        [("abcde", CompletionItemKind_Function, "abcde", True, False, Nothing)-        ,("abcdefghij", CompletionItemKind_Function, "abcdefghij", True, False, Nothing)-        ,("abcdef", CompletionItemKind_Function, "abcdef", True, False, Nothing)-        ,("abcdefg", CompletionItemKind_Function, "abcdefg", True, False, Nothing)-        ,("abcdefgh", CompletionItemKind_Function, "abcdefgh", True, False, Nothing)-        ,("abcdefghi", CompletionItemKind_Function, "abcdefghi", True, False, Nothing)-        ],-    completionTest-        "type family"-        ["{-# LANGUAGE DataKinds, TypeFamilies #-}"-        ,"type family Bar a"-        ,"a :: Ba"-        ]-        (Position 2 7)-        [("Bar", CompletionItemKind_Struct, "Bar", True, False, Nothing)-        ],-    completionTest-        "class method"-        [-          "class Test a where"-        , "    abcd :: a -> ()"-        , "    abcde :: a -> Int"-        , "instance Test Int where"-        , "    abcd = abc"-        ]-        (Position 4 14)-        [("abcd", CompletionItemKind_Function, "abcd", True, False, Nothing)-        ,("abcde", CompletionItemKind_Function, "abcde", True, False, Nothing)-        ],-    testSessionWait "incomplete entries" $ do-        let src a = "data Data = " <> a-        doc <- createDoc "A.hs" "haskell" $ src "AAA"-        void $ waitForTypecheck doc-        let editA rhs =-                changeDoc doc [TextDocumentContentChangeEvent . InR . (.==) #text $ src rhs]-        editA "AAAA"-        void $ waitForTypecheck doc-        editA "AAAAA"-        void $ waitForTypecheck doc--        compls <- getCompletions doc (Position 0 15)-        liftIO $ filter ("AAA" `T.isPrefixOf`) (mapMaybe _insertText compls) @?= ["AAAAA"]-        pure ()-    ]--nonLocalCompletionTests :: [TestTree]-nonLocalCompletionTests =-  [ brokenForWinGhc $ completionTest-      "variable"-      ["module A where", "f = hea"]-      (Position 1 7)-      [("head", CompletionItemKind_Function, "head", True, True, Nothing)],-    completionTest-      "constructor"-      ["{-# OPTIONS_GHC -Wall #-}", "module A where", "f = True"]-      (Position 2 8)-      [ ("True", CompletionItemKind_Constructor, "True", True, True, Nothing)-      ],-    brokenForWinGhc $ completionTest-      "type"-      ["{-# OPTIONS_GHC -Wall #-}", "module A () where", "f :: Boo", "f = True"]-      (Position 2 8)-      [ ("Bool", CompletionItemKind_Struct, "Bool", True, True, Nothing)-      ],-    completionTest-      "qualified"-      ["{-# OPTIONS_GHC -Wunused-binds #-}", "module A () where", "f = Prelude.hea"]-      (Position 2 15)-      [ ("head", CompletionItemKind_Function, "head", True, True, Nothing)-      ],-    completionTest-      "duplicate import"-      ["module A where", "import Data.List", "import Data.List", "f = permu"]-      (Position 3 9)-      [ ("permutations", CompletionItemKind_Function, "permutations", False, False, Nothing)-      ],-    completionTest-       "dont show hidden items"-       [ "{-# LANGUAGE NoImplicitPrelude #-}",-         "module A where",-         "import Control.Monad hiding (join)",-         "f = joi"-       ]-       (Position 3 6)-       [],-    testGroup "ordering"-      [completionTest "qualified has priority"-        ["module A where"-        ,"import qualified Data.ByteString as BS"-        ,"f = BS.read"-        ]-        (Position 2 10)-        [("readFile", CompletionItemKind_Function, "readFile", True, True, Nothing)]-        ],-      -- we need this test to make sure the ghcide completions module does not return completions for language pragmas. this functionality is turned on in hls-     completionTest-      "do not show pragma completions"-      [ "{-# LANGUAGE  ",-        "{module A where}",-        "main = return ()"-      ]-      (Position 0 13)-      []-  ]-  where-    brokenForWinGhc = knownBrokenFor (BrokenSpecific Windows [GHC90, GHC92, GHC94, GHC96, GHC98]) "Windows has strange things in scope for some reason"--otherCompletionTests :: [TestTree]-otherCompletionTests = [-    completionTest-      "keyword"-      ["module A where", "f = newty"]-      (Position 1 9)-      [("newtype", CompletionItemKind_Keyword, "", False, False, Nothing)],-    completionTest-      "type context"-      [ "{-# OPTIONS_GHC -Wunused-binds #-}",-        "module A () where",-        "f = f",-        "g :: Intege"-      ]-      -- At this point the module parses but does not typecheck.-      -- This should be sufficient to detect that we are in a-      -- type context and only show the completion to the type.-      (Position 3 11)-      [("Integer", CompletionItemKind_Struct, "Integer", True, True, Nothing)],--    testSession "duplicate record fields" $ do-      void $-        createDoc "B.hs" "haskell" $-          T.unlines-            [ "{-# LANGUAGE DuplicateRecordFields #-}",-              "module B where",-              "newtype Foo = Foo { member :: () }",-              "newtype Bar = Bar { member :: () }"-            ]-      docA <--        createDoc "A.hs" "haskell" $-          T.unlines-            [ "module A where",-              "import B",-              "memb"-            ]-      _ <- waitForDiagnostics-      compls <- getCompletions docA $ Position 2 4-      let compls' = [txt | CompletionItem {_insertText = Just txt, ..} <- compls, _label == "member"]-      liftIO $ take 1 compls' @?= ["member"],--    testSessionWait "maxCompletions" $ do-        doc <- createDoc "A.hs" "haskell" $ T.unlines-            [ "{-# OPTIONS_GHC -Wunused-binds #-}",-                "module A () where",-                "a = Prelude."-            ]-        _ <- waitForDiagnostics-        compls <- getCompletions  doc (Position 3 13)-        liftIO $ length compls @?= maxCompletions def-  ]--packageCompletionTests :: [TestTree]-packageCompletionTests =-  [ testSession' "fromList" $ \dir -> do-        liftIO $ writeFile (dir </> "hie.yaml")-            "cradle: {direct: {arguments: [-hide-all-packages, -package, base, A]}}"-        doc <- createDoc "A.hs" "haskell" $ T.unlines-            [ "{-# OPTIONS_GHC -Wunused-binds #-}",-                "module A () where",-                "a = fromList"-            ]-        _ <- waitForDiagnostics-        compls <- getCompletions doc (Position 2 12)-        let compls' =-              [T.drop 1 $ T.dropEnd 3 d-              | CompletionItem {_documentation = Just (InR (MarkupContent MarkupKind_Markdown d)), _label}-                <- compls-              , _label == "fromList"-              ]-        liftIO $ take 3 (sort compls') @?=-          map ("Defined in "<>) (-              [ "'Data.List.NonEmpty"-              , "'GHC.Exts"-              ] ++ if ghcVersion >= GHC94 then [ "'GHC.IsList" ] else [])--  , testSessionWait "Map" $ do-        doc <- createDoc "A.hs" "haskell" $ T.unlines-            [ "{-# OPTIONS_GHC -Wunused-binds #-}",-                "module A () where",-                "a :: Map"-            ]-        _ <- waitForDiagnostics-        compls <- getCompletions doc (Position 2 7)-        let compls' =-              [T.drop 1 $ T.dropEnd 3 d-              | CompletionItem {_documentation = Just (InR (MarkupContent MarkupKind_Markdown d)), _label}-                <- compls-              , _label == "Map"-              ]-        liftIO $ take 3 (sort compls') @?=-          map ("Defined in "<>)-              [ "'Data.Map"-              , "'Data.Map.Lazy"-              , "'Data.Map.Strict"-              ]-  , testSessionWait "no duplicates" $ do-        doc <- createDoc "A.hs" "haskell" $ T.unlines-            [ "{-# OPTIONS_GHC -Wunused-binds #-}",-                "module A () where",-                "import GHC.Exts(fromList)",-                "a = fromList"-            ]-        _ <- waitForDiagnostics-        compls <- getCompletions doc (Position 3 13)-        let duplicate =-              filter-                (\case-                  CompletionItem-                    { _insertText = Just "fromList"-                    , _documentation =-                      Just (InR (MarkupContent MarkupKind_Markdown d))-                    } ->-                    "GHC.Exts" `T.isInfixOf` d-                  _ -> False-                ) compls-        liftIO $ length duplicate @?= 1--  , testSessionWait "non-local before global" $ do-    -- non local completions are more specific-        doc <- createDoc "A.hs" "haskell" $ T.unlines-            [ "{-# OPTIONS_GHC -Wunused-binds #-}",-                "module A () where",-                "import GHC.Exts(fromList)",-                "a = fromList"-            ]-        _ <- waitForDiagnostics-        compls <- getCompletions doc (Position 3 13)-        let compls' =-              [_insertText-              | CompletionItem {_label, _insertText} <- compls-              , _label == "fromList"-              ]-        liftIO $ take 3 compls' @?=-          map Just ["fromList"]-  ]--projectCompletionTests :: [TestTree]-projectCompletionTests =-    [ testSession' "from hiedb" $ \dir-> do-        liftIO $ writeFile (dir </> "hie.yaml")-            "cradle: {direct: {arguments: [\"-Wmissing-signatures\", \"A\", \"B\"]}}"-        _ <- createDoc "A.hs" "haskell" $ T.unlines-            [  "module A (anidentifier) where",-               "anidentifier = ()"-            ]-        _ <- waitForDiagnostics-        -- Note that B does not import A-        doc <- createDoc "B.hs" "haskell" $ T.unlines-            [ "module B where",-              "b = anidenti"-            ]-        compls <- getCompletions doc (Position 1 10)-        let compls' =-              [T.drop 1 $ T.dropEnd 3 d-              | CompletionItem {_documentation = Just (InR (MarkupContent MarkupKind_Markdown d)), _label}-                <- compls-              , _label == "anidentifier"-              ]-        liftIO $ compls' @?= ["Defined in 'A"],-      testSession' "auto complete project imports" $ \dir-> do-        liftIO $ writeFile (dir </> "hie.yaml")-            "cradle: {direct: {arguments: [\"-Wmissing-signatures\", \"ALocalModule\", \"B\"]}}"-        _ <- createDoc "ALocalModule.hs" "haskell" $ T.unlines-            [  "module ALocalModule (anidentifier) where",-               "anidentifier = ()"-            ]-        _ <- waitForDiagnostics-        -- Note that B does not import A-        doc <- createDoc "B.hs" "haskell" $ T.unlines-            [ "module B where",-              "import ALocal"-            ]-        compls <- getCompletions doc (Position 1 13)-        let item = head $ filter ((== "ALocalModule") . (^. L.label)) compls-        liftIO $ do-          item ^. L.label @?= "ALocalModule",-      testSession' "auto complete functions from qualified imports without alias" $ \dir-> do-        liftIO $ writeFile (dir </> "hie.yaml")-            "cradle: {direct: {arguments: [\"-Wmissing-signatures\", \"A\", \"B\"]}}"-        _ <- createDoc "A.hs" "haskell" $ T.unlines-            [  "module A (anidentifier) where",-               "anidentifier = ()"-            ]-        _ <- waitForDiagnostics-        doc <- createDoc "B.hs" "haskell" $ T.unlines-            [ "module B where",-              "import qualified A",-              "A."-            ]-        compls <- getCompletions doc (Position 2 2)-        let item = head compls-        liftIO $ do-          item ^. L.label @?= "anidentifier",-      testSession' "auto complete functions from qualified imports with alias" $ \dir-> do-        liftIO $ writeFile (dir </> "hie.yaml")-            "cradle: {direct: {arguments: [\"-Wmissing-signatures\", \"A\", \"B\"]}}"-        _ <- createDoc "A.hs" "haskell" $ T.unlines-            [  "module A (anidentifier) where",-               "anidentifier = ()"-            ]-        _ <- waitForDiagnostics-        doc <- createDoc "B.hs" "haskell" $ T.unlines-            [ "module B where",-              "import qualified A as Alias",-              "foo = Alias."-            ]-        compls <- getCompletions doc (Position 2 12)-        let item = head compls-        liftIO $ do-          item ^. L.label @?= "anidentifier"-    ]--completionDocTests :: [TestTree]-completionDocTests =-  [ testSession "local define" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "foo = ()"-        , "bar = fo"-        ]-      let expected = "*Defined at line 2, column 1 in this module*\n"-      test doc (Position 2 8) "foo" Nothing [expected]-  , testSession "local empty doc" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "foo = ()"-        , "bar = fo"-        ]-      test doc (Position 2 8) "foo" Nothing ["*Defined at line 2, column 1 in this module*\n"]-  , testSession "local single line doc without newline" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "-- |docdoc"-        , "foo = ()"-        , "bar = fo"-        ]-      test doc (Position 3 8) "foo" Nothing ["*Defined at line 3, column 1 in this module*\n* * *\n\n\ndocdoc\n"]-  , testSession "local multi line doc with newline" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "-- | abcabc"-        , "--"-        , "foo = ()"-        , "bar = fo"-        ]-      test doc (Position 4 8) "foo" Nothing ["*Defined at line 4, column 1 in this module*\n* * *\n\n\nabcabc\n"]-  , testSession "local multi line doc without newline" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "-- |     abcabc"-        , "--"-        , "--def"-        , "foo = ()"-        , "bar = fo"-        ]-      test doc (Position 5 8) "foo" Nothing ["*Defined at line 5, column 1 in this module*\n* * *\n\n\nabcabc \n\ndef\n"]-  , testSession "extern empty doc" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "foo = od"-        ]-      let expected = "*Imported from 'Prelude'*\n"-      test doc (Position 1 8) "odd" (Just $ T.length expected) [expected]-  , brokenForMacGhc9 $ brokenForWinGhc90 $ testSession "extern single line doc without '\\n'" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "foo = no"-        ]-      let expected = "*Imported from 'Prelude'*\n* * *\n\n\nBoolean \"not\"\n"-      test doc (Position 1 8) "not" (Just $ T.length expected) [expected]-  , brokenForMacGhc9 $ brokenForWinGhc90 $ testSession "extern mulit line doc" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "foo = i"-        ]-      let expected = "*Imported from 'Prelude'*\n* * *\n\n\nIdentity function. \n```haskell\nid x = x\n```\n"-      test doc (Position 1 7) "id" (Just $ T.length expected) [expected]-  , testSession "extern defined doc" $ do-      doc <- createDoc "A.hs" "haskell" $ T.unlines-        [ "module A where"-        , "foo = i"-        ]-      let expected = "*Imported from 'Prelude'*\n"-      test doc (Position 1 7) "id" (Just $ T.length expected) [expected]-  ]-  where-    brokenForWinGhc90 = knownBrokenFor (BrokenSpecific Windows [GHC90]) "Extern doc doesn't support Windows for ghc9.2"-    -- https://gitlab.haskell.org/ghc/ghc/-/issues/20903-    brokenForMacGhc9 = knownBrokenFor (BrokenSpecific MacOS [GHC90, GHC92, GHC94, GHC96]) "Extern doc doesn't support MacOS for ghc9"-    test doc pos label mn expected = do-      _ <- waitForDiagnostics-      compls <- getCompletions doc pos-      rcompls <- forM compls $ \item -> do-            if isJust (item ^. L.data_)-            then do-                rsp <- request SMethod_CompletionItemResolve item-                case rsp ^. L.result of-                    Left err -> liftIO $ assertFailure ("completionItem/resolve failed with: " <> show err)-                    Right x -> pure x-            else pure item-      let compls' = [-            -- We ignore doc uris since it points to the local path which determined by specific machines-            case mn of-                Nothing -> txt-                Just n  -> T.take n txt-            | CompletionItem {_documentation = Just (InR (MarkupContent MarkupKind_Markdown txt)), ..} <- rcompls-            , _label == label-            ]-      liftIO $ compls' @?= expected
− test/exe/CradleTests.hs
@@ -1,241 +0,0 @@--{-# LANGUAGE GADTs            #-}-{-# LANGUAGE OverloadedLabels #-}--module CradleTests (tests) where--import           Control.Applicative.Combinators-import           Control.Monad.IO.Class          (liftIO)-import           Data.Row-import qualified Data.Text                       as T-import           Development.IDE.GHC.Compat      (GhcVersion (..), ghcVersion)-import           Development.IDE.GHC.Util-import           Development.IDE.Test            (expectDiagnostics,-                                                  expectDiagnosticsWithTags,-                                                  expectNoMoreDiagnostics,-                                                  isReferenceReady,-                                                  waitForAction)-import           Development.IDE.Types.Location-import qualified Language.LSP.Protocol.Lens      as L-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           System.FilePath-import           System.IO.Extra                 hiding (withTempDir)--- import Test.QuickCheck.Instances ()-import           Control.Lens                    ((^.))-import           Development.IDE.Plugin.Test     (WaitForIdeRuleResult (..))-import           GHC.TypeLits                    (symbolVal)-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils---tests :: TestTree-tests = testGroup "cradle"-    [testGroup "dependencies" [sessionDepsArePickedUp]-    ,testGroup "ignore-fatal" [ignoreFatalWarning]-    ,testGroup "loading" [loadCradleOnlyonce, retryFailedCradle]-    ,testGroup "multi"   (multiTests "multi")-    ,ignoreFor (BrokenForGHC [GHC92]) "multiple units not supported on 9.2"-       $ testGroup "multi-unit" (multiTests "multi-unit")-    ,testGroup "sub-directory"   [simpleSubDirectoryTest]-    ,ignoreFor (BrokenForGHC [GHC92]) "multiple units not supported on 9.2"-      $ testGroup "multi-unit-rexport" [multiRexportTest]-    ]--loadCradleOnlyonce :: TestTree-loadCradleOnlyonce = testGroup "load cradle only once"-    [ testSession' "implicit" implicit-    , testSession' "direct"   direct-    ]-    where-        direct dir = do-            liftIO $ writeFileUTF8 (dir </> "hie.yaml")-                "cradle: {direct: {arguments: []}}"-            test dir-        implicit dir = test dir-        test _dir = do-            doc <- createDoc "B.hs" "haskell" "module B where\nimport Data.Foo"-            msgs <- someTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics))-            liftIO $ length msgs @?= 1-            changeDoc doc [TextDocumentContentChangeEvent . InR . (.==) #text $ "module B where\nimport Data.Maybe"]-            msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics))-            liftIO $ length msgs @?= 0-            _ <- createDoc "A.hs" "haskell" "module A where\nimport LoadCradleBar"-            msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message SMethod_TextDocumentPublishDiagnostics))-            liftIO $ length msgs @?= 0--retryFailedCradle :: TestTree-retryFailedCradle = testSession' "retry failed" $ \dir -> do-  -- The false cradle always fails-  let hieContents = "cradle: {bios: {shell: \"false\"}}"-      hiePath = dir </> "hie.yaml"-  liftIO $ writeFile hiePath hieContents-  let aPath = dir </> "A.hs"-  doc <- createDoc aPath "haskell" "main = return ()"-  WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" doc-  liftIO $ "Test assumption failed: cradle should error out" `assertBool` not ideResultSuccess--  -- Fix the cradle and typecheck again-  let validCradle = "cradle: {bios: {shell: \"echo A.hs\"}}"-  liftIO $ writeFileUTF8 hiePath $ T.unpack validCradle-  sendNotification SMethod_WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $-         [FileEvent (filePathToUri $ dir </> "hie.yaml") FileChangeType_Changed ]--  WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" doc-  liftIO $ "No joy after fixing the cradle" `assertBool` ideResultSuccess---cradleLoadedMessage :: Session FromServerMessage-cradleLoadedMessage = satisfy $ \case-        FromServerMess (SMethod_CustomMethod p) (NotMess _) -> symbolVal p == cradleLoadedMethod-        _                                            -> False--cradleLoadedMethod :: String-cradleLoadedMethod = "ghcide/cradle/loaded"--ignoreFatalWarning :: TestTree-ignoreFatalWarning = testCase "ignore-fatal-warning" $ runWithExtraFiles "ignore-fatal" $ \dir -> do-    let srcPath = dir </> "IgnoreFatal.hs"-    src <- liftIO $ readFileUtf8 srcPath-    _ <- createDoc srcPath "haskell" src-    expectNoMoreDiagnostics 5--simpleSubDirectoryTest :: TestTree-simpleSubDirectoryTest =-  testCase "simple-subdirectory" $ runWithExtraFiles "cabal-exe" $ \dir -> do-    let mainPath = dir </> "a/src/Main.hs"-    mainSource <- liftIO $ readFileUtf8 mainPath-    _mdoc <- createDoc mainPath "haskell" mainSource-    expectDiagnosticsWithTags-      [("a/src/Main.hs", [(DiagnosticSeverity_Warning,(2,0), "Top-level binding", Nothing)]) -- So that we know P has been loaded-      ]-    expectNoMoreDiagnostics 0.5--multiTests :: FilePath -> [TestTree]-multiTests dir =-  [simpleMultiTest dir, simpleMultiTest2 dir, simpleMultiTest3 dir, simpleMultiDefTest dir]--multiTestName :: FilePath -> String -> String-multiTestName dir name = "simple-" ++ dir ++ "-" ++ name--simpleMultiTest :: FilePath -> TestTree-simpleMultiTest variant = testCase (multiTestName variant "test") $ withLongTimeout $ runWithExtraFiles variant $ \dir -> do-    let aPath = dir </> "a/A.hs"-        bPath = dir </> "b/B.hs"-    adoc <- openDoc aPath "haskell"-    bdoc <- openDoc bPath "haskell"-    WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" adoc-    liftIO $ assertBool "A should typecheck" ideResultSuccess-    WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" bdoc-    liftIO $ assertBool "B should typecheck" ideResultSuccess-    locs <- getDefinitions bdoc (Position 2 7)-    let fooL = mkL (adoc ^. L.uri) 2 0 2 3-    checkDefs locs (pure [fooL])-    expectNoMoreDiagnostics 0.5---- Like simpleMultiTest but open the files in the other order-simpleMultiTest2 :: FilePath -> TestTree-simpleMultiTest2 variant = testCase (multiTestName variant "test2") $ runWithExtraFiles variant $ \dir -> do-    let aPath = dir </> "a/A.hs"-        bPath = dir </> "b/B.hs"-    bdoc <- openDoc bPath "haskell"-    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" bdoc-    TextDocumentIdentifier auri <- openDoc aPath "haskell"-    skipManyTill anyMessage $ isReferenceReady aPath-    locs <- getDefinitions bdoc (Position 2 7)-    let fooL = mkL auri 2 0 2 3-    checkDefs locs (pure [fooL])-    expectNoMoreDiagnostics 0.5---- Now with 3 components-simpleMultiTest3 :: FilePath -> TestTree-simpleMultiTest3 variant =-  testCase (multiTestName variant "test3") $ runWithExtraFiles variant $ \dir -> do-    let aPath = dir </> "a/A.hs"-        bPath = dir </> "b/B.hs"-        cPath = dir </> "c/C.hs"-    bdoc <- openDoc bPath "haskell"-    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" bdoc-    TextDocumentIdentifier auri <- openDoc aPath "haskell"-    skipManyTill anyMessage $ isReferenceReady aPath-    cdoc <- openDoc cPath "haskell"-    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" cdoc-    locs <- getDefinitions cdoc (Position 2 7)-    let fooL = mkL auri 2 0 2 3-    checkDefs locs (pure [fooL])-    expectNoMoreDiagnostics 0.5---- Like simpleMultiTest but open the files in component 'a' in a separate session-simpleMultiDefTest :: FilePath -> TestTree-simpleMultiDefTest variant = testCase (multiTestName variant "def-test") $ runWithExtraFiles variant $ \dir -> do-    let aPath = dir </> "a/A.hs"-        bPath = dir </> "b/B.hs"-    adoc <- liftIO $ runInDir dir $ do-      aSource <- liftIO $ readFileUtf8 aPath-      adoc <- createDoc aPath "haskell" aSource-      skipManyTill anyMessage $ isReferenceReady aPath-      closeDoc adoc-      pure adoc-    bSource <- liftIO $ readFileUtf8 bPath-    bdoc <- createDoc bPath "haskell" bSource-    locs <- getDefinitions bdoc (Position 2 7)-    let fooL = mkL (adoc ^. L.uri) 2 0 2 3-    checkDefs locs (pure [fooL])-    expectNoMoreDiagnostics 0.5--multiRexportTest :: TestTree-multiRexportTest =-  testCase "multi-unit-reexport-test"  $ runWithExtraFiles "multi-unit-reexport" $ \dir -> do-    let cPath = dir </> "c/C.hs"-    cdoc <- openDoc cPath "haskell"-    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" cdoc-    locs <- getDefinitions cdoc (Position 3 7)-    let aPath = dir </> "a/A.hs"-    let fooL = mkL (filePathToUri aPath) 2 0 2 3-    checkDefs locs (pure [fooL])-    expectNoMoreDiagnostics 0.5--sessionDepsArePickedUp :: TestTree-sessionDepsArePickedUp = testSession'-  "session-deps-are-picked-up"-  $ \dir -> do-    liftIO $-      writeFileUTF8-        (dir </> "hie.yaml")-        "cradle: {direct: {arguments: []}}"-    -- Open without OverloadedStrings and expect an error.-    doc <- createDoc "Foo.hs" "haskell" fooContent-    expectDiagnostics $-        if ghcVersion >= GHC90-            -- String vs [Char] causes this change in error message-            then [("Foo.hs", [(DiagnosticSeverity_Error, (3, 6), "Couldn't match type")])]-            else [("Foo.hs", [(DiagnosticSeverity_Error, (3, 6), "Couldn't match expected type")])]-    -- Update hie.yaml to enable OverloadedStrings.-    liftIO $-      writeFileUTF8-        (dir </> "hie.yaml")-        "cradle: {direct: {arguments: [-XOverloadedStrings]}}"-    sendNotification SMethod_WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $-        [FileEvent (filePathToUri $ dir </> "hie.yaml") FileChangeType_Changed ]-    -- Send change event.-    let change =-          TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 4 0) (Position 4 0)-                                              .+ #rangeLength .== Nothing-                                              .+ #text .== "\n"-    changeDoc doc [change]-    -- Now no errors.-    expectDiagnostics [("Foo.hs", [])]-  where-    fooContent =-      T.unlines-        [ "module Foo where",-          "import Data.Text",-          "foo :: Text",-          "foo = \"hello\""-        ]
− test/exe/DependentFileTest.hs
@@ -1,62 +0,0 @@--{-# LANGUAGE GADTs            #-}-{-# LANGUAGE OverloadedLabels #-}--module DependentFileTest (tests) where--import           Control.Monad.IO.Class         (liftIO)-import           Data.Row-import qualified Data.Text                      as T-import           Development.IDE.GHC.Compat     (GhcVersion (..), ghcVersion)-import           Development.IDE.Test           (expectDiagnostics)-import           Development.IDE.Types.Location-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types    hiding-                                                (SemanticTokenAbsolute (..),-                                                 SemanticTokenRelative (..),-                                                 SemanticTokensEdit (..),-                                                 mkRange)-import           Language.LSP.Test-import           System.FilePath-import           Test.Tasty-import           TestUtils--tests :: TestTree-tests = testGroup "addDependentFile"-    [testGroup "file-changed" [testSession' "test" test]-    ]-    where-      test dir = do-        -- If the file contains B then no type error-        -- otherwise type error-        let depFilePath = dir </> "dep-file.txt"-        liftIO $ writeFile depFilePath "A"-        let fooContent = T.unlines-              [ "{-# LANGUAGE TemplateHaskell #-}"-              , "module Foo where"-              , "import Language.Haskell.TH.Syntax"-              , "foo :: Int"-              , "foo = 1 + $(do"-              , "               qAddDependentFile \"dep-file.txt\""-              , "               f <- qRunIO (readFile \"dep-file.txt\")"-              , "               if f == \"B\" then [| 1 |] else lift f)"-              ]-        let bazContent = T.unlines ["module Baz where", "import Foo ()"]-        _ <- createDoc "Foo.hs" "haskell" fooContent-        doc <- createDoc "Baz.hs" "haskell" bazContent-        expectDiagnostics $-            if ghcVersion >= GHC90-                -- String vs [Char] causes this change in error message-                then [("Foo.hs", [(DiagnosticSeverity_Error, if ghcVersion >= GHC92 then (4,11) else (4, 6), "Couldn't match type")])]-                else [("Foo.hs", [(DiagnosticSeverity_Error, (4, 6), "Couldn't match expected type")])]-        -- Now modify the dependent file-        liftIO $ writeFile depFilePath "B"-        sendNotification SMethod_WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $-            [FileEvent (filePathToUri "dep-file.txt") FileChangeType_Changed ]--        -- Modifying Baz will now trigger Foo to be rebuilt as well-        let change = TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 2 0) (Position 2 6)-                                                         .+ #rangeLength .== Nothing-                                                         .+ #text .== "f = ()"-        changeDoc doc [change]-        expectDiagnostics [("Foo.hs", [])]
− test/exe/DiagnosticTests.hs
@@ -1,593 +0,0 @@--{-# LANGUAGE GADTs            #-}-{-# LANGUAGE OverloadedLabels #-}--module DiagnosticTests (tests) where--import           Control.Applicative.Combinators-import qualified Control.Lens                    as Lens-import           Control.Monad-import           Control.Monad.IO.Class          (liftIO)-import           Data.List.Extra-import           Data.Row-import qualified Data.Text                       as T-import           Development.IDE.GHC.Compat      (GhcVersion (..), ghcVersion)-import           Development.IDE.GHC.Util-import           Development.IDE.Test            (diagnostic,-                                                  expectCurrentDiagnostics,-                                                  expectDiagnostics,-                                                  expectDiagnosticsWithTags,-                                                  expectNoMoreDiagnostics,-                                                  flushMessages, waitForAction)-import           Development.IDE.Types.Location-import qualified Language.LSP.Protocol.Lens      as L-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           System.Directory-import           System.FilePath-import           System.IO.Extra                 hiding (withTempDir)--import           Control.Lens                    ((^.))-import           Control.Monad.Extra             (whenJust)-import           Development.IDE.Plugin.Test     (WaitForIdeRuleResult (..))-import           System.Time.Extra-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = testGroup "diagnostics"-  [ testSessionWait "fix syntax error" $ do-      let content = T.unlines [ "module Testing wher" ]-      doc <- createDoc "Testing.hs" "haskell" content-      expectDiagnostics [("Testing.hs", [(DiagnosticSeverity_Error, (0, 15), "parse error")])]-      let change = TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 0 15) (Position 0 19)-                                                       .+ #rangeLength .== Nothing-                                                       .+ #text .== "where"-      changeDoc doc [change]-      expectDiagnostics [("Testing.hs", [])]-  , testSessionWait "introduce syntax error" $ do-      let content = T.unlines [ "module Testing where" ]-      doc <- createDoc "Testing.hs" "haskell" content-      void $ skipManyTill anyMessage (message SMethod_WindowWorkDoneProgressCreate)-      waitForProgressBegin-      let change = TextDocumentContentChangeEvent$ InL $ #range .== Range (Position 0 15) (Position 0 18)-                                                      .+ #rangeLength .== Nothing-                                                      .+ #text .== "wher"-      changeDoc doc [change]-      expectDiagnostics [("Testing.hs", [(DiagnosticSeverity_Error, (0, 15), "parse error")])]-  , testSessionWait "update syntax error" $ do-      let content = T.unlines [ "module Testing(missing) where" ]-      doc <- createDoc "Testing.hs" "haskell" content-      expectDiagnostics [("Testing.hs", [(DiagnosticSeverity_Error, (0, 15), "Not in scope: 'missing'")])]-      let change = TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 0 15) (Position 0 16)-                                                       .+ #rangeLength .== Nothing-                                                       .+ #text .== "l"-      changeDoc doc [change]-      expectDiagnostics [("Testing.hs", [(DiagnosticSeverity_Error, (0, 15), "Not in scope: 'lissing'")])]-  , testSessionWait "variable not in scope" $ do-      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"-            ]-      _ <- createDoc "Testing.hs" "haskell" content-      expectDiagnostics-        [ ( "Testing.hs"-          , [ (DiagnosticSeverity_Error, (2, 15), "Variable not in scope: ab")-            , (DiagnosticSeverity_Error, (4, 11), "Variable not in scope: cd")-            ]-          )-        ]-  , testSessionWait "type error" $ do-      let content = T.unlines-            [ "module Testing where"-            , "foo :: Int -> String -> Int"-            , "foo a b = a + b"-            ]-      _ <- createDoc "Testing.hs" "haskell" content-      expectDiagnostics-        [ ( "Testing.hs"-          , [(DiagnosticSeverity_Error, (2, 14), "Couldn't match type '[Char]' with 'Int'")]-          )-        ]-  , testSessionWait "typed hole" $ do-      let content = T.unlines-            [ "module Testing where"-            , "foo :: Int -> String"-            , "foo a = _ a"-            ]-      _ <- createDoc "Testing.hs" "haskell" content-      expectDiagnostics-        [ ( "Testing.hs"-          , [(DiagnosticSeverity_Error, (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", [(DiagnosticSeverity_Error, (2,4), aMessage)])-          , ("B.hs", [(DiagnosticSeverity_Error, (3,4), bMessage)])]-        deferralTest title binding msg = testSessionWait title $ do-          _ <- createDoc "A.hs" "haskell" $ sourceA binding-          _ <- createDoc "B.hs" "haskell"   sourceB-          expectDiagnostics $ expectedDs msg-    in-    [ deferralTest "type error"          "True"    "Couldn't match expected type"-    , deferralTest "typed hole"          "_"       "Found hole"-    , deferralTest "out of scope var"    "unbound" "Variable not in scope"-    ]--  , testSessionWait "remove required module" $ do-      let contentA = T.unlines [ "module ModuleA where" ]-      docA <- createDoc "ModuleA.hs" "haskell" contentA-      let contentB = T.unlines-            [ "module ModuleB where"-            , "import ModuleA"-            ]-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      let change = TextDocumentContentChangeEvent $ InL $ #range .== Range (Position 0 0) (Position 0 20)-                                                       .+ #rangeLength .== Nothing-                                                       .+ #text .== ""-      changeDoc docA [change]-      expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Error, (1, 0), "Could not find module")])]-  , testSessionWait "add missing module" $ do-      let contentB = T.unlines-            [ "module ModuleB where"-            , "import ModuleA ()"-            ]-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Error, (1, 7), "Could not find module")])]-      let contentA = T.unlines [ "module ModuleA where" ]-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      expectDiagnostics [("ModuleB.hs", [])]-  , testCase "add missing module (non workspace)" $-    -- By default lsp-test sends FileWatched notifications for all files, which we don't want-    -- as non workspace modules will not be watched by the LSP server.-    -- To work around this, we tell lsp-test that our client doesn't have the-    -- FileWatched capability, which is enough to disable the notifications-    withTempDir $ \tmpDir -> runInDir'' lspTestCapsNoFileWatches tmpDir "." "." [] $ do-      let contentB = T.unlines-            [ "module ModuleB where"-            , "import ModuleA ()"-            ]-      _ <- createDoc (tmpDir </> "ModuleB.hs") "haskell" contentB-      expectDiagnostics [(tmpDir </> "ModuleB.hs", [(DiagnosticSeverity_Error, (1, 7), "Could not find module")])]-      let contentA = T.unlines [ "module ModuleA where" ]-      _ <- createDoc (tmpDir </> "ModuleA.hs") "haskell" contentA-      expectDiagnostics [(tmpDir </> "ModuleB.hs", [])]-  , testSessionWait "cyclic module dependency" $ do-      let contentA = T.unlines-            [ "module ModuleA where"-            , "import ModuleB"-            ]-      let contentB = T.unlines-            [ "module ModuleB where"-            , "import ModuleA"-            ]-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      expectDiagnostics-        [ ( "ModuleA.hs"-          , [(DiagnosticSeverity_Error, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")]-          )-        , ( "ModuleB.hs"-          , [(DiagnosticSeverity_Error, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")]-          )-        ]-  , testSession' "deeply nested cyclic module dependency" $ \path -> do-      let contentA = unlines-            [ "module ModuleA where" , "import ModuleB" ]-      let contentB = unlines-            [ "module ModuleB where" , "import ModuleA" ]-      let contentC = unlines-            [ "module ModuleC where" , "import ModuleB" ]-      let contentD = T.unlines-            [ "module ModuleD where" , "import ModuleC" ]-          cradle =-            "cradle: {direct: {arguments: [ModuleA, ModuleB, ModuleC, ModuleD]}}"-      liftIO $ writeFile (path </> "ModuleA.hs") contentA-      liftIO $ writeFile (path </> "ModuleB.hs") contentB-      liftIO $ writeFile (path </> "ModuleC.hs") contentC-      liftIO $ writeFile (path </> "hie.yaml") cradle-      _ <- createDoc "ModuleD.hs" "haskell" contentD-      expectDiagnostics-        [ ( "ModuleB.hs"-          , [(DiagnosticSeverity_Error, (1, 7), "Cyclic module dependency between ModuleA, ModuleB")]-          )-        ]-  , testSessionWait "cyclic module dependency with hs-boot" $ do-      let contentA = T.unlines-            [ "module ModuleA where"-            , "import {-# SOURCE #-} ModuleB"-            ]-      let contentB = T.unlines-            [ "{-# OPTIONS -Wmissing-signatures#-}"-            , "module ModuleB where"-            , "import ModuleA"-            -- introduce an artificial diagnostic-            , "foo = ()"-            ]-      let contentBboot = T.unlines-            [ "module ModuleB where"-            ]-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      _ <- createDoc "ModuleB.hs-boot" "haskell" contentBboot-      expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Warning, (3,0), "Top-level binding")])]-  , testSession' "bidirectional module dependency with hs-boot" $ \path -> do-      let cradle = unlines-            [ "cradle:"-            , "  direct: {arguments: [ModuleA, ModuleB]}"-            ]-      let contentA = T.unlines-            [ "module ModuleA where"-            , "import {-# SOURCE #-} ModuleB"-            ]-      let contentB = T.unlines-            [ "{-# OPTIONS -Wmissing-signatures#-}"-            , "module ModuleB where"-            , "import {-# SOURCE #-} ModuleA"-            -- introduce an artificial diagnostic-            , "foo = ()"-            ]-      let contentBboot = T.unlines-            [ "module ModuleB where"-            ]-      let contentAboot = T.unlines-            [ "module ModuleA where"-            ]-      liftIO $ writeFile (path </> "hie.yaml") cradle-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      _ <- createDoc "ModuleA.hs-boot" "haskell" contentAboot-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      _ <- createDoc "ModuleB.hs-boot" "haskell" contentBboot-      expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Warning, (3,0), "Top-level binding")])]-  , testSessionWait "correct reference used with hs-boot" $ do-      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-            [ "{-# OPTIONS -Wmissing-signatures #-}"-            , "module ModuleC where"-            , "import ModuleA"-            -- this reference will fail if it gets incorrectly-            -- resolved to the hs-boot file-            , "y = x"-            ]-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      _ <- createDoc "ModuleA.hs-boot" "haskell" contentAboot-      _ <- createDoc "ModuleC.hs" "haskell" contentC-      expectDiagnostics [("ModuleC.hs", [(DiagnosticSeverity_Warning, (3,0), "Top-level binding")])]-  , testSessionWait "redundant import" $ do-      let contentA = T.unlines ["module ModuleA where"]-      let contentB = T.unlines-            [ "{-# OPTIONS_GHC -Wunused-imports #-}"-            , "module ModuleB where"-            , "import ModuleA"-            ]-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      expectDiagnosticsWithTags-        [ ( "ModuleB.hs"-          , [(DiagnosticSeverity_Warning, (2, 0), "The import of 'ModuleA' is redundant", Just DiagnosticTag_Unnecessary)]-          )-        ]-  , testSessionWait "redundant import even without warning" $ do-      let contentA = T.unlines ["module ModuleA where"]-      let contentB = T.unlines-            [ "{-# OPTIONS_GHC -Wno-unused-imports -Wmissing-signatures #-}"-            , "module ModuleB where"-            , "import ModuleA"-            -- introduce an artificial warning for testing purposes-            , "foo = ()"-            ]-      _ <- createDoc "ModuleA.hs" "haskell" contentA-      _ <- createDoc "ModuleB.hs" "haskell" contentB-      expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Warning, (3,0), "Top-level binding")])]-  , testSessionWait "package imports" $ do-      let thisDataListContent = T.unlines-            [ "module Data.List where"-            , "x :: Integer"-            , "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"-            , "main = pure ()"-            ]-      _ <- createDoc "Data/List.hs" "haskell" thisDataListContent-      _ <- createDoc "Main.hs" "haskell" mainContent-      expectDiagnostics-        [ ( "Main.hs"-          , [(DiagnosticSeverity_Error, (6, 9),-                if ghcVersion >= GHC96 then-                  "Variable not in scope: ThisList.map"-                else if ghcVersion >= GHC94 then-                  "Variable not in scope: map" -- See https://gitlab.haskell.org/ghc/ghc/-/issues/22130-                else-                  "Not in scope: \8216ThisList.map\8217")-            ,(DiagnosticSeverity_Error, (7, 9),-                if ghcVersion >= GHC96 then-                  "Variable not in scope: BaseList.x"-                else if ghcVersion >= GHC94 then-                  "Variable not in scope: x" -- See https://gitlab.haskell.org/ghc/ghc/-/issues/22130-                else-                  "Not in scope: \8216BaseList.x\8217")-            ]-          )-        ]-  , testSessionWait "unqualified warnings" $ do-      let fooContent = T.unlines-            [ "{-# OPTIONS_GHC -Wredundant-constraints #-}"-            , "module Foo where"-            , "foo :: Ord a => a -> Int"-            , "foo _a = 1"-            ]-      _ <- createDoc "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-      -- something like 'GHC.Classes.Ord'. The choice of redundant-constraints to-      -- test this is fairly arbitrary.-          , [(DiagnosticSeverity_Warning, (2, if ghcVersion >= GHC94 then 7 else 0), "Redundant constraint: Ord a")-            ]-          )-        ]-    , 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 (lower 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 (lower drive) suffix)-          let itemA = TextDocumentItem uriA "haskell" 0 aContent-          let a = TextDocumentIdentifier uriA-          sendNotification SMethod_TextDocumentDidOpen (DidOpenTextDocumentParams itemA)-          TNotificationMessage{_params = PublishDiagnosticsParams fileUri _ diags} <- skipManyTill anyMessage diagnostic-          -- 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 :: T.Text = (head diags) ^. L.message-          liftIO $ unless ("redundant" `T.isInfixOf` msg) $-              assertFailure ("Expected redundant import but got " <> T.unpack msg)-          closeDoc a-  , testSessionWait "haddock parse error" $ do-      let fooContent = T.unlines-            [ "module Foo where"-            , "foo :: Int"-            , "foo = 1 {-|-}"-            ]-      _ <- createDoc "Foo.hs" "haskell" fooContent-      if ghcVersion >= GHC90 then-          -- Haddock parse errors are ignored on ghc-9.0-            pure ()-      else-        expectDiagnostics-            [ ( "Foo.hs"-              , [(DiagnosticSeverity_Warning, (2, 8), "Haddock parse error on input")]-              )-            ]-  , testSessionWait "strip file path" $ do-      let-          name = "Testing"-          content = T.unlines-            [ "module " <> name <> " where"-            , "value :: Maybe ()"-            , "value = [()]"-            ]-      _ <- createDoc (T.unpack name <> ".hs") "haskell" content-      notification <- skipManyTill anyMessage diagnostic-      let-          offenders =-            L.params .-            L.diagnostics .-            Lens.folded .-            L.message .-            Lens.filtered (T.isInfixOf ("/" <> name <> ".hs:"))-          failure msg = liftIO $ assertFailure $ "Expected file path to be stripped but got " <> T.unpack msg-      Lens.mapMOf_ offenders failure notification-  , testSession' "-Werror in cradle is ignored" $ \sessionDir -> do-      liftIO $ writeFile (sessionDir </> "hie.yaml")-        "cradle: {direct: {arguments: [\"-Wall\", \"-Werror\"]}}"-      let fooContent = T.unlines-            [ "module Foo where"-            , "foo = ()"-            ]-      _ <- createDoc "Foo.hs" "haskell" fooContent-      expectDiagnostics-        [ ( "Foo.hs"-          , [(DiagnosticSeverity_Warning, (1, 0), "Top-level binding with no type signature:")-            ]-          )-        ]-  , testSessionWait "-Werror in pragma is ignored" $ do-      let fooContent = T.unlines-            [ "{-# OPTIONS_GHC -Wall -Werror #-}"-            , "module Foo() where"-            , "foo :: Int"-            , "foo = 1"-            ]-      _ <- createDoc "Foo.hs" "haskell" fooContent-      expectDiagnostics-        [ ( "Foo.hs"-          , [(DiagnosticSeverity_Warning, (3, 0), "Defined but not used:")-            ]-          )-        ]-  , testCase "typecheck-all-parents-of-interest" $ runWithExtraFiles "recomp" $ \dir -> do-    let bPath = dir </> "B.hs"-        pPath = dir </> "P.hs"-        aPath = dir </> "A.hs"--    bSource <- liftIO $ readFileUtf8 bPath -- y :: Int-    pSource <- liftIO $ readFileUtf8 pPath -- bar = x :: Int-    aSource <- liftIO $ readFileUtf8 aPath -- x = y :: Int--    bdoc <- createDoc bPath "haskell" bSource-    _pdoc <- createDoc pPath "haskell" pSource-    expectDiagnostics-      [("P.hs", [(DiagnosticSeverity_Warning,(4,0), "Top-level binding")])] -- So that we know P has been loaded--    -- Change y from Int to B which introduces a type error in A (imported from P)-    changeDoc bdoc [TextDocumentContentChangeEvent . InR . (.==) #text $-                    T.unlines ["module B where", "y :: Bool", "y = undefined"]]-    expectDiagnostics-      [("A.hs", [(DiagnosticSeverity_Error, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])-      ]--    -- Open A and edit to fix the type error-    adoc <- createDoc aPath "haskell" aSource-    changeDoc adoc [TextDocumentContentChangeEvent . InR . (.==) #text $-                    T.unlines ["module A where", "import B", "x :: Bool", "x = y"]]--    expectDiagnostics-      [ ( "P.hs",-          [ (DiagnosticSeverity_Error, (4, 6), "Couldn't match expected type 'Int' with actual type 'Bool'"),-            (DiagnosticSeverity_Warning, (4, 0), "Top-level binding")-          ]-        ),-        ("A.hs", [])-      ]-    expectNoMoreDiagnostics 1--  , testSessionWait "deduplicate missing module diagnostics" $  do-      let fooContent = T.unlines [ "module Foo() where" , "import MissingModule" ]-      doc <- createDoc "Foo.hs" "haskell" fooContent-      expectDiagnostics [("Foo.hs", [(DiagnosticSeverity_Error, (1,7), "Could not find module 'MissingModule'")])]--      changeDoc doc [TextDocumentContentChangeEvent  . InR . (.==) #text $ "module Foo() where" ]-      expectDiagnostics []--      changeDoc doc [TextDocumentContentChangeEvent  . InR . (.==) #text $ T.unlines-            [ "module Foo() where" , "import MissingModule" ] ]-      expectDiagnostics [("Foo.hs", [(DiagnosticSeverity_Error, (1,7), "Could not find module 'MissingModule'")])]--  , testGroup "Cancellation"-    [ cancellationTestGroup "edit header" editHeader yesSession noParse  noTc-    , cancellationTestGroup "edit import" editImport noSession  yesParse noTc-    , cancellationTestGroup "edit body"   editBody   yesSession yesParse yesTc-    ]-  ]-  where-      editPair x y = let p = Position x y ; p' = Position x (y+2) in-        (TextDocumentContentChangeEvent $ InL $ #range .== Range p p-                                             .+ #rangeLength .== Nothing-                                             .+ #text .== "fd"-        ,TextDocumentContentChangeEvent $ InL $ #range .== Range p p'-                                             .+ #rangeLength .== Nothing-                                             .+ #text .== "")-      editHeader = editPair 0 0-      editImport = editPair 2 10-      editBody   = editPair 3 10--      noParse = False-      yesParse = True--      noSession = False-      yesSession = True--      noTc = False-      yesTc = True--cancellationTestGroup :: TestName -> (TextDocumentContentChangeEvent, TextDocumentContentChangeEvent) -> Bool -> Bool -> Bool -> TestTree-cancellationTestGroup name edits sessionDepsOutcome parseOutcome tcOutcome = testGroup name-    [ cancellationTemplate edits Nothing-    , cancellationTemplate edits $ Just ("GetFileContents", True)-    , cancellationTemplate edits $ Just ("GhcSession", True)-      -- the outcome for GetModSummary is always True because parseModuleHeader never fails (!)-    , cancellationTemplate edits $ Just ("GetModSummary", True)-    , cancellationTemplate edits $ Just ("GetModSummaryWithoutTimestamps", True)-      -- getLocatedImports never fails-    , cancellationTemplate edits $ Just ("GetLocatedImports", True)-    , cancellationTemplate edits $ Just ("GhcSessionDeps", sessionDepsOutcome)-    , cancellationTemplate edits $ Just ("GetParsedModule", parseOutcome)-    , cancellationTemplate edits $ Just ("TypeCheck", tcOutcome)-    , cancellationTemplate edits $ Just ("GetHieAst", tcOutcome)-    ]--cancellationTemplate :: (TextDocumentContentChangeEvent, TextDocumentContentChangeEvent) -> Maybe (String, Bool) -> TestTree-cancellationTemplate (edit, undoEdit) mbKey = testCase (maybe "-" fst mbKey) $ runTestNoKick $ do-      doc <- createDoc "Foo.hs" "haskell" $ T.unlines-            [ "{-# OPTIONS_GHC -Wall #-}"-            , "module Foo where"-            , "import Data.List()"-            , "f0 x = (x,x)"-            ]--      -- for the example above we expect one warning-      let missingSigDiags = [(DiagnosticSeverity_Warning, (3, 0), "Top-level binding") ]-      typeCheck doc >> expectCurrentDiagnostics doc missingSigDiags--      -- Now we edit the document and wait for the given key (if any)-      changeDoc doc [edit]-      whenJust mbKey $ \(key, expectedResult) -> do-        WaitForIdeRuleResult{ideResultSuccess} <- waitForAction key doc-        liftIO $ ideResultSuccess @?= expectedResult--      -- The 2nd edit cancels the active session and unbreaks the file-      -- wait for typecheck and check that the current diagnostics are accurate-      changeDoc doc [undoEdit]-      typeCheck doc >> expectCurrentDiagnostics doc missingSigDiags--      expectNoMoreDiagnostics 0.5-    where-        -- similar to run except it disables kick-        runTestNoKick s = withTempDir $ \dir -> runInDir' dir "." "." ["--test-no-kick"] s--        typeCheck doc = do-            WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" doc-            liftIO $ assertBool "The file should typecheck" ideResultSuccess-            -- wait for the debouncer to publish diagnostics if the rule runs-            liftIO $ sleep 0.2-            -- flush messages to ensure current diagnostics state is updated-            flushMessages
− test/exe/ExceptionTests.hs
@@ -1,156 +0,0 @@--module ExceptionTests (tests) where--import           Control.Exception                 (ArithException (DivideByZero),-                                                    throwIO)-import           Control.Lens-import           Control.Monad.Error.Class         (MonadError (throwError))-import           Control.Monad.IO.Class            (liftIO)-import qualified Data.Aeson                        as A-import           Data.Text                         as T-import           Development.IDE.Core.Shake        (IdeState (..))-import qualified Development.IDE.LSP.Notifications as Notifications-import qualified Development.IDE.Main              as IDE-import           Development.IDE.Plugin.HLS        (toResponseError)-import           Development.IDE.Plugin.Test       as Test-import           Development.IDE.Types.Options-import           GHC.Base                          (coerce)-import           Ide.Logger                        (Logger, Recorder,-                                                    WithPriority, cmapWithPrio)-import           Ide.Plugin.Error-import           Ide.Plugin.HandleRequestTypes     (RejectionReason (DisabledGlobally))-import           Ide.PluginUtils                   (idePluginsToPluginDesc,-                                                    pluginDescToIdePlugins)-import           Ide.Types-import qualified Language.LSP.Protocol.Lens        as L-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types       hiding-                                                   (SemanticTokenAbsolute (..),-                                                    SemanticTokenRelative (..),-                                                    SemanticTokensEdit (..),-                                                    mkRange)-import           Language.LSP.Test-import           LogType                           (Log (..))-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: Recorder (WithPriority Log) -> Logger -> TestTree-tests recorder logger = do-  testGroup "Exceptions and PluginError" [-    testGroup "Testing that IO Exceptions are caught in..."-      [ testCase "PluginHandlers" $ do-          let pluginId = "plugin-handler-exception"-              plugins = pluginDescToIdePlugins $-                  [ (defaultPluginDescriptor pluginId "")-                      { pluginHandlers = mconcat-                          [ mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do-                              _ <- liftIO $ throwIO DivideByZero-                              pure (InL [])-                          ]-                      }]-          testIde recorder (testingLite recorder logger plugins) $ do-              doc <- createDoc "A.hs" "haskell" "module A where"-              waitForProgressDone-              (view L.result -> lens) <- request SMethod_TextDocumentCodeLens (CodeLensParams Nothing Nothing doc)-              case lens of-                Left (ResponseError {_code = InR ErrorCodes_InternalError, _message}) ->-                  liftIO $ assertBool "We caught an error, but it wasn't ours!"-                          (T.isInfixOf "divide by zero" _message && T.isInfixOf (coerce pluginId) _message)-                _ -> liftIO $ assertFailure $ show lens--        , testCase "Commands" $ do-          let pluginId = "command-exception"-              commandId = CommandId "exception"-              plugins = pluginDescToIdePlugins $-                  [ (defaultPluginDescriptor pluginId "")-                      { pluginCommands =-                          [ PluginCommand commandId "Causes an exception" $ \_ _ (_::Int) -> do-                              _ <- liftIO $ throwIO DivideByZero-                              pure (InR Null)-                          ]-                      }]-          testIde recorder (testingLite recorder logger plugins) $ do-              _ <- createDoc "A.hs" "haskell" "module A where"-              waitForProgressDone-              let cmd = mkLspCommand (coerce pluginId) commandId "" (Just [A.toJSON (1::Int)])-                  execParams = ExecuteCommandParams Nothing (cmd ^. L.command) (cmd ^. L.arguments)-              (view L.result -> res) <- request SMethod_WorkspaceExecuteCommand execParams-              case res of-                Left (ResponseError {_code = InR ErrorCodes_InternalError, _message}) ->-                  liftIO $ assertBool "We caught an error, but it wasn't ours!"-                          (T.isInfixOf "divide by zero" _message && T.isInfixOf (coerce pluginId) _message)-                _ -> liftIO $ assertFailure $ show res--        , testCase "Notification Handlers" $ do-          let pluginId = "notification-exception"-              plugins = pluginDescToIdePlugins $-                  [ (defaultPluginDescriptor pluginId "")-                      { pluginNotificationHandlers = mconcat-                          [  mkPluginNotificationHandler SMethod_TextDocumentDidOpen $ \_ _ _ _ ->-                              liftIO $ throwIO DivideByZero-                          ]-                        , pluginHandlers = mconcat-                          [ mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do-                              pure (InL [])-                          ]-                      }]-          testIde recorder (testingLite recorder logger plugins) $ do-              doc <- createDoc "A.hs" "haskell" "module A where"-              waitForProgressDone-              (view L.result -> lens) <- request SMethod_TextDocumentCodeLens (CodeLensParams Nothing Nothing doc)-              case lens of-                Right (InL []) ->-                  -- We don't get error responses from notification handlers, so-                  -- we can only make sure that the server is still responding-                  pure ()-                _ -> liftIO $ assertFailure $ "We should have had an empty list" <> show lens]--   , testGroup "Testing PluginError order..."-      [ pluginOrderTestCase recorder logger  "InternalError over InvalidParams" (PluginInternalError "error test") (PluginInvalidParams "error test")-      , pluginOrderTestCase recorder logger  "InvalidParams over InvalidUserState" (PluginInvalidParams "error test") (PluginInvalidUserState "error test")-      , pluginOrderTestCase recorder logger  "InvalidUserState over RequestRefused" (PluginInvalidUserState "error test") (PluginRequestRefused DisabledGlobally)-      ]-   ]--testingLite :: Recorder (WithPriority Log) -> Logger -> IdePlugins IdeState -> IDE.Arguments-testingLite recorder logger plugins =-  let-    arguments@IDE.Arguments{ argsIdeOptions } =-        IDE.defaultArguments (cmapWithPrio LogIDEMain recorder) logger plugins-    hlsPlugins = pluginDescToIdePlugins $-      idePluginsToPluginDesc plugins-      ++ [Notifications.descriptor (cmapWithPrio LogNotifications recorder) "ghcide-core"]-      ++ [Test.blockCommandDescriptor "block-command", Test.plugin]-    ideOptions config sessionLoader =-      let-        defOptions = argsIdeOptions config sessionLoader-      in-        defOptions{ optTesting = IdeTesting True }-  in-    arguments-      { IDE.argsHlsPlugins = hlsPlugins-      , IDE.argsIdeOptions = ideOptions-      }--pluginOrderTestCase :: Recorder (WithPriority Log) -> Logger -> TestName -> PluginError -> PluginError -> TestTree-pluginOrderTestCase recorder logger msg err1 err2 =-  testCase msg $ do-      let pluginId = "error-order-test"-          plugins = pluginDescToIdePlugins $-              [ (defaultPluginDescriptor pluginId "")-                  { pluginHandlers = mconcat-                      [ mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do-                          throwError err1-                        ,mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do-                          throwError err2-                      ]-                  }]-      testIde recorder (testingLite recorder logger plugins) $ do-          doc <- createDoc "A.hs" "haskell" "module A where"-          waitForProgressDone-          (view L.result -> lens) <- request SMethod_TextDocumentCodeLens (CodeLensParams Nothing Nothing doc)-          case lens of-            Left re | toResponseError (pluginId, err1) == re -> pure ()-                    | otherwise -> liftIO $ assertFailure "We caught an error, but it wasn't ours!"-            _ -> liftIO $ assertFailure $ show lens
− test/exe/FindDefinitionAndHoverTests.hs
@@ -1,249 +0,0 @@--{-# LANGUAGE MultiWayIf #-}--module FindDefinitionAndHoverTests (tests) where--import           Control.Monad-import           Control.Monad.IO.Class         (liftIO)-import           Data.Foldable-import           Data.Maybe-import qualified Data.Text                      as T-import           Development.IDE.GHC.Compat     (GhcVersion (..), ghcVersion)-import           Development.IDE.GHC.Util-import           Development.IDE.Test           (expectDiagnostics,-                                                 standardizeQuotes)-import           Development.IDE.Types.Location-import qualified Language.LSP.Protocol.Lens     as L-import           Language.LSP.Protocol.Types    hiding-                                                (SemanticTokenAbsolute (..),-                                                 SemanticTokenRelative (..),-                                                 SemanticTokensEdit (..),-                                                 mkRange)-import           Language.LSP.Test-import           System.FilePath-import           System.Info.Extra              (isWindows)--import           Control.Lens                   ((^.))-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils-import           Text.Regex.TDFA                ((=~))--tests :: TestTree-tests = let--  tst :: (TextDocumentIdentifier -> Position -> Session a, a -> Session [Expect] -> Session ()) -> Position -> String -> Session [Expect] -> String -> TestTree-  tst (get, check) pos sfp targetRange title = testSessionWithExtraFiles "hover" title $ \dir -> do--    -- Dirty the cache to check that definitions work even in the presence of iface files-    liftIO $ runInDir dir $ do-      let fooPath = dir </> "Foo.hs"-      fooSource <- liftIO $ readFileUtf8 fooPath-      fooDoc <- createDoc fooPath "haskell" fooSource-      _ <- getHover fooDoc $ Position 4 3-      closeDoc fooDoc--    doc <- openTestDataDoc (dir </> sfp)-    waitForProgressDone-    found <- get doc pos-    check found targetRange----  checkHover :: Maybe Hover -> Session [Expect] -> Session ()-  checkHover hover expectations = traverse_ check =<< expectations where--    check expected =-      case hover of-        Nothing -> unless (expected == ExpectNoHover) $ liftIO $ assertFailure "no hover found"-        Just Hover{_contents = (InL MarkupContent{_value = standardizeQuotes -> msg})-                  ,_range    = rangeInHover } ->-          case expected of-            ExpectRange  expectedRange -> checkHoverRange expectedRange rangeInHover msg-            ExpectHoverRange expectedRange -> checkHoverRange expectedRange rangeInHover msg-            ExpectHoverText snippets -> liftIO $ traverse_ (`assertFoundIn` msg) snippets-            ExpectHoverExcludeText snippets -> liftIO $ traverse_ (`assertNotFoundIn` msg) snippets-            ExpectHoverTextRegex re -> liftIO $ assertBool ("Regex not found in " <> T.unpack msg) (msg =~ re :: Bool)-            ExpectNoHover -> liftIO $ assertFailure $ "Expected no hover but got " <> show hover-            _ -> pure () -- all other expectations not relevant to hover-        _ -> liftIO $ assertFailure $ "test not expecting this kind of hover info" <> show hover--  extractLineColFromHoverMsg :: T.Text -> [T.Text]-  extractLineColFromHoverMsg = T.splitOn ":" . head . T.splitOn "*" . last . T.splitOn (sourceFileName <> ":")--  checkHoverRange :: Range -> Maybe Range -> T.Text -> Session ()-  checkHoverRange expectedRange rangeInHover msg =-    let-      lineCol = extractLineColFromHoverMsg msg-      -- looks like hovers use 1-based numbering while definitions use 0-based-      -- turns out that they are stored 1-based in RealSrcLoc by GHC itself.-      adjust Position{_line = l, _character = c} =-        Position{_line = l + 1, _character = c + 1}-    in-    case map (read . T.unpack) lineCol of-      [l,c] -> liftIO $ adjust (expectedRange ^. L.start) @=? Position l c-      _     -> liftIO $ assertFailure $-        "expected: " <> show ("[...]" <> sourceFileName <> ":<LINE>:<COL>**[...]", Just expectedRange) <>-        "\n but got: " <> show (msg, rangeInHover)--  assertFoundIn :: T.Text -> T.Text -> Assertion-  assertFoundIn part whole = assertBool-    (T.unpack $ "failed to find: `" <> part <> "` in hover message:\n" <> whole)-    (part `T.isInfixOf` whole)--  assertNotFoundIn :: T.Text -> T.Text -> Assertion-  assertNotFoundIn part whole = assertBool-    (T.unpack $ "found unexpected: `" <> part <> "` in hover message:\n" <> whole)-    (not . T.isInfixOf part $ whole)--  sourceFilePath = T.unpack sourceFileName-  sourceFileName = "GotoHover.hs"--  mkFindTests tests = testGroup "get"-    [ testGroup "definition" $ mapMaybe fst tests-    , testGroup "hover"      $ mapMaybe snd tests-    , checkFileCompiles sourceFilePath $-        expectDiagnostics-          [ ( "GotoHover.hs", [(DiagnosticSeverity_Error, (62, 7), "Found hole: _")])-          , ( "GotoHover.hs", [(DiagnosticSeverity_Error, (65, 8), "Found hole: _")])-          ]-    , testGroup "type-definition" typeDefinitionTests-    , testGroup "hover-record-dot-syntax" recordDotSyntaxTests ]--  typeDefinitionTests = [ tst (getTypeDefinitions, checkDefs) aaaL14 sourceFilePath (pure tcData) "Saturated data con"-                        , tst (getTypeDefinitions, checkDefs) aL20 sourceFilePath (pure [ExpectNoDefinitions]) "Polymorphic variable"]--  recordDotSyntaxTests-    | ghcVersion >= GHC92 =-        [ tst (getHover, checkHover) (Position 17 24) (T.unpack "RecordDotSyntax.hs") (pure [ExpectHoverText ["x :: MyRecord"]]) "hover over parent"-        , tst (getHover, checkHover) (Position 17 25) (T.unpack "RecordDotSyntax.hs") (pure [ExpectHoverText ["_ :: MyChild"]]) "hover over dot shows child"-        , tst (getHover, checkHover) (Position 17 26) (T.unpack "RecordDotSyntax.hs") (pure [ExpectHoverText ["_ :: MyChild"]]) "hover over child"-        ]-    | otherwise = []--  test runDef runHover look expect = testM runDef runHover look (return expect)--  testM runDef runHover look expect title =-    ( runDef   $ tst def   look sourceFilePath expect title-    , runHover $ tst hover look sourceFilePath expect title ) where-      def   = (getDefinitions, checkDefs)-      hover = (getHover      , checkHover)--  -- search locations            expectations on results-  fffL4  = fffR  ^. L.start;  fffR = mkRange 8  4    8  7 ; fff  = [ExpectRange fffR]-  fffL8  = Position 12  4  ;-  fffL14 = Position 18  7  ;-  aL20   = Position 19 15-  aaaL14 = Position 18 20  ;  aaa    = [mkR  11  0   11  3]-  dcL7   = Position 11 11  ;  tcDC   = [mkR   7 23    9 16]-  dcL12  = Position 16 11  ;-  xtcL5  = Position  9 11  ;  xtc    = [ExpectExternFail,   ExpectHoverText ["Int", "Defined in ", "GHC.Types", "ghc-prim"]]-  tcL6   = Position 10 11  ;  tcData = [mkR   7  0    9 16, ExpectHoverText ["TypeConstructor", "GotoHover.hs:8:1"]]-  vvL16  = Position 20 12  ;  vv     = [mkR  20  4   20  6]-  opL16  = Position 20 15  ;  op     = [mkR  21  2   21  4]-  opL18  = Position 22 22  ;  opp    = [mkR  22 13   22 17]-  aL18   = Position 22 20  ;  apmp   = [mkR  22 10   22 11]-  b'L19  = Position 23 13  ;  bp     = [mkR  23  6   23  7]-  xvL20  = Position 24  8  ;  xvMsg  = [ExpectExternFail,   ExpectHoverText ["pack", ":: String -> Text", "Data.Text", "text"]]-  clL23  = Position 27 11  ;  cls    = [mkR  25  0   26 20, ExpectHoverText ["MyClass", "GotoHover.hs:26:1"]]-  clL25  = Position 29  9-  eclL15 = Position 19  8  ;  ecls   = [ExpectExternFail, ExpectHoverText ["Num", "Defined in ", "GHC.Num", "base"]]-  dnbL29 = Position 33 18  ;  dnb    = [ExpectHoverText [":: ()"],   mkR  33 12   33 21]-  dnbL30 = Position 34 23-  lcbL33 = Position 37 26  ;  lcb    = [ExpectHoverText [":: Char"], mkR  37 26   37 27]-  lclL33 = Position 37 22-  mclL36 = Position 40  1  ;  mcl    = [mkR  40  0   40 14]-  mclL37 = Position 41  1-  spaceL37 = Position 41  24 ; space = [ExpectNoDefinitions, ExpectHoverText [":: Char"]]-  docL41 = Position 45  1  ;  doc    = [ExpectHoverText ["Recognizable docs: kpqz"]]-                           ;  constr = [ExpectHoverText ["Monad m"]]-  eitL40 = Position 44 28  ;  kindE  = [ExpectHoverText [if ghcVersion >= GHC92 then ":: Type -> Type -> Type\n" else ":: * -> * -> *\n"]]-  intL40 = Position 44 34  ;  kindI  = [ExpectHoverText [if ghcVersion >= GHC92 then ":: Type\n" else ":: *\n"]]-  tvrL40 = Position 44 37  ;  kindV  = [ExpectHoverText [":: * -> *\n"]]-  intL41 = Position 45 20  ;  litI   = [ExpectHoverText ["7518"]]-  chrL36 = Position 41 24  ;  litC   = [ExpectHoverText ["'f'"]]-  txtL8  = Position 12 14  ;  litT   = [ExpectHoverText ["\"dfgy\""]]-  lstL43 = Position 47 12  ;  litL   = [ExpectHoverText ["[8391 :: Int, 6268]"]]-  outL45 = Position 49  3  ;  outSig = [ExpectHoverText ["outer", "Bool"], mkR 50 0 50 5]-  innL48 = Position 52  5  ;  innSig = [ExpectHoverText ["inner", "Char"], mkR 49 2 49 7]-  holeL60 = Position 62 7  ;  hleInfo = [ExpectHoverText ["_ ::"]]-  holeL65 = Position 65 8  ;  hleInfo2 = [ExpectHoverText ["_ :: a -> Maybe a"]]-  cccL17 = Position 17 16  ;  docLink = [ExpectHoverTextRegex "\\*Defined in 'GHC.Types'\\* \\*\\(ghc-prim-[0-9.]+\\)\\*\n\n"]-  imported = Position 56 13 ; importedSig = getDocUri "Foo.hs" >>= \foo -> return [ExpectHoverText ["foo", "Foo", "Haddock"], mkL foo 5 0 5 3]-  reexported = Position 55 14 ; reexportedSig = getDocUri "Bar.hs" >>= \bar -> return [ExpectHoverText ["Bar", "Bar", "Haddock"], mkL bar 3 (if ghcVersion >= GHC94 then 5 else 0) 3 (if ghcVersion >= GHC94 then 8 else 14)]-  thLocL57 = Position 59 10 ; thLoc = [ExpectHoverText ["Identity"]]-  cmtL68 = Position 67  0  ;  lackOfdEq = [ExpectHoverExcludeText ["$dEq"]]-  import310 = Position 3 10; pkgTxt = [ExpectHoverText ["Data.Text\n\ntext-"]]-  in-  mkFindTests-  --      def    hover  look       expect-  [-    if ghcVersion >= GHC90 then-        -- It suggests either going to the constructor or to the field-        test  broken yes    fffL4      fff           "field in record definition"-    else-        test  yes    yes    fffL4      fff           "field in record definition"-  , test  yes    yes    fffL8      fff           "field in record construction    #1102"-  , test  yes    yes    fffL14     fff           "field name used as accessor"           -- https://github.com/haskell/ghcide/pull/120 in Calculate.hs-  , test  yes    yes    aaaL14     aaa           "top-level name"                        -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    dcL7       tcDC          "data constructor record         #1029"-  , test  yes    yes    dcL12      tcDC          "data constructor plain"                -- https://github.com/haskell/ghcide/pull/121-  , test  yes    yes    tcL6       tcData        "type constructor                #1028" -- https://github.com/haskell/ghcide/pull/147-  , test  broken yes    xtcL5      xtc           "type constructor external   #717,1028"-  , test  broken yes    xvL20      xvMsg         "value external package           #717" -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    vvL16      vv            "plain parameter"                       -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    aL18       apmp          "pattern match name"                    -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    opL16      op            "top-level operator               #713" -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    opL18      opp           "parameter operator"                    -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    b'L19      bp            "name in backticks"                     -- https://github.com/haskell/ghcide/pull/120-  , test  yes    yes    clL23      cls           "class in instance declaration   #1027"-  , test  yes    yes    clL25      cls           "class in signature              #1027" -- https://github.com/haskell/ghcide/pull/147-  , test  broken yes    eclL15     ecls          "external class in signature #717,1027"-  , test  yes    yes    dnbL29     dnb           "do-notation   bind              #1073"-  , test  yes    yes    dnbL30     dnb           "do-notation lookup"-  , test  yes    yes    lcbL33     lcb           "listcomp   bind                 #1073"-  , test  yes    yes    lclL33     lcb           "listcomp lookup"-  , test  yes    yes    mclL36     mcl           "top-level fn 1st clause"-  , test  yes    yes    mclL37     mcl           "top-level fn 2nd clause         #1030"-  , test  yes    yes    spaceL37   space         "top-level fn on space           #1002"-  , test  no     yes    docL41     doc           "documentation                   #1129"-  , test  no     yes    eitL40     kindE         "kind of Either                  #1017"-  , test  no     yes    intL40     kindI         "kind of Int                     #1017"-  , test  no     broken tvrL40     kindV         "kind of (* -> *) type variable  #1017"-  , test  no     broken intL41     litI          "literal Int  in hover info      #1016"-  , test  no     broken chrL36     litC          "literal Char in hover info      #1016"-  , test  no     broken txtL8      litT          "literal Text in hover info      #1016"-  , test  no     broken lstL43     litL          "literal List in hover info      #1016"-  , test  yes    yes    cmtL68     lackOfdEq     "no Core symbols                 #3280"-  , if ghcVersion >= GHC90 then-        test  no     yes    docL41     constr        "type constraint in hover info   #1012"-    else-        test  no     broken docL41     constr        "type constraint in hover info   #1012"-  , test  no     yes    outL45     outSig        "top-level signature              #767"-  , test  broken broken innL48     innSig        "inner     signature              #767"-  , test  no     yes    holeL60    hleInfo       "hole without internal name       #831"-  , test  no     yes    holeL65    hleInfo2      "hole with variable"-  , test  no     yes    cccL17     docLink       "Haddock html links"-  , testM yes    yes    imported   importedSig   "Imported symbol"-  , if | isWindows ->-        -- Flaky on Windows: https://github.com/haskell/haskell-language-server/issues/2997-        testM no     yes    reexported reexportedSig "Imported symbol (reexported)"-       | otherwise ->-        testM yes    yes    reexported reexportedSig "Imported symbol (reexported)"-  , if | ghcVersion == GHC90 && isWindows ->-        test  no     broken    thLocL57   thLoc         "TH Splice Hover"-       | otherwise ->-        test  no     yes       thLocL57   thLoc         "TH Splice Hover"-  , test yes yes import310 pkgTxt "show package name and its version"-  ]-  where 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-        --skip = const Nothing -- unreliable, don't run--checkFileCompiles :: FilePath -> Session () -> TestTree-checkFileCompiles fp diag =-  testSessionWithExtraFiles "hover" ("Does " ++ fp ++ " compile") $ \dir -> do-    void (openTestDataDoc (dir </> fp))-    diag
− test/exe/FuzzySearch.hs
@@ -1,129 +0,0 @@-module FuzzySearch (tests) where--import           Data.Char                  (toLower)-import           Data.Maybe                 (catMaybes)-import qualified Data.Monoid.Textual        as T-import           Data.Text                  (Text, inits, pack)-import qualified Data.Text                  as Text-import           Prelude                    hiding (filter)-import           System.Directory           (doesFileExist)-import           System.IO.Unsafe           (unsafePerformIO)-import           Test.QuickCheck-import           Test.Tasty-import           Test.Tasty.ExpectedFailure-import           Test.Tasty.QuickCheck      (testProperty)-import qualified Text.Fuzzy                 as Fuzzy-import           Text.Fuzzy                 (Fuzzy (..))-import           Text.Fuzzy.Parallel--tests :: TestTree-tests =-  testGroup-    "Fuzzy search"-    [ needDictionary $-        testGroup-          "match works as expected on the english dictionary"-          [ testProperty "for legit words" propLegit,-            testProperty "for prefixes" propPrefix,-            testProperty "for typos" propTypo-          ]-    ]--test :: Text -> Bool-test candidate = do-  let previous =-        catMaybes-          [ (d,) . Fuzzy.score-              <$> referenceImplementation candidate d "" "" id-            | d <- dictionary-          ]-      new = catMaybes [(d,) <$> match candidate d | d <- dictionary]-  previous == new--propLegit :: Property-propLegit = forAll (elements dictionary) test--propPrefix :: Property-propPrefix = forAll (elements dictionary >>= elements . inits) test--propTypo :: Property-propTypo = forAll typoGen test--typoGen :: Gen Text-typoGen = do-  w <- elements dictionary-  l <- elements [0 .. Text.length w -1]-  let wl = Text.index w l-  c <- elements [ c | c <- ['a' .. 'z'], c /= wl]-  return $ replaceAt w l c--replaceAt :: Text -> Int -> Char -> Text-replaceAt t i c =-  let (l, r) = Text.splitAt i t-   in l <> Text.singleton c <> r--dictionaryPath :: FilePath-dictionaryPath = "/usr/share/dict/words"--{-# NOINLINE dictionary #-}-dictionary :: [Text]-dictionary = unsafePerformIO $ do-  existsDictionary <- doesFileExist dictionaryPath-  if existsDictionary-    then map pack . words <$> readFile dictionaryPath-    else pure []--referenceImplementation ::-  (T.TextualMonoid s) =>-  -- | Pattern in lowercase except for first character-  s ->-  -- | The value containing the text to search in.-  t ->-  -- | The text to add before each match.-  s ->-  -- | The text to add after each match.-  s ->-  -- | The function to extract the text from the container.-  (t -> s) ->-  -- | The original value, rendered string and score.-  Maybe (Fuzzy t s)-referenceImplementation pattern t pre post extract =-  if null pat then Just (Fuzzy t result totalScore) else Nothing-  where-    null :: (T.TextualMonoid s) => s -> Bool-    null = not . T.any (const True)--    s = extract t-    (totalScore, _currScore, result, pat, _) =-      T.foldl'-        undefined-        ( \(tot, cur, res, pat, isFirst) c ->-            case T.splitCharacterPrefix pat of-              Nothing -> (tot, 0, res <> T.singleton c, pat, isFirst)-              Just (x, xs) ->-                -- the case of the first character has to match-                -- otherwise use lower case since the pattern is assumed lower-                let !c' = if isFirst then c else toLower c-                 in if x == c'-                      then-                        let cur' = cur * 2 + 1-                         in ( tot + cur',-                              cur',-                              res <> pre <> T.singleton c <> post,-                              xs,-                              False-                            )-                      else (tot, 0, res <> T.singleton c, pat, isFirst)-        )-        ( 0,-          1, -- matching at the start gives a bonus (cur = 1)-          mempty,-          pattern,-          True-        )-        s--needDictionary :: TestTree -> TestTree-needDictionary-  | null dictionary = ignoreTestBecause ("not found: " <> dictionaryPath)-  | otherwise = id
− test/exe/GarbageCollectionTests.hs
@@ -1,94 +0,0 @@--{-# LANGUAGE OverloadedLabels #-}--module GarbageCollectionTests (tests) where--import           Control.Monad.IO.Class      (liftIO)-import           Data.Row-import qualified Data.Set                    as Set-import qualified Data.Text                   as T-import           Development.IDE.Test        (expectCurrentDiagnostics,-                                              getStoredKeys, waitForGC,-                                              waitForTypecheck)-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test-import           System.FilePath--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils-import           Text.Printf                 (printf)--tests :: TestTree-tests = testGroup "garbage collection"-  [ testGroup "dirty keys"-        [ testSession' "are collected" $ \dir -> do-            liftIO $ writeFile (dir </> "hie.yaml") "cradle: {direct: {arguments: [A]}}"-            doc <- generateGarbage "A" dir-            closeDoc doc-            garbage <- waitForGC-            liftIO $ assertBool "no garbage was found" $ not $ null garbage--        , testSession' "are deleted from the state" $ \dir -> do-            liftIO $ writeFile (dir </> "hie.yaml") "cradle: {direct: {arguments: [A]}}"-            docA <- generateGarbage "A" dir-            keys0 <- getStoredKeys-            closeDoc docA-            garbage <- waitForGC-            liftIO $ assertBool "something is wrong with this test - no garbage found" $ not $ null garbage-            keys1 <- getStoredKeys-            liftIO $ assertBool "keys were not deleted from the state" (length keys1 < length keys0)--        , testSession' "are not regenerated unless needed" $ \dir -> do-            liftIO $ writeFile (dir </> "hie.yaml") "cradle: {direct: {arguments: [A.hs, B.hs]}}"-            docA <- generateGarbage "A" dir-            _docB <- generateGarbage "B" dir--            -- garbage collect A keys-            keysBeforeGC <- getStoredKeys-            closeDoc docA-            garbage <- waitForGC-            liftIO $ assertBool "something is wrong with this test - no garbage found" $ not $ null garbage-            keysAfterGC <- getStoredKeys-            liftIO $ assertBool "something is wrong with this test - keys were not deleted from the state"-                (length keysAfterGC < length keysBeforeGC)--            -- re-typecheck B and check that the keys for A have not materialized back-            _docB <- generateGarbage "B" dir-            keysB <- getStoredKeys-            let regeneratedKeys = Set.filter (not . isExpected) $-                    Set.intersection (Set.fromList garbage) (Set.fromList keysB)-            liftIO $ regeneratedKeys @?= mempty--        , testSession' "regenerate successfully" $ \dir -> do-            liftIO $ writeFile (dir </> "hie.yaml") "cradle: {direct: {arguments: [A]}}"-            docA <- generateGarbage "A" dir-            closeDoc docA-            garbage <- waitForGC-            liftIO $ assertBool "no garbage was found" $ not $ null garbage-            let edit = T.unlines-                        [ "module A where"-                        , "a :: Bool"-                        , "a = ()"-                        ]-            doc <- generateGarbage "A" dir-            changeDoc doc [TextDocumentContentChangeEvent . InR . (.==) #text $ edit]-            builds <- waitForTypecheck doc-            liftIO $ assertBool "it still builds" builds-            expectCurrentDiagnostics doc [(DiagnosticSeverity_Error, (2,4), "Couldn't match expected type")]-        ]-  ]-  where-    isExpected k = any (`T.isPrefixOf` k) ["GhcSessionIO"]--    generateGarbage :: String -> FilePath -> Session TextDocumentIdentifier-    generateGarbage modName dir = do-        let fp = modName <> ".hs"-            body = printf "module %s where" modName-        doc <- createDoc fp "haskell" (T.pack body)-        liftIO $ writeFile (dir </> fp) body-        builds <- waitForTypecheck doc-        liftIO $ assertBool "something is wrong with this test" builds-        return doc
− test/exe/HaddockTests.hs
@@ -1,90 +0,0 @@--module HaddockTests (tests) where--import           Development.IDE.Spans.Common--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           Test.Tasty.HUnit--tests :: TestTree-tests-  = testGroup "haddock"-      [ testCase "Num" $ checkHaddock-          (unlines-             [ "However, '(+)' and '(*)' are"-             , "customarily expected to define a ring and have the following properties:"-             , ""-             , "[__Associativity of (+)__]: @(x + y) + z@ = @x + (y + z)@"-             , "[__Commutativity of (+)__]: @x + y@ = @y + x@"-             , "[__@fromInteger 0@ is the additive identity__]: @x + fromInteger 0@ = @x@"-             ]-          )-          (unlines-             [ ""-             , ""-             , "However,  `(+)`  and  `(*)`  are"-             , "customarily expected to define a ring and have the following properties: "-             , "+ ****Associativity of (+)****: `(x + y) + z`  =  `x + (y + z)`"-             , "+ ****Commutativity of (+)****: `x + y`  =  `y + x`"-             , "+ ****`fromInteger 0`  is the additive identity****: `x + fromInteger 0`  =  `x`"-             ]-          )-      , testCase "unsafePerformIO" $ checkHaddock-          (unlines-             [ "may require"-             , "different precautions:"-             , ""-             , "  * Use @{\\-\\# NOINLINE foo \\#-\\}@ as a pragma on any function @foo@"-             , "        that calls 'unsafePerformIO'.  If the call is inlined,"-             , "        the I\\/O may be performed more than once."-             , ""-             , "  * Use the compiler flag @-fno-cse@ to prevent common sub-expression"-             , "        elimination being performed on the module."-             , ""-             ]-          )-          (unlines-             [ ""-             , ""-             , "may require"-             , "different precautions: "-             , "+ Use  `{-# NOINLINE foo #-}`  as a pragma on any function  `foo` "-             , "  that calls  `unsafePerformIO` .  If the call is inlined,"-             , "  the I/O may be performed more than once."-             , ""-             , "+ Use the compiler flag  `-fno-cse`  to prevent common sub-expression"-             , "  elimination being performed on the module."-             , ""-             ]-          )-      , testCase "ordered list" $ checkHaddock-          (unlines-             [ "may require"-             , "different precautions:"-             , ""-             , "  1. Use @{\\-\\# NOINLINE foo \\#-\\}@ as a pragma on any function @foo@"-             , "        that calls 'unsafePerformIO'.  If the call is inlined,"-             , "        the I\\/O may be performed more than once."-             , ""-             , "  2. Use the compiler flag @-fno-cse@ to prevent common sub-expression"-             , "        elimination being performed on the module."-             , ""-             ]-          )-          (unlines-             [ ""-             , ""-             , "may require"-             , "different precautions: "-             , "1. Use  `{-# NOINLINE foo #-}`  as a pragma on any function  `foo` "-             , "  that calls  `unsafePerformIO` .  If the call is inlined,"-             , "  the I/O may be performed more than once."-             , ""-             , "2. Use the compiler flag  `-fno-cse`  to prevent common sub-expression"-             , "  elimination being performed on the module."-             , ""-             ]-          )-      ]-  where-    checkHaddock s txt = spanDocToMarkdownForTest s @?= txt
− test/exe/HieDbRetry.hs
@@ -1,136 +0,0 @@-{-# LANGUAGE MultiWayIf #-}-module HieDbRetry (tests) where--import           Control.Concurrent.Extra (Var, modifyVar, newVar, readVar,-                                           withVar)-import           Control.Exception        (ErrorCall (ErrorCall), evaluate,-                                           throwIO, tryJust)-import           Control.Monad.IO.Class   (MonadIO (liftIO))-import           Data.Tuple.Extra         (dupe)-import qualified Database.SQLite.Simple   as SQLite-import           Development.IDE.Session  (retryOnException, retryOnSqliteBusy)-import qualified Development.IDE.Session  as Session-import           Ide.Logger               (Recorder (Recorder, logger_),-                                           WithPriority (WithPriority, payload),-                                           cmapWithPrio)-import qualified System.Random            as Random-import           Test.Tasty               (TestTree, testGroup)-import           Test.Tasty.HUnit         (assertFailure, testCase, (@?=))--data Log-  = LogSession Session.Log-  deriving Show--makeLogger :: Var [Log] -> Recorder (WithPriority Log)-makeLogger msgsVar =-  Recorder {-    logger_ = \WithPriority{ payload = msg } -> liftIO $ modifyVar msgsVar (\msgs -> pure (msg : msgs, ()))-  }--rng :: Random.StdGen-rng = Random.mkStdGen 0--retryOnSqliteBusyForTest :: Recorder (WithPriority Log) -> Int -> IO a -> IO a-retryOnSqliteBusyForTest recorder maxRetryCount = retryOnException isErrorBusy (cmapWithPrio LogSession recorder) 1 1 maxRetryCount rng--isErrorBusy :: SQLite.SQLError -> Maybe SQLite.SQLError-isErrorBusy e-  | SQLite.SQLError { sqlError = SQLite.ErrorBusy } <- e = Just e-  | otherwise = Nothing--errorBusy :: SQLite.SQLError-errorBusy = SQLite.SQLError{ sqlError = SQLite.ErrorBusy, sqlErrorDetails = "", sqlErrorContext = "" }--isErrorCall :: ErrorCall -> Maybe ErrorCall-isErrorCall e-  | ErrorCall _ <- e = Just e--tests :: TestTree-tests = testGroup "RetryHieDb"-  [ testCase "retryOnException throws exception after max retries" $ do-      logMsgsVar <- newVar []-      let logger = makeLogger logMsgsVar-      let maxRetryCount = 1--      result <- tryJust isErrorBusy (retryOnSqliteBusyForTest logger maxRetryCount (throwIO errorBusy))--      case result of-        Left exception -> do-          exception @?= errorBusy-          withVar logMsgsVar $ \logMsgs ->-            length logMsgs @?= 2-            -- uncomment if want to compare log msgs-            -- logMsgs @?= []-        Right _ -> assertFailure "Expected ErrorBusy exception"--   , testCase "retryOnException doesn't throw if given function doesn't throw" $ do-      let expected = 1 :: Int-      let maxRetryCount = 0--      actual <- retryOnSqliteBusyForTest mempty maxRetryCount (pure expected)--      actual @?= expected--   , testCase "retryOnException retries the number of times it should" $ do-      countVar <- newVar 0-      let maxRetryCount = 3-      let incrementThenThrow = modifyVar countVar (\count -> pure (dupe (count + 1))) >> throwIO errorBusy--      _ <- tryJust isErrorBusy (retryOnSqliteBusyForTest mempty maxRetryCount incrementThenThrow)--      withVar countVar $ \count ->-        count @?= maxRetryCount + 1--   , testCase "retryOnException doesn't retry if exception is not ErrorBusy" $ do-      countVar <- newVar (0 :: Int)-      let maxRetryCount = 1--      let throwThenIncrement = do-            count <- readVar countVar-            if count == 0 then-              evaluate (error "dummy exception")-            else-              modifyVar countVar (\count -> pure (dupe (count + 1)))---      _ <- tryJust isErrorCall (retryOnSqliteBusyForTest mempty maxRetryCount throwThenIncrement)--      withVar countVar $ \count ->-        count @?= 0--   , testCase "retryOnSqliteBusy retries on ErrorBusy" $ do-      countVar <- newVar (0 :: Int)--      let incrementThenThrowThenIncrement = do-            count <- readVar countVar-            if count == 0 then-              modifyVar countVar (\count -> pure (dupe (count + 1))) >> throwIO errorBusy-            else-              modifyVar countVar (\count -> pure (dupe (count + 1)))--      _ <- retryOnSqliteBusy mempty rng incrementThenThrowThenIncrement--      withVar countVar $ \count ->-        count @?= 2--    , testCase "retryOnException exponentially backs off" $ do-       logMsgsVar <- newVar ([] :: [Log])--       let maxDelay = 100-       let baseDelay = 1-       let maxRetryCount = 6-       let logger = makeLogger logMsgsVar--       result <- tryJust isErrorBusy (retryOnException isErrorBusy (cmapWithPrio LogSession logger) maxDelay baseDelay maxRetryCount rng (throwIO errorBusy))--       case result of-         Left _ -> do-           withVar logMsgsVar $ \logMsgs ->-             -- uses log messages to check backoff...-             if | (LogSession (Session.LogHieDbRetriesExhausted baseDelay maximumDelay maxRetryCount _) : _) <- logMsgs -> do-                  baseDelay @?= 64-                  maximumDelay @?= 100-                  maxRetryCount @?= 0-                | otherwise -> assertFailure "Expected more than 0 log messages"-         Right _ -> assertFailure "Expected ErrorBusy exception"-  ]
− test/exe/HighlightTests.hs
@@ -1,79 +0,0 @@--module HighlightTests (tests) where--import           Control.Monad.IO.Class         (liftIO)-import qualified Data.Text                      as T-import           Development.IDE.GHC.Compat     (GhcVersion (..))-import           Development.IDE.Types.Location-import           Language.LSP.Protocol.Types    hiding-                                                (SemanticTokenAbsolute (..),-                                                 SemanticTokenRelative (..),-                                                 SemanticTokensEdit (..),-                                                 mkRange)-import           Language.LSP.Test-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = testGroup "highlight"-  [ testSessionWait "value" $ do-    doc <- createDoc "A.hs" "haskell" source-    _ <- waitForDiagnostics-    highlights <- getHighlights doc (Position 3 2)-    liftIO $ highlights @?=-            [ DocumentHighlight (R 2 0 2 3) (Just DocumentHighlightKind_Read)-            , DocumentHighlight (R 3 0 3 3) (Just DocumentHighlightKind_Write)-            , DocumentHighlight (R 4 6 4 9) (Just DocumentHighlightKind_Read)-            , DocumentHighlight (R 5 22 5 25) (Just DocumentHighlightKind_Read)-            ]-  , testSessionWait "type" $ do-    doc <- createDoc "A.hs" "haskell" source-    _ <- waitForDiagnostics-    highlights <- getHighlights doc (Position 2 8)-    liftIO $ highlights @?=-            [ DocumentHighlight (R 2 7 2 10) (Just DocumentHighlightKind_Read)-            , DocumentHighlight (R 3 11 3 14) (Just DocumentHighlightKind_Read)-            ]-  , testSessionWait "local" $ do-    doc <- createDoc "A.hs" "haskell" source-    _ <- waitForDiagnostics-    highlights <- getHighlights doc (Position 6 5)-    liftIO $ highlights @?=-            [ DocumentHighlight (R 6 4 6 7) (Just DocumentHighlightKind_Write)-            , DocumentHighlight (R 6 10 6 13) (Just DocumentHighlightKind_Read)-            , DocumentHighlight (R 7 12 7 15) (Just DocumentHighlightKind_Read)-            ]-  , knownBrokenForGhcVersions [GHC90, GHC92, GHC94, GHC96, GHC98] "Ghc9 highlights the constructor and not just this field" $-        testSessionWait "record" $ do-        doc <- createDoc "A.hs" "haskell" recsource-        _ <- waitForDiagnostics-        highlights <- getHighlights doc (Position 4 15)-        liftIO $ highlights @?=-          [ DocumentHighlight (R 4 8 4 10) (Just DocumentHighlightKind_Write)-          , DocumentHighlight (R 4 14 4 20) (Just DocumentHighlightKind_Read)-          ]-        highlights <- getHighlights doc (Position 3 17)-        liftIO $ highlights @?=-          [ DocumentHighlight (R 3 17 3 23) (Just DocumentHighlightKind_Write)-          , DocumentHighlight (R 4 8 4 10) (Just DocumentHighlightKind_Read)-          ]-  ]-  where-    source = T.unlines-      ["{-# OPTIONS_GHC -Wunused-binds #-}"-      ,"module Highlight () where"-      ,"foo :: Int"-      ,"foo = 3 :: Int"-      ,"bar = foo"-      ,"  where baz = let x = foo in x"-      ,"baz arg = arg + x"-      ,"  where x = arg"-      ]-    recsource = T.unlines-      ["{-# LANGUAGE RecordWildCards #-}"-      ,"{-# OPTIONS_GHC -Wunused-binds #-}"-      ,"module Highlight () where"-      ,"data Rec = Rec { field1 :: Int, field2 :: Char }"-      ,"foo Rec{..} = field2 + field1"-      ]
− test/exe/IfaceTests.hs
@@ -1,163 +0,0 @@--{-# LANGUAGE OverloadedLabels #-}--module IfaceTests (tests) where--import           Control.Monad.IO.Class        (liftIO)-import           Data.Row-import qualified Data.Text                     as T-import           Development.IDE.GHC.Util-import           Development.IDE.Test          (configureCheckProject,-                                                expectDiagnostics,-                                                expectNoMoreDiagnostics,-                                                getInterfaceFilesDir)-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types   hiding-                                               (SemanticTokenAbsolute (..),-                                                SemanticTokenRelative (..),-                                                SemanticTokensEdit (..),-                                                mkRange)-import           Language.LSP.Test-import           System.Directory-import           System.FilePath-import           System.IO.Extra               hiding (withTempDir)-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = testGroup "Interface loading tests"-    [ -- https://github.com/haskell/ghcide/pull/645/-      ifaceErrorTest-    , ifaceErrorTest2-    , ifaceErrorTest3-    , ifaceTHTest-    ]----- | test that TH reevaluates across interfaces-ifaceTHTest :: TestTree-ifaceTHTest = testCase "iface-th-test" $ runWithExtraFiles "TH" $ \dir -> do-    let aPath = dir </> "THA.hs"-        bPath = dir </> "THB.hs"-        cPath = dir </> "THC.hs"--    aSource <- liftIO $ readFileUtf8 aPath -- [TH] a :: ()-    _bSource <- liftIO $ readFileUtf8 bPath -- a :: ()-    cSource <- liftIO $ readFileUtf8 cPath -- c = a :: ()--    cdoc <- createDoc cPath "haskell" cSource--    -- Change [TH]a from () to Bool-    liftIO $ writeFileUTF8 aPath (unlines $ init (lines $ T.unpack aSource) ++ ["th_a = [d| a = False|]"])--    -- Check that the change propagates to C-    changeDoc cdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ cSource]-    expectDiagnostics-      [("THC.hs", [(DiagnosticSeverity_Error, (4, 4), "Couldn't match expected type '()' with actual type 'Bool'")])-      ,("THB.hs", [(DiagnosticSeverity_Warning, (4,thDollarIdx), "Top-level binding")])]-    closeDoc cdoc--ifaceErrorTest :: TestTree-ifaceErrorTest = testCase "iface-error-test-1" $ runWithExtraFiles "recomp" $ \dir -> do-    configureCheckProject True-    let bPath = dir </> "B.hs"-        pPath = dir </> "P.hs"--    bSource <- liftIO $ readFileUtf8 bPath -- y :: Int-    pSource <- liftIO $ readFileUtf8 pPath -- bar = x :: Int--    bdoc <- createDoc bPath "haskell" bSource-    expectDiagnostics-      [("P.hs", [(DiagnosticSeverity_Warning,(4,0), "Top-level binding")])] -- So what we know P has been loaded--    -- Change y from Int to B-    changeDoc bdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ T.unlines ["module B where", "y :: Bool", "y = undefined"]]-    -- save so that we can that the error propagates to A-    sendNotification SMethod_TextDocumentDidSave (DidSaveTextDocumentParams bdoc Nothing)---    -- Check that the error propagates to A-    expectDiagnostics-      [("A.hs", [(DiagnosticSeverity_Error, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])]--    -- Check that we wrote the interfaces for B when we saved-    hidir <- getInterfaceFilesDir bdoc-    hi_exists <- liftIO $ doesFileExist $ hidir </> "B.hi"-    liftIO $ assertBool ("Couldn't find B.hi in " ++ hidir) hi_exists--    pdoc <- openDoc pPath "haskell"-    expectDiagnostics-      [("P.hs", [(DiagnosticSeverity_Warning,(4,0), "Top-level binding")])-      ]-    changeDoc pdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ pSource <> "\nfoo = y :: Bool" ]-    -- Now in P we have-    -- bar = x :: Int-    -- foo = y :: Bool-    -- HOWEVER, in A...-    -- x = y  :: Int-    -- This is clearly inconsistent, and the expected outcome a bit surprising:-    --   - The diagnostic for A has already been received. Ghcide does not repeat diagnostics-    --   - P is being typechecked with the last successful artifacts for A.-    expectDiagnostics-      [("P.hs", [(DiagnosticSeverity_Warning,(4,0), "Top-level binding")])-      ,("P.hs", [(DiagnosticSeverity_Warning,(6,0), "Top-level binding")])-      ]-    expectNoMoreDiagnostics 2--ifaceErrorTest2 :: TestTree-ifaceErrorTest2 = testCase "iface-error-test-2" $ runWithExtraFiles "recomp" $ \dir -> do-    let bPath = dir </> "B.hs"-        pPath = dir </> "P.hs"--    bSource <- liftIO $ readFileUtf8 bPath -- y :: Int-    pSource <- liftIO $ readFileUtf8 pPath -- bar = x :: Int--    bdoc <- createDoc bPath "haskell" bSource-    pdoc <- createDoc pPath "haskell" pSource-    expectDiagnostics-      [("P.hs", [(DiagnosticSeverity_Warning,(4,0), "Top-level binding")])] -- So that we know P has been loaded--    -- Change y from Int to B-    changeDoc bdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ T.unlines ["module B where", "y :: Bool", "y = undefined"]]--    -- Add a new definition to P-    changeDoc pdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ pSource <> "\nfoo = y :: Bool" ]-    -- Now in P we have-    -- bar = x :: Int-    -- foo = y :: Bool-    -- HOWEVER, in A...-    -- x = y  :: Int-    expectDiagnostics-    -- As in the other test, P is being typechecked with the last successful artifacts for A-    -- (ot thanks to -fdeferred-type-errors)-      [("A.hs", [(DiagnosticSeverity_Error, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])-      ,("P.hs", [(DiagnosticSeverity_Warning, (4, 0), "Top-level binding")])-      ,("P.hs", [(DiagnosticSeverity_Warning, (6, 0), "Top-level binding")])-      ]--    expectNoMoreDiagnostics 2--ifaceErrorTest3 :: TestTree-ifaceErrorTest3 = testCase "iface-error-test-3" $ runWithExtraFiles "recomp" $ \dir -> do-    let bPath = dir </> "B.hs"-        pPath = dir </> "P.hs"--    bSource <- liftIO $ readFileUtf8 bPath -- y :: Int-    pSource <- liftIO $ readFileUtf8 pPath -- bar = x :: Int--    bdoc <- createDoc bPath "haskell" bSource--    -- Change y from Int to B-    changeDoc bdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ T.unlines ["module B where", "y :: Bool", "y = undefined"]]--    -- P should not typecheck, as there are no last valid artifacts for A-    _pdoc <- createDoc pPath "haskell" pSource--    -- In this example the interface file for A should not exist (modulo the cache folder)-    -- Despite that P still type checks, as we can generate an interface file for A thanks to -fdeferred-type-errors-    expectDiagnostics-      [("A.hs", [(DiagnosticSeverity_Error, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])-      ,("P.hs", [(DiagnosticSeverity_Warning,(4,0), "Top-level binding")])-      ]-    expectNoMoreDiagnostics 2
− test/exe/InitializeResponseTests.hs
@@ -1,97 +0,0 @@--{-# LANGUAGE DataKinds        #-}-{-# LANGUAGE OverloadedLabels #-}--module InitializeResponseTests (tests) where--import           Control.Monad-import           Data.List.Extra-import           Data.Row-import qualified Data.Text                         as T-import           Development.IDE.Plugin.TypeLenses (typeLensCommandId)-import qualified Language.LSP.Protocol.Lens        as L-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types       hiding-                                                   (SemanticTokenAbsolute (..),-                                                    SemanticTokenRelative (..),-                                                    SemanticTokensEdit (..),-                                                    mkRange)-import           Language.LSP.Test--import           Control.Lens                      ((^.))-import           Development.IDE.Plugin.Test       (blockCommandId)-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = 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 (TResponseMessage Method_Initialize) -> TestTree-  tests getInitializeResponse =-    testGroup "initialize response capabilities"-    [ chk "   text doc sync"             _textDocumentSync  tds-    , chk "   hover"                         _hoverProvider (Just $ InR (HoverOptions (Just False)))-    , chk "   completion"               _completionProvider (Just $ CompletionOptions (Just False) (Just ["."]) Nothing (Just True) Nothing)-    , chk "NO signature help"        _signatureHelpProvider Nothing-    , chk "   goto definition"          _definitionProvider (Just $ InR (DefinitionOptions (Just False)))-    , chk "   goto type definition" _typeDefinitionProvider (Just $ InR (InL (TypeDefinitionOptions (Just False))))-    -- BUG in lsp-test, this test fails, just change the accepted response-    -- for now-    , chk "NO goto implementation"  _implementationProvider Nothing-    , chk "   find references"          _referencesProvider (Just $ InR (ReferenceOptions (Just False)))-    , chk "   doc highlight"     _documentHighlightProvider (Just $ InR (DocumentHighlightOptions (Just False)))-    , chk "   doc symbol"           _documentSymbolProvider (Just $ InR (DocumentSymbolOptions (Just False) Nothing))-    , chk "   workspace symbol"    _workspaceSymbolProvider (Just $ InR (WorkspaceSymbolOptions (Just False) (Just False)))-    , chk "NO code action"             _codeActionProvider  Nothing-    , chk "   code lens"                 _codeLensProvider  (Just $ CodeLensOptions (Just False) (Just True))-    , 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"                   (^. L.colorProvider) Nothing-    , chk "NO folding range"          _foldingRangeProvider Nothing-    , che "   execute command"      _executeCommandProvider [typeLensCommandId, blockCommandId]-    , chk "   workspace"                   (^. L.workspace) (Just $ #workspaceFolders .== Just WorkspaceFoldersServerCapabilities{_supported = Just True, _changeNotifications = Just ( InR True )}-                                                                 .+ #fileOperations   .== Nothing)-    , chk "NO experimental"             (^. L.experimental) Nothing-    ] where--      tds = Just (InL (TextDocumentSyncOptions-                              { _openClose = Just True-                              , _change    = Just TextDocumentSyncKind_Incremental-                              , _willSave  = Nothing-                              , _willSaveWaitUntil = Nothing-                              , _save = Just (InR $ SaveOptions {_includeText = Nothing})}))--      chk :: (Eq a, Show a) => TestName -> (ServerCapabilities -> a) -> a -> TestTree-      chk title getActual expected =-        testCase title $ getInitializeResponse >>= \ir -> expected @=? (getActual . innerCaps) ir--      che :: TestName -> (ServerCapabilities -> Maybe ExecuteCommandOptions) -> [T.Text] -> TestTree-      che title getActual expected = testCase title $ do-        ir <- getInitializeResponse-        ExecuteCommandOptions {_commands = commands} <- case getActual $ innerCaps ir of-          Just eco -> pure eco-          Nothing -> assertFailure "Was expecting Just ExecuteCommandOptions, got Nothing"-        let commandNames = (!! 2) . T.splitOn ":" <$> commands-        zipWithM_ (\e o -> T.isSuffixOf e o @? show (e,o)) (sort expected) (sort commandNames)--  innerCaps :: TResponseMessage Method_Initialize -> ServerCapabilities-  innerCaps (TResponseMessage _ _ (Right (InitializeResult c _))) = c-  innerCaps (TResponseMessage _ _ (Left _)) = error "Initialization error"--  acquire :: IO (TResponseMessage Method_Initialize)-  acquire = run initializeResponse--  release :: TResponseMessage Method_Initialize -> IO ()-  release = mempty-
− test/exe/LogType.hs
@@ -1,21 +0,0 @@-module LogType (Log(..)) where--import qualified Development.IDE.LSP.Notifications as Notifications-import qualified Development.IDE.Main              as IDE-import qualified Development.IDE.Plugin.HLS.GhcIde as Ghcide--import           Ide.Logger                        (Pretty (pretty))-import           Language.LSP.VFS                  (VfsLog)--data Log-  = LogGhcIde Ghcide.Log-  | LogIDEMain IDE.Log-  | LogVfs VfsLog-  | LogNotifications Notifications.Log--instance Pretty Log where-  pretty = \case-    LogGhcIde log  -> pretty log-    LogIDEMain log -> pretty log-    LogVfs log     -> pretty log-    LogNotifications log -> pretty log
− test/exe/Main.hs
@@ -1,127 +0,0 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0--{-- NOTE On enforcing determinism--   The tests below use two mechanisms to enforce deterministic LSP sequences:--    1. Progress reporting: waitForProgress(Begin|Done)-    2. Diagnostics: expectDiagnostics--    Either is fine, but diagnostics are generally more reliable.--    Mixing them both in the same test is NOT FINE as it will introduce race-    conditions since multiple interleavings are possible. In other words,-    the sequence of diagnostics and progress reports is not deterministic.-    For example:--    < do something >-    waitForProgressDone-    expectDiagnostics [...]--    - When the diagnostics arrive after the progress done message, as they usually do, the test will pass-    - When the diagnostics arrive before the progress done msg, when on a slow machine occasionally, the test will timeout--    Therefore, avoid mixing both progress reports and diagnostics in the same test- -}----module Main (main) where--- import Test.QuickCheck.Instances ()-import           Data.Function                            ((&))-import           Ide.Logger             (Logger (Logger),-                                                           LoggingColumn (DataColumn, PriorityColumn),-                                                           Pretty (pretty),-                                                           Priority (Debug),-                                                           Recorder (Recorder, logger_),-                                                           WithPriority (WithPriority, priority),-                                                           cfilter,-                                                           cmapWithPrio,-                                                           makeDefaultStderrRecorder)-import           GHC.Stack                                (emptyCallStack)-import qualified HieDbRetry-import           Test.Tasty-import           Test.Tasty.Ingredients.Rerun--import LogType ()-import OpenCloseTest-import InitializeResponseTests-import CompletionTests-import CPPTests-import DiagnosticTests-import CodeLensTests-import OutlineTests-import HighlightTests-import FindDefinitionAndHoverTests-import PluginSimpleTests-import PluginParsedResultTests-import PreprocessorTests-import THTests-import SymlinkTests-import SafeTests-import UnitTests-import HaddockTests-import PositionMappingTests-import WatchedFileTests-import CradleTests-import DependentFileTest-import NonLspCommandLine-import IfaceTests-import BootTests-import RootUriTests-import AsyncTests-import ClientSettingsTests-import ReferenceTests-import GarbageCollectionTests-import ExceptionTests--main :: IO ()-main = do-  docWithPriorityRecorder <- makeDefaultStderrRecorder (Just [PriorityColumn, DataColumn])--  let docWithFilteredPriorityRecorder@Recorder{ logger_ } =-        docWithPriorityRecorder-        & cfilter (\WithPriority{ priority } -> priority >= Debug)--  -- exists so old-style logging works. intended to be phased out-  let logger = Logger $ \p m -> logger_ (WithPriority p emptyCallStack (pretty m))--  let recorder = docWithFilteredPriorityRecorder-               & cmapWithPrio pretty--  -- We mess with env vars so run single-threaded.-  defaultMainWithRerun $ testGroup "ghcide"-    [ OpenCloseTest.tests-    , InitializeResponseTests.tests-    , CompletionTests.tests-    , CPPTests.tests-    , DiagnosticTests.tests-    , CodeLensTests.tests-    , OutlineTests.tests-    , HighlightTests.tests-    , FindDefinitionAndHoverTests.tests-    , PluginSimpleTests.tests-    , PluginParsedResultTests.tests-    , PreprocessorTests.tests-    , THTests.tests-    , SymlinkTests.tests-    , SafeTests.tests-    , UnitTests.tests recorder logger-    , HaddockTests.tests-    , PositionMappingTests.tests-    , WatchedFileTests.tests-    , CradleTests.tests-    , DependentFileTest.tests-    , NonLspCommandLine.tests-    , IfaceTests.tests-    , BootTests.tests-    , RootUriTests.tests-    , AsyncTests.tests-    , ClientSettingsTests.tests-    , ReferenceTests.tests-    , GarbageCollectionTests.tests-    , HieDbRetry.tests-    , ExceptionTests.tests recorder logger-    ]
− test/exe/NonLspCommandLine.hs
@@ -1,27 +0,0 @@--module NonLspCommandLine (tests) where--import           Development.IDE.Test.Runfiles-import           System.Environment.Blank      (setEnv)-import           System.Exit                   (ExitCode (ExitSuccess))-import           System.Process.Extra          (CreateProcess (cwd), proc,-                                                readCreateProcessWithExitCode)-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils----- A test to ensure that the command line ghcide workflow stays working-tests :: TestTree-tests = testGroup "ghcide command line"-  [ testCase "works" $ withTempDir $ \dir -> do-        ghcide <- locateGhcideExecutable-        copyTestDataFiles dir "multi"-        let cmd = (proc ghcide ["a/A.hs"]){cwd = Just dir}--        setEnv "HOME" "/homeless-shelter" False--        (ec, _, _) <- readCreateProcessWithExitCode cmd ""--        ec @?= ExitSuccess-  ]
− test/exe/OpenCloseTest.hs
@@ -1,18 +0,0 @@--module OpenCloseTest (tests) where--import           Control.Applicative.Combinators-import           Control.Monad-import           Language.LSP.Protocol.Message-import           Language.LSP.Test--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           TestUtils--tests :: TestTree-tests = testSession "open close" $ do-    doc <- createDoc "Testing.hs" "haskell" ""-    void (skipManyTill anyMessage $ message SMethod_WindowWorkDoneProgressCreate)-    waitForProgressBegin-    closeDoc doc-    waitForProgressDone
− test/exe/OutlineTests.hs
@@ -1,189 +0,0 @@--module OutlineTests (tests) where--import           Control.Monad.IO.Class      (liftIO)-import qualified Data.Text                   as T-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = testGroup-  "outline"-  [ testSessionWait "type class" $ do-    let source = T.unlines ["module A where", "class A a where a :: a -> Bool"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [ moduleSymbol-          "A"-          (R 0 7 0 8)-          [ classSymbol "A a"-                        (R 1 0 1 30)-                        [docSymbol' "a" SymbolKind_Method (R 1 16 1 30) (R 1 16 1 17)]-          ]-      ]-  , testSessionWait "type class instance " $ do-    let source = T.unlines ["class A a where", "instance A () where"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [ classSymbol "A a" (R 0 0 0 15) []-      , docSymbol "A ()" SymbolKind_Interface (R 1 0 1 19)-      ]-  , testSessionWait "type family" $ do-    let source = T.unlines ["{-# language TypeFamilies #-}", "type family A"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right [docSymbolD "A" "type family" SymbolKind_Function (R 1 0 1 13)]-  , testSessionWait "type family instance " $ do-    let source = T.unlines-          [ "{-# language TypeFamilies #-}"-          , "type family A a"-          , "type instance A () = ()"-          ]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [ docSymbolD "A a"   "type family" SymbolKind_Function     (R 1 0 1 15)-      , docSymbol "A ()" SymbolKind_Interface (R 2 0 2 23)-      ]-  , testSessionWait "data family" $ do-    let source = T.unlines ["{-# language TypeFamilies #-}", "data family A"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right [docSymbolD "A" "data family" SymbolKind_Function (R 1 0 1 11)]-  , testSessionWait "data family instance " $ do-    let source = T.unlines-          [ "{-# language TypeFamilies #-}"-          , "data family A a"-          , "data instance A () = A ()"-          ]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [ docSymbolD "A a"   "data family" SymbolKind_Function     (R 1 0 1 11)-      , docSymbol "A ()" SymbolKind_Interface (R 2 0 2 25)-      ]-  , testSessionWait "constant" $ do-    let source = T.unlines ["a = ()"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [docSymbol "a" SymbolKind_Function (R 0 0 0 6)]-  , testSessionWait "pattern" $ do-    let source = T.unlines ["Just foo = Just 21"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [docSymbol "Just foo" SymbolKind_Function (R 0 0 0 18)]-  , testSessionWait "pattern with type signature" $ do-    let source = T.unlines ["{-# language ScopedTypeVariables #-}", "a :: () = ()"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [docSymbol "a :: ()" SymbolKind_Function (R 1 0 1 12)]-  , testSessionWait "function" $ do-    let source = T.unlines ["a _x = ()"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right [docSymbol "a" SymbolKind_Function (R 0 0 0 9)]-  , testSessionWait "type synonym" $ do-    let source = T.unlines ["type A = Bool"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [docSymbol' "A" SymbolKind_TypeParameter (R 0 0 0 13) (R 0 5 0 6)]-  , testSessionWait "datatype" $ do-    let source = T.unlines ["data A = C"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [ docSymbolWithChildren "A"-                              SymbolKind_Struct-                              (R 0 0 0 10)-                              [docSymbol "C" SymbolKind_Constructor (R 0 9 0 10)]-      ]-  , testSessionWait "record fields" $ do-    let source = T.unlines ["data A = B {", "  x :: Int", "  , y :: Int}"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [ docSymbolWithChildren "A" SymbolKind_Struct (R 0 0 2 13)-          [ docSymbolWithChildren' "B" SymbolKind_Constructor (R 0 9 2 13) (R 0 9 0 10)-            [ docSymbol "x" SymbolKind_Field (R 1 2 1 3)-            , docSymbol "y" SymbolKind_Field (R 2 4 2 5)-            ]-          ]-      ]-  , testSessionWait "import" $ do-    let source = T.unlines ["import Data.Maybe ()"]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [docSymbolWithChildren "imports"-                             SymbolKind_Module-                             (R 0 0 0 20)-                             [ docSymbol "import Data.Maybe" SymbolKind_Module (R 0 0 0 20)-                             ]-      ]-  , testSessionWait "multiple import" $ do-    let source = T.unlines ["", "import Data.Maybe ()", "", "import Control.Exception ()", ""]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right-      [docSymbolWithChildren "imports"-                             SymbolKind_Module-                             (R 1 0 3 27)-                             [ docSymbol "import Data.Maybe" SymbolKind_Module (R 1 0 1 20)-                             , docSymbol "import Control.Exception" SymbolKind_Module (R 3 0 3 27)-                             ]-      ]-  , testSessionWait "foreign import" $ do-    let source = T.unlines-          [ "{-# language ForeignFunctionInterface #-}"-          , "foreign import ccall \"a\" a :: Int"-          ]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right [docSymbolD "a" "import" SymbolKind_Object (R 1 0 1 33)]-  , testSessionWait "foreign export" $ do-    let source = T.unlines-          [ "{-# language ForeignFunctionInterface #-}"-          , "foreign export ccall odd :: Int -> Bool"-          ]-    docId   <- createDoc "A.hs" "haskell" source-    symbols <- getDocumentSymbols docId-    liftIO $ symbols @?= Right [docSymbolD "odd" "export" SymbolKind_Object (R 1 0 1 39)]-  ]- where-  docSymbol name kind loc =-    DocumentSymbol name Nothing kind Nothing Nothing loc loc Nothing-  docSymbol' name kind loc selectionLoc =-    DocumentSymbol name Nothing kind Nothing Nothing loc selectionLoc Nothing-  docSymbolD name detail kind loc =-    DocumentSymbol name (Just detail) kind Nothing Nothing loc loc Nothing-  docSymbolWithChildren name kind loc cc =-    DocumentSymbol name Nothing kind Nothing Nothing loc loc (Just cc)-  docSymbolWithChildren' name kind loc selectionLoc cc =-    DocumentSymbol name Nothing kind Nothing Nothing loc selectionLoc (Just cc)-  moduleSymbol name loc cc = DocumentSymbol name-                                            Nothing-                                            SymbolKind_File-                                            Nothing-                                            Nothing-                                            (R 0 0 maxBound 0)-                                            loc-                                            (Just cc)-  classSymbol name loc cc = DocumentSymbol name-                                           (Just "class")-                                           SymbolKind_Interface-                                           Nothing-                                           Nothing-                                           loc-                                           loc-                                           (Just cc)
− test/exe/PluginParsedResultTests.hs
@@ -1,16 +0,0 @@--module PluginParsedResultTests (tests) where--import           Development.IDE.Test (expectNoMoreDiagnostics)-import           Language.LSP.Test-import           System.FilePath--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           TestUtils--tests :: TestTree-tests =-  ignoreForGHC92Plus "No need for this plugin anymore!" $-  testSessionWithExtraFiles "plugin-recorddot" "parsedResultAction plugin" $ \dir -> do-    _ <- openDoc (dir</> "RecordDot.hs") "haskell"-    expectNoMoreDiagnostics 2
− test/exe/PluginSimpleTests.hs
@@ -1,50 +0,0 @@--module PluginSimpleTests (tests) where--import           Control.Monad.IO.Class      (liftIO)-import           Development.IDE.GHC.Compat  (GhcVersion (..))-import           Development.IDE.Test        (expectDiagnostics)-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test-import           System.FilePath--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           TestUtils--tests :: TestTree-tests =-  -- Build profile: -w ghc-9.4.2 -O1-  -- In order, the following will be built (use -v for more details):-  -- - ghc-typelits-natnormalise-0.7.7 (lib) (requires build)-  -- - ghc-typelits-knownnat-0.7.7 (lib) (requires build)-  -- - plugin-1.0.0 (lib) (first run)-  -- Starting     ghc-typelits-natnormalise-0.7.7 (lib)-  -- Building     ghc-typelits-natnormalise-0.7.7 (lib)--  -- Failed to build ghc-typelits-natnormalise-0.7.7.-  -- Build log (-  -- C:\cabal\logs\ghc-9.4.2\ghc-typelits-_-0.7.7-3f036a52a0d9bfc3389d1852a87da2e87c6de2e4.log-  -- ):-  -- Preprocessing library for ghc-typelits-natnormalise-0.7.7..-  -- Building library for ghc-typelits-natnormalise-0.7.7..-  -- [1 of 3] Compiling GHC.TypeLits.Normalise.SOP ( src\GHC\TypeLits\Normalise\SOP.hs, dist\build\GHC\TypeLits\Normalise\SOP.o )-  -- [2 of 3] Compiling GHC.TypeLits.Normalise.Unify ( src\GHC\TypeLits\Normalise\Unify.hs, dist\build\GHC\TypeLits\Normalise\Unify.o )-  -- [3 of 3] Compiling GHC.TypeLits.Normalise ( src-ghc-9.4\GHC\TypeLits\Normalise.hs, dist\build\GHC\TypeLits\Normalise.o )-  -- C:\tools\ghc-9.4.2\lib\../mingw/bin/llvm-ar.exe: error: dist\build\objs-5156\libHSghc-typelits-_-0.7.7-3f036a52a0d9bfc3389d1852a87da2e87c6de2e4.a: No such file or directory--  -- Error: cabal: Failed to build ghc-typelits-natnormalise-0.7.7 (which is-  -- required by plugin-1.0.0). See the build log above for details.-  ignoreFor (BrokenForGHC [GHC96, GHC98]) "fragile, frequently times out" $-  ignoreFor (BrokenSpecific Windows [GHC94]) "ghc-typelist-natnormalise fails to build on GHC 9.4.2 for windows only" $-  testSessionWithExtraFiles "plugin-knownnat" "simple plugin" $ \dir -> do-    _ <- openDoc (dir </> "KnownNat.hs") "haskell"-    liftIO $ writeFile (dir</>"hie.yaml")-      "cradle: {cabal: [{path: '.', component: 'lib:plugin'}]}"--    expectDiagnostics-      [ ( "KnownNat.hs",-          [(DiagnosticSeverity_Error, (9, 15), "Variable not in scope: c")]-          )-      ]
− test/exe/PositionMappingTests.hs
@@ -1,222 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE OverloadedLabels    #-}--module PositionMappingTests (tests) where--import qualified Data.EnumMap.Strict                  as EM-import           Data.Row-import qualified Data.Text                            as T-import           Data.Text.Utf16.Rope.Mixed           (Rope)-import qualified Data.Text.Utf16.Rope.Mixed           as Rope-import           Development.IDE.Core.PositionMapping (PositionResult (..),-                                                       fromCurrent,-                                                       positionResultToMaybe,-                                                       toCurrent,-                                                       toCurrentPosition)-import           Development.IDE.Types.Location-import           Language.LSP.Protocol.Types          hiding-                                                      (SemanticTokenAbsolute (..),-                                                       SemanticTokenRelative (..),-                                                       SemanticTokensEdit (..),-                                                       mkRange)-import           Language.LSP.VFS                     (applyChange)-import           Test.QuickCheck--- import Test.QuickCheck.Instances ()-import           Control.Arrow                        (second)-import           Data.Functor.Identity                (runIdentity)-import           Data.Text                            (Text)-import           Development.IDE.Core.Shake           (updatePositionMappingHelper)-import           Test.Tasty-import           Test.Tasty.HUnit-import           Test.Tasty.QuickCheck--enumMapMappingTest :: TestTree-enumMapMappingTest = testCase "enumMapMappingTest" $ do-  let mkChangeEvent :: Range -> Text -> TextDocumentContentChangeEvent-      mkChangeEvent r t = TextDocumentContentChangeEvent $ InL $ #range .== r .+ #rangeLength .== Nothing .+ #text .== t-      mkCE :: UInt -> UInt -> UInt -> UInt -> Text -> TextDocumentContentChangeEvent-      mkCE l1 c1 l2 c2 = mkChangeEvent (Range (Position l1 c1) (Position l2 c2))-      events :: [(Int32, [TextDocumentContentChangeEvent])]-      events = map (second return) [(0, mkCE 0 0 0 0 ""), (1, mkCE 0 1 0 1 " "), (2, mkCE 0 2 0 2 " "), (3, mkCE 0 3 0 3 " "), (4, mkCE 0 4 0 4 " "), (5, mkCE 0 5 0 5 " ")]-      finalMap = Prelude.foldl (\m (i, e) -> updatePositionMappingHelper i e m) mempty events-  let updatePose fromPos = do-        mapping <- snd <$> EM.lookup 0 finalMap-        toCurrentPosition mapping fromPos-  updatePose (Position 0 4) @?= Just (Position 0 9)-  updatePose (Position 0 5) @?= Just (Position 0 10)---tests ::  TestTree-tests =-    testGroup "position mapping"-        [-        enumMapMappingTest-        , testGroup "toCurrent"-              [ testCase "before" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "ab"-                    (Position 0 0) @?= PositionExact (Position 0 0)-              , testCase "after, same line, same length" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "ab"-                    (Position 0 3) @?= PositionExact (Position 0 3)-              , testCase "after, same line, increased length" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc"-                    (Position 0 3) @?= PositionExact (Position 0 4)-              , testCase "after, same line, decreased length" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "a"-                    (Position 0 3) @?= PositionExact (Position 0 2)-              , testCase "after, next line, no newline" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc"-                    (Position 1 3) @?= PositionExact (Position 1 3)-              , testCase "after, next line, newline" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc\ndef"-                    (Position 1 0) @?= PositionExact (Position 2 0)-              , testCase "after, same line, newline" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc\nd"-                    (Position 0 4) @?= PositionExact (Position 1 2)-              , testCase "after, same line, newline + newline at end" $-                toCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc\nd\n"-                    (Position 0 4) @?= PositionExact (Position 2 1)-              , testCase "after, same line, newline + newline at end" $-                toCurrent-                    (Range (Position 0 1) (Position 0 1))-                    "abc"-                    (Position 0 1) @?= PositionExact (Position 0 4)-              ]-        , testGroup "fromCurrent"-              [ testCase "before" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "ab"-                    (Position 0 0) @?= PositionExact (Position 0 0)-              , testCase "after, same line, same length" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "ab"-                    (Position 0 3) @?= PositionExact (Position 0 3)-              , testCase "after, same line, increased length" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc"-                    (Position 0 4) @?= PositionExact (Position 0 3)-              , testCase "after, same line, decreased length" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "a"-                    (Position 0 2) @?= PositionExact (Position 0 3)-              , testCase "after, next line, no newline" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc"-                    (Position 1 3) @?= PositionExact (Position 1 3)-              , testCase "after, next line, newline" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc\ndef"-                    (Position 2 0) @?= PositionExact (Position 1 0)-              , testCase "after, same line, newline" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc\nd"-                    (Position 1 2) @?= PositionExact (Position 0 4)-              , testCase "after, same line, newline + newline at end" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 3))-                    "abc\nd\n"-                    (Position 2 1) @?= PositionExact (Position 0 4)-              , testCase "after, same line, newline + newline at end" $-                fromCurrent-                    (Range (Position 0 1) (Position 0 1))-                    "abc"-                    (Position 0 4) @?= PositionExact (Position 0 1)-              ]-        , adjustOption (\(QuickCheckTests i) -> QuickCheckTests (max 1000 i)) $ testGroup "properties"-              [ testProperty "fromCurrent r t <=< toCurrent r t" $ do-                -- Note that it is important to use suchThatMap on all values at once-                -- instead of only using it on the position. Otherwise you can get-                -- into situations where there is no position that can be mapped back-                -- for the edit which will result in QuickCheck looping forever.-                let gen = do-                        rope <- genRope-                        range <- genRange rope-                        PrintableText replacement <- arbitrary-                        oldPos <- genPosition rope-                        pure (range, replacement, oldPos)-                forAll-                    (suchThatMap gen-                        (\(range, replacement, oldPos) -> positionResultToMaybe $ (range, replacement, oldPos,) <$> toCurrent range replacement oldPos)) $-                    \(range, replacement, oldPos, newPos) ->-                    fromCurrent range replacement newPos === PositionExact oldPos-              , testProperty "toCurrent r t <=< fromCurrent r t" $ do-                let gen = do-                        rope <- genRope-                        range <- genRange rope-                        PrintableText replacement <- arbitrary-                        let newRope = runIdentity $ applyChange mempty rope-                                (TextDocumentContentChangeEvent $ InL $ #range .== range-                                                                     .+ #rangeLength .== Nothing-                                                                     .+ #text .== replacement)-                        newPos <- genPosition newRope-                        pure (range, replacement, newPos)-                forAll-                    (suchThatMap gen-                        (\(range, replacement, newPos) -> positionResultToMaybe $ (range, replacement, newPos,) <$> fromCurrent range replacement newPos)) $-                    \(range, replacement, newPos, oldPos) ->-                    toCurrent range replacement oldPos === PositionExact newPos-              ]-        ]--newtype PrintableText = PrintableText { getPrintableText :: T.Text }-    deriving Show--instance Arbitrary PrintableText where-    arbitrary = PrintableText . T.pack . getPrintableString <$> arbitrary--genRope :: Gen Rope-genRope = Rope.fromText . getPrintableText <$> arbitrary--genPosition :: Rope -> Gen Position-genPosition r = do-    let rows :: Int = fromIntegral $ Rope.lengthInLines r-    row <- choose (0, max 0 $ rows - 1) `suchThat` inBounds @UInt-    let columns = T.length (nthLine (fromIntegral row) r)-    column <- choose (0, max 0 $ columns - 1) `suchThat` inBounds @UInt-    pure $ Position (fromIntegral row) (fromIntegral column)--genRange :: Rope -> Gen Range-genRange r = do-    let rows :: Int = fromIntegral $ Rope.lengthInLines r-    startPos@(Position startLine startColumn) <- genPosition r-    let maxLineDiff = max 0 $ rows - 1 - fromIntegral startLine-    endLine <- choose (fromIntegral startLine, fromIntegral startLine + maxLineDiff) `suchThat` inBounds @UInt-    let columns = T.length (nthLine (fromIntegral endLine) r)-    endColumn <--        if fromIntegral startLine == endLine-            then choose (fromIntegral startColumn, columns)-            else choose (0, max 0 $ columns - 1)-        `suchThat` inBounds @UInt-    pure $ Range startPos (Position (fromIntegral endLine) (fromIntegral endColumn))--inBounds :: forall b a . (Integral a, Integral b, Bounded b) => a -> Bool-inBounds a = let i = toInteger a in i <= toInteger (maxBound @b) && i >= toInteger (minBound @b)---- | Get the ith line of a rope, starting from 0. Trailing newline not included.-nthLine :: Int -> Rope -> T.Text-nthLine i r-    | Rope.null r = ""-    | otherwise = Rope.lines r !! i
− test/exe/PreprocessorTests.hs
@@ -1,27 +0,0 @@--module PreprocessorTests (tests) where--import qualified Data.Text                   as T-import           Development.IDE.Test        (expectDiagnostics)-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           TestUtils--tests :: TestTree-tests = testSessionWait "preprocessor" $ do-  let content =-        T.unlines-          [ "{-# OPTIONS_GHC -F -pgmF=ghcide-test-preprocessor #-}"-          , "module Testing where"-          , "y = x + z" -- plugin replaces x with y, making this have only one diagnostic-          ]-  _ <- createDoc "Testing.hs" "haskell" content-  expectDiagnostics-    [ ( "Testing.hs",-        [(DiagnosticSeverity_Error, (2, 8), "Variable not in scope: z")]-      )-    ]
− test/exe/Progress.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE PackageImports #-}-module Progress (tests) where--import           Control.Concurrent.STM-import           Data.Foldable                          (for_)-import qualified Data.HashMap.Strict                    as Map-import           Development.IDE                        (NormalizedFilePath)-import           Development.IDE.Core.ProgressReporting-import qualified "list-t" ListT-import qualified StmContainers.Map                      as STM-import           Test.Tasty-import           Test.Tasty.HUnit--tests :: TestTree-tests = testGroup "Progress"-    [ reportProgressTests-    ]--data InProgressModel = InProgressModel {-    done, todo :: Int,-    current    :: Map.HashMap NormalizedFilePath Int-}--reportProgressTests :: TestTree-reportProgressTests = testGroup "recordProgress"-    [ test "addNew"   addNew-    , test "increase" increase-    , test "decrease" decrease-    , test "done" done-    ]-    where-        p0 = pure $ InProgressModel 0 0 mempty-        addNew = recordProgressModel "A" succ p0-        increase = recordProgressModel "A" succ addNew-        decrease = recordProgressModel "A" succ increase-        done = recordProgressModel "A" pred decrease-        recordProgressModel key change state =-            model state $ \st -> recordProgress st key change-        model stateModelIO k = do-            state <- fromModel =<< stateModelIO-            _ <- k state-            toModel state-        test name p = testCase name $ do-            InProgressModel{..} <- p-            (done, todo) @?= (length (filter (==0) (Map.elems current)), Map.size current)--fromModel :: InProgressModel -> IO InProgressState-fromModel InProgressModel{..} = do-    doneVar <- newTVarIO done-    todoVar <- newTVarIO todo-    currentVar <- STM.newIO-    atomically $ for_ (Map.toList current) $ \(k,v) -> STM.insert v k currentVar-    return InProgressState{..}--toModel :: InProgressState -> IO InProgressModel-toModel InProgressState{..} = atomically $ do-    done <- readTVar doneVar-    todo <- readTVar todoVar-    current <- Map.fromList <$> ListT.toList (STM.listT currentVar)-    return InProgressModel{..}
− test/exe/ReferenceTests.hs
@@ -1,199 +0,0 @@--module ReferenceTests (tests) where--import           Control.Applicative.Combinators-import qualified Control.Lens                    as Lens-import           Control.Monad-import           Control.Monad.IO.Class          (liftIO)-import           Data.List.Extra-import qualified Data.Set                        as Set-import           Development.IDE.Test            (configureCheckProject,-                                                  referenceReady)-import           Development.IDE.Types.Location-import qualified Language.LSP.Protocol.Lens      as L-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           System.Directory-import           System.FilePath--- import Test.QuickCheck.Instances ()-import           Control.Lens                    ((^.))-import           Data.Tuple.Extra-import           Test.Tasty-import           Test.Tasty.ExpectedFailure-import           Test.Tasty.HUnit-import           TestUtils---tests :: TestTree-tests = testGroup "references"-    [ testGroup "can get references to FOIs"-          [ referenceTest "can get references to symbols"-                          ("References.hs", 4, 7)-                          YesIncludeDeclaration-                          [ ("References.hs", 4, 6)-                          , ("References.hs", 6, 0)-                          , ("References.hs", 6, 14)-                          , ("References.hs", 9, 7)-                          , ("References.hs", 10, 11)-                          ]--          , referenceTest "can get references to data constructor"-                          ("References.hs", 13, 2)-                          YesIncludeDeclaration-                          [ ("References.hs", 13, 2)-                          , ("References.hs", 16, 14)-                          , ("References.hs", 19, 21)-                          ]--          , referenceTest "getting references works in the other module"-                          ("OtherModule.hs", 6, 0)-                          YesIncludeDeclaration-                          [ ("OtherModule.hs", 6, 0)-                          , ("OtherModule.hs", 8, 16)-                          ]--          , referenceTest "getting references works in the Main module"-                          ("Main.hs", 9, 0)-                          YesIncludeDeclaration-                          [ ("Main.hs", 9, 0)-                          , ("Main.hs", 10, 4)-                          ]--          , referenceTest "getting references to main works"-                          ("Main.hs", 5, 0)-                          YesIncludeDeclaration-                          [ ("Main.hs", 4, 0)-                          , ("Main.hs", 5, 0)-                          ]--          , referenceTest "can get type references"-                          ("Main.hs", 9, 9)-                          YesIncludeDeclaration-                          [ ("Main.hs", 9, 0)-                          , ("Main.hs", 9, 9)-                          , ("Main.hs", 10, 0)-                          ]--          , expectFailBecause "references provider does not respect includeDeclaration parameter" $- referenceTest "works when we ask to exclude declarations"-                          ("References.hs", 4, 7)-                          NoExcludeDeclaration-                          [ ("References.hs", 6, 0)-                          , ("References.hs", 6, 14)-                          , ("References.hs", 9, 7)-                          , ("References.hs", 10, 11)-                          ]--          , referenceTest "INCORRECTLY returns declarations when we ask to exclude them"-                          ("References.hs", 4, 7)-                          NoExcludeDeclaration-                          [ ("References.hs", 4, 6)-                          , ("References.hs", 6, 0)-                          , ("References.hs", 6, 14)-                          , ("References.hs", 9, 7)-                          , ("References.hs", 10, 11)-                          ]-          ]--    , testGroup "can get references to non FOIs"-          [ referenceTest "can get references to symbol defined in a module we import"-                          ("References.hs", 22, 4)-                          YesIncludeDeclaration-                          [ ("References.hs", 22, 4)-                          , ("OtherModule.hs", 0, 20)-                          , ("OtherModule.hs", 4, 0)-                          ]--          , referenceTest "can get references in modules that import us to symbols we define"-                          ("OtherModule.hs", 4, 0)-                          YesIncludeDeclaration-                          [ ("References.hs", 22, 4)-                          , ("OtherModule.hs", 0, 20)-                          , ("OtherModule.hs", 4, 0)-                          ]--          , referenceTest "can get references to symbol defined in a module we import transitively"-                          ("References.hs", 24, 4)-                          YesIncludeDeclaration-                          [ ("References.hs", 24, 4)-                          , ("OtherModule.hs", 0, 48)-                          , ("OtherOtherModule.hs", 2, 0)-                          ]--          , referenceTest "can get references in modules that import us transitively to symbols we define"-                          ("OtherOtherModule.hs", 2, 0)-                          YesIncludeDeclaration-                          [ ("References.hs", 24, 4)-                          , ("OtherModule.hs", 0, 48)-                          , ("OtherOtherModule.hs", 2, 0)-                          ]--          , referenceTest "can get type references to other modules"-                          ("Main.hs", 12, 10)-                          YesIncludeDeclaration-                          [ ("Main.hs", 12, 7)-                          , ("Main.hs", 13, 0)-                          , ("References.hs", 12, 5)-                          , ("References.hs", 16, 0)-                          ]-          ]-    ]---- | When we ask for all references to symbol "foo", should the declaration "foo--- = 2" be among the references returned?-data IncludeDeclaration =-    YesIncludeDeclaration-    | NoExcludeDeclaration--getReferences' :: SymbolLocation -> IncludeDeclaration -> Session ([Location])-getReferences' (file, l, c) includeDeclaration = do-    doc <- openDoc file "haskell"-    getReferences doc (Position l c) $ toBool includeDeclaration-    where toBool YesIncludeDeclaration = True-          toBool NoExcludeDeclaration  = False--referenceTestSession :: String -> FilePath -> [FilePath] -> (FilePath -> Session ()) -> TestTree-referenceTestSession name thisDoc docs' f = testSessionWithExtraFiles "references" name $ \dir -> do-  -- needed to build whole project indexing-  configureCheckProject True-  let docs = map (dir </>) $ delete thisDoc $ nubOrd docs'-  -- Initial Index-  docid <- openDoc thisDoc "haskell"-  let-    loop :: [FilePath] -> Session ()-    loop [] = pure ()-    loop docs = do-      doc <- skipManyTill anyMessage $ referenceReady (`elem` docs)-      loop (delete doc docs)-  loop docs-  f dir-  closeDoc docid---- | Given a location, lookup the symbol and all references to it. Make sure--- they are the ones we expect.-referenceTest :: String -> SymbolLocation -> IncludeDeclaration -> [SymbolLocation] -> TestTree-referenceTest name loc includeDeclaration expected =-    referenceTestSession name (fst3 loc) docs $ \dir -> do-        actual <- getReferences' loc includeDeclaration-        liftIO $ actual `expectSameLocations` map (first3 (dir </>)) expected-  where-    docs = map fst3 expected--type SymbolLocation = (FilePath, UInt, UInt)--expectSameLocations :: [Location] -> [SymbolLocation] -> Assertion-expectSameLocations actual expected = do-    let actual' =-            Set.map (\location -> (location ^. L.uri-                                   , location ^. L.range . L.start . L.line . Lens.to fromIntegral-                                   , location ^. L.range . L.start . L.character . Lens.to fromIntegral))-            $ Set.fromList actual-    expected' <- Set.fromList <$>-        (forM expected $ \(file, l, c) -> do-                              fp <- canonicalizePath file-                              return (filePathToUri fp, l, c))-    actual' @?= expected'
− test/exe/RootUriTests.hs
@@ -1,26 +0,0 @@--module RootUriTests (tests) where--import           Control.Monad.IO.Class   (liftIO)-import           Development.IDE.GHC.Util-import           Development.IDE.Test     (expectNoMoreDiagnostics)-import           Language.LSP.Test-import           System.FilePath--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils----- | checks if we use InitializeParams.rootUri for loading session-tests :: TestTree-tests = testCase "use rootUri" . runTest "dirA" "dirB" $ \dir -> do-  let bPath = dir </> "dirB/Foo.hs"-  liftIO $ copyTestDataFiles dir "rootUri"-  bSource <- liftIO $ readFileUtf8 bPath-  _ <- createDoc "Foo.hs" "haskell" bSource-  expectNoMoreDiagnostics 0.5-  where-    -- similar to run' except we can configure where to start ghcide and session-    runTest :: FilePath -> FilePath -> (FilePath -> Session ()) -> IO ()-    runTest dir1 dir2 s = withTempDir $ \dir -> runInDir' dir dir1 dir2 [] (s dir)
− test/exe/SafeTests.hs
@@ -1,38 +0,0 @@--module SafeTests (tests) where--import qualified Data.Text            as T-import           Development.IDE.Test (expectNoMoreDiagnostics)-import           Language.LSP.Test--import           Test.Tasty-import           TestUtils--tests :: TestTree-tests =-  testGroup-    "SafeHaskell"-    [ -- Test for https://github.com/haskell/ghcide/issues/424-      testSessionWait "load" $ do-        let sourceA =-              T.unlines-                ["{-# LANGUAGE Trustworthy #-}"-                ,"module A where"-                ,"import System.IO.Unsafe"-                ,"import System.IO ()"-                ,"trustWorthyId :: a -> a"-                ,"trustWorthyId i = unsafePerformIO $ do"-                ,"  putStrLn \"I'm safe\""-                ,"  return i"]-            sourceB =-              T.unlines-                ["{-# LANGUAGE Safe #-}"-                ,"module B where"-                ,"import A"-                ,"safeId :: a -> a"-                ,"safeId = trustWorthyId"-                ]--        _ <- createDoc "A.hs" "haskell" sourceA-        _ <- createDoc "B.hs" "haskell" sourceB-        expectNoMoreDiagnostics 1 ]
− test/exe/SymlinkTests.hs
@@ -1,27 +0,0 @@--module SymlinkTests (tests) where--import           Control.Monad.IO.Class      (liftIO)-import           Development.IDE.Test        (expectDiagnosticsWithTags)-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test-import           System.Directory-import           System.FilePath--import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils---- | Tests for projects that use symbolic links one way or another-tests :: TestTree-tests =-  testGroup "Projects using Symlinks"-    [ testCase "Module is symlinked" $ runWithExtraFiles "symlink" $ \dir -> do-        liftIO $ createFileLink (dir </> "some_loc" </> "Sym.hs") (dir </> "other_loc" </> "Sym.hs")-        let fooPath = dir </> "src" </> "Foo.hs"-        _ <- openDoc fooPath "haskell"-        expectDiagnosticsWithTags  [("src" </> "Foo.hs", [(DiagnosticSeverity_Warning, (2, 0), "The import of 'Sym' is redundant", Just DiagnosticTag_Unnecessary)])]-        pure ()-    ]
− test/exe/THTests.hs
@@ -1,194 +0,0 @@--{-# LANGUAGE OverloadedLabels #-}--module THTests (tests) where--import           Control.Monad.IO.Class      (liftIO)-import           Data.Row-import qualified Data.Text                   as T-import           Development.IDE.GHC.Util-import           Development.IDE.Test        (expectCurrentDiagnostics,-                                              expectDiagnostics,-                                              expectNoMoreDiagnostics)-import           Language.LSP.Protocol.Types hiding (SemanticTokenAbsolute (..),-                                              SemanticTokenRelative (..),-                                              SemanticTokensEdit (..), mkRange)-import           Language.LSP.Test-import           System.FilePath-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests =-  testGroup-    "TemplateHaskell"-    [ -- Test for https://github.com/haskell/ghcide/pull/212-      testSessionWait "load" $ do-        let sourceA =-              T.unlines-                [ "{-# LANGUAGE PackageImports #-}",-                  "{-# LANGUAGE TemplateHaskell #-}",-                  "module A where",-                  "import \"template-haskell\" Language.Haskell.TH",-                  "a :: Integer",-                  "a = $(litE $ IntegerL 3)"-                ]-            sourceB =-              T.unlines-                [ "{-# LANGUAGE PackageImports #-}",-                  "{-# LANGUAGE TemplateHaskell #-}",-                  "module B where",-                  "import A",-                  "import \"template-haskell\" Language.Haskell.TH",-                  "b :: Integer",-                  "b = $(litE $ IntegerL $ a) + n"-                ]-        _ <- createDoc "A.hs" "haskell" sourceA-        _ <- createDoc "B.hs" "haskell" sourceB-        expectDiagnostics [ ( "B.hs", [(DiagnosticSeverity_Error, (6, 29), "Variable not in scope: n")] ) ]-    , testSessionWait "newtype-closure" $ do-        let sourceA =-              T.unlines-                [ "{-# LANGUAGE DeriveDataTypeable #-}"-                  ,"{-# LANGUAGE TemplateHaskell #-}"-                  ,"module A (a) where"-                  ,"import Data.Data"-                  ,"import Language.Haskell.TH"-                  ,"newtype A = A () deriving (Data)"-                  ,"a :: ExpQ"-                  ,"a = [| 0 |]"]-        let sourceB =-              T.unlines-                [ "{-# LANGUAGE TemplateHaskell #-}"-                ,"module B where"-                ,"import A"-                ,"b :: Int"-                ,"b = $( a )" ]-        _ <- createDoc "A.hs" "haskell" sourceA-        _ <- createDoc "B.hs" "haskell" sourceB-        return ()-    , thReloadingTest False-    , thLoadingTest-    , thCoreTest-    , ignoreInWindowsBecause "Broken in windows" $ thReloadingTest True-    -- Regression test for https://github.com/haskell/haskell-language-server/issues/891-    , thLinkingTest False-    , ignoreInWindowsBecause "Broken in windows" $ thLinkingTest True-    , testSessionWait "findsTHIdentifiers" $ do-        let sourceA =-              T.unlines-                [ "{-# LANGUAGE TemplateHaskell #-}"-                , "module A (a) where"-                , "import Language.Haskell.TH (ExpQ)"-                , "a :: ExpQ" -- TH 2.17 requires an explicit type signature since splices are polymorphic-                , "a = [| glorifiedID |]"-                , "glorifiedID :: a -> a"-                , "glorifiedID = id" ]-        let sourceB =-              T.unlines-                [ "{-# OPTIONS_GHC -Wall #-}"-                , "{-# LANGUAGE TemplateHaskell #-}"-                , "module B where"-                , "import A"-                , "main = $a (putStrLn \"success!\")"]-        _ <- createDoc "A.hs" "haskell" sourceA-        _ <- createDoc "B.hs" "haskell" sourceB-        expectDiagnostics [ ( "B.hs", [(DiagnosticSeverity_Warning, (4, 0), "Top-level binding with no type signature: main :: IO ()")] ) ]-    , testCase "findsTHnewNameConstructor" $ runWithExtraFiles "THNewName" $ \dir -> do--    -- This test defines a TH value with the meaning "data A = A" in A.hs-    -- Loads and export the template in B.hs-    -- And checks wether the constructor A can be loaded in C.hs-    -- This test does not fail when either A and B get manually loaded before C.hs-    -- or when we remove the seemingly unnecessary TH pragma from C.hs--    let cPath = dir </> "C.hs"-    _ <- openDoc cPath "haskell"-    expectDiagnostics [ ( cPath, [(DiagnosticSeverity_Warning, (3, 0), "Top-level binding with no type signature: a :: A")] ) ]-    ]----- | Test that all modules have linkables-thLoadingTest :: TestTree-thLoadingTest = testCase "Loading linkables" $ runWithExtraFiles "THLoading" $ \dir -> do-    let thb = dir </> "THB.hs"-    _ <- openDoc thb "haskell"-    expectNoMoreDiagnostics 1--thCoreTest :: TestTree-thCoreTest = testCase "Verifying TH core files" $ runWithExtraFiles "THCoreFile" $ \dir -> do-    let thc = dir </> "THC.hs"-    _ <- openDoc thc "haskell"-    expectNoMoreDiagnostics 1---- | test that TH is reevaluated on typecheck-thReloadingTest :: Bool -> TestTree-thReloadingTest unboxed = testCase name $ runWithExtraFiles dir $ \dir -> do--    let aPath = dir </> "THA.hs"-        bPath = dir </> "THB.hs"-        cPath = dir </> "THC.hs"--    aSource <- liftIO $ readFileUtf8 aPath --  th = [d|a = ()|]-    bSource <- liftIO $ readFileUtf8 bPath --  $th-    cSource <- liftIO $ readFileUtf8 cPath --  c = a :: ()--    adoc <- createDoc aPath "haskell" aSource-    bdoc <- createDoc bPath "haskell" bSource-    cdoc <- createDoc cPath "haskell" cSource--    expectDiagnostics [("THB.hs", [(DiagnosticSeverity_Warning, (4,thDollarIdx), "Top-level binding")])]--    -- Change th from () to Bool-    let aSource' = T.unlines $ init (T.lines aSource) ++ ["th_a = [d| a = False|]"]-    changeDoc adoc [TextDocumentContentChangeEvent . InR . (.==) #text $ aSource']-    -- generate an artificial warning to avoid timing out if the TH change does not propagate-    changeDoc cdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ cSource <> "\nfoo=()"]--    -- Check that the change propagates to C-    expectDiagnostics-        [("THC.hs", [(DiagnosticSeverity_Error, (4, 4), "Couldn't match expected type '()' with actual type 'Bool'")])-        ,("THC.hs", [(DiagnosticSeverity_Warning, (6,0), "Top-level binding")])-        ,("THB.hs", [(DiagnosticSeverity_Warning, (4,thDollarIdx), "Top-level bindin")])-        ]--    closeDoc adoc-    closeDoc bdoc-    closeDoc cdoc-  where-    name = "reloading-th-test" <> if unboxed then "-unboxed" else ""-    dir | unboxed = "THUnboxed"-        | otherwise = "TH"--thLinkingTest :: Bool -> TestTree-thLinkingTest unboxed = testCase name $ runWithExtraFiles dir $ \dir -> do--    let aPath = dir </> "THA.hs"-        bPath = dir </> "THB.hs"--    aSource <- liftIO $ readFileUtf8 aPath --  th_a = [d|a :: ()|]-    bSource <- liftIO $ readFileUtf8 bPath --  $th_a--    adoc <- createDoc aPath "haskell" aSource-    bdoc <- createDoc bPath "haskell" bSource--    expectDiagnostics [("THB.hs", [(DiagnosticSeverity_Warning, (4,thDollarIdx), "Top-level binding")])]--    let aSource' = T.unlines $ init (init (T.lines aSource)) ++ ["th :: DecsQ", "th = [d| a = False|]"]-    changeDoc adoc [TextDocumentContentChangeEvent . InR . (.==) #text $ aSource']--    -- modify b too-    let bSource' = T.unlines $ init (T.lines bSource) ++ ["$th"]-    changeDoc bdoc [TextDocumentContentChangeEvent . InR . (.==) #text $ bSource']-    waitForProgressBegin-    waitForAllProgressDone--    expectCurrentDiagnostics bdoc [(DiagnosticSeverity_Warning, (4,thDollarIdx), "Top-level binding")]--    closeDoc adoc-    closeDoc bdoc-  where-    name = "th-linking-test" <> if unboxed then "-unboxed" else ""-    dir | unboxed = "THUnboxed"-        | otherwise = "TH"
− test/exe/TestUtils.hs
@@ -1,325 +0,0 @@--{-# LANGUAGE GADTs           #-}-{-# LANGUAGE PatternSynonyms #-}--module TestUtils where--import           Control.Applicative.Combinators-import           Control.Concurrent.Async-import           Control.Exception               (bracket_, finally)-import           Control.Lens                    ((.~))-import qualified Control.Lens                    as Lens-import qualified Control.Lens.Extras             as Lens-import           Control.Monad-import           Control.Monad.IO.Class          (liftIO)-import           Data.Foldable-import           Data.Function                   ((&))-import           Data.Maybe-import qualified Data.Text                       as T-import           Development.IDE.GHC.Compat      (GhcVersion (..), ghcVersion)-import           Development.IDE.GHC.Util-import qualified Development.IDE.Main            as IDE-import           Development.IDE.Test            (canonicalizeUri,-                                                  configureCheckProject,-                                                  expectNoMoreDiagnostics)-import           Development.IDE.Test.Runfiles-import           Development.IDE.Types.Location-import           Development.Shake               (getDirectoryFilesIO)-import           Ide.Logger                      (Recorder, WithPriority,-                                                  cmapWithPrio)-import qualified Language.LSP.Protocol.Lens      as L-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           System.Directory-import           System.Environment.Blank        (getEnv, setEnv, unsetEnv)-import           System.FilePath-import           System.Info.Extra               (isMac, isWindows)-import qualified System.IO.Extra-import           System.Process.Extra            (createPipe)-import           Test.Tasty-import           Test.Tasty.ExpectedFailure-import           Test.Tasty.HUnit--import           LogType---- | Wait for the next progress begin step-waitForProgressBegin :: Session ()-waitForProgressBegin = skipManyTill anyMessage $ satisfyMaybe $ \case-  FromServerMess  SMethod_Progress  (TNotificationMessage _ _ (ProgressParams _ v)) | Lens.is _workDoneProgressBegin v-> Just ()-  _ -> Nothing---- | Wait for the first progress end step--- Also implemented in hls-test-utils Test.Hls-waitForProgressDone :: Session ()-waitForProgressDone = skipManyTill anyMessage $ satisfyMaybe $ \case-  FromServerMess  SMethod_Progress  (TNotificationMessage _ _ (ProgressParams _ v)) | Lens.is _workDoneProgressEnd v -> Just ()-  _ -> Nothing---- | Wait for all progress to be done--- Needs at least one progress done notification to return--- Also implemented in hls-test-utils Test.Hls-waitForAllProgressDone :: Session ()-waitForAllProgressDone = loop-  where-    loop = do-      ~() <- skipManyTill anyMessage $ satisfyMaybe $ \case-        FromServerMess  SMethod_Progress  (TNotificationMessage _ _ (ProgressParams _ v)) |Lens.is _workDoneProgressEnd v-> Just ()-        _ -> Nothing-      done <- null <$> getIncompleteProgressSessions-      unless done loop--run :: Session a -> IO a-run s = run' (const s)--run' :: (FilePath -> Session a) -> IO a-run' s = withTempDir $ \dir -> runInDir dir (s dir)--runInDir :: FilePath -> Session a -> IO a-runInDir dir = runInDir' dir "." "." []---- | Takes a directory as well as relative paths to where we should launch the executable as well as the session root.-runInDir' :: FilePath -> FilePath -> FilePath -> [String] -> Session a -> IO a-runInDir' = runInDir'' lspTestCaps--runInDir''-    :: ClientCapabilities-    -> FilePath-    -> FilePath-    -> FilePath-    -> [String]-    -> Session b-    -> IO b-runInDir'' lspCaps dir startExeIn startSessionIn extraOptions s = do--  ghcideExe <- locateGhcideExecutable-  let startDir = dir </> startExeIn-  let projDir = dir </> startSessionIn--  createDirectoryIfMissing True startDir-  createDirectoryIfMissing True projDir-  -- 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 $ projDir ++ "/Data"--  shakeProfiling <- getEnv "SHAKE_PROFILING"-  let cmd = unwords $-       [ghcideExe, "--lsp", "--test", "--verify-core-file", "--verbose", "-j2", "--cwd", startDir-       ] ++ ["--shake-profiling=" <> dir | Just dir <- [shakeProfiling]-       ] ++ extraOptions-  -- HIE calls getXgdDirectory which assumes that HOME is set.-  -- Only sets HOME if it wasn't already set.-  setEnv "HOME" "/homeless-shelter" False-  conf <- getConfigFromEnv-  runSessionWithConfig conf cmd lspCaps projDir $ do-      configureCheckProject False-      s---- | Version of 'System.IO.Extra.withTempDir' that canonicalizes the path--- Which we need to do on macOS since the $TMPDIR can be in @/private/var@ or--- @/var@-withTempDir :: (FilePath -> IO a) -> IO a-withTempDir f = System.IO.Extra.withTempDir $ \dir -> do-  dir' <- canonicalizePath dir-  f dir'--lspTestCaps :: ClientCapabilities-lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities (Just True) Nothing Nothing }--getConfigFromEnv :: IO SessionConfig-getConfigFromEnv = do-  logColor <- fromMaybe True <$> checkEnv "LSP_TEST_LOG_COLOR"-  timeoutOverride <- fmap read <$> getEnv "LSP_TIMEOUT"-  return defaultConfig-    { messageTimeout = fromMaybe (messageTimeout defaultConfig) timeoutOverride-    , logColor-    }-  where-    checkEnv :: String -> IO (Maybe Bool)-    checkEnv s = fmap convertVal <$> getEnv s-    convertVal "0" = False-    convertVal _   = True--testSessionWait :: HasCallStack => String -> Session () -> TestTree-testSessionWait name = testSession name .-      -- 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)--testSession :: String -> Session () -> TestTree-testSession name = testCase name . run--xfail :: TestTree -> String -> TestTree-xfail = flip expectFailBecause--ignoreInWindowsBecause :: String -> TestTree -> TestTree-ignoreInWindowsBecause = ignoreFor (BrokenForOS Windows)--ignoreForGHC92Plus :: String -> TestTree -> TestTree-ignoreForGHC92Plus = ignoreFor (BrokenForGHC [GHC92, GHC94, GHC96, GHC98])--knownBrokenForGhcVersions :: [GhcVersion] -> String -> TestTree -> TestTree-knownBrokenForGhcVersions ghcVers = knownBrokenFor (BrokenForGHC ghcVers)--data BrokenOS = Linux | MacOS | Windows deriving (Show)--data IssueSolution = Broken | Ignore deriving (Show)--data BrokenTarget =-    BrokenSpecific BrokenOS [GhcVersion]-    -- ^Broken for `BrokenOS` with `GhcVersion`-    | BrokenForOS BrokenOS-    -- ^Broken for `BrokenOS`-    | BrokenForGHC [GhcVersion]-    -- ^Broken for `GhcVersion`-    deriving (Show)---- | Ignore test for specific os and ghc with reason.-ignoreFor :: BrokenTarget -> String -> TestTree -> TestTree-ignoreFor = knownIssueFor Ignore---- | Known broken for specific os and ghc with reason.-knownBrokenFor :: BrokenTarget -> String -> TestTree -> TestTree-knownBrokenFor = knownIssueFor Broken---- | Deal with `IssueSolution` for specific OS and GHC.-knownIssueFor :: IssueSolution -> BrokenTarget -> String -> TestTree -> TestTree-knownIssueFor solution = go . \case-    BrokenSpecific bos vers -> isTargetOS bos && isTargetGhc vers-    BrokenForOS bos         -> isTargetOS bos-    BrokenForGHC vers       -> isTargetGhc vers-    where-        isTargetOS = \case-            Windows -> isWindows-            MacOS   -> isMac-            Linux   -> not isWindows && not isMac--        isTargetGhc = elem ghcVersion--        go True = case solution of-            Broken -> expectFailBecause-            Ignore -> ignoreTestBecause-        go False = const id--data Expect-  = ExpectRange Range -- Both gotoDef and hover should report this range-  | ExpectLocation Location---  | ExpectDefRange Range -- Only gotoDef should report this range-  | ExpectHoverRange Range -- Only hover should report this range-  | ExpectHoverText [T.Text] -- the hover message must contain these snippets-  | ExpectHoverExcludeText [T.Text] -- the hover message must _not_ contain these snippets-  | ExpectHoverTextRegex T.Text -- the hover message must match this pattern-  | ExpectExternFail -- definition lookup in other file expected to fail-  | ExpectNoDefinitions-  | ExpectNoHover---  | ExpectExtern -- TODO: as above, but expected to succeed: need some more info in here, once we have some working examples-  deriving Eq--mkR :: UInt -> UInt -> UInt -> UInt -> Expect-mkR startLine startColumn endLine endColumn = ExpectRange $ mkRange startLine startColumn endLine endColumn--mkL :: Uri -> UInt -> UInt -> UInt -> UInt -> Expect-mkL uri startLine startColumn endLine endColumn = ExpectLocation $ Location uri $ mkRange startLine startColumn endLine endColumn----testSessionWithExtraFiles :: FilePath -> String -> (FilePath -> Session ()) -> TestTree-testSessionWithExtraFiles prefix name = testCase name . runWithExtraFiles prefix--testSession' :: String -> (FilePath -> Session ()) -> TestTree-testSession' name = testCase name . run'----mkRange :: UInt -> UInt -> UInt -> UInt -> Range-mkRange a b c d = Range (Position a b) (Position c d)---runWithExtraFiles :: FilePath -> (FilePath -> Session a) -> IO a-runWithExtraFiles prefix s = withTempDir $ \dir -> do-  copyTestDataFiles dir prefix-  runInDir dir (s dir)--copyTestDataFiles :: FilePath -> FilePath -> IO ()-copyTestDataFiles dir prefix = do-  -- Copy all the test data files to the temporary workspace-  testDataFiles <- getDirectoryFilesIO ("test/data" </> prefix) ["//*"]-  for_ testDataFiles $ \f -> do-    createDirectoryIfMissing True $ dir </> takeDirectory f-    copyFile ("test/data" </> prefix </> f) (dir </> f)--withLongTimeout :: IO a -> IO a-withLongTimeout = bracket_ (setEnv "LSP_TIMEOUT" "120" True) (unsetEnv "LSP_TIMEOUT")----lspTestCapsNoFileWatches :: ClientCapabilities-lspTestCapsNoFileWatches = lspTestCaps & L.workspace . Lens._Just . L.didChangeWatchedFiles .~ Nothing--openTestDataDoc :: FilePath -> Session TextDocumentIdentifier-openTestDataDoc path = do-  source <- liftIO $ readFileUtf8 $ "test/data" </> path-  createDoc path "haskell" source--pattern R :: UInt -> UInt -> UInt -> UInt -> Range-pattern R x y x' y' = Range (Position x y) (Position x' y')--checkDefs :: Definition |? ([DefinitionLink] |? Null) -> Session [Expect] -> Session ()-checkDefs (defToLocation -> defs) mkExpectations = traverse_ check =<< mkExpectations where-  check (ExpectRange expectedRange) = do-    def <- assertOneDefinitionFound defs-    assertRangeCorrect def expectedRange-  check (ExpectLocation expectedLocation) = do-    def <- assertOneDefinitionFound defs-    liftIO $ do-      canonActualLoc <- canonicalizeLocation def-      canonExpectedLoc <- canonicalizeLocation expectedLocation-      canonActualLoc @?= canonExpectedLoc-  check ExpectNoDefinitions = do-    liftIO $ assertBool "Expecting no definitions" $ null defs-  check ExpectExternFail = liftIO $ assertFailure "Expecting to fail to find in external file"-  check _ = pure () -- all other expectations not relevant to getDefinition--  assertOneDefinitionFound :: [Location] -> Session Location-  assertOneDefinitionFound [def] = pure def-  assertOneDefinitionFound xs = liftIO . assertFailure $ "Expecting exactly one definition, got " <> show (length xs)--  assertRangeCorrect Location{_range = foundRange} expectedRange =-    liftIO $ expectedRange @=? foundRange--canonicalizeLocation :: Location -> IO Location-canonicalizeLocation (Location uri range) = Location <$> canonicalizeUri uri <*> pure range--defToLocation :: Definition |? ([DefinitionLink] |? Null) -> [Location]-defToLocation (InL (Definition (InL l))) = [l]-defToLocation (InL (Definition (InR ls))) = ls-defToLocation (InR (InL defLink)) = (\(DefinitionLink LocationLink{_targetUri,_targetRange}) -> Location _targetUri _targetRange) <$> defLink-defToLocation (InR (InR Null)) = []---- | Ghc 9 doesn't include the $-sign in TH warnings like earlier versions did-thDollarIdx :: UInt-thDollarIdx | ghcVersion >= GHC90 = 1-            | otherwise = 0--testIde :: Recorder (WithPriority Log) -> IDE.Arguments -> Session () -> IO ()-testIde recorder arguments session = do-    config <- getConfigFromEnv-    cwd <- getCurrentDirectory-    (hInRead, hInWrite) <- createPipe-    (hOutRead, hOutWrite) <- createPipe-    let projDir = "."-    let server = IDE.defaultMain (cmapWithPrio LogIDEMain recorder) arguments-            { IDE.argsHandleIn = pure hInRead-            , IDE.argsHandleOut = pure hOutWrite-            }--    flip finally (setCurrentDirectory cwd) $ withAsync server $ \_ ->-        runSessionWithHandles hInWrite hOutRead config lspTestCaps projDir session
− test/exe/UnitTests.hs
@@ -1,110 +0,0 @@--module UnitTests (tests) where--import           Control.Concurrent-import           Control.Monad.IO.Class            (liftIO)-import           Data.IORef-import           Data.IORef.Extra                  (atomicModifyIORef_)-import           Data.List.Extra-import           Data.String                       (IsString (fromString))-import qualified Data.Text                         as T-import           Development.IDE.Core.FileStore    (getModTime)-import qualified Development.IDE.Main              as IDE-import qualified Development.IDE.Plugin.HLS.GhcIde as Ghcide-import qualified Development.IDE.Types.Diagnostics as Diagnostics-import           Development.IDE.Types.Location-import qualified FuzzySearch-import           Ide.Logger                        (Logger, Recorder,-                                                    WithPriority, cmapWithPrio)-import           Ide.PluginUtils                   (pluginDescToIdePlugins)-import           Ide.Types-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types       hiding-                                                   (SemanticTokenAbsolute (..),-                                                    SemanticTokenRelative (..),-                                                    SemanticTokensEdit (..),-                                                    mkRange)-import           Language.LSP.Test-import           LogType                           (Log (..))-import           Network.URI-import qualified Progress-import           System.IO.Extra                   hiding (withTempDir)-import           System.Mem                        (performGC)-import           Test.Tasty-import           Test.Tasty.ExpectedFailure-import           Test.Tasty.HUnit-import           TestUtils-import           Text.Printf                       (printf)--tests :: Recorder (WithPriority Log) -> Logger -> TestTree-tests recorder logger = do-  testGroup "Unit"-     [ testCase "empty file path does NOT work with the empty String literal" $-         uriToFilePath' (fromNormalizedUri $ filePathToUri' "") @?= Just "."-     , testCase "empty file path works using toNormalizedFilePath'" $-         uriToFilePath' (fromNormalizedUri $ filePathToUri' (toNormalizedFilePath' "")) @?= Just ""-     , testCase "empty path URI" $ do-         Just URI{..} <- pure $ parseURI (T.unpack $ getUri $ fromNormalizedUri emptyPathUri)-         uriScheme @?= "file:"-         uriPath @?= ""-     , testCase "from empty path URI" $ do-         let uri = Uri "file://"-         uriToFilePath' uri @?= Just ""-     , testCase "showDiagnostics prints ranges 1-based (like vscode)" $ do-         let diag = ("", Diagnostics.ShowDiag, Diagnostic-               {  _codeDescription = Nothing-                , _data_ = Nothing-                , _range = Range-                   { _start = Position{_line = 0, _character = 1}-                   , _end = Position{_line = 2, _character = 3}-                   }-               , _severity = Nothing-               , _code = Nothing-               , _source = Nothing-               , _message = ""-               , _relatedInformation = Nothing-               , _tags = Nothing-               })-         let shown = T.unpack (Diagnostics.showDiagnostics [diag])-         let expected = "1:2-3:4"-         assertBool (unwords ["expected to find range", expected, "in diagnostic", shown]) $-             expected `isInfixOf` shown-     , testCase "notification handlers run in priority order" $ do-        orderRef <- newIORef []-        let plugins = pluginDescToIdePlugins $-                [ (priorityPluginDescriptor i)-                    { pluginNotificationHandlers = mconcat-                        [ mkPluginNotificationHandler SMethod_TextDocumentDidOpen $ \_ _ _ _ ->-                            liftIO $ atomicModifyIORef_ orderRef (i:)-                        ]-                    }-                    | i <- [1..20]-                ] ++ Ghcide.descriptors (cmapWithPrio LogGhcIde recorder)-            priorityPluginDescriptor i = (defaultPluginDescriptor (fromString $ show i) ""){pluginPriority = i}--        testIde recorder (IDE.testing (cmapWithPrio LogIDEMain recorder) logger plugins) $ do-            _ <- createDoc "A.hs" "haskell" "module A where"-            waitForProgressDone-            actualOrder <- liftIO $ reverse <$> readIORef orderRef--            -- Handlers are run in priority descending order-            liftIO $ actualOrder @?= [20, 19 .. 1]-     , ignoreTestBecause "The test fails sometimes showing 10000us" $-         testCase "timestamps have millisecond resolution" $ do-           resolution_us <- findResolution_us 1-           let msg = printf "Timestamps do not have millisecond resolution: %dus" resolution_us-           assertBool msg (resolution_us <= 1000)-     , Progress.tests-     , FuzzySearch.tests-     ]--findResolution_us :: Int -> IO Int-findResolution_us delay_us | delay_us >= 1000000 = error "Unable to compute timestamp resolution"-findResolution_us delay_us = withTempFile $ \f -> withTempFile $ \f' -> do-    performGC-    writeFile f ""-    threadDelay delay_us-    writeFile f' ""-    t <- getModTime f-    t' <- getModTime f'-    if t /= t' then return delay_us else findResolution_us (delay_us * 10)
− test/exe/WatchedFileTests.hs
@@ -1,84 +0,0 @@--{-# LANGUAGE GADTs #-}--module WatchedFileTests (tests) where--import           Control.Applicative.Combinators-import           Control.Monad.IO.Class          (liftIO)-import qualified Data.Aeson                      as A-import qualified Data.Text                       as T-import           Development.IDE.Test            (expectDiagnostics)-import           Language.LSP.Protocol.Message-import           Language.LSP.Protocol.Types     hiding-                                                 (SemanticTokenAbsolute (..),-                                                  SemanticTokenRelative (..),-                                                  SemanticTokensEdit (..),-                                                  mkRange)-import           Language.LSP.Test-import           System.Directory-import           System.FilePath--- import Test.QuickCheck.Instances ()-import           Test.Tasty-import           Test.Tasty.HUnit-import           TestUtils--tests :: TestTree-tests = testGroup "watched files"-  [ testGroup "Subscriptions"-    [ testSession' "workspace files" $ \sessionDir -> do-        liftIO $ writeFile (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-isrc\", \"A\", \"WatchedFilesMissingModule\"]}}"-        _doc <- createDoc "A.hs" "haskell" "{-#LANGUAGE NoImplicitPrelude #-}\nmodule A where\nimport WatchedFilesMissingModule"-        setIgnoringRegistrationRequests False-        watchedFileRegs <- getWatchedFilesSubscriptionsUntil SMethod_TextDocumentPublishDiagnostics--        -- Expect 2 subscriptions: one for all .hs files and one for the hie.yaml cradle-        liftIO $ length watchedFileRegs @?= 2--    , testSession' "non workspace file" $ \sessionDir -> do-        tmpDir <- liftIO getTemporaryDirectory-        let yaml = "cradle: {direct: {arguments: [\"-i" <> tail(init(show tmpDir)) <> "\", \"A\", \"WatchedFilesMissingModule\"]}}"-        liftIO $ writeFile (sessionDir </> "hie.yaml") yaml-        _doc <- createDoc "A.hs" "haskell" "{-# LANGUAGE NoImplicitPrelude#-}\nmodule A where\nimport WatchedFilesMissingModule"-        setIgnoringRegistrationRequests False-        watchedFileRegs <- getWatchedFilesSubscriptionsUntil SMethod_TextDocumentPublishDiagnostics--        -- Expect 2 subscriptions: one for all .hs files and one for the hie.yaml cradle-        liftIO $ length watchedFileRegs @?= 2--    -- TODO add a test for didChangeWorkspaceFolder-    ]-  , testGroup "Changes"-    [-      testSession' "workspace files" $ \sessionDir -> do-        liftIO $ writeFile (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-isrc\", \"A\", \"B\"]}}"-        liftIO $ writeFile (sessionDir </> "B.hs") $ unlines-          ["module B where"-          ,"b :: Bool"-          ,"b = False"]-        _doc <- createDoc "A.hs" "haskell" $ T.unlines-          ["module A where"-          ,"import B"-          ,"a :: ()"-          ,"a = b"-          ]-        expectDiagnostics [("A.hs", [(DiagnosticSeverity_Error, (3, 4), "Couldn't match expected type '()' with actual type 'Bool'")])]-        -- modify B off editor-        liftIO $ writeFile (sessionDir </> "B.hs") $ unlines-          ["module B where"-          ,"b :: Int"-          ,"b = 0"]-        sendNotification SMethod_WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $-               [FileEvent (filePathToUri $ sessionDir </> "B.hs") FileChangeType_Changed ]-        expectDiagnostics [("A.hs", [(DiagnosticSeverity_Error, (3, 4), "Couldn't match expected type '()' with actual type 'Int'")])]-    ]-  ]--getWatchedFilesSubscriptionsUntil :: forall m. SServerMethod m -> Session [DidChangeWatchedFilesRegistrationOptions]-getWatchedFilesSubscriptionsUntil m = do-      msgs <- manyTill (Just <$> message SMethod_ClientRegisterCapability <|> Nothing <$ anyMessage) (message m)-      return-            [ x-            | Just TRequestMessage{_params = RegistrationParams regs} <- msgs-            , Registration _id "workspace/didChangeWatchedFiles" (Just args) <- regs-            , Just x@(DidChangeWatchedFilesRegistrationOptions _) <- [A.decode . A.encode $ args]-            ]
test/src/Development/IDE/Test.hs view
@@ -175,7 +175,7 @@  checkDiagnosticsForDoc :: HasCallStack => TextDocumentIdentifier -> [(DiagnosticSeverity, Cursor, T.Text)] -> [Diagnostic] -> Session () checkDiagnosticsForDoc TextDocumentIdentifier {_uri} expected obtained = do-    let expected' = Map.fromList [(nuri, map (\(ds, c, t) -> (ds, c, t, Nothing)) expected)]+    let expected' = Map.singleton nuri (map (\(ds, c, t) -> (ds, c, t, Nothing)) expected)         nuri = toNormalizedUri _uri     expectDiagnosticsWithTags' (return (_uri, obtained)) expected'