packages feed

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 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