imp (empty) → 0.2024.3.18
raw patch · 26 files changed
+688/−0 lines, 26 filesdep +basedep +containersdep +exceptions
Dependencies added: base, containers, exceptions, ghc, hspec, imp
Files
- CHANGELOG.md +4/−0
- LICENSE.txt +21/−0
- README.md +5/−0
- imp.cabal +100/−0
- source/ghc-9.4/Imp/Ghc.hs +24/−0
- source/ghc-9.6/Imp/Ghc.hs +27/−0
- source/library/Imp.hs +94/−0
- source/library/Imp/Exception/InvalidAlias.hs +13/−0
- source/library/Imp/Exception/InvalidModuleName.hs +13/−0
- source/library/Imp/Exception/InvalidOption.hs +15/−0
- source/library/Imp/Exception/ShowHelp.hs +15/−0
- source/library/Imp/Exception/ShowVersion.hs +15/−0
- source/library/Imp/Exception/UnexpectedArgument.hs +13/−0
- source/library/Imp/Exception/UnknownOption.hs +13/−0
- source/library/Imp/Extra/Exception.hs +11/−0
- source/library/Imp/Extra/HsModule.hs +10/−0
- source/library/Imp/Extra/HsParsedModule.hs +12/−0
- source/library/Imp/Extra/ImportDecl.hs +8/−0
- source/library/Imp/Extra/ModuleName.hs +11/−0
- source/library/Imp/Extra/ParsedResult.hs +10/−0
- source/library/Imp/Extra/ReadP.hs +7/−0
- source/library/Imp/Type/Alias.hs +25/−0
- source/library/Imp/Type/Config.hs +32/−0
- source/library/Imp/Type/Context.hs +24/−0
- source/library/Imp/Type/Flag.hs +52/−0
- source/test-suite/Main.hs +114/−0
+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# Change log++Imp follows the [Package Versioning Policy](https://pvp.haskell.org).+You can find release notes [on GitHub](https://github.com/tfausak/imp/releases).
+ LICENSE.txt view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2024 Taylor Fausak++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,5 @@+# Imp++Imp is a GHC plugin that automatically imports modules when they are used, like+GHCi does. This allows writing code using qualified identifiers without+manually importing the modules.
+ imp.cabal view
@@ -0,0 +1,100 @@+cabal-version: 2.2+name: imp+version: 0.2024.3.18+synopsis: Automatically import modules.+description:+ Imp is a GHC plugin that automatically imports modules when they are used,+ like GHCi does. This allows writing code using qualified identifiers without+ manually importing the modules.++category: Plugin+maintainer: Taylor Fausak+license: MIT+license-file: LICENSE.txt+extra-doc-files:+ CHANGELOG.md+ README.md++source-repository head+ location: https://github.com/tfausak/imp+ type: git++flag pedantic+ default: False+ manual: True++common library+ build-depends: base ^>=4.17.0.0 || ^>=4.18.0.0 || ^>=4.19.0.0+ default-language: Haskell2010+ ghc-options:+ -Weverything+ -Wno-all-missed-specialisations+ -Wno-implicit-prelude+ -Wno-missed-specialisations+ -Wno-missing-deriving-strategies+ -Wno-missing-export-lists+ -Wno-missing-kind-signatures+ -Wno-missing-safe-haskell-mode+ -Wno-prepositive-qualified-module+ -Wno-safe+ -Wno-unsafe++common executable+ import: library+ build-depends: imp+ ghc-options:+ -rtsopts+ -threaded++library+ import: library+ autogen-modules: Paths_imp+ build-depends:+ containers ^>=0.6.7,+ exceptions ^>=0.10.5,+ ghc ^>=9.4.1 || ^>=9.6.1 || ^>=9.8.1,++ exposed-modules: Imp.Ghc+ -- cabal-gild: discover source/library+ exposed-modules:+ Imp+ Imp.Exception.InvalidAlias+ Imp.Exception.InvalidModuleName+ Imp.Exception.InvalidOption+ Imp.Exception.ShowHelp+ Imp.Exception.ShowVersion+ Imp.Exception.UnexpectedArgument+ Imp.Exception.UnknownOption+ Imp.Extra.Exception+ Imp.Extra.HsModule+ Imp.Extra.HsParsedModule+ Imp.Extra.ImportDecl+ Imp.Extra.ModuleName+ Imp.Extra.ParsedResult+ Imp.Extra.ReadP+ Imp.Type.Alias+ Imp.Type.Config+ Imp.Type.Context+ Imp.Type.Flag++ hs-source-dirs: source/library+ other-modules: Paths_imp++ if impl(ghc >= 9.6)+ hs-source-dirs: source/ghc-9.6+ else+ hs-source-dirs: source/ghc-9.4++ if flag(pedantic)+ ghc-options: -Werror++test-suite imp-test-suite+ import: executable+ build-depends:+ exceptions,+ ghc,+ hspec ^>=2.11.7,++ hs-source-dirs: source/test-suite+ main-is: Main.hs+ type: exitcode-stdio-1.0
+ source/ghc-9.4/Imp/Ghc.hs view
@@ -0,0 +1,24 @@+module Imp.Ghc where++import qualified GHC.Hs as Hs+import qualified GHC.Plugins as Plugin+import qualified GHC.Types.SourceText as SourceText++type HsModulePs = Hs.HsModule++newImportDecl ::+ Plugin.ModuleName ->+ Hs.ImportDecl Hs.GhcPs+newImportDecl moduleName =+ Hs.ImportDecl+ { Hs.ideclExt = Hs.noAnn,+ Hs.ideclSourceSrc = SourceText.NoSourceText,+ Hs.ideclImplicit = False,+ Hs.ideclName = Hs.noLocA moduleName,+ Hs.ideclPkgQual = Plugin.NoRawPkgQual,+ Hs.ideclSource = Plugin.NotBoot,+ Hs.ideclSafe = False,+ Hs.ideclQualified = Hs.QualifiedPre,+ Hs.ideclAs = Nothing,+ Hs.ideclHiding = Nothing+ }
+ source/ghc-9.6/Imp/Ghc.hs view
@@ -0,0 +1,27 @@+module Imp.Ghc where++import qualified GHC.Hs as Hs+import qualified GHC.Plugins as Plugin+import qualified GHC.Types.SourceText as SourceText++type HsModulePs = Hs.HsModule Hs.GhcPs++newImportDecl ::+ Plugin.ModuleName ->+ Hs.ImportDecl Hs.GhcPs+newImportDecl moduleName =+ Hs.ImportDecl+ { Hs.ideclExt =+ Hs.XImportDeclPass+ { Hs.ideclAnn = Hs.noAnn,+ Hs.ideclSourceText = SourceText.NoSourceText,+ Hs.ideclImplicit = False+ },+ Hs.ideclName = Hs.noLocA moduleName,+ Hs.ideclPkgQual = Plugin.NoRawPkgQual,+ Hs.ideclSource = Hs.NotBoot,+ Hs.ideclSafe = False,+ Hs.ideclQualified = Hs.QualifiedPre,+ Hs.ideclAs = Nothing,+ Hs.ideclImportList = Nothing+ }
+ source/library/Imp.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE TypeApplications #-}++module Imp where++import qualified Control.Monad.Catch as Exception+import qualified Data.Data as Data+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified GHC.Hs as Hs+import qualified GHC.Plugins as Plugin+import qualified Imp.Exception.ShowHelp as ShowHelp+import qualified Imp.Exception.ShowVersion as ShowVersion+import qualified Imp.Extra.Exception as Exception+import qualified Imp.Extra.HsModule as HsModule+import qualified Imp.Extra.HsParsedModule as HsParsedModule+import qualified Imp.Extra.ImportDecl as ImportDecl+import qualified Imp.Extra.ParsedResult as ParsedResult+import qualified Imp.Ghc as Ghc+import qualified Imp.Type.Config as Config+import qualified Imp.Type.Context as Context+import qualified Imp.Type.Flag as Flag+import qualified System.Exit as Exit+import qualified System.IO as IO++plugin :: Plugin.Plugin+plugin =+ Plugin.defaultPlugin+ { Plugin.parsedResultAction = parsedResultAction,+ Plugin.pluginRecompile = Plugin.purePlugin+ }++parsedResultAction ::+ [Plugin.CommandLineOption] ->+ modSummary ->+ Plugin.ParsedResult ->+ Plugin.Hsc Plugin.ParsedResult+parsedResultAction commandLineOptions _ =+ Plugin.liftIO+ . Exception.handle handleException+ . ParsedResult.overModule (HsParsedModule.overModule $ imp commandLineOptions)++handleException :: Exception.SomeException -> IO a+handleException e = do+ IO.hPutStrLn IO.stderr $ Exception.displayException e+ Exit.exitWith $ exceptionToExitCode e++exceptionToExitCode :: Exception.SomeException -> Exit.ExitCode+exceptionToExitCode e+ | Exception.isType @ShowHelp.ShowHelp e = Exit.ExitSuccess+ | Exception.isType @ShowVersion.ShowVersion e = Exit.ExitSuccess+ | otherwise = Exit.ExitFailure 1++imp ::+ (Exception.MonadThrow m) =>+ [String] ->+ Plugin.Located Ghc.HsModulePs ->+ m (Plugin.Located Ghc.HsModulePs)+imp arguments lHsModule = do+ flags <- Flag.fromArguments arguments+ config <- Config.fromFlags flags+ context <- Context.fromConfig config+ let aliases = Context.aliases context+ moduleNames =+ Set.fromList @Plugin.ModuleName+ . biplate+ . Hs.hsmodDecls+ $ Plugin.unLoc lHsModule+ pure $ fmap (HsModule.overImports $ updateImports aliases moduleNames) lHsModule++biplate :: (Data.Data a, Data.Data b) => a -> [b]+biplate = concat . Data.gmapQ (\d -> maybe (biplate d) pure $ Data.cast d)++updateImports ::+ Map.Map Plugin.ModuleName Plugin.ModuleName ->+ Set.Set Plugin.ModuleName ->+ [Hs.LImportDecl Hs.GhcPs] ->+ [Hs.LImportDecl Hs.GhcPs]+updateImports aliases want imports =+ let have = Set.fromList $ fmap (ImportDecl.toModuleName . Plugin.unLoc) imports+ need = Set.toList $ Set.difference want have+ in imports <> fmap (Hs.noLocA . createImport aliases) need++createImport ::+ Map.Map Plugin.ModuleName Plugin.ModuleName ->+ Plugin.ModuleName ->+ Hs.ImportDecl Hs.GhcPs+createImport aliases target =+ let source = Map.findWithDefault target target aliases+ in (Ghc.newImportDecl source)+ { Hs.ideclAs =+ if source == target+ then Nothing+ else Just $ Hs.noLocA target+ }
+ source/library/Imp/Exception/InvalidAlias.hs view
@@ -0,0 +1,13 @@+module Imp.Exception.InvalidAlias where++import qualified Control.Monad.Catch as Exception++newtype InvalidAlias+ = InvalidAlias String+ deriving (Eq, Show)++instance Exception.Exception InvalidAlias where+ displayException (InvalidAlias x) = "invalid alias: " <> show x++new :: String -> InvalidAlias+new = InvalidAlias
+ source/library/Imp/Exception/InvalidModuleName.hs view
@@ -0,0 +1,13 @@+module Imp.Exception.InvalidModuleName where++import qualified Control.Monad.Catch as Exception++newtype InvalidModuleName+ = InvalidModuleName String+ deriving (Eq, Show)++instance Exception.Exception InvalidModuleName where+ displayException (InvalidModuleName x) = "invalid module name: " <> show x++new :: String -> InvalidModuleName+new = InvalidModuleName
+ source/library/Imp/Exception/InvalidOption.hs view
@@ -0,0 +1,15 @@+module Imp.Exception.InvalidOption where++import qualified Control.Monad.Catch as Exception+import qualified Data.Char as Char+import qualified Data.List as List++newtype InvalidOption+ = InvalidOption String+ deriving (Eq, Show)++instance Exception.Exception InvalidOption where+ displayException (InvalidOption x) = "invalid option: " <> x++new :: String -> InvalidOption+new = InvalidOption . List.dropWhile Char.isSpace
+ source/library/Imp/Exception/ShowHelp.hs view
@@ -0,0 +1,15 @@+module Imp.Exception.ShowHelp where++import qualified Control.Monad.Catch as Exception+import qualified Imp.Type.Flag as Flag+import qualified System.Console.GetOpt as GetOpt++data ShowHelp+ = ShowHelp+ deriving (Eq, Show)++instance Exception.Exception ShowHelp where+ displayException = const $ GetOpt.usageInfo "imp" Flag.options++new :: ShowHelp+new = ShowHelp
+ source/library/Imp/Exception/ShowVersion.hs view
@@ -0,0 +1,15 @@+module Imp.Exception.ShowVersion where++import qualified Control.Monad.Catch as Exception+import qualified Data.Version as Version+import qualified Paths_imp as This++data ShowVersion+ = ShowVersion+ deriving (Eq, Show)++instance Exception.Exception ShowVersion where+ displayException = const $ Version.showVersion This.version++new :: ShowVersion+new = ShowVersion
+ source/library/Imp/Exception/UnexpectedArgument.hs view
@@ -0,0 +1,13 @@+module Imp.Exception.UnexpectedArgument where++import qualified Control.Monad.Catch as Exception++newtype UnexpectedArgument+ = UnexpectedArgument String+ deriving (Eq, Show)++instance Exception.Exception UnexpectedArgument where+ displayException (UnexpectedArgument x) = "unexpected argument: " <> show x++new :: String -> UnexpectedArgument+new = UnexpectedArgument
+ source/library/Imp/Exception/UnknownOption.hs view
@@ -0,0 +1,13 @@+module Imp.Exception.UnknownOption where++import qualified Control.Monad.Catch as Exception++newtype UnknownOption+ = UnknownOption String+ deriving (Eq, Show)++instance Exception.Exception UnknownOption where+ displayException (UnknownOption x) = "unknown option: " <> show x++new :: String -> UnknownOption+new = UnknownOption
+ source/library/Imp/Extra/Exception.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Imp.Extra.Exception where++import qualified Control.Monad.Catch as Exception+import qualified Data.Maybe as Maybe++isType :: forall e. (Exception.Exception e) => Exception.SomeException -> Bool+isType = Maybe.isJust . Exception.fromException @e
+ source/library/Imp/Extra/HsModule.hs view
@@ -0,0 +1,10 @@+module Imp.Extra.HsModule where++import qualified GHC.Hs as Hs+import qualified Imp.Ghc as Ghc++overImports ::+ ([Hs.LImportDecl Hs.GhcPs] -> [Hs.LImportDecl Hs.GhcPs]) ->+ Ghc.HsModulePs ->+ Ghc.HsModulePs+overImports f x = x {Hs.hsmodImports = f $ Hs.hsmodImports x}
+ source/library/Imp/Extra/HsParsedModule.hs view
@@ -0,0 +1,12 @@+module Imp.Extra.HsParsedModule where++import qualified GHC.Hs as Hs+import qualified GHC.Plugins as Plugin+import qualified Imp.Ghc as Ghc++overModule ::+ (Functor f) =>+ (Plugin.Located Ghc.HsModulePs -> f (Plugin.Located Ghc.HsModulePs)) ->+ Plugin.HsParsedModule ->+ f Plugin.HsParsedModule+overModule f x = (\y -> x {Hs.hpm_module = y}) <$> f (Hs.hpm_module x)
+ source/library/Imp/Extra/ImportDecl.hs view
@@ -0,0 +1,8 @@+module Imp.Extra.ImportDecl where++import qualified Data.Maybe as Maybe+import qualified GHC.Hs as Hs+import qualified GHC.Plugins as Plugin++toModuleName :: Hs.ImportDecl Hs.GhcPs -> Plugin.ModuleName+toModuleName x = Plugin.unLoc . Maybe.fromMaybe (Hs.ideclName x) $ Hs.ideclAs x
+ source/library/Imp/Extra/ModuleName.hs view
@@ -0,0 +1,11 @@+module Imp.Extra.ModuleName where++import qualified Control.Monad.Catch as Exception+import qualified GHC.Plugins as Plugin+import qualified Imp.Exception.InvalidModuleName as InvalidModuleName+import qualified Imp.Extra.ReadP as ReadP++fromString :: (Exception.MonadThrow m) => String -> m Plugin.ModuleName+fromString x =+ maybe (Exception.throwM $ InvalidModuleName.new x) pure $+ ReadP.run Plugin.parseModuleName x
+ source/library/Imp/Extra/ParsedResult.hs view
@@ -0,0 +1,10 @@+module Imp.Extra.ParsedResult where++import qualified GHC.Plugins as Plugin++overModule ::+ (Functor f) =>+ (Plugin.HsParsedModule -> f Plugin.HsParsedModule) ->+ Plugin.ParsedResult ->+ f Plugin.ParsedResult+overModule f x = (\y -> x {Plugin.parsedResultModule = y}) <$> f (Plugin.parsedResultModule x)
+ source/library/Imp/Extra/ReadP.hs view
@@ -0,0 +1,7 @@+module Imp.Extra.ReadP where++import qualified Data.List as List+import qualified Text.ParserCombinators.ReadP as ReadP++run :: ReadP.ReadP a -> String -> Maybe a+run x = fmap fst . List.find (null . snd) . ReadP.readP_to_S x
+ source/library/Imp/Type/Alias.hs view
@@ -0,0 +1,25 @@+module Imp.Type.Alias where++import qualified Control.Monad as Monad+import qualified Control.Monad.Catch as Exception+import qualified Data.Map as Map+import qualified GHC.Plugins as Plugin+import qualified Imp.Exception.InvalidAlias as InvalidAlias+import qualified Imp.Extra.ModuleName as ModuleName++data Alias = Alias+ { source :: Plugin.ModuleName,+ target :: Plugin.ModuleName+ }+ deriving (Eq, Show)++fromString :: (Exception.MonadThrow m) => String -> m Alias+fromString string = do+ let (before, after) = break (== ':') string+ Monad.when (null after) . Exception.throwM $ InvalidAlias.new string+ src <- ModuleName.fromString before+ tgt <- ModuleName.fromString . drop 1 $ after+ pure Alias {source = src, target = tgt}++toMap :: [Alias] -> Map.Map Plugin.ModuleName Plugin.ModuleName+toMap = Map.fromListWith (const id) . fmap (\x -> (target x, source x))
+ source/library/Imp/Type/Config.hs view
@@ -0,0 +1,32 @@+module Imp.Type.Config where++import qualified Control.Monad as Monad+import qualified Control.Monad.Catch as Exception+import qualified Imp.Type.Alias as Alias+import qualified Imp.Type.Flag as Flag++data Config = Config+ { aliases :: [Alias.Alias],+ help :: Bool,+ version :: Bool+ }+ deriving (Eq, Show)++initial :: Config+initial =+ Config+ { aliases = [],+ help = False,+ version = False+ }++fromFlags :: (Exception.MonadThrow m) => [Flag.Flag] -> m Config+fromFlags = Monad.foldM applyFlag initial++applyFlag :: (Exception.MonadThrow m) => Config -> Flag.Flag -> m Config+applyFlag config flag = case flag of+ Flag.Alias string -> do+ alias <- Alias.fromString string+ pure config {aliases = alias : aliases config}+ Flag.Help bool -> pure config {help = bool}+ Flag.Version bool -> pure config {version = bool}
+ source/library/Imp/Type/Context.hs view
@@ -0,0 +1,24 @@+module Imp.Type.Context where++import qualified Control.Monad as Monad+import qualified Control.Monad.Catch as Exception+import qualified Data.Map as Map+import qualified GHC.Plugins as Plugin+import qualified Imp.Exception.ShowHelp as ShowHelp+import qualified Imp.Exception.ShowVersion as ShowVersion+import qualified Imp.Type.Alias as Alias+import qualified Imp.Type.Config as Config++newtype Context = Context+ { aliases :: Map.Map Plugin.ModuleName Plugin.ModuleName+ }+ deriving (Eq, Show)++fromConfig :: (Exception.MonadThrow m) => Config.Config -> m Context+fromConfig config = do+ Monad.when (Config.help config) $ Exception.throwM ShowHelp.new+ Monad.when (Config.version config) $ Exception.throwM ShowVersion.new+ pure+ Context+ { aliases = Alias.toMap $ Config.aliases config+ }
+ source/library/Imp/Type/Flag.hs view
@@ -0,0 +1,52 @@+module Imp.Type.Flag where++import qualified Control.Monad.Catch as Exception+import qualified Imp.Exception.InvalidOption as InvalidOption+import qualified Imp.Exception.UnexpectedArgument as UnexpectedArgument+import qualified Imp.Exception.UnknownOption as UnknownOption+import qualified System.Console.GetOpt as GetOpt++data Flag+ = Alias String+ | Help Bool+ | Version Bool+ deriving (Eq, Show)++options :: [GetOpt.OptDescr Flag]+options =+ [ GetOpt.Option+ ['h', '?']+ ["help"]+ (GetOpt.NoArg $ Help True)+ "Prints this help message then exits.",+ GetOpt.Option+ []+ ["no-help"]+ (GetOpt.NoArg $ Help False)+ "",+ GetOpt.Option+ ['v']+ ["version"]+ (GetOpt.NoArg $ Version True)+ "Prints the version number then exits.",+ GetOpt.Option+ []+ ["no-version"]+ (GetOpt.NoArg $ Version False)+ "",+ GetOpt.Option+ []+ ["alias"]+ (GetOpt.ReqArg Alias "SOURCE:TARGET")+ "Adds a new alias, allowing TARGET to be used in place of SOURCE. \+ \For example `--alias=Data.String:String` allows `String.words` to mean `Data.String.words`. \+ \Later aliases will overwrite earlier ones."+ ]++fromArguments :: (Exception.MonadThrow m) => [String] -> m [Flag]+fromArguments arguments = do+ let (flgs, args, opts, errs) = GetOpt.getOpt' GetOpt.Permute options arguments+ mapM_ (Exception.throwM . UnexpectedArgument.new) args+ mapM_ (Exception.throwM . UnknownOption.new) opts+ mapM_ (Exception.throwM . InvalidOption.new) errs+ pure flgs
+ source/test-suite/Main.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE CPP #-}++import qualified Control.Monad.Catch as Exception+import qualified GHC.Data.EnumSet as EnumSet+import qualified GHC.Data.FastString as FastString+import qualified GHC.Data.StringBuffer as StringBuffer+import qualified GHC.Parser as Parser+import qualified GHC.Parser.Lexer as Lexer+import qualified GHC.Stack as Stack+import qualified GHC.Types.SrcLoc as SrcLoc+import qualified GHC.Utils.Error as Error+import qualified GHC.Utils.Outputable as Outputable+import qualified Imp+import qualified Imp.Ghc as Ghc+import qualified Test.Hspec as Hspec++main :: IO ()+main = Hspec.hspec . Hspec.parallel . Hspec.describe "Imp" $ do+ Hspec.it "does nothing with an empty module" $ do+ expectImp+ []+ ""+ ""++ Hspec.it "does nothing when nothing needs to be imported" $ do+ expectImp+ []+ "true = True"+ "true = True"++ Hspec.it "inserts an import for a qualified value" $ do+ expectImp+ []+ "true = Data.Bool.True"+ "import qualified Data.Bool\ntrue = Data.Bool.True"++ Hspec.it "inserts an aliased import" $ do+ expectImp+ ["--alias=Data.Bool:Bool"]+ "true = Bool.True"+ "import qualified Data.Bool as Bool\ntrue = Bool.True"++ Hspec.it "prefers later aliases over earlier ones" $ do+ expectImp+ ["--alias=Relude.Bool:Bool", "--alias=Data.Bool:Bool"]+ "true = Bool.True"+ "import qualified Data.Bool as Bool\ntrue = Bool.True"++ Hspec.it "inserts an import for a qualified type" $ do+ expectImp+ []+ "true = True :: Data.Bool.Bool"+ "import qualified Data.Bool\ntrue = True :: Data.Bool.Bool"++ Hspec.it "inserts multiple imports sorted" $ do+ expectImp+ []+ "true :: Relude.Bool.Bool\ntrue = Data.Bool.True"+ "import qualified Data.Bool\nimport qualified Relude.Bool\ntrue :: Relude.Bool.Bool\ntrue = Data.Bool.True"++ Hspec.it "does not re-import an open import" $ do+ expectImp+ []+ "import Data.Bool\ntrue = Data.Bool.True"+ "import Data.Bool\ntrue = Data.Bool.True"++ Hspec.it "does not re-import a qualified import" $ do+ expectImp+ []+ "import qualified Data.Bool\ntrue = Data.Bool.True"+ "import qualified Data.Bool\ntrue = Data.Bool.True"++ Hspec.it "does not re-import an aliased import" $ do+ expectImp+ []+ "import qualified Data.Bool as Bool\ntrue = Bool.True"+ "import qualified Data.Bool as Bool\ntrue = Bool.True"++ Hspec.it "inserts imports after existing ones" $ do+ expectImp+ []+ "import qualified Relude.Bool\ntrue :: Relude.Bool.Bool\ntrue = Data.Bool.True"+ "import qualified Relude.Bool\nimport qualified Data.Bool\ntrue :: Relude.Bool.Bool\ntrue = Data.Bool.True"++expectImp :: (Stack.HasCallStack) => [String] -> String -> String -> Hspec.Expectation+expectImp arguments input expected = do+ before <- parseModule input+ after <- Imp.imp arguments before+ let actual = Outputable.showPprUnsafe after+ actual `Hspec.shouldBe` expected++parseModule :: (Exception.MonadThrow m) => String -> m (SrcLoc.Located Ghc.HsModulePs)+parseModule input = do+ let parserOpts = Lexer.mkParserOpts EnumSet.empty emptyDiagOpts [] False False False False+ stringBuffer = StringBuffer.stringToStringBuffer input+ realSrcLoc = SrcLoc.mkRealSrcLoc (FastString.mkFastString "<interactive>") 1 1+ pState = Lexer.initParserState parserOpts stringBuffer realSrcLoc+ parseResult = Lexer.unP Parser.parseModule pState+ case parseResult of+ Lexer.PFailed _ -> Exception.throwM $ InvalidInput input+ Lexer.POk _ lHsModule -> pure lHsModule++emptyDiagOpts :: Error.DiagOpts+#if MIN_VERSION_ghc(9, 8, 1)+emptyDiagOpts = Error.emptyDiagOpts+#else+emptyDiagOpts = Error.DiagOpts EnumSet.empty EnumSet.empty False False Nothing Outputable.defaultSDocContext+#endif++newtype InvalidInput+ = InvalidInput String+ deriving (Eq, Show)++instance Exception.Exception InvalidInput