Skip to content

Resolve for explicit-imports #3682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ source-repository head
type: git
location: https://github.com/haskell/haskell-language-server.git

common warnings
ghc-options: -Wall -Werror

library
import: warnings
buildable: True
exposed-modules: Ide.Plugin.ExplicitImports
hs-source-dirs: src
Expand All @@ -32,8 +36,10 @@ library
, ghcide == 2.1.0.0
, hls-graph
, hls-plugin-api == 2.1.0.0
, lens
, lsp
, text
, transformers
, unordered-containers

default-language: Haskell2010
Expand All @@ -42,6 +48,7 @@ library
TypeOperators

test-suite tests
import: warnings
buildable: True
type: exitcode-stdio-1.0
default-language: Haskell2010
Expand All @@ -53,5 +60,6 @@ test-suite tests
, filepath
, hls-explicit-imports-plugin
, hls-test-utils
, lens
, lsp-types
, text
374 changes: 207 additions & 167 deletions plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs

Large diffs are not rendered by default.

69 changes: 48 additions & 21 deletions plugins/hls-explicit-imports-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ module Main
( main
) where

import Data.Foldable (find, forM_)
import Control.Lens ((^.))
import Data.Foldable (find)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Ide.Plugin.ExplicitImports as ExplicitImports
import qualified Language.LSP.Protocol.Lens as L
import Language.LSP.Protocol.Message
import System.FilePath ((<.>), (</>))
import System.FilePath ((</>))
import Test.Hls

explicitImportsPlugin :: PluginTestDescriptor ExplicitImports.Log
explicitImportsPlugin = mkPluginTestDescriptor ExplicitImports.descriptor "explicitImports"

longModule :: T.Text
longModule = "F" <> T.replicate 80 "o"

