Skip to content

Commit d621fc4

Browse files
3kyromergify[bot]
andauthored
Add qualified imports in postfix position when ImportQualifiedPost and WarnPrePositiveQualifiedModule are set (#3399)
* Import qualified in postfix when applicable * Dont import `EnumSet` * Add `QualifiedImportStyle` * Add qualified import test * Ignore functional code action tests for windows-9.4.2 The test fails with: test\functional\FunctionalCodeAction.hs:116: expected: "Not in scope: \8216Control.when\8217\nNo module named \8216Control\8217 is imported." but got: "Variable not in scope: when :: Bool -> IO () -> IO ()" Seems like diagnostics for missing variables differ * Add `importQualifiedPostTests` Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 6e76fce commit d621fc4

File tree

5 files changed

+96
-17
lines changed

5 files changed

+96
-17
lines changed

plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ extendImportHandler' ideState ExtendImport {..}
240240
extendImport (T.unpack <$> thingParent) (T.unpack newThing) (makeDeltaAst imp)
241241

242242
Nothing -> do
243-
let n = newImport importName sym importQual False
243+
let qns = (,) <$> importQual <*> Just (qualifiedImportStyle df)
244+
n = newImport importName sym qns False
244245
sym = if isNothing importQual then Just it else Nothing
245246
it = case thingParent of
246247
Nothing -> newThing
@@ -1417,8 +1418,8 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
14171418
| otherwise -> []
14181419
where moduleText = moduleNameText identInfo
14191420

1420-
suggestNewImport :: ExportsMap -> Annotated ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, TextEdit)]
1421-
suggestNewImport packageExportsMap ps fileContents Diagnostic{_message}
1421+
suggestNewImport :: DynFlags -> ExportsMap -> Annotated ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, TextEdit)]
1422+
suggestNewImport df packageExportsMap ps fileContents Diagnostic{_message}
14221423
| msg <- unifySpaces _message
14231424
, Just thingMissing <- extractNotInScopeName msg
14241425
, qual <- extractQualifiedModuleName msg
@@ -1430,16 +1431,17 @@ suggestNewImport packageExportsMap ps fileContents Diagnostic{_message}
14301431
, Just (range, indent) <- newImportInsertRange ps fileContents
14311432
, extendImportSuggestions <- matchRegexUnifySpaces msg
14321433
"Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"
1433-
= let suggestions = nubSortBy simpleCompareImportSuggestion
1434-
(constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions) in
1434+
= let qis = qualifiedImportStyle df
1435+
suggestions = nubSortBy simpleCompareImportSuggestion
1436+
(constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions qis) in
14351437
map (\(ImportSuggestion _ kind (unNewImport -> imp)) -> (imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))) suggestions
14361438
where
14371439
L _ HsModule {..} = astA ps
1438-
suggestNewImport _ _ _ _ = []
1440+
suggestNewImport _ _ _ _ _ = []
14391441

