diff --git a/CHANGELOG.md b/CHANGELOG.md index fb6503e85..328675cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +New Features +- `spago install` works on more advanced Dhall expressions as stored in `spago.dhall (#849) + ## [0.20.4] - 2022-01-29 Bugfixes: diff --git a/spago.cabal b/spago.cabal index f92a12290..d45fb9240 100644 --- a/spago.cabal +++ b/spago.cabal @@ -92,6 +92,7 @@ library Spago.Command.Path Spago.Command.Verify Spago.Config + Spago.Config.AST Spago.Dhall Spago.DryRun Spago.Env diff --git a/src/Spago/Config.hs b/src/Spago/Config.hs index 5b24b7fb6..28cc21c1d 100644 --- a/src/Spago/Config.hs +++ b/src/Spago/Config.hs @@ -16,7 +16,7 @@ import Spago.Prelude import Spago.Env import qualified Data.List as List -import qualified Data.List.NonEmpty as NonEmpty +import qualified Data.Foldable as Foldable import qualified Data.Map as Map import qualified Data.SemVer as SemVer import qualified Data.Sequence as Seq @@ -31,6 +31,7 @@ import qualified Web.Bower.PackageMeta as Bower import qualified Spago.Dhall as Dhall import qualified Spago.Messages as Messages +import qualified Spago.Config.AST as AST import qualified Spago.PackageSet as PackageSet import qualified Spago.PscPackage as PscPackage import qualified Spago.Templates as Templates @@ -110,7 +111,8 @@ parsePackageSet pkgs = do pure PackageSet{..} --- | Tries to read in a Spago Config +-- | Tries to parse the raw Dhall expression stored +-- in the @./spago.dhall@ file into a `Config` value. parseConfig :: (HasLogFunc env, HasConfigPath env) => RIO env Config @@ -120,30 +122,41 @@ parseConfig = do ConfigPath path <- view (the @ConfigPath) expr <- liftIO $ Dhall.inputExpr $ "./" <> path - case expr of - Dhall.RecordLit ks' -> do - let ks = Dhall.extractRecordValues ks' - let sourcesType = Dhall.list (Dhall.auto :: Dhall.Decoder SourcePath) - name <- Dhall.requireTypedKey ks "name" Dhall.strictText - dependencies <- Set.fromList <$> Dhall.requireTypedKey ks "dependencies" dependenciesType - configSourcePaths <- Set.fromList <$> Dhall.requireTypedKey ks "sources" sourcesType - alternateBackend <- Dhall.maybeTypedKey ks "backend" Dhall.strictText - - let ensurePublishConfig = do - publishLicense <- Dhall.requireTypedKey ks "license" Dhall.strictText - publishRepository <- Dhall.requireTypedKey ks "repository" Dhall.strictText - pure PublishConfig{..} - publishConfig <- try ensurePublishConfig - - packageSet <- Dhall.requireKey ks "packages" (\case - Dhall.RecordLit pkgs -> parsePackageSet (Dhall.extractRecordValues pkgs) - something -> throwM $ Dhall.PackagesIsNotRecord something) - - pure Config{..} - _ -> case Dhall.TypeCheck.typeOf expr of + maybeConfig <- parseConfigNormalizedExpr expr + case maybeConfig of + Just config -> pure config + Nothing -> case Dhall.TypeCheck.typeOf expr of Right e -> throwM $ Dhall.ConfigIsNotRecord e Left err -> throwM err +-- | +-- Attempts to parse a normalized Dhall expression (i.e. all imports have been resolved) +-- into a `Config` value. +parseConfigNormalizedExpr + :: (HasLogFunc env) + => ResolvedExpr -> RIO env (Maybe Config) +parseConfigNormalizedExpr = \case + Dhall.RecordLit ks' -> do + let ks = Dhall.extractRecordValues ks' + let sourcesType = Dhall.list (Dhall.auto :: Dhall.Decoder SourcePath) + name <- Dhall.requireTypedKey ks "name" Dhall.strictText + dependencies <- Set.fromList <$> Dhall.requireTypedKey ks "dependencies" dependenciesType + configSourcePaths <- Set.fromList <$> Dhall.requireTypedKey ks "sources" sourcesType + alternateBackend <- Dhall.maybeTypedKey ks "backend" Dhall.strictText + + let ensurePublishConfig = do + publishLicense <- Dhall.requireTypedKey ks "license" Dhall.strictText + publishRepository <- Dhall.requireTypedKey ks "repository" Dhall.strictText + pure PublishConfig{..} + publishConfig <- try ensurePublishConfig + + packageSet <- Dhall.requireKey ks "packages" (\case + Dhall.RecordLit pkgs -> parsePackageSet (Dhall.extractRecordValues pkgs) + something -> throwM $ Dhall.PackagesIsNotRecord something) + + pure $ Just Config{..} + _ -> + pure Nothing -- | Checks that the Spago config is there and readable ensureConfig @@ -220,7 +233,7 @@ makeConfig force comments = do logInfo "Found a \"psc-package.json\" file, migrating to a new Spago config.." -- try to update the dependencies (will fail if not found in package set) let pscPackages = map PackageName $ PscPackage.depends pscConfig - void $ withConfigAST ( addRawDeps config pscPackages + void $ withConfigAST ( addRawDeps config (Set.fromList pscPackages) . updateName (PscPackage.name pscConfig)) (_, True) -> do -- read the bowerfile @@ -242,7 +255,7 @@ makeConfig force comments = do else do logWarn $ display $ showBowerErrors bowerErrors - void $ withConfigAST ( addRawDeps config bowerPackages + void $ withConfigAST ( addRawDeps config (Set.fromList bowerPackages) . updateName bowerName) _ -> pure () @@ -320,37 +333,33 @@ updateName newName (Dhall.RecordLit kvs) $ Dhall.Map.insert "name" (Dhall.makeRecordField $ Dhall.toTextLit newName) kvs updateName _ other = other -addRawDeps :: HasLogFunc env => Config -> [PackageName] -> Expr -> RIO env Expr -addRawDeps config newPackages r@(Dhall.RecordLit kvs) = case Dhall.Map.lookup "dependencies" kvs of - Just (Dhall.RecordField { recordFieldValue = Dhall.ListLit _ dependencies }) -> do - case NonEmpty.nonEmpty notInPackageSet of - -- If none of the newPackages are outside of the set, add them to existing dependencies - Nothing -> do - oldPackages <- traverse (throws . Dhall.fromTextLit) dependencies - let newDepsExpr - = Dhall.makeRecordField - $ Dhall.ListLit Nothing $ fmap (Dhall.toTextLit . packageName) - $ Seq.sort $ nubSeq (Seq.fromList newPackages <> fmap PackageName oldPackages) - pure $ Dhall.RecordLit $ Dhall.Map.insert "dependencies" newDepsExpr kvs - Just pkgs -> do - logWarn $ display $ Messages.failedToAddDeps $ NonEmpty.map packageName pkgs - pure r - where - Config { packageSet = PackageSet{..} } = config - notInPackageSet = filter (\p -> Map.notMember p packagesDB) newPackages - - -- | Code from https://stackoverflow.com/questions/45757839 - nubSeq :: Ord a => Seq a -> Seq a - nubSeq xs = (fmap fst . Seq.filter (uncurry notElem)) (Seq.zip xs seens) - where - seens = Seq.scanl (flip Set.insert) Set.empty xs - Just _ -> do - logWarn "Failed to add dependencies. The `dependencies` field wasn't a List of Strings." - pure r - Nothing -> do - logWarn "Failed to add dependencies. You should have a record with the `dependencies` key for this to work." - pure r -addRawDeps _ _ other = pure other +addRawDeps :: HasLogFunc env => Config -> Set PackageName -> Expr -> RIO env Expr +addRawDeps config newPackages expr = + case notInPackageSet config newPackages of + pkgsNotInSet + | not $ Set.null pkgsNotInSet -> do + logWarn $ display $ Messages.failedToAddDeps $ Set.map packageName pkgsNotInSet + pure expr + -- If none of the newPackages are outside of the set, add them to existing dependencies + | otherwise -> case expr of + r@(Dhall.RecordLit kvs) -> + case Dhall.Map.lookup "dependencies" kvs of + Just Dhall.RecordField { recordFieldValue = Dhall.ListLit _ dependencies } -> do + oldPackages <- traverse (throws . Dhall.fromTextLit) dependencies + let uniquePkgs = newPackages <> Set.fromList (PackageName <$> Foldable.toList oldPackages) + let newDepsExpr + = Dhall.makeRecordField + $ Dhall.ListLit Nothing $ fmap (Dhall.toTextLit . packageName) + $ Seq.sort $ nubSeq (Seq.fromList $ Set.toList uniquePkgs) + pure $ Dhall.RecordLit $ Dhall.Map.insert "dependencies" newDepsExpr kvs + Just _ -> do + logWarn "Failed to add dependencies. The `dependencies` field wasn't a List of Strings." + pure r + Nothing -> do + logWarn "Failed to add dependencies. You should have a record with the `dependencies` key for this to work." + pure r + _ -> + pure expr addSourcePaths :: Expr -> Expr addSourcePaths (Dhall.RecordLit kvs) @@ -398,6 +407,30 @@ withConfigAST transform = do else logDebug "Transformed config is the same as the read one, not overwriting it" pure exprHasChanged +-- | Takes a function that manipulates the Dhall AST of the Config, and tries to run it +-- on the current config. If it succeeds, it writes back to file the result returned. +withRawConfigAST + :: (HasLogFunc env, HasConfigPath env) + => (AST.ResolvedUnresolvedExpr -> RIO env Expr) -> RIO env Bool +withRawConfigAST transform = do + ConfigPath path <- view (the @ConfigPath) + rawConfig <- liftIO $ Dhall.readRawExpr path + normalizedExpr <- liftIO $ Dhall.inputExpr $ "./" <> path + case rawConfig of + Nothing -> die [ display $ Messages.cannotFindConfig path ] + Just (header, expr) -> do + let + unresolved = Dhall.Core.denote expr + resolved = normalizedExpr + + newExpr <- transform $ AST.ResolvedUnresolvedExpr (resolved, unresolved) + -- Write the new expression only if it has actually changed + let exprHasChanged = Dhall.Core.denote expr /= newExpr + if exprHasChanged + then liftIO $ Dhall.writeRawExpr path (header, newExpr) + else logDebug "Transformed config is the same as the read one, not overwriting it" + pure exprHasChanged + transformMExpr :: MonadIO m @@ -416,10 +449,143 @@ transformMExpr rules = -- If everything is fine instead, it will add the new deps, sort all the -- dependencies, and write the Config back to file. addDependencies - :: (HasLogFunc env, HasConfigPath env) - => Config -> [PackageName] + :: forall env + . (HasLogFunc env, HasConfigPath env) + => Config -> Set PackageName -> RIO env () -addDependencies config newPackages = do - configHasChanged <- withConfigAST $ addRawDeps config newPackages +addDependencies config@Config { dependencies = deps, publishConfig = pubConfig } newPackages = do + configHasChanged <- case notInPackageSet config newPackages of + pkgsNotInSet + | not $ Set.null pkgsNotInSet -> do + logWarn $ display $ Messages.failedToAddDeps $ Set.map packageName pkgsNotInSet + pure False + | otherwise -> do + let + expectedConfig :: Config + expectedConfig = config { dependencies = mkExpectedConfigDeps, publishConfig = mkExpectedPubConifg } + withRawConfigAST $ \sameExpr -> do + newExpr <- AST.modifyRawConfigExpression (AST.AddPackages newPackages) sameExpr + -- Verify that returned expression produces the expected `Config` value if parsed + -- before we return it. + normalizedExpr <- liftIO $ Dhall.inputExpr $ pretty newExpr + maybeResult <- parseConfigNormalizedExpr normalizedExpr `catch` (\(_ :: SomeException) -> pure Nothing) + case maybeResult of + Just parsedConfig -> do + validModification <- expectedConfig `isSemanticallyEquivalentTo` parsedConfig + if validModification then do + pure newExpr + else do + logWarn "Failed to add dependencies." + logDebug $ + "Raw AST modification did not produce the expected Dhall expression. " <> + "If parsed in a future command, the AST would not produce the expected `Config` value." + pure $ snd $ AST.resolvedUnresolvedExpr sameExpr + Nothing -> do + logWarn "Failed to add dependencies." + logDebug "Raw AST modification did not produce a valid `spago.dhall` file." + pure $ snd $ AST.resolvedUnresolvedExpr sameExpr + unless configHasChanged $ logWarn "Configuration file was not updated." + + where + mkExpectedConfigDeps = deps <> newPackages + + -- | + -- If the @pubConfig@ parsing fails, it will fail on the first key checked (i.e. the @license@ key). + -- When it does fail, it records a map of the expression and that map does not include the new packages + -- When the modified expression is parsed, it will also fail at the @license@ key. However, it\'s + -- map will include the new packages. + -- + -- Thus, we need to update the map in the expected config, so the equality check will pass. + mkExpectedPubConifg = case pubConfig of + Left (Dhall.RequiredKeyMissing key kvs) -> + Left (Dhall.RequiredKeyMissing key newKvs) + where + newKvs = Dhall.Map.insertWith insertNewPackages "dependencies" newPackagesExpr kvs + + newPackagesExpr :: ResolvedExpr + newPackagesExpr = Dhall.ListLit Nothing $ Seq.fromList $ Set.toList $ Set.map (Dhall.toTextLit . packageName) newPackages + + insertNewPackages :: ResolvedExpr -> ResolvedExpr -> ResolvedExpr + insertNewPackages (Dhall.ListLit an left) (Dhall.ListLit _ right) = + Dhall.ListLit an $ nubSeq $ left <> right + insertNewPackages other _ = other + + x -> x + + -- | + -- Unfortunately, we cannot just check whether the expected config is equal to the actual config + -- because "Dhall.Map.Map" keeps track of order when equating two maps. + -- For some cases, this \"values are only equal if ordered the same\" check will cause a failure when + -- we attempt to parse the @PublishConfig@ and fail. In such circumstances, the failure + -- message will be @Left (Dhall.RequiredKeyMissing licenseOrRepositoryText map)@ and @map@ will have a different + -- order in the expected config than it will in the parsed config. + -- + -- Moreover, if the config equality check below fails, it is more helpful to understand what parts of + -- the @Config@ values were considered unequal. Thus, besides doing a typical @expected == actual@ check, + -- we will log debug messages to the console while checking all values in case there are multiple + -- values that are different. + isSemanticallyEquivalentTo :: Config -> Config -> RIO env Bool + isSemanticallyEquivalentTo + Config { name = expN, dependencies = expD, packageSet = expPS, alternateBackend = expAB, configSourcePaths = expCSP, publishConfig = expPC } + Config { name = actN, dependencies = actD, packageSet = actPS, alternateBackend = actAB, configSourcePaths = actCSP, publishConfig = actPC } + = checkAll + [ checkValue expN actN "Config: name" + , checkValue expD actD "Config: dependencies" + , checkValue expPS actPS "Config: package set" + , checkValue expAB actAB "Config: alternate backend" + , checkValue expCSP actCSP "Config: config source paths" + , checkPC expPC actPC + ] + + checkAll :: [RIO env Bool] -> RIO env Bool + checkAll = foldl' (\acc n -> do + prev <- acc + next <- n + pure $ prev && next) (pure True) + + checkValue :: forall a. Eq a => Show a => a -> a -> Utf8Builder -> RIO env Bool + checkValue expected actual msg + | expected == actual = do + logDebug $ msg <> " - No problem here." + pure True + | otherwise = do + logDebug $ msg <> " - Found mismatch" + logDebug $ displayShow expected + logDebug $ displayShow actual + pure False + + checkPC :: Either (Dhall.ReadError Void) PublishConfig -> Either (Dhall.ReadError Void) PublishConfig -> RIO env Bool + checkPC (Right l) (Right r) = do + checkValue l r "Config: pubConfig - Right" + checkPC (Left (Dhall.RequiredKeyMissing k1 kvs1)) (Left (Dhall.RequiredKeyMissing k2 kvs2)) = do + checkAll + [ checkValue k1 k2 "Config: pubConfig - Left RequiredKeyMissing: keys" + , checkValue (sortDependencies kvs1) (sortDependencies kvs2) "Config: pubConfig - Left RequiredKeyMissing: maps" + ] + checkPC l r = do + logDebug "Config: pubConfig: unexpected value in both" + logDebug $ "Expected value: " <> displayShow l + logDebug $ "Actual value: " <> displayShow r + pure False + + sortDependencies :: Dhall.Map.Map Text ResolvedExpr -> Dhall.Map.Map Text ResolvedExpr + sortDependencies x = case Dhall.Map.lookup dependenciesText x of + Just (Dhall.ListLit a pkgs) -> + Dhall.Map.insert dependenciesText (Dhall.ListLit a (Seq.sortOn toText pkgs)) x + _ -> + x + where + dependenciesText = "dependencies" + + toText = \case + Dhall.TextLit (Dhall.Chunks [] t) -> t + _ -> error "impossible: A normalized expression that produced a valid `Config` value should only have a `TextLit` here" + +-- | +-- Returns a possibly empty set of packages not found in the package set. +notInPackageSet + :: Config -> Set PackageName -> Set PackageName +notInPackageSet Config { packageSet = PackageSet{..} } newPackages = + Set.filter (\p -> Map.notMember p packagesDB) newPackages diff --git a/src/Spago/Config/AST.hs b/src/Spago/Config/AST.hs new file mode 100644 index 000000000..bb63bd671 --- /dev/null +++ b/src/Spago/Config/AST.hs @@ -0,0 +1,690 @@ +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE OverloadedLists #-} +module Spago.Config.AST + ( ConfigModification(..) + , ResolvedUnresolvedExpr(..) + , modifyRawConfigExpression + ) where + +import Spago.Prelude +import Spago.Env + +import qualified Data.Sequence as Seq +import qualified Data.Set as Set +import qualified Data.Text as Text +import qualified Dhall.Core +import qualified Dhall.Map +import qualified Dhall.Parser as Parser +import qualified Spago.Dhall as Dhall + + +type Expr = Dhall.Expr Parser.Src Dhall.Import +type ResolvedExpr = Dhall.Expr Parser.Src Void + +-- | +-- Indicates the change Spago wants to make to the Dhall expression +-- used to produce a 'Spago.Config.Config' value. +data ConfigModification + = AddPackages !(Set PackageName) + -- ^ Adds packages to the @dependencies@ field + +-- | +-- Indicates the change to make once inside the Dhall expression, +-- regardless of what value(s) it produces. +data AstUpdate + = InsertListText (Seq Expr) + +-- | +-- Newtype over a Tuple that stores the same expression +-- in the version where its imports are resolved (i.e. @Expr Src Void@) +-- and the version where its imports are not resolved (i.e. @Expr Src Import@). +newtype ResolvedUnresolvedExpr = + ResolvedUnresolvedExpr { resolvedUnresolvedExpr :: (ResolvedExpr, Expr) } + +-- | +-- +-- Modifies a Dhall expression that can be parsed to produce a `Spago.Config.Config` value. +-- +-- For now, such an expression is anything that, when normalized, produces a +-- \"record expression\" whose +-- * @dependencies@ key contains a \"list expression\" of text that +-- corresponds to package names. +-- +-- Since the user may be requesting changes that result in a no-op +-- (e.g. add packages that have already been added), +-- we first determine if a change needs to be made, +-- and then attempt to make that change. +-- +-- To accomplish this goal, we normalize the expression first. There are two reasons why. +-- First, it makes the implementation simpler. Rather than figuring out what the expression +-- currently contains (e.g. whether its @dependencies@ field has the packages we want to add) +-- while we are trying to make a change (e.g. adding new packages to the @dependencies@ field), +-- we break this into two steps: +-- 1. determining what change we actually need to make +-- (e.g. if we are trying to add packages, @foo@ and @bar@, to the @dependencies@ field +-- but @foo@ is already added, then we only need to add @bar@), and +-- 2. where to make the change in the AST. +-- +-- Breaking this down into two problems makes the solving the second problem easier. +-- +-- Second, by confirming below that the normalized expression found in the @spago.dhall@ file +-- IS a @RecordLit@ with the field we need to modify (e.g. it has a @dependencies@ field), +-- we can make some assumptions about the @Expr@ passed into `modifyRawDhallExpression`. +-- +-- For example, we don't need to know what @Embed@ data constructor cases are because we can infer +-- based on where we are in the expression whether they are a Record expression that has +-- our desired field (e.g. the @dependencies@ field) or a List expression. +-- +-- In other words, `modifyRawDhallExpression` can actually succeed for the cases we support. +modifyRawConfigExpression :: HasLogFunc env => ConfigModification -> ResolvedUnresolvedExpr -> RIO env Expr +modifyRawConfigExpression configMod ResolvedUnresolvedExpr { resolvedUnresolvedExpr = (normalizedExpr, originalExpr) } = case configMod of + AddPackages newPackages -> do + maybeAllInstalledPkgs <- findListTextValues dependenciesText PackageName + case maybeAllInstalledPkgs of + Nothing -> do + pure originalExpr + Just allInstalledPkgs -> do + let pkgsToInstall = nubSeq $ Seq.filter (`notElem` allInstalledPkgs) $ Seq.fromList $ Set.toList newPackages + if null pkgsToInstall + then do + pure originalExpr + else do + modifyRawDhallExpression dependenciesText (InsertListText (Dhall.toTextLit . packageName <$> pkgsToInstall)) originalExpr + where + findListTextValues :: HasLogFunc env => Text -> (Text -> a) -> RIO env (Maybe (Seq a)) + findListTextValues key f = case normalizedExpr of + Dhall.RecordLit kvs -> case Dhall.Map.lookup key kvs of + Just Dhall.RecordField { recordFieldValue } -> case recordFieldValue of + Dhall.ListLit _ dependencies -> do + Just . fmap f <$> traverse (throws . Dhall.fromTextLit) dependencies + _ -> do + logDebug $ display $ "In normalized expression, did not find a `ListLit` for key, '" <> key <> "'." + pure Nothing + _ -> do + logDebug $ display $ "In normalized expression, did not find a field for key, '" <> key <> "'." + pure Nothing + _ -> do + logDebug "In normalized expression, did not find a `RecordLit`." + pure Nothing + +-- | \"dependencies\" - Reduce chance of spelling mistakes/typos +dependenciesText :: Text +dependenciesText = "dependencies" + +-- | Indicates where we are in the expression +-- +-- When the stack is empty, we are at the place in the expression +-- where we should do the update. +-- Otherwise, we are trying to find a record expression +-- that has the next key on the stack +-- +-- Here's how the @levelKeyStack@ gets modified +-- - @Field@ pushes new keys on top of the stack +-- - @RecordLit@ pops off the key at the top of the stack +-- and looks up the field corresponding to it +-- - @With@ sometimes pops off one or more of the keys in +-- the @levelKeyStack@ when it can update that part of it. +newtype ExprLevel = ExprLevel { levelKeyStack :: [Text] } + deriving (Eq, Show) + +data UpdateResult + = Updated !Expr + -- ^ The expression was succesfully updated with the requested change. + | VariableName !(Text, Int) ![Text] + -- ^ The expression to update is the binding value with this name + -- that corresponds to the specified de Brujin index. + -- See "Dhall.Core#t:Var". + -- + -- The @[Text]@ arg stores the @levelKeyStack@ at the time + -- the variable that needs to be updated was found. This value + -- should be used instead of whatever the keyStack is for a given level + -- once the correct binding is found. + +-- | +-- Basically 'Prelude.fmap' but only for the @Updated@ case. +mapUpdated :: (Expr -> Expr) -> UpdateResult -> UpdateResult +mapUpdated f (Updated e) = Updated (f e) +mapUpdated _ other = other + +printUpdateResult :: UpdateResult -> Text +printUpdateResult = \case + Updated _ -> + "Updated " + VariableName (name, idx) newKeyStack -> + "VariableName " <> name <> " " <> Text.pack (show idx) <> " [" <> Text.intercalate ", " newKeyStack <> "]" + +-- | +-- Modifies any supported Dhall expression with the requested changes. +-- +-- To make this implementation cover most of the usual cases while still making this simple, +-- the following cases will NOT be supported: +-- - Lam binding expr - @λ(binding : Text) -> { dependencies = [ x ] }@ +-- Although a @Lam@ ca not be a root-level expression, it could still appear in various places. +-- We could try to update the function\'s body, but without greater context, it\'s possible +-- that updating the body could affect other things, too. If someone is using something as +-- complicated as lambdas, we will force them to update the file manually. +-- - App func arg - @(λ(x : A) -> { dependencies = [ x ] }) \"bar\"@ +-- This can produce a record expression and unlike @Lam@ can be a root-level expression. +-- While we could update this using the same logic for @Embed@, +-- it is likely better to let the user manually update their file instead. If one is using +-- an advanced feature like this, there are likely reasons why that we can not anticipate here. +-- Thus, we won't be covering it and instead will force the user to update the file manually. +-- - BoolIf condition thenPath elsePath - @if x then y else z@ +-- If the update is in either the @thenPath@ or the @elsePath@, which one do we update? +-- Without more context, we can not know and might update the value incorrectly. +-- Thus, we force the user to manually add the dependencies if this is used. +-- - Project expr (Right typeExpr) - @let P = { a : Text } in { a = \"1\", b = 2 }.(P)@ +-- This only produces a record expresion. We don't support the @Right@ version of @Project@ +-- because it requires traversing type-level constructors correctly. We will, however, +-- support its @Left@ version. +-- +-- The below cases will be supported. Each is described below with a small description of how to update them: +-- - Embed _ - @./spago.dhall@ +-- This imports and refers to another Dhall expression elsewhere. +-- As long as we have previously normalized the original expression +-- and verified that it will produce the \"shape\" we are expecting, +-- then when we encounter an @Embed@ constructor, we can make +-- one assumption about it: the type of the expression +-- must match what we are looking for in the current @ExprLevel@. +-- +-- For example, if the @levelKeyStack@ is empty and we encounter an @Embed@ case +-- and we are trying to add packages to the @dependencies@ field, then we know +-- the import will produce an @List Text@-like expression. In such a case, +-- we can wrap it in a @ListAppend embedExpr newListLitExpr@. +-- +-- If we the @levelKeyStack@ is not empty and we encounter an @Embed@ case, then we know +-- the import will produce a record expression. In such a case, we can wrap +-- it in a let binding and do the update with a @With@ expression that +-- appends the update to the part of the record we wish to update. +-- - ListLit - @[\"a\", \"literal\", \"list\", \"of\", \"values\"]@ +-- When the `AstUpdate` is a @InsertListText additions@, updating this expression +-- merely means adding the @additions@ to its list. +-- - ListAppend - @[\"list1\"] # [\"list2\"]@ +-- Similar to @ListLit@ except it appends two list expressions together. +-- If it doesn't contain a @ListLit@ (e.g. @expr1 # expr2@), we can wrap the entire +-- expression in a @ListAppend originalExpr listExprWithNewPkgs@. +-- - RecordLit - @{ key = value }@ +-- This is ultimately what we are looking for, so we can update the respective field. +-- However, due to supporting @Field@, which selects values within a record, +-- we need to support looking up keys besides the main key (e.g @dependencies@). +-- - Field recordExpr selection - @{ key = { dependencies = [\"bar\"] } }.key@ +-- This can produce any expression since it is extracting the field within a record expression. +-- Let's say we were originally going to update a record expression's @depenencies@ field. +-- In such a case, we're looking for a record expression that has such a field. However, +-- when we come across a @Field@, it informs us that we must now find a record expression +-- that has a key matching the field being selected, and go "down" that key before +-- we can continue our original search (i.e. the record expression that has a @dependencies@ field), +-- - Project expr (Left keys) - @{ dependencies = [\"bar\"], other = \"foo\" }.{ dependencies }@ +-- This produces a record expresion. We only need to update this expression if +-- one of its keys is the next key on the @levelKeyStack@ we want to update. +-- - Prefer recordExpr overrideRecordExpr - @{ dependencies = [\"foo\"] } \/\/ { dependencies = [\"bar\"] }@ +-- This produces a record expression. If we update the @recordExpr@ +-- arg and the field we are updating (e.g. @dependencies@) is overridden by @overrideRecordExpr@, +-- then the update is pointless. +-- If the @overrideRecordExpr@ value overrides something that we are not trying to update +-- (e.g. the @sources@ field when updating the @dependencies@ field), +-- then we need to update the @recordExpr@. So, we need to try to update the @overrideRecordExpr@ arg first +-- and only if that fails do we attempt to update the @recordExpr@. +-- - With recordExpr field update - @{ dependencies = [\"foo\"] } with dependencies = [\"bar\"]@ +-- This produces a record expression. Similar to @Prefer@, +-- we should attempt to update the @update@ value first before attempting to update @recordExpr@. +-- Since a @With@ can use nested fields (e.g. @recordExpr with outer.inner.dependencies = update@), +-- we will see whether all of the values at the top of the @levelKeyStack@ match the nested fields +-- the @With@ is using to update part of the record expression. If it does, then we +-- try to update the @update@ expression. If that fails or if the match fails, +-- then we update the record expression as the update is updating something +-- irrelevant to our request. +-- - Let binding inExpr - @let binding = value in expr@ +-- The expression we need to update will often be in the @inExpr@ (i.e. after the @in@ keyword; +-- > let src = [ "src" ] in { ..., dependencies = [ "old" ], sources = src }`) +-- However, we might need to update the expression associated with the bound variable name. +-- (e.g. @let config = { dependencies = [ \"foo\" ] } in config@). We will not know +-- where we need to update the value to which the binding refers until after we have +-- learned how it is used. +-- +-- However, updating the value to which a binding refers can introduce a side-effect if +-- the binding is used in two or more places. +-- > let x = "foo" in { usage1 = x, usage2 = x } +-- If we update the value for @x@ with the intent of updating @usage1@, we will also update +-- @usage2@, which might be invalid. On the other hand, sometimes this side-effect +-- is actually intentional if one binding is building off of another. Consider: +-- > let x = "foo" let y = x # ["bar"] in { useX = x, useY = y } +-- +-- Thus, for this implementation, we assume that we should update the binding's value +-- if the @levelKeyStack@ is not empty. Otherwise, we do the update in the @inExpr@. +-- +-- Since this is modifying the raw AST and might produce an invalid configuration file, +-- the returned expression should be verified to produce a valid configuration format. +modifyRawDhallExpression :: HasLogFunc env => Text -> AstUpdate -> Expr -> RIO env Expr +modifyRawDhallExpression initialKey astMod originalExpr = do + result <- updateExpr (ExprLevel { levelKeyStack = [initialKey] }) originalExpr + case result of + Just (Updated newExpr) -> do + pure newExpr + _ -> do + pure originalExpr + where + -- | + -- Adds the additions to a @ListLit@'s @Seq@ argument + updateListTextByAppending :: Seq Expr -> Seq Expr -> Seq Expr + updateListTextByAppending additions listLitSeqArg = Seq.sort (additions <> listLitSeqArg) + + -- | + -- Removes some boilerplate: changes @expr@ to @expr # [\"new\"]@ + updateListTextByWrappingListAppend :: Seq Expr -> Expr -> Expr + updateListTextByWrappingListAppend additions expr = + Dhall.ListAppend expr $ Dhall.ListLit Nothing $ updateListTextByAppending additions Seq.empty + + -- | + -- Tries to find a @ListLit@ in a potentially-nested @ListAppend@ expression + -- and tries to append the additions to that @ListLit@ + updateListTextByMergingListLits :: Seq Expr -> Expr -> Maybe Expr + updateListTextByMergingListLits additions expr = case expr of + Dhall.ListLit ann ls -> Just $ Dhall.ListLit ann $ updateListTextByAppending additions ls + Dhall.ListAppend left right -> do + (\newLeft -> Dhall.ListAppend newLeft right) <$> updateListTextByMergingListLits additions left + <|> (\newRight -> Dhall.ListAppend left newRight) <$> updateListTextByMergingListLits additions right + _ -> Nothing + + -- | + -- > ./spago.dhall + -- (or some other expression where the required update is within the embed (e.g. @./spago.dhall // { sources = [\"foo\"] }@) + -- to + -- > let varName = ./spago.dhall + -- > in varName + -- > with dependencies = varName.dependencies # ["new"] + updateListTextByWrappingLetBinding :: NonEmpty Text -> Seq Expr -> Text -> Expr -> Expr + updateListTextByWrappingLetBinding keyStack additions varName expr = do + let + var = Dhall.Var (Dhall.V varName 0) + + -- `let __embed = expr` + binding = Dhall.makeBinding varName expr + + -- `__embed.key1.key2.key3` + varSelect = foldl' (\acc nextKey -> Dhall.Field acc (Dhall.makeFieldSelection nextKey)) var keyStack + + -- `__embed.dependencies # ["new"]` + lsAppend = + Dhall.ListAppend + varSelect + (Dhall.ListLit Nothing $ updateListTextByAppending additions Seq.empty) + Dhall.Let binding $ Dhall.With var keyStack lsAppend + + debugCase level caseMsg = + -- Change @logDebug@ to @logWarn@ to see results in tests + logDebug $ "Level: " <> displayShow level <> " - " <> caseMsg + + debugResult level caseMsg maybeResult = do + debugCase level (caseMsg <> " - got: " <> displayShow (fmap printUpdateResult maybeResult)) + pure maybeResult + + -- | + -- Updates an expression by recursively updating any subexpressions + -- until the update succeeds (@Just Updated@) or fails (@Nothing@). If called + -- on a \"root-level\" expression, a @Just VariableName@ also + -- counts as failure. For subexpressions, a returned @Just VariableName@ value may be used to determine how to + -- update a subexpression found within the \"root-level\" expression. + updateExpr :: HasLogFunc env => ExprLevel -> Expr -> RIO env (Maybe UpdateResult) + updateExpr level@ExprLevel{..} expr = case expr of + -- ./spago.dhall + Dhall.Embed _ -> do + debugCase level "Embed" + case astMod of + InsertListText additions -> do + case levelKeyStack of + -- `{ ..., dependencies = ./spago-deps.dhall }` + -- to + -- `{ ..., dependencies = ./spago-deps.dhall # additions }` + [] -> do + pure $ Just $ Updated $ updateListTextByWrappingListAppend additions expr + + -- `./spago.dhall` + -- to + -- `let __embed = ./spago.dhall in __embed with key = __embed.key # additions` + (key:keys) -> do + pure $ Just $ Updated $ updateListTextByWrappingLetBinding (key :| keys) additions "__embed" expr + + -- let varname = ... in ... varName + Dhall.Var (Dhall.V varName deBrujinIndex) -> do + debugCase level $ "Var(varName =" <> displayShow varName <> ", deBrujin Index = " <> displayShow deBrujinIndex <> ")" + case levelKeyStack of + -- Since the @levelKeyStack@ is empty, we should do the update here rather than + -- traversing back up those let bindings because we don't know if those + -- let bindings are used in two or more places. + -- + -- This means the update won't be as nice sylistically in some circumstances, + -- but it does guarantee that modification is made safely. Otherwise, + -- the let binding might be used elsewhere in a way we weren't anticipating. + -- + -- For example: + -- `let x = ["foo"] let y = x let z = y in z` + -- to + -- `let x = ["foo"] let y = x let z = y in z # ["new"]` + [] -> do + case astMod of + InsertListText additions -> do + pure $ Just $ Updated $ updateListTextByWrappingListAppend additions expr + + -- Since, we still need to go "down" fields in various record expressions, + -- we can't do the update here. For example, the second `config` in + -- `let config = { ..., dependencies = ... } in config` + _ -> do + pure $ Just $ VariableName (varName, deBrujinIndex) levelKeyStack + + -- ["foo", "bar"] + Dhall.ListLit ann ls -> do + debugCase level $ "ListLit( ls =" <> displayShow ls <> ")" + case levelKeyStack of + (_:_) -> do + -- This could happen if one was accessing part of a list via `List/head` or something. + -- However, since we don't support functions yet, we don't need to do anything here. + pure Nothing + + [] -> do + case astMod of + -- `{ ..., dependencies = ["old"] } + -- to + -- `{ ..., dependencies = ["old", "new"] } + InsertListText additions -> do + pure $ Just $ Updated $ Dhall.ListLit ann $ updateListTextByAppending additions ls + + -- left # right + Dhall.ListAppend left right -> do + debugCase level $ "ListAppend( left = " <> displayShow left <> ", right = " <> displayShow right <> ")" + case levelKeyStack of + (_:_) -> do + -- This could happen if one was accessing part of a list via `List/head` or something. + -- However, since we don't support functions yet, we don't need to do anything here. + pure Nothing + + [] -> do + case astMod of + -- `["foo"] # expr` -> `["old", "new"] # expr` + -- `expr # ["old"]` -> `expr # ["old", "new"]` + -- `expr1 # ["old"] # expr2` -> `expr1 # ["old", "new"] # expr2` + -- `expr1 # expr2` -> `expr1 # expr2 # ["new"]` + InsertListText additions -> do + Just . Updated <$> do + let + mergeResult = + (\newLeft -> Dhall.ListAppend newLeft right) <$> updateListTextByMergingListLits additions left + <|> (\newRight -> Dhall.ListAppend left newRight) <$> updateListTextByMergingListLits additions right + case mergeResult of + Just lsAppend -> do + pure lsAppend + Nothing -> do + -- Since we couldn't add the update to an existing ListLit, + -- we'll just add it the the end + pure $ updateListTextByWrappingListAppend additions expr + + -- { key = value, ... } + Dhall.RecordLit kvs -> do + let + caseMsg = "RecordLit( keys = " <> displayShow (Dhall.Map.keys kvs) + debugCase level caseMsg + case levelKeyStack of + -- Invalid Dhall expression + [] -> do + pure Nothing + + (key:keys) -> + case Dhall.Map.lookup key kvs of + Nothing -> do + pure Nothing + Just Dhall.RecordField { recordFieldValue } -> do + let + updateRecordLit = + Dhall.RecordLit + . flip (Dhall.Map.insert key) kvs + . Dhall.makeRecordField + + newLevel = ExprLevel { levelKeyStack = keys } + + maybeResult <- fmap (mapUpdated updateRecordLit) <$> updateExpr newLevel recordFieldValue + debugResult level caseMsg maybeResult + + -- recordExpr.selection + Dhall.Field recordExpr selection@Dhall.FieldSelection { fieldSelectionLabel } -> do + let caseMsg = "Field( fieldSelectionLabel = " <> displayShow fieldSelectionLabel <> ")" + debugCase level caseMsg + -- `{ config = { ..., dependencies = [ "package" ] } }.config` + -- to + -- `{ config = { ..., dependencies = [ "package", "new" ] } }.config` + let + newLevel = ExprLevel { levelKeyStack = fieldSelectionLabel : levelKeyStack } + maybeResult <- updateExpr newLevel recordExpr + debugResult level caseMsg maybeResult + case maybeResult of + Just (Updated newRecordExpr) -> do + pure $ Just $ Updated $ Dhall.Field newRecordExpr selection + + Just VariableName{} -> do + case levelKeyStack of + -- Don't traverse back up the let binding because it will introduce a side-effect. + -- For example, if we were updating the `dependencies` field... + -- Given: `let x = ["foo"] in { name = x, dependencies = { useX = x }.useX, ... }` + -- Wrong: `let x = ["foo", "new"] in { name = x, dependencies = { useX = x }.useX, ... }` + -- Right: `let x = ["foo"] in { name = x, dependencies = ({ useX = x }.useX) # [ "new" ], ... }` + [] -> do + case astMod of + InsertListText additions -> do + pure $ Just $ Updated $ updateListTextByWrappingListAppend additions expr + + -- We still need to lookup a key, so it's safe to update a previous let binding. + _ -> do + pure maybeResult + + Nothing -> do + pure maybeResult + + -- { foo = "bar", baz = "2" }.{ foo } == { foo = "bar" } + Dhall.Project recordExpr projectKeys@(Left keysExposed) -> do + let caseMsg = "Project( Left keys = (" <> displayShow keysExposed <> ")" + debugCase level caseMsg + case levelKeyStack of + -- We could only do an update here if we were replacing an entire record expression + -- with a new one. We don't currently support that and likely never will. + -- The below example shows how the `rec` value is replaced with a new record expresion. + -- `{ a = "foo", rec = { value = "bar" } }` + -- to + -- `{ a = "foo", rec = { something = "else" } }` + [] -> do + pure Nothing + + -- We can only update the underlying record expression if + -- one of the keys exposed by the projection is the next key we're trying to find. + -- `({ config = { dependencies = ["old"], ... } }.{ config }).config` + -- to + -- `({ config = { dependencies = ["old", "new"], ... } }.{ config }).config` + (key:_) | key `elem` keysExposed -> do + maybeRecordExpr <- updateExpr level recordExpr + void $ debugResult level caseMsg maybeRecordExpr + case maybeRecordExpr of + Just (Updated newRecordExpr) -> do + pure $ Just $ Updated $ Dhall.Project newRecordExpr projectKeys + + Just VariableName{} -> do + pure maybeRecordExpr + + Nothing -> do + pure maybeRecordExpr + + -- None of the keys exposed by the projection are ones we're interestd in. + -- For example, if we are trying to update the `dependencies` field, + -- we would update the record expression on the left of the `Prefer` / `//` + -- rather than the right record expression. + -- `{ dependencies = ["old"] } // { hiddenKey = "bar", exposedKey = ["foo" ] }.{ exposedKey } + -- to + -- `{ dependencies = ["old", "new"] } // { hiddenKey = "bar", exposedKey = ["foo" ] }.{ exposedKey } + (_:_) -> do + pure Nothing + + -- left // right + Dhall.Prefer charSet preferAnn left right -> do + let caseMsg = "Prefer" + debugCase level caseMsg + + maybeRight <- updateExpr level right + void $ debugResult level (caseMsg <> " - right") maybeRight + case maybeRight of + Just VariableName{} -> do + pure maybeRight + + Just (Updated newRight) -> do + pure $ Just $ Updated $ Dhall.Prefer charSet preferAnn left newRight + + Nothing -> do + maybeLeft <- updateExpr level left + void $ debugResult level (caseMsg <> " - left") maybeLeft + case maybeLeft of + Just VariableName{} -> do + pure maybeLeft + + Just (Updated newLeft) -> do + pure $ Just $ Updated $ Dhall.Prefer charSet preferAnn newLeft right + + Nothing -> do + pure maybeLeft + + -- recordExpr with field1.field2.field3 = update + Dhall.With recordExpr field update -> do + debugCase level caseMsg + -- ``` + -- { outer = + -- { config = + -- { ..., dependencies = ["old"] } + -- } with config.dependencies = ["package"]` + -- }.outer.config + -- ``` + -- to + -- { outer = + -- { config = + -- { ..., dependencies = ["old"] } + -- } with config.dependencies = ["package", "new"]` + -- }.outer.config + -- ``` + let + levelForUpdate :: [Text] -> [Text] -> Maybe ExprLevel + levelForUpdate (fieldKey:fieldKeys') (nextKey:nextKeys') + | fieldKey == nextKey = levelForUpdate fieldKeys' nextKeys' + levelForUpdate [] nextKeys' = Just $ ExprLevel { levelKeyStack = nextKeys' } + levelForUpdate _ _ = Nothing + + case levelForUpdate (toList field) levelKeyStack of + Just levelForUpdateSearch -> do + maybeUpdate <- updateExpr levelForUpdateSearch update + void $ debugResult level (caseMsg <> " - update") maybeUpdate + case maybeUpdate of + Just VariableName{} -> do + pure maybeUpdate + + Just (Updated newUpdate) -> do + pure $ Just $ Updated $ Dhall.With recordExpr field newUpdate + + Nothing -> + updateRecordExpr + + Nothing -> do + updateRecordExpr + + where + caseMsg = "With( field = " <> displayShow field <> ")" + + updateRecordExpr = do + maybeRecordExpr <- updateExpr level recordExpr + void $ debugResult level (caseMsg <> " - recordExpr") maybeRecordExpr + case maybeRecordExpr of + Just VariableName{} -> do + pure maybeRecordExpr + + Just (Updated newRecordExpr) -> do + pure $ Just $ Updated $ Dhall.With newRecordExpr field update + + Nothing -> do + pure maybeRecordExpr + + Dhall.Let binding@Dhall.Binding { variable, value } inExpr -> do + debugCase level caseMsg + maybeResult <- updateExpr level inExpr + void $ debugResult level (caseMsg <> " - inExpr") maybeResult + case maybeResult of + Just (Updated newInExpr) -> do + pure $ Just $ Updated $ Dhall.Let binding newInExpr + + -- We've learned that we need to update a let binding's value, but + -- the variable below is referring to a binding with the same name, + -- but which appeared earlier in the expression. + -- So, we need to update that binding, not this one. + -- + -- `let x = 1 let x = 2 in x == 2` + -- `let x = 1 let x = 2 in x@0 == 2` + -- `let x = 1 let x = 2 in x@1 == 1` + Just (VariableName (name, deBrujinIndex) newKeyStack) | name == variable, deBrujinIndex > 0 -> do + if shouldStopUpdwardsTraversal then do + debugUpwardsTraversalStop + case astMod of + -- `let lsBinding = ["old"] ... in { dependencies = let x = lsBinding in x }` + -- to + -- `let lsBinding = ["old"] ... in { dependencies = (let x = lsBinding in x) # ["new"] }` + InsertListText additions -> do + pure $ Just $ Updated $ updateListTextByWrappingListAppend additions expr + else do + debugUpwardsTraversalAllow + pure $ Just $ VariableName (name, deBrujinIndex - 1) newKeyStack + + -- This expression is the binding whose value we need to update. + Just (VariableName (name, deBrujinIndex) newKeyStack) | name == variable && deBrujinIndex == 0 -> do + let valueLevel = ExprLevel { levelKeyStack = newKeyStack } + maybeValue <- updateExpr valueLevel value + void $ debugResult level (caseMsg <> " - value") maybeValue + case maybeValue of + Just (Updated newValue) -> do + pure $ Just $ Updated $ Dhall.Let (Dhall.makeBinding variable newValue) inExpr + + Just VariableName{} -> do + if shouldStopUpdwardsTraversal then do + debugUpwardsTraversalStop + case astMod of + -- `let lsBinding = ["old"] ... in { dependencies = let x = lsBinding in x }` + -- to + -- `let lsBinding = ["old"] ... in { dependencies = (let x = lsBinding in x) # ["new"] }` + InsertListText additions -> do + pure $ Just $ Updated $ Dhall.Let (Dhall.makeBinding variable + $ updateListTextByWrappingListAppend additions value) inExpr + + else do + debugUpwardsTraversalAllow + -- `let x = "foo" let y = x in y` + -- This binding refers to another binding + pure maybeValue + + Nothing -> do + pure maybeValue + + Just VariableName{} -> do + -- Variable name doesn't match this let binding's name + pure maybeResult + + Nothing -> do + pure maybeResult + where + caseMsg = "Let( variable = " <> displayShow variable <> ")" + debugUpwardsTraversalStop = debugCase level (caseMsg <> " - stopping upwards traversal and updating here") + debugUpwardsTraversalAllow = debugCase level (caseMsg <> " - allowing upwards traversal") + + -- Normally, we would continue traversing up the expression and update the + -- referenced variable. However, since the @levelKeyStack@ is empty, we are currently + -- at the location within the expression that we want to update. + -- If we leave the current expression and go back "up" to its parent expression, + -- we might update a let binding's value in such a way that it produces a side-effect. + -- If the let binding is used in two places, then we only want to update it for one + -- usage but not the other. To ensure that happens, we'll stop here and make the update. + -- + -- It would be safe to update the let binding if we confirmed that it's only being used + -- in a single place, but such a check would require traversing the entire expression. + shouldStopUpdwardsTraversal = null levelKeyStack + + _ -> do + debugCase level "Unsupported case" + pure Nothing diff --git a/src/Spago/Dhall.hs b/src/Spago/Dhall.hs index 6e983c719..1f6a431cf 100644 --- a/src/Spago/Dhall.hs +++ b/src/Spago/Dhall.hs @@ -177,6 +177,8 @@ data ReadError a where -- | a key is missing from a Dhall map RequiredKeyMissing :: Typeable a => Text -> Dhall.Map.Map Text (DhallExpr a) -> ReadError a +deriving instance (Eq a) => Eq (ReadError a) + instance (Pretty a, Typeable a) => Exception (ReadError a) instance (Pretty a) => Show (ReadError a) where diff --git a/src/Spago/Messages.hs b/src/Spago/Messages.hs index 0a94a6b7f..bd4a2b4da 100644 --- a/src/Spago/Messages.hs +++ b/src/Spago/Messages.hs @@ -2,6 +2,7 @@ module Spago.Messages where import Spago.Prelude +import qualified Data.Set as Set import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Text as Text @@ -131,13 +132,13 @@ failedToReachGitHub err = makeMessage , tshow err ] -failedToAddDeps :: NonEmpty Text -> Text +failedToAddDeps :: Set Text -> Text failedToAddDeps pkgs = makeMessage $ [ "Some of the dependencies you tried to add were not found in the package-set." , "Not adding any new dependencies to your new spago config." , "We didn't find:" ] - <> map ("- " <>) (NonEmpty.toList pkgs) + <> map ("- " <>) (Set.toList pkgs) <> [""] updatingPackageSet :: Text -> Text diff --git a/src/Spago/Packages.hs b/src/Spago/Packages.hs index 34b30e0fe..ba2d07200 100644 --- a/src/Spago/Packages.hs +++ b/src/Spago/Packages.hs @@ -163,7 +163,7 @@ install newPackages = do -- Also skip the write if there are no new packages to be written case existingNewPackages of [] -> pure () - additional -> Config.addDependencies config additional + additional -> Config.addDependencies config $ Set.fromList additional Fetch.fetchPackages deps diff --git a/src/Spago/Prelude.hs b/src/Spago/Prelude.hs index d0df6bdf6..88999eaba 100644 --- a/src/Spago/Prelude.hs +++ b/src/Spago/Prelude.hs @@ -13,6 +13,7 @@ module Spago.Prelude , lastMay , empty , ifM + , nubSeq -- * Logging, errors, printing, etc , Pretty @@ -122,6 +123,8 @@ import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Search as BSS import qualified Data.ByteString.UTF8 as UTF8 import qualified System.IO as IO +import qualified Data.Sequence as Seq +import qualified Data.Set as Set -- | Generic Error that we throw on program exit. @@ -150,6 +153,15 @@ ifM p x y = p >>= \b -> if b then x else y hush :: Either a b -> Maybe b hush = either (const Nothing) Just +-- | +-- Removes duplicate elements in a @Seq@. +-- +-- Code from https://stackoverflow.com/questions/45757839 +nubSeq :: Ord a => Seq a -> Seq a +nubSeq xs = (fmap fst . Seq.filter (uncurry notElem)) (Seq.zip xs seens) + where + seens = Seq.scanl (flip Set.insert) Set.empty xs + pathFromText :: Text -> Turtle.FilePath pathFromText = Turtle.fromText diff --git a/src/Spago/RunEnv.hs b/src/Spago/RunEnv.hs index f720f595a..589fd1d17 100644 --- a/src/Spago/RunEnv.hs +++ b/src/Spago/RunEnv.hs @@ -8,6 +8,7 @@ import qualified System.Environment as Env import qualified RIO import qualified System.Info import qualified Turtle +import qualified Data.Set as Set import qualified Spago.Config as Config import qualified Spago.GlobalCache as Cache @@ -205,7 +206,7 @@ getPackageSet = do getMaybeGraph :: HasPursEnv env => BuildOptions -> Config -> [(PackageName, Package)] -> RIO env Graph getMaybeGraph BuildOptions{ depsOnly, sourcePaths } Config{ configSourcePaths } deps = do logDebug "Running `getMaybeGraph`" - let partitionedGlobs = Packages.getGlobs deps depsOnly $ toList configSourcePaths + let partitionedGlobs = Packages.getGlobs deps depsOnly $ Set.toList configSourcePaths globs = Packages.getGlobsSourcePaths partitionedGlobs <> sourcePaths supportsGraph <- Purs.hasMinPursVersion "0.14.0" if not supportsGraph diff --git a/src/Spago/Types.hs b/src/Spago/Types.hs index 4a03af3f0..c651ae557 100644 --- a/src/Spago/Types.hs +++ b/src/Spago/Types.hs @@ -50,7 +50,7 @@ data PackageSet = PackageSet { packagesDB :: Map PackageName Package , packagesMinPursVersion :: Maybe Version.SemVer } - deriving (Show, Generic) + deriving (Eq, Show, Generic) -- | We consider a "Repo" a "box of source to include in the build" @@ -190,14 +190,14 @@ data Config = Config , alternateBackend :: Maybe Text , configSourcePaths :: Set SourcePath , publishConfig :: Either (Dhall.ReadError Void) PublishConfig - } deriving (Show, Generic) + } deriving (Eq, Show, Generic) -- | The extra fields that are only needed for publishing libraries. data PublishConfig = PublishConfig { publishLicense :: Text , publishRepository :: Text - } deriving (Show, Generic) + } deriving (Eq, Show, Generic) data PursCmd = PursCmd { purs :: Text diff --git a/test/SpagoSpec.hs b/test/SpagoSpec.hs index 075b00978..510432c12 100644 --- a/test/SpagoSpec.hs +++ b/test/SpagoSpec.hs @@ -9,7 +9,7 @@ import Test.Hspec (Spec, around_, describe, it, shouldBe, shou import Turtle (ExitCode (..), cd, cp, decodeString, empty, encodeString, mkdir, mktree, mv, pwd, readTextFile, rm, shell, shellStrictWithErr, testdir, writeTextFile, ()) -import Utils (checkFileHasInfix, checkFixture, checkFileExist, outputShouldEqual, +import Utils (checkFileHasInfix, checkFixture, checkFileExist, checkInstallFixtureFail, checkInstallFixtureSucceed, outputShouldEqual, readFixture, runFor, shouldBeFailure, shouldBeFailureInfix, shouldBeFailureStderr, shouldBeSuccess, shouldBeSuccessOutput, shouldBeSuccessOutputWithErr, shouldBeSuccessStderr, spago, @@ -120,11 +120,24 @@ spec = around_ setup $ do threadDelay 1000000 spago ["install", "-j", "10"] >>= shouldBeSuccess - it "Spago should warn that config was not changed, when trying to install package already present in project dependencies" $ do + describe "Spago should warn that config was not changed, when trying to install package already present in project dependencies" $ do - spago ["init"] >>= shouldBeSuccess - spago ["install"] >>= shouldBeSuccess - spago ["install", "effect"] >>= shouldBeSuccessStderr "spago-install-existing-dep-stderr.txt" + it "... when a `[ \"actualDependency\", ... ]` expression is used" $ do + + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + spago ["install", "effect"] >>= shouldBeSuccessStderr "spago-install-existing-dep-stderr.txt" + + it "... when a `list1 # list2` expression is used" $ do + + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + spagoFileContent <- readFixture "spago-install-append-no-op-success.dhall" + writeTextFile "spago.dhall" spagoFileContent + spago ["-j 10", "install", "console", "prelude"] >>= shouldBeSuccessStderr "spago-install-existing-dep-stderr.txt" + mv "spago.dhall" "spago-install-append-no-op-success.dhall" + checkFixture "spago-install-append-no-op-success.dhall" it "Spago should strip 'purescript-' prefix and give warning if package without prefix is present in package set" $ do @@ -134,13 +147,61 @@ spec = around_ setup $ do -- dep added without "purescript-" prefix checkFileHasInfix "spago.dhall" "\"newtype\"" - it "Spago should be able to add dependencies" $ do + describe "Spago should be able to add dependencies" $ do - writeTextFile "psc-package.json" "{ \"name\": \"aaa\", \"depends\": [ \"prelude\" ], \"set\": \"foo\", \"source\": \"bar\" }" - spago ["init"] >>= shouldBeSuccess - spago ["-j 10", "install", "simple-json", "foreign"] >>= shouldBeSuccess - mv "spago.dhall" "spago-install-success.dhall" - checkFixture "spago-install-success.dhall" + it "... when a `[ \"actualDependency\", ... ]` expression is used" $ do + + writeTextFile "psc-package.json" "{ \"name\": \"aaa\", \"depends\": [ \"prelude\" ], \"set\": \"foo\", \"source\": \"bar\" }" + spago ["init"] >>= shouldBeSuccess + spago ["-j 10", "install", "simple-json", "foreign"] >>= shouldBeSuccess + mv "spago.dhall" "spago-install-success.dhall" + checkFixture "spago-install-success.dhall" + + describe "when a `list1 # list2` expression is used..." $ do + + it "append-right: otherDeps # [ \"actualDeps\" ]" $ do + + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + spagoFileContent <- readFixture "spago-install-append-right-before.dhall" + writeTextFile "spago.dhall" spagoFileContent + spago ["-j 10", "install", "arrays"] >>= shouldBeSuccess + mv "spago.dhall" "spago-install-append-right-success.dhall" + checkFixture "spago-install-append-right-success.dhall" + + it "append-left: [ \"actualDeps\" ] # otherDeps" $ do + + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + spagoFileContent <- readFixture "spago-install-append-left-before.dhall" + writeTextFile "spago.dhall" spagoFileContent + spago ["-j 10", "install", "arrays"] >>= shouldBeSuccess + mv "spago.dhall" "spago-install-append-left-success.dhall" + checkFixture "spago-install-append-left-success.dhall" + + it "append-middle: otherDeps1 # [ \"actualDeps\" ] # otherDeps2" $ do + + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + spagoFileContent <- readFixture "spago-install-append-middle-before.dhall" + writeTextFile "spago.dhall" spagoFileContent + spago ["-j 10", "install", "arrays"] >>= shouldBeSuccess + mv "spago.dhall" "spago-install-append-middle-success.dhall" + checkFixture "spago-install-append-middle-success.dhall" + + it "... and only dependencies not yet installed are actually installed" $ do + + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + spagoFileContent <- readFixture "spago-install-append-some-before.dhall" + writeTextFile "spago.dhall" spagoFileContent + spago ["-j 10", "install", "console", "effect", "newtype" ] >>= shouldBeSuccess + mv "spago.dhall" "spago-install-append-some-success.dhall" + checkFixture "spago-install-append-some-success.dhall" it "Spago should not add dependencies that are not in the package set" $ do @@ -185,12 +246,232 @@ spec = around_ setup $ do writeTextFile "packages.dhall" "let pkgs = ./packagesBase.dhall in pkgs // { spago = { dependencies = [\"prelude\"], repo = \"https://github.com/purescript/spago.git\", version = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\" }}" spago ["install", "spago"] >>= shouldBeFailure - it "Spago should be able to update dependencies in an alternative config" $ do + describe "Spago should be able to update dependencies in an alternative config" $ do - spago ["init"] >>= shouldBeSuccess - writeTextFile "alternative1.dhall" "./spago.dhall // {dependencies = [\"prelude\"]}" - spago ["-x", "alternative1.dhall", "install", "simple-json"] >>= shouldBeSuccess - checkFixture "alternative1.dhall" + it "... alternative1.dhall" $ do + + spago ["init"] >>= shouldBeSuccess + writeTextFile "alternative1.dhall" "./spago.dhall // {dependencies = [\"prelude\"]}" + spago ["-x", "alternative1.dhall", "install", "simple-json"] >>= shouldBeSuccess + checkFixture "alternative1.dhall" + + it "... alternative3.dhall" $ do + + checkInstallFixtureSucceed + "alternative3-before.dhall" + "alternative3-success.dhall" + + describe "... alternative4.dhall files - simple root-level expressions" $ do + + it "... alternative4-root-embed.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-embed-before.dhall" + "alternative4-root-embed-success.dhall" + + it "... alternative4-root-field-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-before.dhall" + "alternative4-root-field-success.dhall" + + it "... alternative4-root-let-inExpr-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-let-inExpr-before.dhall" + "alternative4-root-let-inExpr-success.dhall" + + it "... alternative4-root-let-value-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-let-value-before.dhall" + "alternative4-root-let-value-success.dhall" + + it "... alternative4-root-listappend.dhall" $ do + + checkInstallFixtureFail "alternative4-root-listappend.dhall" + + it "... alternative4-root-listlit.dhall" $ do + + checkInstallFixtureFail "alternative4-root-listlit.dhall" + + it "... alternative4-root-prefer-left.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-prefer-left-before.dhall" + "alternative4-root-prefer-left-success.dhall" + + it "... alternative4-root-prefer-right.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-prefer-right-before.dhall" + "alternative4-root-prefer-right-success.dhall" + + it "... alternative4-root-textlit.dhall" $ do + + checkInstallFixtureFail "alternative4-root-textlit.dhall" + + it "... alternative4-root-var.dhall" $ do + + checkInstallFixtureFail "alternative4-root-var.dhall" + + it "... alternative4-root-with-left.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-with-left-before.dhall" + "alternative4-root-with-left-success.dhall" + + it "... alternative4-root-with-right.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-with-right-before.dhall" + "alternative4-root-with-right-success.dhall" + + it "... alternative4-root-textlit.dhall" $ do + + checkInstallFixtureFail "alternative4-root-textlit.dhall" + + describe "... alternative4.dhall files - complex root-level field expressions" $ do + + it "... alternative4-root-field-embed.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-embed-before.dhall" + "alternative4-root-field-embed-success.dhall" + + it "... alternative4-root-field-field-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-field-before.dhall" + "alternative4-root-field-field-success.dhall" + + it "... alternative4-root-field-let-inExpr-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-let-inExpr-before.dhall" + "alternative4-root-field-let-inExpr-success.dhall" + + it "... alternative4-root-field-let-skip1-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-let-skip1-before.dhall" + "alternative4-root-field-let-skip1-success.dhall" + + it "... alternative4-root-field-let-skip2-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-let-skip2-before.dhall" + "alternative4-root-field-let-skip2-success.dhall" + + it "... alternative4-root-field-let-value-before.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-let-value-before.dhall" + "alternative4-root-field-let-value-success.dhall" + + it "... alternative4-root-field-listappend.dhall" $ do + + checkInstallFixtureFail "alternative4-root-field-listappend.dhall" + + it "... alternative4-root-field-listlit.dhall" $ do + + checkInstallFixtureFail "alternative4-root-field-listlit.dhall" + + it "... alternative4-root-field-prefer-left.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-prefer-left-before.dhall" + "alternative4-root-field-prefer-left-success.dhall" + + it "... alternative4-root-field-prefer-right.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-prefer-right-before.dhall" + "alternative4-root-field-prefer-right-success.dhall" + + it "... alternative4-root-field-textlit.dhall" $ do + + checkInstallFixtureFail "alternative4-root-field-textlit.dhall" + + it "... alternative4-root-field-var.dhall" $ do + + checkInstallFixtureFail "alternative4-root-field-var.dhall" + + it "... alternative4-root-field-with-left.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-with-left-before.dhall" + "alternative4-root-field-with-left-success.dhall" + + it "... alternative4-root-field-with-right.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-with-right-before.dhall" + "alternative4-root-field-with-right-success.dhall" + + it "... alternative4-root-field-textlit.dhall" $ do + + checkInstallFixtureFail "alternative4-root-field-textlit.dhall" + + it "... alternative4-root-let-value-embed-field.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-let-value-embed-field-before.dhall" + "alternative4-root-let-value-embed-field-success.dhall" + + it "... alternative4-dependencies-embed.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-dependencies-embed-before.dhall" + "alternative4-dependencies-embed-success.dhall" + + it "... alternative4-root-let-inExpr-field.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-let-inExpr-field-before.dhall" + "alternative4-root-let-inExpr-field-success.dhall" + + it "... alternative4-root-prefer-prefer-left.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-prefer-prefer-left-before.dhall" + "alternative4-root-prefer-prefer-left-success.dhall" + + it "... alternative4-root-prefer-prefer-right.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-prefer-prefer-right-before.dhall" + "alternative4-root-prefer-prefer-right-success.dhall" + + it "... alternative4-root-project_left.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-project_left-before.dhall" + "alternative4-root-project_left-success.dhall" + + it "... alternative4-root-field-with-left-nested.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-with-left-nested-before.dhall" + "alternative4-root-field-with-left-nested-success.dhall" + + it "... alternative4-root-field-with-right-nested.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-field-with-right-nested-before.dhall" + "alternative4-root-field-with-right-nested-success.dhall" + + it "... alternative4-root-recordlit-let.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-recordlit-let-before.dhall" + "alternative4-root-recordlit-let-success.dhall" + + it "... alternative4-root-let-trap-let.dhall" $ do + + checkInstallFixtureSucceed + "alternative4-root-let-trap-before.dhall" + "alternative4-root-let-trap-success.dhall" it "Spago should fail when the alternate config file doesn't exist" $ do spago ["init"] >>= shouldBeSuccess @@ -209,8 +490,12 @@ spec = around_ setup $ do spago ["init"] >>= shouldBeSuccess writeTextFile "alternative2.dhall" "./spago.dhall // { sources = [ \"src/**/*.purs\" ] }\n" spago ["-x", "alternative2.dhall", "install", "simple-json"] >>= shouldBeSuccess - spago ["-x", "alternative2.dhall", "install", "simple-json"] >>= shouldBeSuccessStderr "alternative2install-stderr.txt" - checkFixture "alternative2.dhall" + cp "alternative2.dhall" "alternative2-post-install.dhall" + checkFixture "alternative2-post-install.dhall" + rm "alternative2-post-install.dhall" + spago ["-x", "alternative2.dhall", "install", "simple-json"] >>= shouldBeSuccessStderr "alternative2-post-install2-stderr.txt" + cp "alternative2.dhall" "alternative2-post-install.dhall" + checkFixture "alternative2-post-install.dhall" it "Spago should install successfully when there are local dependencies sharing the same packages.dhall" $ do diff --git a/test/Utils.hs b/test/Utils.hs index 6dba28a12..30babb69c 100644 --- a/test/Utils.hs +++ b/test/Utils.hs @@ -2,6 +2,8 @@ module Utils ( checkFixture , checkFileHasInfix , checkFileExist + , checkInstallFixtureSucceed + , checkInstallFixtureFail , readFixture , getHighestTag , git @@ -34,7 +36,7 @@ import System.Directory (removePathForcibly, doesFileExist) import qualified System.Process as Process import Test.Hspec (HasCallStack, shouldBe, shouldSatisfy) import Turtle (ExitCode (..), FilePath, Text, cd, empty, encodeString, export, - inproc, limit, need, pwd, readTextFile, strict) + inproc, limit, mv, need, pwd, readTextFile, strict, toText, writeTextFile) import qualified Turtle.Bytes @@ -152,6 +154,40 @@ checkFileHasInfix path needle = do actual <- readTextFile path actual `shouldSatisfy` Text.isInfixOf needle +checkInstallFixtureSucceed :: HasCallStack => FilePath -> FilePath -> IO () +checkInstallFixtureSucceed beforeInstallFilePath successFilePath = do + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + let embedNameFilePath = "embed-name.dhall" + + embedName <- readFixture embedNameFilePath + writeTextFile embedNameFilePath embedName + + let embedDependenciesFilePath = "embed-dependencies.dhall" + + embedDeps <- readFixture embedDependenciesFilePath + writeTextFile embedDependenciesFilePath embedDeps + + let beforeFilePathArg = either (error . Text.unpack) id $ toText beforeInstallFilePath + + spagoFileContent <- readFixture beforeInstallFilePath + writeTextFile beforeInstallFilePath spagoFileContent + spago ["-x", beforeFilePathArg, "install", "newtype"] >>= shouldBeSuccess + mv beforeInstallFilePath successFilePath + checkFixture successFilePath + +checkInstallFixtureFail :: HasCallStack => FilePath -> IO () +checkInstallFixtureFail beforeInstallFilePath = do + spago ["init"] >>= shouldBeSuccess + spago ["install"] >>= shouldBeSuccess + + let beforeFilePathArg = either (error . Text.unpack) id $ toText beforeInstallFilePath + + spagoFileContent <- readFixture beforeInstallFilePath + writeTextFile beforeInstallFilePath spagoFileContent + spago ["-x", beforeFilePathArg, "install", "newtype"] >>= shouldBeFailure + rmtree :: FilePath -> IO () rmtree = removePathForcibly . encodeString diff --git a/test/fixtures/alternative2-post-install.dhall b/test/fixtures/alternative2-post-install.dhall new file mode 100644 index 000000000..82923b558 --- /dev/null +++ b/test/fixtures/alternative2-post-install.dhall @@ -0,0 +1,6 @@ + ( let __embed = ./spago.dhall + + in __embed + with dependencies = __embed.dependencies # [ "simple-json" ] + ) +// { sources = [ "src/**/*.purs" ] } diff --git a/test/fixtures/alternative2-post-install2-stderr.txt b/test/fixtures/alternative2-post-install2-stderr.txt new file mode 100644 index 000000000..919d2475d --- /dev/null +++ b/test/fixtures/alternative2-post-install2-stderr.txt @@ -0,0 +1 @@ +[warn] Configuration file was not updated. diff --git a/test/fixtures/alternative2install-stderr.txt b/test/fixtures/alternative2install-stderr.txt deleted file mode 100644 index 13dce3a69..000000000 --- a/test/fixtures/alternative2install-stderr.txt +++ /dev/null @@ -1,2 +0,0 @@ -[warn] Failed to add dependencies. You should have a record with the `dependencies` key for this to work. -[warn] Configuration file was not updated. diff --git a/test/fixtures/alternative3-before.dhall b/test/fixtures/alternative3-before.dhall new file mode 100644 index 000000000..fca4d3362 --- /dev/null +++ b/test/fixtures/alternative3-before.dhall @@ -0,0 +1,10 @@ +let deps = [] : List Text + +let x = ( ./spago.dhall + with dependencies = (./spago.dhall).dependencies # deps + with sources = (./spago.dhall).sources # ([] : List Text) + ) + // ({ subConfig = { name = "foo"} }.subConfig) + +let y = x +in y with sources = y.sources # ([] : List Text) diff --git a/test/fixtures/alternative3-success.dhall b/test/fixtures/alternative3-success.dhall new file mode 100644 index 000000000..5dee21864 --- /dev/null +++ b/test/fixtures/alternative3-success.dhall @@ -0,0 +1,14 @@ +let deps = [] : List Text + +let x = + ( ./spago.dhall + with dependencies = + (./spago.dhall).dependencies # deps # [ "newtype" ] + with sources = (./spago.dhall).sources # ([] : List Text) + ) + // { subConfig.name = "foo" }.subConfig + +let y = x + +in y + with sources = y.sources # ([] : List Text) diff --git a/test/fixtures/alternative4-dependencies-embed-before.dhall b/test/fixtures/alternative4-dependencies-embed-before.dhall new file mode 100644 index 000000000..cc45a911a --- /dev/null +++ b/test/fixtures/alternative4-dependencies-embed-before.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = ./embed-dependencies.dhall +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/alternative4-dependencies-embed-success.dhall b/test/fixtures/alternative4-dependencies-embed-success.dhall new file mode 100644 index 000000000..82c944a77 --- /dev/null +++ b/test/fixtures/alternative4-dependencies-embed-success.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = ./embed-dependencies.dhall # [ "newtype" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/alternative4-root-embed-before.dhall b/test/fixtures/alternative4-root-embed-before.dhall new file mode 100644 index 000000000..23743922d --- /dev/null +++ b/test/fixtures/alternative4-root-embed-before.dhall @@ -0,0 +1 @@ +./spago.dhall \ No newline at end of file diff --git a/test/fixtures/alternative4-root-embed-success.dhall b/test/fixtures/alternative4-root-embed-success.dhall new file mode 100644 index 000000000..9acfca706 --- /dev/null +++ b/test/fixtures/alternative4-root-embed-success.dhall @@ -0,0 +1,4 @@ +let __embed = ./spago.dhall + +in __embed + with dependencies = __embed.dependencies # [ "newtype" ] diff --git a/test/fixtures/alternative4-root-field-before.dhall b/test/fixtures/alternative4-root-field-before.dhall new file mode 100644 index 000000000..0c87f9282 --- /dev/null +++ b/test/fixtures/alternative4-root-field-before.dhall @@ -0,0 +1,7 @@ +{ config = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +}.config diff --git a/test/fixtures/alternative4-root-field-embed-before.dhall b/test/fixtures/alternative4-root-field-embed-before.dhall new file mode 100644 index 000000000..67aca4d51 --- /dev/null +++ b/test/fixtures/alternative4-root-field-embed-before.dhall @@ -0,0 +1,2 @@ +{ config = ./spago.dhall +}.config diff --git a/test/fixtures/alternative4-root-field-embed-success.dhall b/test/fixtures/alternative4-root-field-embed-success.dhall new file mode 100644 index 000000000..faf920b9d --- /dev/null +++ b/test/fixtures/alternative4-root-field-embed-success.dhall @@ -0,0 +1,6 @@ +{ config = + let __embed = ./spago.dhall + + in __embed + with dependencies = __embed.dependencies # [ "newtype" ] +}.config diff --git a/test/fixtures/alternative4-root-field-field-before.dhall b/test/fixtures/alternative4-root-field-field-before.dhall new file mode 100644 index 000000000..c6f1e0ae1 --- /dev/null +++ b/test/fixtures/alternative4-root-field-field-before.dhall @@ -0,0 +1,8 @@ +{ inner.config = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +, other = "some other irrelevant value" +}.inner.config diff --git a/test/fixtures/alternative4-root-field-field-success.dhall b/test/fixtures/alternative4-root-field-field-success.dhall new file mode 100644 index 000000000..e11047c16 --- /dev/null +++ b/test/fixtures/alternative4-root-field-field-success.dhall @@ -0,0 +1,9 @@ +{ inner.config + = + { name = "my-project" + , dependencies = [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +, other = "some other irrelevant value" +}.inner.config diff --git a/test/fixtures/alternative4-root-field-let-inExpr-before.dhall b/test/fixtures/alternative4-root-field-let-inExpr-before.dhall new file mode 100644 index 000000000..c521abd81 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-inExpr-before.dhall @@ -0,0 +1,8 @@ +{ config = + let x = "ignored" + in { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +}.config diff --git a/test/fixtures/alternative4-root-field-let-inExpr-success.dhall b/test/fixtures/alternative4-root-field-let-inExpr-success.dhall new file mode 100644 index 000000000..9b6d30d71 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-inExpr-success.dhall @@ -0,0 +1,10 @@ +{ config = + let x = "ignored" + + in { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +}.config diff --git a/test/fixtures/alternative4-root-field-let-skip1-before.dhall b/test/fixtures/alternative4-root-field-let-skip1-before.dhall new file mode 100644 index 000000000..fee97cf12 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-skip1-before.dhall @@ -0,0 +1,19 @@ +{ config = + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + let x = { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + let x = { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + in x@1 +}.config diff --git a/test/fixtures/alternative4-root-field-let-skip1-success.dhall b/test/fixtures/alternative4-root-field-let-skip1-success.dhall new file mode 100644 index 000000000..2d70bef09 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-skip1-success.dhall @@ -0,0 +1,25 @@ +{ config = + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + let x = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + in x@1 +}.config diff --git a/test/fixtures/alternative4-root-field-let-skip2-before.dhall b/test/fixtures/alternative4-root-field-let-skip2-before.dhall new file mode 100644 index 000000000..e07c6a7b9 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-skip2-before.dhall @@ -0,0 +1,19 @@ +{ config = + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + let x = { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + let x = { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + in x@2 +}.config diff --git a/test/fixtures/alternative4-root-field-let-skip2-success.dhall b/test/fixtures/alternative4-root-field-let-skip2-success.dhall new file mode 100644 index 000000000..0380e967c --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-skip2-success.dhall @@ -0,0 +1,25 @@ +{ config = + let x = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + in x@2 +}.config diff --git a/test/fixtures/alternative4-root-field-let-value-before.dhall b/test/fixtures/alternative4-root-field-let-value-before.dhall new file mode 100644 index 000000000..71cc63d22 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-value-before.dhall @@ -0,0 +1,9 @@ +{ config = + let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + in x +}.config diff --git a/test/fixtures/alternative4-root-field-let-value-success.dhall b/test/fixtures/alternative4-root-field-let-value-success.dhall new file mode 100644 index 000000000..549a58728 --- /dev/null +++ b/test/fixtures/alternative4-root-field-let-value-success.dhall @@ -0,0 +1,11 @@ +{ config = + let x = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + + in x +}.config diff --git a/test/fixtures/alternative4-root-field-listappend.dhall b/test/fixtures/alternative4-root-field-listappend.dhall new file mode 100644 index 000000000..11535762d --- /dev/null +++ b/test/fixtures/alternative4-root-field-listappend.dhall @@ -0,0 +1,2 @@ +{ config = ["invalid"] # ["expr"] +}.config diff --git a/test/fixtures/alternative4-root-field-listlit.dhall b/test/fixtures/alternative4-root-field-listlit.dhall new file mode 100644 index 000000000..5526e80aa --- /dev/null +++ b/test/fixtures/alternative4-root-field-listlit.dhall @@ -0,0 +1,2 @@ +{ config = ["invalid expression"] +}.config diff --git a/test/fixtures/alternative4-root-field-prefer-left-before.dhall b/test/fixtures/alternative4-root-field-prefer-left-before.dhall new file mode 100644 index 000000000..b07f945f8 --- /dev/null +++ b/test/fixtures/alternative4-root-field-prefer-left-before.dhall @@ -0,0 +1,7 @@ +{ config = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } with sources = [ "src/**/*.purs", "test/**/*.purs" ] +}.config diff --git a/test/fixtures/alternative4-root-field-prefer-left-success.dhall b/test/fixtures/alternative4-root-field-prefer-left-success.dhall new file mode 100644 index 000000000..8518a41aa --- /dev/null +++ b/test/fixtures/alternative4-root-field-prefer-left-success.dhall @@ -0,0 +1,9 @@ +{ config = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } + with sources = [ "src/**/*.purs", "test/**/*.purs" ] +}.config diff --git a/test/fixtures/alternative4-root-field-prefer-right-before.dhall b/test/fixtures/alternative4-root-field-prefer-right-before.dhall new file mode 100644 index 000000000..bf29cf055 --- /dev/null +++ b/test/fixtures/alternative4-root-field-prefer-right-before.dhall @@ -0,0 +1,7 @@ +{ config = + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } // { dependencies = [ "console", "effect", "prelude", "psci-support" ] } +}.config diff --git a/test/fixtures/alternative4-root-field-prefer-right-success.dhall b/test/fixtures/alternative4-root-field-prefer-right-success.dhall new file mode 100644 index 000000000..59f6f2a24 --- /dev/null +++ b/test/fixtures/alternative4-root-field-prefer-right-success.dhall @@ -0,0 +1,10 @@ +{ config = + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + // { dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + } +}.config diff --git a/test/fixtures/alternative4-root-field-success.dhall b/test/fixtures/alternative4-root-field-success.dhall new file mode 100644 index 000000000..41341ea10 --- /dev/null +++ b/test/fixtures/alternative4-root-field-success.dhall @@ -0,0 +1,7 @@ +{ config = + { name = "my-project" + , dependencies = [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +}.config diff --git a/test/fixtures/alternative4-root-field-textlit.dhall b/test/fixtures/alternative4-root-field-textlit.dhall new file mode 100644 index 000000000..19c22ca35 --- /dev/null +++ b/test/fixtures/alternative4-root-field-textlit.dhall @@ -0,0 +1,2 @@ +{ config = "invalid expression" +}.config diff --git a/test/fixtures/alternative4-root-field-var.dhall b/test/fixtures/alternative4-root-field-var.dhall new file mode 100644 index 000000000..1f4605e17 --- /dev/null +++ b/test/fixtures/alternative4-root-field-var.dhall @@ -0,0 +1,2 @@ +{ config = x@3 +}.config diff --git a/test/fixtures/alternative4-root-field-with-left-before.dhall b/test/fixtures/alternative4-root-field-with-left-before.dhall new file mode 100644 index 000000000..b07f945f8 --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-left-before.dhall @@ -0,0 +1,7 @@ +{ config = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } with sources = [ "src/**/*.purs", "test/**/*.purs" ] +}.config diff --git a/test/fixtures/alternative4-root-field-with-left-nested-before.dhall b/test/fixtures/alternative4-root-field-with-left-nested-before.dhall new file mode 100644 index 000000000..3e1aa4b5c --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-left-nested-before.dhall @@ -0,0 +1,11 @@ +{ outer = + { inner = + { config = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } + } + } with inner.config.sources = [ "src/**/*.purs", "test/**/*.purs" ] +}.outer.inner.config diff --git a/test/fixtures/alternative4-root-field-with-left-nested-success.dhall b/test/fixtures/alternative4-root-field-with-left-nested-success.dhall new file mode 100644 index 000000000..6feff2bc9 --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-left-nested-success.dhall @@ -0,0 +1,12 @@ +{ outer = + { inner.config + = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } + } + with inner.config.sources = [ "src/**/*.purs", "test/**/*.purs" ] +}.outer.inner.config diff --git a/test/fixtures/alternative4-root-field-with-left-success.dhall b/test/fixtures/alternative4-root-field-with-left-success.dhall new file mode 100644 index 000000000..8518a41aa --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-left-success.dhall @@ -0,0 +1,9 @@ +{ config = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } + with sources = [ "src/**/*.purs", "test/**/*.purs" ] +}.config diff --git a/test/fixtures/alternative4-root-field-with-right-before.dhall b/test/fixtures/alternative4-root-field-with-right-before.dhall new file mode 100644 index 000000000..f84b13f4d --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-right-before.dhall @@ -0,0 +1,7 @@ +{ config = + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } with dependencies = [ "console", "effect", "prelude", "psci-support" ] +}.config diff --git a/test/fixtures/alternative4-root-field-with-right-nested-before.dhall b/test/fixtures/alternative4-root-field-with-right-nested-before.dhall new file mode 100644 index 000000000..f1849e671 --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-right-nested-before.dhall @@ -0,0 +1,11 @@ +{ outer = + { inner = + { config = + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + } + } with inner.config.dependencies = [ "console", "effect", "prelude", "psci-support" ] +}.outer.inner.config diff --git a/test/fixtures/alternative4-root-field-with-right-nested-success.dhall b/test/fixtures/alternative4-root-field-with-right-nested-success.dhall new file mode 100644 index 000000000..3b6e66bf3 --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-right-nested-success.dhall @@ -0,0 +1,13 @@ +{ outer = + { inner.config + = + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + } + with inner.config.dependencies + = + [ "console", "effect", "newtype", "prelude", "psci-support" ] +}.outer.inner.config diff --git a/test/fixtures/alternative4-root-field-with-right-success.dhall b/test/fixtures/alternative4-root-field-with-right-success.dhall new file mode 100644 index 000000000..63efb59f5 --- /dev/null +++ b/test/fixtures/alternative4-root-field-with-right-success.dhall @@ -0,0 +1,9 @@ +{ config = + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + with dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] +}.config diff --git a/test/fixtures/alternative4-root-let-inExpr-before.dhall b/test/fixtures/alternative4-root-let-inExpr-before.dhall new file mode 100644 index 000000000..bf1b884d1 --- /dev/null +++ b/test/fixtures/alternative4-root-let-inExpr-before.dhall @@ -0,0 +1,7 @@ +let x = "ignored" +in +{ name = "my-project" +, dependencies = [ "console", "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/alternative4-root-let-inExpr-field-before.dhall b/test/fixtures/alternative4-root-let-inExpr-field-before.dhall new file mode 100644 index 000000000..7c1ed6e05 --- /dev/null +++ b/test/fixtures/alternative4-root-let-inExpr-field-before.dhall @@ -0,0 +1,7 @@ +let x = { dependencies = [ "console", "effect", "prelude", "psci-support" ] } +in +{ name = "my-project" +, dependencies = x.dependencies +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/alternative4-root-let-inExpr-field-success.dhall b/test/fixtures/alternative4-root-let-inExpr-field-success.dhall new file mode 100644 index 000000000..f93fc9bf9 --- /dev/null +++ b/test/fixtures/alternative4-root-let-inExpr-field-success.dhall @@ -0,0 +1,7 @@ +let x = { dependencies = [ "console", "effect", "prelude", "psci-support" ] } + +in { name = "my-project" + , dependencies = x.dependencies # [ "newtype" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/alternative4-root-let-inExpr-success.dhall b/test/fixtures/alternative4-root-let-inExpr-success.dhall new file mode 100644 index 000000000..9b9a90328 --- /dev/null +++ b/test/fixtures/alternative4-root-let-inExpr-success.dhall @@ -0,0 +1,8 @@ +let x = "ignored" + +in { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/alternative4-root-let-trap-before.dhall b/test/fixtures/alternative4-root-let-trap-before.dhall new file mode 100644 index 000000000..2cde0765c --- /dev/null +++ b/test/fixtures/alternative4-root-let-trap-before.dhall @@ -0,0 +1,7 @@ +let listBinding = [ "console", "effect", "prelude", "psci-support" ] + +in { name = "my-project" + , dependencies = let x = listBinding let y = x let z = y in z + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/alternative4-root-let-trap-success.dhall b/test/fixtures/alternative4-root-let-trap-success.dhall new file mode 100644 index 000000000..64ec1de7e --- /dev/null +++ b/test/fixtures/alternative4-root-let-trap-success.dhall @@ -0,0 +1,8 @@ +let listBinding = [ "console", "effect", "prelude", "psci-support" ] + +in { name = "my-project" + , dependencies = + let x = listBinding let y = x let z = y in z # [ "newtype" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/alternative4-root-let-value-before.dhall b/test/fixtures/alternative4-root-let-value-before.dhall new file mode 100644 index 000000000..e072cea30 --- /dev/null +++ b/test/fixtures/alternative4-root-let-value-before.dhall @@ -0,0 +1,7 @@ +let x = + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +in x \ No newline at end of file diff --git a/test/fixtures/alternative4-root-let-value-embed-field-before.dhall b/test/fixtures/alternative4-root-let-value-embed-field-before.dhall new file mode 100644 index 000000000..5b2f3868c --- /dev/null +++ b/test/fixtures/alternative4-root-let-value-embed-field-before.dhall @@ -0,0 +1 @@ +let x = { key1 = ./spago.dhall } in x.key1 \ No newline at end of file diff --git a/test/fixtures/alternative4-root-let-value-embed-field-success.dhall b/test/fixtures/alternative4-root-let-value-embed-field-success.dhall new file mode 100644 index 000000000..ac3dea763 --- /dev/null +++ b/test/fixtures/alternative4-root-let-value-embed-field-success.dhall @@ -0,0 +1,9 @@ +let x = + { key1 = + let __embed = ./spago.dhall + + in __embed + with dependencies = __embed.dependencies # [ "newtype" ] + } + +in x.key1 diff --git a/test/fixtures/alternative4-root-let-value-success.dhall b/test/fixtures/alternative4-root-let-value-success.dhall new file mode 100644 index 000000000..fec869a53 --- /dev/null +++ b/test/fixtures/alternative4-root-let-value-success.dhall @@ -0,0 +1,9 @@ +let x = + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } + +in x diff --git a/test/fixtures/alternative4-root-listappend.dhall b/test/fixtures/alternative4-root-listappend.dhall new file mode 100644 index 000000000..033029054 --- /dev/null +++ b/test/fixtures/alternative4-root-listappend.dhall @@ -0,0 +1 @@ +[ "invalid expression" ] # [ "still invalid" ] \ No newline at end of file diff --git a/test/fixtures/alternative4-root-listlit.dhall b/test/fixtures/alternative4-root-listlit.dhall new file mode 100644 index 000000000..5eba8c908 --- /dev/null +++ b/test/fixtures/alternative4-root-listlit.dhall @@ -0,0 +1 @@ +[ "invalid expression" ] \ No newline at end of file diff --git a/test/fixtures/alternative4-root-prefer-left-before.dhall b/test/fixtures/alternative4-root-prefer-left-before.dhall new file mode 100644 index 000000000..31238f102 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-left-before.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [] : List Text +} // { sources = [ "src/**/*.purs", "test/**/*.purs" ] } diff --git a/test/fixtures/alternative4-root-prefer-left-success.dhall b/test/fixtures/alternative4-root-prefer-left-success.dhall new file mode 100644 index 000000000..503a9a638 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-left-success.dhall @@ -0,0 +1,7 @@ + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } +// { sources = [ "src/**/*.purs", "test/**/*.purs" ] } diff --git a/test/fixtures/alternative4-root-prefer-prefer-left-before.dhall b/test/fixtures/alternative4-root-prefer-prefer-left-before.dhall new file mode 100644 index 000000000..c52143b86 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-prefer-left-before.dhall @@ -0,0 +1,7 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [] : List Text +} +// { sources = [ "src/**/*.purs", "test/**/*.purs" ] } +// { name = "my-project" } diff --git a/test/fixtures/alternative4-root-prefer-prefer-left-success.dhall b/test/fixtures/alternative4-root-prefer-prefer-left-success.dhall new file mode 100644 index 000000000..0726c383c --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-prefer-left-success.dhall @@ -0,0 +1,8 @@ + { name = "my-project" + , dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [] : List Text + } +// { sources = [ "src/**/*.purs", "test/**/*.purs" ] } +// { name = "my-project" } diff --git a/test/fixtures/alternative4-root-prefer-prefer-right-before.dhall b/test/fixtures/alternative4-root-prefer-prefer-right-before.dhall new file mode 100644 index 000000000..f265faf51 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-prefer-right-before.dhall @@ -0,0 +1,7 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} +// { dependencies = [ "console", "effect", "prelude", "psci-support" ] } +// { name = "my-project" } diff --git a/test/fixtures/alternative4-root-prefer-prefer-right-success.dhall b/test/fixtures/alternative4-root-prefer-prefer-right-success.dhall new file mode 100644 index 000000000..cb2895696 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-prefer-right-success.dhall @@ -0,0 +1,9 @@ + { name = "my-project" + , dependencies = [ "console", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +// { dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + } +// { name = "my-project" } diff --git a/test/fixtures/alternative4-root-prefer-right-before.dhall b/test/fixtures/alternative4-root-prefer-right-before.dhall new file mode 100644 index 000000000..34b969193 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-right-before.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = [] : List Text +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} // { dependencies = [ "console", "effect", "prelude", "psci-support" ] } diff --git a/test/fixtures/alternative4-root-prefer-right-success.dhall b/test/fixtures/alternative4-root-prefer-right-success.dhall new file mode 100644 index 000000000..0c7e814b0 --- /dev/null +++ b/test/fixtures/alternative4-root-prefer-right-success.dhall @@ -0,0 +1,8 @@ + { name = "my-project" + , dependencies = [] : List Text + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } +// { dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] + } diff --git a/test/fixtures/alternative4-root-project_left-before.dhall b/test/fixtures/alternative4-root-project_left-before.dhall new file mode 100644 index 000000000..6148d5d52 --- /dev/null +++ b/test/fixtures/alternative4-root-project_left-before.dhall @@ -0,0 +1,7 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +, unexposedKey1 = 1 +, unexposedKey2 = 2 +}.{ name, dependencies, packages, sources } diff --git a/test/fixtures/alternative4-root-project_left-success.dhall b/test/fixtures/alternative4-root-project_left-success.dhall new file mode 100644 index 000000000..dd5bb5089 --- /dev/null +++ b/test/fixtures/alternative4-root-project_left-success.dhall @@ -0,0 +1,7 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "newtype", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +, unexposedKey1 = 1 +, unexposedKey2 = 2 +}.{ name, dependencies, packages, sources } diff --git a/test/fixtures/alternative4-root-recordlit-let-before.dhall b/test/fixtures/alternative4-root-recordlit-let-before.dhall new file mode 100644 index 000000000..990ef5f5d --- /dev/null +++ b/test/fixtures/alternative4-root-recordlit-let-before.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = let x = [ "console", "effect", "prelude", "psci-support" ] in x +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/alternative4-root-recordlit-let-success.dhall b/test/fixtures/alternative4-root-recordlit-let-success.dhall new file mode 100644 index 000000000..6ff6ea053 --- /dev/null +++ b/test/fixtures/alternative4-root-recordlit-let-success.dhall @@ -0,0 +1,8 @@ +{ name = "my-project" +, dependencies = + let x = [ "console", "effect", "prelude", "psci-support" ] + + in x # [ "newtype" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/alternative4-root-textlit.dhall b/test/fixtures/alternative4-root-textlit.dhall new file mode 100644 index 000000000..d26ffd18c --- /dev/null +++ b/test/fixtures/alternative4-root-textlit.dhall @@ -0,0 +1 @@ +"invalid expression" \ No newline at end of file diff --git a/test/fixtures/alternative4-root-var.dhall b/test/fixtures/alternative4-root-var.dhall new file mode 100644 index 000000000..e772ac108 --- /dev/null +++ b/test/fixtures/alternative4-root-var.dhall @@ -0,0 +1 @@ +x@3 \ No newline at end of file diff --git a/test/fixtures/alternative4-root-with-left-before.dhall b/test/fixtures/alternative4-root-with-left-before.dhall new file mode 100644 index 000000000..2b9a7cd03 --- /dev/null +++ b/test/fixtures/alternative4-root-with-left-before.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [] : List Text +} with sources = [ "src/**/*.purs", "test/**/*.purs" ] diff --git a/test/fixtures/alternative4-root-with-left-success.dhall b/test/fixtures/alternative4-root-with-left-success.dhall new file mode 100644 index 000000000..41a88916f --- /dev/null +++ b/test/fixtures/alternative4-root-with-left-success.dhall @@ -0,0 +1,6 @@ +{ name = "my-project" +, dependencies = [ "console", "effect", "newtype", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [] : List Text +} + with sources = [ "src/**/*.purs", "test/**/*.purs" ] diff --git a/test/fixtures/alternative4-root-with-right-before.dhall b/test/fixtures/alternative4-root-with-right-before.dhall new file mode 100644 index 000000000..fac259a5e --- /dev/null +++ b/test/fixtures/alternative4-root-with-right-before.dhall @@ -0,0 +1,5 @@ +{ name = "my-project" +, dependencies = [] : List Text +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} with dependencies = [ "console", "effect", "prelude", "psci-support" ] diff --git a/test/fixtures/alternative4-root-with-right-success.dhall b/test/fixtures/alternative4-root-with-right-success.dhall new file mode 100644 index 000000000..98d6c1d21 --- /dev/null +++ b/test/fixtures/alternative4-root-with-right-success.dhall @@ -0,0 +1,7 @@ +{ name = "my-project" +, dependencies = [] : List Text +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} + with dependencies = + [ "console", "effect", "newtype", "prelude", "psci-support" ] diff --git a/test/fixtures/embed-dependencies.dhall b/test/fixtures/embed-dependencies.dhall new file mode 100644 index 000000000..aa55fd80a --- /dev/null +++ b/test/fixtures/embed-dependencies.dhall @@ -0,0 +1 @@ +[ "console", "effect", "prelude", "psci-support" ] \ No newline at end of file diff --git a/test/fixtures/embed-name.dhall b/test/fixtures/embed-name.dhall new file mode 100644 index 000000000..e8d23ebcb --- /dev/null +++ b/test/fixtures/embed-name.dhall @@ -0,0 +1 @@ +"project-name-via-embedded-expression" \ No newline at end of file diff --git a/test/fixtures/spago-install-append-left-before.dhall b/test/fixtures/spago-install-append-left-before.dhall new file mode 100644 index 000000000..b7fcb2b7a --- /dev/null +++ b/test/fixtures/spago-install-append-left-before.dhall @@ -0,0 +1,8 @@ +let otherDependencies = [ "console" ] +in +{ name = "aaa" +, dependencies = + [ "effect", "prelude", "psci-support" ] # otherDependencies +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/spago-install-append-left-success.dhall b/test/fixtures/spago-install-append-left-success.dhall new file mode 100644 index 000000000..3e3de1adc --- /dev/null +++ b/test/fixtures/spago-install-append-left-success.dhall @@ -0,0 +1,8 @@ +let otherDependencies = [ "console" ] + +in { name = "aaa" + , dependencies = + [ "arrays", "effect", "prelude", "psci-support" ] # otherDependencies + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/spago-install-append-middle-before.dhall b/test/fixtures/spago-install-append-middle-before.dhall new file mode 100644 index 000000000..59619461a --- /dev/null +++ b/test/fixtures/spago-install-append-middle-before.dhall @@ -0,0 +1,9 @@ +let otherDependencies1 = [ "console" ] +let otherDependencies2 = [ "effect" ] +in +{ name = "aaa" +, dependencies = + otherDependencies1 # [ "prelude", "psci-support" ] # otherDependencies2 +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/spago-install-append-middle-success.dhall b/test/fixtures/spago-install-append-middle-success.dhall new file mode 100644 index 000000000..38363c354 --- /dev/null +++ b/test/fixtures/spago-install-append-middle-success.dhall @@ -0,0 +1,12 @@ +let otherDependencies1 = [ "console" ] + +let otherDependencies2 = [ "effect" ] + +in { name = "aaa" + , dependencies = + otherDependencies1 + # [ "arrays", "prelude", "psci-support" ] + # otherDependencies2 + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/spago-install-append-no-op-success.dhall b/test/fixtures/spago-install-append-no-op-success.dhall new file mode 100644 index 000000000..2c5efb62a --- /dev/null +++ b/test/fixtures/spago-install-append-no-op-success.dhall @@ -0,0 +1,12 @@ +let otherDependencies1 = [ "console" ] + +let otherDependencies2 = [ "effect" ] + +in { name = "aaa" + , dependencies = + otherDependencies1 + # [ "prelude", "psci-support" ] + # otherDependencies2 + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/spago-install-append-right-before.dhall b/test/fixtures/spago-install-append-right-before.dhall new file mode 100644 index 000000000..612d60614 --- /dev/null +++ b/test/fixtures/spago-install-append-right-before.dhall @@ -0,0 +1,8 @@ +let otherDependencies = [ "console" ] +in +{ name = "aaa" +, dependencies = + otherDependencies # [ "effect", "prelude", "psci-support" ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/spago-install-append-right-success.dhall b/test/fixtures/spago-install-append-right-success.dhall new file mode 100644 index 000000000..24aaa6a92 --- /dev/null +++ b/test/fixtures/spago-install-append-right-success.dhall @@ -0,0 +1,8 @@ +let otherDependencies = [ "console" ] + +in { name = "aaa" + , dependencies = + otherDependencies # [ "arrays", "effect", "prelude", "psci-support" ] + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + } diff --git a/test/fixtures/spago-install-append-some-before.dhall b/test/fixtures/spago-install-append-some-before.dhall new file mode 100644 index 000000000..59619461a --- /dev/null +++ b/test/fixtures/spago-install-append-some-before.dhall @@ -0,0 +1,9 @@ +let otherDependencies1 = [ "console" ] +let otherDependencies2 = [ "effect" ] +in +{ name = "aaa" +, dependencies = + otherDependencies1 # [ "prelude", "psci-support" ] # otherDependencies2 +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/test/fixtures/spago-install-append-some-success.dhall b/test/fixtures/spago-install-append-some-success.dhall new file mode 100644 index 000000000..58dca9685 --- /dev/null +++ b/test/fixtures/spago-install-append-some-success.dhall @@ -0,0 +1,12 @@ +let otherDependencies1 = [ "console" ] + +let otherDependencies2 = [ "effect" ] + +in { name = "aaa" + , dependencies = + otherDependencies1 + # [ "newtype", "prelude", "psci-support" ] + # otherDependencies2 + , packages = ./packages.dhall + , sources = [ "src/**/*.purs", "test/**/*.purs" ] + }