Skip to content

Commit 9fa6abe

Browse files
Not suggest exported imports (#2329)
* Not suggest exported imports * Add testing explicit-imports-plugin to github workflow * Add comment Co-authored-by: Michael Peyton Jones <me@michaelpj.com> Co-authored-by: Michael Peyton Jones <me@michaelpj.com>
1 parent a0cd84b commit 9fa6abe

File tree

9 files changed

+157
-2
lines changed

9 files changed

+157
-2
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ jobs:
208208
name: Test hls-refine-imports-plugin test suite
209209
run: cabal test hls-refine-imports-plugin --test-options="-j1 --rerun-update" || cabal test hls-refine-imports-plugin --test-options="-j1 --rerun" || LSP_TEST_LOG_COLOR=0 LSP_TEST_LOG_MESSAGES=true LSP_TEST_LOG_STDERR=true cabal test hls-refine-imports-plugin --test-options="-j1 --rerun"
210210

211+
- if: matrix.test
212+
name: Test hls-explicit-imports-plugin test suite
213+
run: cabal test hls-explicit-imports-plugin --test-options="-j1 --rerun-update" || cabal test hls-explicit-imports-plugin --test-options="-j1 --rerun" || LSP_TEST_LOG_COLOR=0 LSP_TEST_LOG_MESSAGES=true LSP_TEST_LOG_STDERR=true cabal test hls-explicit-imports-plugin --test-options="-j1 --rerun"
214+
211215
- if: matrix.test
212216
name: Test hls-call-hierarchy-plugin test suite
213217
run: cabal test hls-call-hierarchy-plugin --test-options="-j1 --rerun-update" || cabal test hls-call-hierarchy-plugin --test-options="-j1 --rerun" || LSP_TEST_LOG_COLOR=0 LSP_TEST_LOG_MESSAGES=true LSP_TEST_LOG_STDERR=true cabal test hls-call-hierarchy-plugin --test-options="-j1 --rerun"

plugins/hls-explicit-imports-plugin/hls-explicit-imports-plugin.cabal

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,16 @@ library
3232
default-extensions:
3333
DataKinds
3434
TypeOperators
35+
36+
test-suite tests
37+
type: exitcode-stdio-1.0
38+
default-language: Haskell2010
39+
hs-source-dirs: test
40+
main-is: Main.hs
41+
ghc-options: -threaded -rtsopts -with-rtsopts=-N
42+
build-depends:
43+
, base
44+
, filepath
45+
, hls-explicit-imports-plugin
46+
, hls-test-utils
47+
, text

plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import Data.Aeson.Types (FromJSON)
2323
import qualified Data.HashMap.Strict as HashMap
2424
import Data.IORef (readIORef)
2525
import qualified Data.Map.Strict as Map
26-
import Data.Maybe (catMaybes, fromMaybe)
26+
import Data.Maybe (catMaybes, fromMaybe,
27+
isJust)
2728
import qualified Data.Text as T
2829
import Development.IDE hiding (pluginHandlers,
2930
pluginRules)
@@ -175,6 +176,13 @@ instance Show MinimalImportsResult where show _ = "<minimalImportsResult>"
175176

176177
instance NFData MinimalImportsResult where rnf = rwhnf
177178

179+
exportedModuleStrings :: ParsedModule -> [String]
180+
exportedModuleStrings ParsedModule{pm_parsed_source = L _ HsModule{..}}
181+
| Just export <- hsmodExports,
182+
exports <- unLoc export
183+
= map show exports
184+
exportedModuleStrings _ = []
185+
178186
minimalImportsRule :: Rules ()
179187
minimalImportsRule = define $ \MinimalImports nfp -> do
180188
-- Get the typechecking artifacts from the module
@@ -207,19 +215,29 @@ extractMinimalImports (Just hsc) (Just TcModuleResult {..}) = do
207215
let tcEnv = tmrTypechecked
208216
(_, imports, _, _) = tmrRenamed
209217
ParsedModule {pm_parsed_source = L loc _} = tmrParsed
218+
emss = exportedModuleStrings tmrParsed
210219
span = fromMaybe (error "expected real") $ realSpan loc
220+
-- Don't make suggestions for modules which are also exported, the user probably doesn't want this!
221+
-- See https://github.com/haskell/haskell-language-server/issues/2079
222+
let notExportedImports = filter (notExported emss) imports
211223

212224
-- GHC is secretly full of mutable state
213225
gblElts <- readIORef (tcg_used_gres tcEnv)
214226

215227
-- call findImportUsage does exactly what we need
216228
-- GHC is full of treats like this
217-
let usage = findImportUsage imports gblElts
229+
let usage = findImportUsage notExportedImports gblElts
218230
(_, minimalImports) <-
219231
initTcWithGbl (hscEnv hsc) tcEnv span $ getMinimalImports usage
220232

221233
-- return both the original imports and the computed minimal ones
222234
return (imports, minimalImports)
235+
where
236+
notExported :: [String] -> LImportDecl GhcRn -> Bool
237+
notExported [] _ = True
238+
notExported exports (L _ ImportDecl{ideclName = L _ name}) =
239+
not $ any (\e -> ("module " ++ moduleNameString name) == e) exports
240+
notExported _ _ = False
223241
extractMinimalImports _ _ = return ([], Nothing)
224242

225243
mkExplicitEdit :: (ModuleName -> Bool) -> PositionMapping -> LImportDecl pass -> T.Text -> Maybe TextEdit
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{-# LANGUAGE DisambiguateRecordFields #-}
2+
{-# LANGUAGE NamedFieldPuns #-}
3+
{-# LANGUAGE OverloadedStrings #-}
4+
{-# LANGUAGE TypeOperators #-}
5+
{-# LANGUAGE ViewPatterns #-}
6+
7+
module Main
8+
( main
9+
) where
10+
11+
import Data.Foldable (find, forM_)
12+
import Data.Text (Text)
13+
import qualified Data.Text as T
14+
import qualified Ide.Plugin.ExplicitImports as ExplicitImports
15+
import System.FilePath ((<.>), (</>))
16+
import Test.Hls
17+
18+
explicitImportsPlugin :: PluginDescriptor IdeState
19+
explicitImportsPlugin = ExplicitImports.descriptor "explicitImports"
20+
21+
22+
main :: IO ()
23+
main = defaultTestRunner $
24+
testGroup
25+
"Refine Imports"
26+
[ codeActionGoldenTest "UsualCase" 3 0
27+
, codeLensGoldenTest "UsualCase" 0
28+
, testCase "No CodeAction when exported" $
29+
runSessionWithServer explicitImportsPlugin testDataDir $ do
30+
doc <- openDoc "Exported.hs" "haskell"
31+
action <- getCodeActions doc (pointRange 3 0)
32+
liftIO $ action @?= []
33+
, testCase "No CodeLens when exported" $
34+
runSessionWithServer explicitImportsPlugin testDataDir $ do
35+
doc <- openDoc "Exported.hs" "haskell"
36+
lenses <- getCodeLenses doc
37+
liftIO $ lenses @?= []
38+
]
39+
40+
-- code action tests
41+
42+
codeActionGoldenTest :: FilePath -> Int -> Int -> TestTree
43+
codeActionGoldenTest fp l c = goldenWithExplicitImports fp $ \doc -> do
44+
actions <- getCodeActions doc (pointRange l c)
45+
case find ((== Just "Make all imports explicit") . caTitle) actions of
46+
Just (InR x) -> executeCodeAction x
47+
_ -> liftIO $ assertFailure "Unable to find CodeAction"
48+
49+
caTitle :: (Command |? CodeAction) -> Maybe Text
50+
caTitle (InR CodeAction {_title}) = Just _title
51+
caTitle _ = Nothing
52+
53+
-- code lens tests
54+
55+
codeLensGoldenTest :: FilePath -> Int -> TestTree
56+
codeLensGoldenTest fp codeLensIdx = goldenWithExplicitImports fp $ \doc -> do
57+
codeLens <- (!! codeLensIdx) <$> getCodeLensesBy isExplicitImports doc
58+
mapM_ executeCmd
59+
[c | CodeLens{_command = Just c} <- [codeLens]]
60+
61+
getCodeLensesBy :: (CodeLens -> Bool) -> TextDocumentIdentifier -> Session [CodeLens]
62+
getCodeLensesBy f doc = filter f <$> getCodeLenses doc
63+
64+
isExplicitImports :: CodeLens -> Bool
65+
isExplicitImports (CodeLens _ (Just (Command _ cmd _)) _)
66+
| ":explicitImports:" `T.isInfixOf` cmd = True
67+
isExplicitImports _ = False
68+
69+
-- Execute command and wait for result
70+
executeCmd :: Command -> Session ()
71+
executeCmd cmd = do
72+
executeCommand cmd
73+
_resp <- skipManyTill anyMessage (message SWorkspaceApplyEdit)
74+
-- liftIO $ print _resp
75+
return ()
76+
77+
-- helpers
78+
79+
goldenWithExplicitImports :: FilePath -> (TextDocumentIdentifier -> Session ()) -> TestTree
80+
goldenWithExplicitImports fp = goldenWithHaskellDoc explicitImportsPlugin (fp <> " (golden)") testDataDir fp "expected" "hs"
81+
82+
testDataDir :: String
83+
testDataDir = "test" </> "testdata"
84+
85+
pointRange :: Int -> Int -> Range
86+
pointRange
87+
(subtract 1 -> line)
88+
(subtract 1 -> col) =
89+
Range (Position line col) (Position line $ col + 1)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module A where
2+
3+
a1 :: String
4+
a1 = "a1"
5+
6+
a2 :: String
7+
a2 = "a2"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Exported (module A) where
2+
3+
import A
4+
5+
main :: IO ()
6+
main = putStrLn $ "hello " ++ a1
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Main where
2+
3+
import A ( a1 )
4+
5+
main :: IO ()
6+
main = putStrLn $ "hello " ++ a1
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Main where
2+
3+
import A
4+
5+
main :: IO ()
6+
main = putStrLn $ "hello " ++ a1
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cradle:
2+
direct:
3+
arguments:
4+
- UsualCase.hs
5+
- Exported.hs
6+
- A.hs

0 commit comments

Comments
 (0)