14401442
constructNewImportSuggestions
1441-
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> [ImportSuggestion]
1442-
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules = nubOrdBy simpleCompareImportSuggestion
1443+
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> QualifiedImportStyle -> [ImportSuggestion]
1444+
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules qis = nubOrdBy simpleCompareImportSuggestion
14431445
[ suggestion
14441446
| Just name <- [T.stripPrefix (maybe "" (<> ".") qual) $ notInScope thingMissing] -- strip away qualified module names from the unknown name
14451447
, identInfo <- maybe [] Set.toList $ (lookupOccEnv (getExportsMap exportsMap) (mkVarOrDataOcc name)) <> (lookupOccEnv (getExportsMap exportsMap) (mkTypeOcc name)) -- look up the modified unknown name in the export map
@@ -1451,7 +1453,7 @@ constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules =
14511453
renderNewImport :: IdentInfo -> [ImportSuggestion]
14521454
renderNewImport identInfo
14531455
| Just q <- qual
1454-
= [ImportSuggestion importanceScore (quickFixImportKind "new.qualified") (newQualImport m q)]
1456+
= [ImportSuggestion importanceScore (quickFixImportKind "new.qualified") (newQualImport m q qis)]
14551457
| otherwise
14561458
= [ImportSuggestion importanceScore (quickFixImportKind' "new" importStyle) (newUnqualImport m (renderImportStyle importStyle) False)
14571459
| importStyle <- NE.toList $ importStyles identInfo] ++
@@ -1629,10 +1631,10 @@ checkPragma name = check
16291631
newImport
16301632
:: T.Text -- ^ module name
16311633
-> Maybe T.Text -- ^ the symbol
1632-
-> Maybe T.Text -- ^ qualified name
1634+
-> Maybe (T.Text, QualifiedImportStyle) -- ^ qualified name and style
16331635
-> Bool -- ^ the symbol is to be imported or hidden
16341636
-> NewImport
1635-
newImport modName mSymbol mQual hiding = NewImport impStmt
1637+
newImport modName mSymbol mQualNameStyle hiding = NewImport impStmt
16361638
where
16371639
symImp
16381640
| Just symbol <- mSymbol
@@ -1641,14 +1643,18 @@ newImport modName mSymbol mQual hiding = NewImport impStmt
16411643
| otherwise = ""
16421644
impStmt =
16431645
"import "
1644-
<> maybe "" (const "qualified ") mQual
1645-
<> modName
1646+
<> qualifiedModName (snd <$> mQualNameStyle)
16461647
<> (if hiding then " hiding" else "")
16471648
<> symImp
16481649
<> maybe "" (\qual -> if modName == qual then "" else " as " <> qual) mQual
1650+
mQual = fst <$> mQualNameStyle
1651+
qualifiedModName Nothing = modName
1652+
qualifiedModName (Just QualifiedImportPrefix) = "qualified " <> modName
1653+
qualifiedModName (Just QualifiedImportPostfix) = modName <> " qualified"
16491654

1650-
newQualImport :: T.Text -> T.Text -> NewImport
1651-
newQualImport modName qual = newImport modName Nothing (Just qual) False
1655+
1656+
newQualImport :: T.Text -> T.Text -> QualifiedImportStyle -> NewImport
1657+
newQualImport modName qual qis = newImport modName Nothing (Just (qual, qis)) False
16521658

16531659
newUnqualImport :: T.Text -> T.Text -> Bool -> NewImport
16541660
newUnqualImport modName symbol = newImport modName (Just symbol) Nothing

plugins/hls-refactor-plugin/src/Development/IDE/Plugin/Plugins/ImportUtils.hs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ module Development.IDE.Plugin.Plugins.ImportUtils
44
quickFixImportKind,
55
renderImportStyle,
66
unImportStyle,
7-
importStyles
7+
importStyles,
8+
QualifiedImportStyle(..),
9+
qualifiedImportStyle
810
) where
911

1012
import Data.List.NonEmpty (NonEmpty ((:|)))
1113
import qualified Data.Text as T
14+
import Development.IDE.GHC.Compat
1215
import Development.IDE.Plugin.CodeAction.ExactPrint (wildCardSymbol)
1316
import Development.IDE.Types.Exports
1417
import Language.LSP.Types (CodeActionKind (..))
@@ -83,3 +86,13 @@ quickFixImportKind' x (ImportAllConstructors _) = CodeActionUnknown $ "quickfix.
8386

8487
quickFixImportKind :: T.Text -> CodeActionKind
8588
quickFixImportKind x = CodeActionUnknown $ "quickfix.import." <> x
89+
90+
-- | Possible import styles for qualified imports
91+
data QualifiedImportStyle = QualifiedImportPostfix | QualifiedImportPrefix
92+
deriving Show
93+
94+
qualifiedImportStyle :: DynFlags -> QualifiedImportStyle
95+
qualifiedImportStyle df | hasImportQualifedPostEnabled && hasPrePositiveQualifiedWarning = QualifiedImportPostfix
96+
| otherwise = QualifiedImportPrefix
97+
where hasImportQualifedPostEnabled = xopt ImportQualifiedPost df
98+
hasPrePositiveQualifiedWarning = wopt Opt_WarnPrepositiveQualifiedModule df

test/functional/FunctionalCodeAction.hs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ tests :: TestTree
2727
tests = testGroup "code actions" [
2828
#if hls_refactor
2929
importTests
30+
, ignoreInEnv [HostOS Windows, GhcVer GHC94] "Diagnostic failure for Windows-ghc9.4.2" importQualifiedTests
31+
, ignoreInEnv [HostOS Windows, GhcVer GHC94] "Diagnostic failure for Windows-ghc9.4.2" importQualifiedPostTests
3032
, packageTests
3133
, redundantImportTests
3234
, renameTests
@@ -80,7 +82,7 @@ renameTests = testGroup "rename suggestions" [
8082

8183
importTests :: TestTree
8284
importTests = testGroup "import suggestions" [
83-
testCase "works with 3.8 code action kinds" $ runSession hlsCommand fullCaps "test/testdata" $ do
85+
testCase "import works with 3.8 code action kinds" $ runSession hlsCommand fullCaps "test/testdata" $ do
8486
doc <- openDoc "CodeActionImport.hs" "haskell"
8587
-- No Formatting:
8688
let config = def { formattingProvider = "none" }
@@ -103,6 +105,58 @@ importTests = testGroup "import suggestions" [
103105
liftIO $ contents @?= "import Control.Monad\nmain :: IO ()\nmain = when True $ putStrLn \"hello\""
104106
]
105107

108+
importQualifiedTests :: TestTree
109+
importQualifiedTests = testGroup "import qualified prefix suggestions" [
110+
testCase "qualified import works with 3.8 code action kinds" $ runSession hlsCommand fullCaps "test/testdata" $ do
111+
doc <- openDoc "CodeActionImportQualified.hs" "haskell"
112+
-- No Formatting:
113+
let config = def { formattingProvider = "none" }
114+
sendConfigurationChanged (toJSON config)
115+
116+
(diag:_) <- waitForDiagnosticsFrom doc
117+
liftIO $ diag ^. L.message @?= "Not in scope: ‘Control.when’\nNo module named ‘Control’ is imported."
118+
119+
actionsOrCommands <- getAllCodeActions doc
120+
let actns = map fromAction actionsOrCommands
121+
122+
let importQualifiedSuggestion = "import qualified Control.Monad as Control"
123+
importControlMonadQualified <- liftIO $ inspectCodeAction actionsOrCommands [importQualifiedSuggestion]
124+
liftIO $ do
125+
dontExpectCodeAction actionsOrCommands ["import Control.Monad (when)"]
126+
length actns >= 10 @? "There are some actions"
127+
128+
executeCodeAction importControlMonadQualified
129+
130+
contents <- documentContents doc
131+
liftIO $ contents @?= "import qualified Control.Monad as Control\nmain :: IO ()\nmain = Control.when True $ putStrLn \"hello\"\n"
132+
]
133+
134+
importQualifiedPostTests :: TestTree
135+
importQualifiedPostTests = testGroup "import qualified postfix suggestions" [
136+
testCase "qualified import in postfix position works with 3.8 code action kinds" $ runSession hlsCommand fullCaps "test/testdata" $ do
137+
doc <- openDoc "CodeActionImportPostQualified.hs" "haskell"
138+
-- No Formatting:
139+
let config = def { formattingProvider = "none" }
140+
sendConfigurationChanged (toJSON config)
141+
142+
(diag:_) <- waitForDiagnosticsFrom doc
143+
liftIO $ diag ^. L.message @?= "Not in scope: ‘Control.when’\nNo module named ‘Control’ is imported."
144+
145+
actionsOrCommands <- getAllCodeActions doc
146+
let actns = map fromAction actionsOrCommands
147+
148+
let importQualifiedPostSuggestion = "import Control.Monad qualified as Control"
149+
importControlMonadQualified <- liftIO $ inspectCodeAction actionsOrCommands [importQualifiedPostSuggestion]
150+
liftIO $ do
151+
dontExpectCodeAction actionsOrCommands ["import qualified Control.Monad as Control", "import Control.Monad (when)"]
152+
length actns >= 10 @? "There are some actions"
153+
154+
executeCodeAction importControlMonadQualified
155+
156+
contents <- documentContents doc
157+
liftIO $ T.lines contents !! 2 @?= "import Control.Monad qualified as Control"
158+
]
159+
106160
packageTests :: TestTree
107161
packageTests = testGroup "add package suggestions" [
108162
ignoreTestBecause "no support for adding dependent packages via code action" $ testCase "adds to .cabal files" $ do
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{-# LANGUAGE ImportQualifiedPost #-}
2+
{-# OPTIONS_GHC -Wprepositive-qualified-module #-}
3+
main :: IO ()
4+
main = Control.when True $ putStrLn "hello"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
main :: IO ()
2+
main = Control.when True $ putStrLn "hello"

0 commit comments

Comments
 (0)