main :: IO ()
main = defaultTestRunner $
testGroup
"Make imports explicit"
[ codeActionGoldenTest "UsualCase" 3 0
[ codeActionAllGoldenTest "UsualCase" 3 0
, codeActionAllResolveGoldenTest "UsualCase" 3 0
, codeActionOnlyGoldenTest "OnlyThis" 3 0
, codeActionOnlyResolveGoldenTest "OnlyThis" 3 0
, codeLensGoldenTest "UsualCase" 0
, testCase "No CodeAction when exported" $
runSessionWithServer explicitImportsPlugin testDataDir $ do
Expand Down Expand Up @@ -65,32 +67,57 @@ main = defaultTestRunner $

-- code action tests

codeActionGoldenTest :: FilePath -> Int -> Int -> TestTree
codeActionGoldenTest fp l c = goldenWithExplicitImports fp $ \doc -> do
codeActionAllGoldenTest :: FilePath -> Int -> Int -> TestTree
codeActionAllGoldenTest fp l c = goldenWithExplicitImports " code action" fp codeActionNoResolveCaps $ \doc -> do
actions <- getCodeActions doc (pointRange l c)
case find ((== Just "Make all imports explicit") . caTitle) actions of
Just (InR x) -> executeCodeAction x
_ -> liftIO $ assertFailure "Unable to find CodeAction"

codeActionAllResolveGoldenTest :: FilePath -> Int -> Int -> TestTree
codeActionAllResolveGoldenTest fp l c = goldenWithExplicitImports " code action resolve" fp codeActionResolveCaps $ \doc -> do
actions <- getCodeActions doc (pointRange l c)
Just (InR x) <- pure $ find ((== Just "Make all imports explicit") . caTitle) actions
resolved <- resolveCodeAction x
executeCodeAction resolved

codeActionOnlyGoldenTest :: FilePath -> Int -> Int -> TestTree
codeActionOnlyGoldenTest fp l c = goldenWithExplicitImports " code action" fp codeActionNoResolveCaps $ \doc -> do
actions <- getCodeActions doc (pointRange l c)
case find ((== Just "Make this import explicit") . caTitle) actions of
Just (InR x) -> executeCodeAction x
_ -> liftIO $ assertFailure "Unable to find CodeAction"

codeActionOnlyResolveGoldenTest :: FilePath -> Int -> Int -> TestTree
codeActionOnlyResolveGoldenTest fp l c = goldenWithExplicitImports " code action resolve" fp codeActionResolveCaps $ \doc -> do
actions <- getCodeActions doc (pointRange l c)
Just (InR x) <- pure $ find ((== Just "Make this import explicit") . caTitle) actions
resolved <- resolveCodeAction x
executeCodeAction resolved

resolveCodeAction :: CodeAction -> Session CodeAction
resolveCodeAction ca = do
resolveResponse <- request SMethod_CodeActionResolve ca
Right resolved <- pure $ resolveResponse ^. L.result
pure resolved

caTitle :: (Command |? CodeAction) -> Maybe Text
caTitle (InR CodeAction {_title}) = Just _title
caTitle _ = Nothing

-- code lens tests

codeLensGoldenTest :: FilePath -> Int -> TestTree
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe these things could also go in hls-test-utils? Seems like you've been repeating something similar a few times. Good to deduplicate the test stuff too!

codeLensGoldenTest fp codeLensIdx = goldenWithExplicitImports fp $ \doc -> do
codeLens <- (!! codeLensIdx) <$> getCodeLensesBy isExplicitImports doc
mapM_ executeCmd
[c | CodeLens{_command = Just c} <- [codeLens]]

getCodeLensesBy :: (CodeLens -> Bool) -> TextDocumentIdentifier -> Session [CodeLens]
getCodeLensesBy f doc = filter f <$> getCodeLenses doc
codeLensGoldenTest fp _ = goldenWithExplicitImports " code lens" fp codeActionNoResolveCaps $ \doc -> do
(codeLens: _) <- getCodeLenses doc
CodeLens {_command = Just c} <- resolveCodeLens codeLens
executeCmd c

isExplicitImports :: CodeLens -> Bool
isExplicitImports (CodeLens _ (Just (Command _ cmd _)) _)
| ":explicitImports:" `T.isInfixOf` cmd = True
isExplicitImports _ = False
resolveCodeLens :: CodeLens -> Session CodeLens
resolveCodeLens cl = do
resolveResponse <- request SMethod_CodeLensResolve cl
Right resolved <- pure $ resolveResponse ^. L.result
pure resolved

-- Execute command and wait for result
executeCmd :: Command -> Session ()
Expand All @@ -102,8 +129,8 @@ executeCmd cmd = do

-- helpers

goldenWithExplicitImports :: FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree
goldenWithExplicitImports fp = goldenWithHaskellDoc explicitImportsPlugin (fp <> " (golden)") testDataDir fp "expected" "hs"
goldenWithExplicitImports :: String -> FilePath -> ClientCapabilities -> (TextDocumentIdentifier -> Session ()) -> TestTree
goldenWithExplicitImports title fp caps = goldenWithHaskellAndCaps caps explicitImportsPlugin (fp <> title <> " (golden)") testDataDir fp "expected" "hs"

testDataDir :: String
testDataDir = "test" </> "testdata"
Expand Down
7 changes: 7 additions & 0 deletions plugins/hls-explicit-imports-plugin/test/testdata/B.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module B where

b1 :: String
b1 = "b1"

b2 :: String
b2 = "b2"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module OnlyThis where

import A ( a1 )
import B

main :: IO ()
main = putStrLn $ "hello " ++ a1 ++ b1
7 changes: 7 additions & 0 deletions plugins/hls-explicit-imports-plugin/test/testdata/OnlyThis.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module OnlyThis where

import A
import B

main :: IO ()
main = putStrLn $ "hello " ++ a1 ++ b1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Main where
module UsualCase where

import A ( a1 )

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Main where
module UsualCase where

import A

Expand Down
3 changes: 3 additions & 0 deletions plugins/hls-explicit-imports-plugin/test/testdata/hie.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

cradle:
direct:
arguments:
- OnlyThis.hs
- UsualCase.hs
- Exported.hs
- A.hs
- B.hs
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ instance NFData RefineImportsResult where rnf = rwhnf
refineImportsRule :: Recorder (WithPriority Log) -> Rules ()
refineImportsRule recorder = define (cmapWithPrio LogShake recorder) $ \RefineImports nfp -> do
-- Get the typechecking artifacts from the module
tmr <- use TypeCheck nfp
Just tmr <- use TypeCheck nfp
-- We also need a GHC session with all the dependencies
hsc <- use GhcSessionDeps nfp
Just hsc <- use GhcSessionDeps nfp

-- 2 layer map ModuleName -> ModuleName -> [Avails] (exports)
import2Map <- do
Expand All @@ -205,7 +205,7 @@ refineImportsRule recorder = define (cmapWithPrio LogShake recorder) $ \RefineIm
-- We shouldn't blindly refine imports
-- instead we should generate imports statements
-- for modules/symbols actually got used
(imports, mbMinImports) <- liftIO $ extractMinimalImports hsc tmr
Just (imports, mbMinImports) <- liftIO $ extractMinimalImports hsc tmr

let filterByImport
:: LImportDecl GhcRn
Expand Down Expand Up @@ -259,7 +259,7 @@ refineImportsRule recorder = define (cmapWithPrio LogShake recorder) $ \RefineIm
. Map.toList
$ filteredInnerImports)
-- for every minimal imports
| Just minImports <- [mbMinImports]
| minImports <- [mbMinImports]
, i@(L _ ImportDecl{ideclName = L _ mn}) <- minImports
-- we check for the inner imports
, Just innerImports <- [Map.lookup mn import2Map]
Expand All @@ -268,7 +268,7 @@ refineImportsRule recorder = define (cmapWithPrio LogShake recorder) $ \RefineIm
-- if no symbols from this modules then don't need to generate new import
, not $ null filteredInnerImports
]
return ([], RefineImportsResult res <$ mbMinImports)
return ([], Just $ RefineImportsResult res)

where
-- Check if a name is exposed by AvailInfo (the available information of a module)
Expand Down