packages feed

semver-range 0.2.2 → 0.2.7

raw patch · 4 files changed

+313/−177 lines, 4 files

Files

semver-range.cabal view
@@ -1,5 +1,5 @@ name:                semver-range-version:             0.2.2+version:             0.2.7 synopsis:            An implementation of semver and semantic version ranges. license:             MIT license-file:        LICENSE
src/Data/SemVer/Parser.hs view
@@ -3,10 +3,10 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLists #-} -module Data.SemVer.Parser where -- (-  --   parseSemVer, parseSemVerRange, pSemVerRange, pSemVer, p-  --   fromHaskellVersion, matchText-  -- ) where+module Data.SemVer.Parser (+    parseSemVer, parseSemVerRange, pSemVerRange, pSemVer,+    fromHaskellVersion, matchText, splitWS+  ) where  import qualified Prelude as P import ClassyPrelude hiding (try, many)@@ -20,22 +20,26 @@  type Parser = ParsecT String () Identity +-- | Split a text on whitespace. Why isn't this in the stdlib.+splitWS :: Text -> [Text]+splitWS = filter (/= "") . T.split (flip elem (" \t\n\r" :: String))+ ------------------------------------------------------------------------------- -- Wildcards: intermediate representations of semvers -- -- | A partially specified semantic version. Implicitly defines -- a range of acceptable versions, as seen in @wildcardToRange@. data Wildcard = Any-              | One Int-              | Two Int Int+              | Maj Int+              | Min Int Int               | Full SemVer               deriving (Show, Eq)  -- | Fills in zeros in a wildcard. wildcardToSemver :: Wildcard -> SemVer wildcardToSemver Any = semver 0 0 0-wildcardToSemver (One n) = semver n 0 0-wildcardToSemver (Two n m) = semver n m 0+wildcardToSemver (Maj n) = semver n 0 0+wildcardToSemver (Min n m) = semver n m 0 wildcardToSemver (Full sv) = sv  -- | Translates a wildcard (partially specified version) to a range.@@ -44,8 +48,8 @@ wildcardToRange :: Wildcard -> SemVerRange wildcardToRange = \case   Any -> Geq $ semver 0 0 0-  One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)-  Two n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)+  Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)+  Min n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)   Full sv -> Eq sv  -- | Translates a ~wildcard to a range.@@ -55,9 +59,9 @@   -- I'm not sure this is officially supported, but just in case...   Any -> tildeToRange (Full $ semver 0 0 0)   -- ~1 := >=1.0.0 <(1+1).0.0 := >=1.0.0 <2.0.0 (Same as 1.x)-  One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)+  Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)   -- ~1.2 := >=1.2.0 <1.(2+1).0 := >=1.2.0 <1.3.0 (Same as 1.2.x)-  Two n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)+  Min n m -> Geq (semver n m 0) `And` Lt (semver n (m+1) 0)   -- ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0   Full (SemVer n m o [] _) -> Geq (semver n m o) `And` Lt (semver n (m+1) 0)   -- ~1.2.3-beta.2 := >=1.2.3-beta.2 <1.3.0@@ -67,8 +71,8 @@ -- Ex: ^1.2.x := >=1.2.0 <2.0.0 caratToRange :: Wildcard -> SemVerRange caratToRange = \case-  One n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)-  Two n m -> Geq (semver n m 0) `And` Lt (semver (n+1) 0 0)+  Maj n -> Geq (semver n 0 0) `And` Lt (semver (n+1) 0 0)+  Min n m -> Geq (semver n m 0) `And` Lt (semver (n+1) 0 0)   Full (SemVer 0 n m tags _) -> Geq (semver' 0 n m tags) `And` Lt (semver' 0 (n+1) 0 tags)   Full (SemVer n m o tags _) -> Geq (semver' n m o tags) `And` Lt (semver' (n+1) 0 0 tags) @@ -79,12 +83,12 @@ hyphenatedRange :: Wildcard -> Wildcard -> SemVerRange hyphenatedRange wc1 wc2 = And sv1 sv2 where   sv1 = case wc1 of Any -> anyVersion-                    One n -> Geq (semver n 0 0)-                    Two n m -> Geq (semver n m 0)+                    Maj n -> Geq (semver n 0 0)+                    Min n m -> Geq (semver n m 0)                     Full sv -> Geq sv   sv2 = case wc2 of Any -> anyVersion-                    One n -> Lt (semver (n+1) 0 0)-                    Two n m -> Lt (semver n (m+1) 0)+                    Maj n -> Lt (semver (n+1) 0 0)+                    Min n m -> Lt (semver n (m+1) 0)                     Full sv -> Lt sv  -- | Given a parser and a string, attempts to parse the string.@@ -96,11 +100,11 @@  -- | Consumes any spaces (not other whitespace). spaces :: Parser String-spaces = many $ oneOf [' ', '\t']+spaces = many $ oneOf [' ', '\t', '\n', '\r']  -- | Consumes at least one space (not other whitespace). spaces1 :: Parser String-spaces1 = many1 $ oneOf [' ', '\t']+spaces1 = many1 $ oneOf [' ', '\t', '\n', '\r']  -- | Parses the given string and any trailing spaces. sstring :: String -> Parser String@@ -128,15 +132,17 @@   -- Handle a few special cases   "" -> return anyVersion   "||" -> return anyVersion-  t -> parse pSemVerRange t+  t -> parse (pSemVerRange <* eof) t  -- | Parse a string as an explicit version, or return an error. parseSemVer :: Text -> Either ParseError SemVer-parseSemVer = parse pSemVer+parseSemVer = parse pSemVer . T.strip  -- | Parses a semantic version. pSemVer :: Parser SemVer-pSemVer = wildcardToSemver <$> pWildCard+pSemVer = do+  optional (char '=')+  wildcardToSemver <$> pWildCard  pVersionComp :: Parser SemVerRange pVersionComp = cmp >>= \case@@ -154,8 +160,8 @@   where     topOf = \case       Any -> semver 0 0 0-      One n -> semver (n+1) 0 0-      Two n m -> semver n (m+1) 0+      Maj n -> semver (n+1) 0 0+      Min n m -> semver n (m+1) 0       Full sv -> sv  -- | Parses a comparison operator.@@ -176,8 +182,10 @@ pJoinedSemVerRange = do   first <- pSemVerRangeSingle   option first $ do-    lookAhead (sstring "||" <|> cmp) >>= \case+    let next = choice [sstring "||", sstring "&&", map singleton anyChar]+    lookAhead next >>= \case       "||" -> Or first <$> (sstring "||" *> pJoinedSemVerRange)+      "&&" -> And first <$> (sstring "&&" *> pJoinedSemVerRange)       _ -> And first <$> pJoinedSemVerRange  -- | Parses a hyphenated range.@@ -197,17 +205,18 @@   optional (char 'v')   res <- takeWhile isJust <$> sepBy1 bound (sstring ".") >>= \case     [] -> return Any-    [Just n] -> return $ One n-    [Just n, Just m] -> return $ Two n m+    [Just n] -> return $ Maj n+    [Just n, Just m] -> return $ Min n m     [Just n, Just m, Just o] -> option (Full $ semver n m o) $ do-      -- Release tags might be separated by a hyphen, or not.-      optional (char '-')-      tags <- PrereleaseTags <$> (tag `sepBy1` char '.')+      tags <- option [] $ do+        -- Release tags might be separated by a hyphen, or not.+        optional (char '-')+        PrereleaseTags <$> (tag `sepBy1` char '.')       -- Grab metadata if there is any-      option (Full $ semver'' n m o tags []) $ do+      metadata <- option [] $ do         char '+'-        metadata <- many1 (letter <|> digit <|> char '-') `sepBy1` char '.'-        return $ Full $ semver'' n m o tags (map pack metadata)+        many1 (letter <|> digit <|> char '-') `sepBy1` char '.'+      return $ Full $ semver'' n m o tags (map pack metadata)     w -> unexpected ("Invalid version " ++ show w)   spaces *> return res 
src/Data/SemVer/Types.hs view
@@ -150,6 +150,17 @@   And svr1 svr2 -> versionsOf svr1 <> versionsOf svr2   Or svr1 svr2 -> versionsOf svr1 <> versionsOf svr2 +-- | Strip out all prerelease tags from a given 'SemVerRange'.+stripRangeTags :: SemVerRange -> SemVerRange+stripRangeTags = \case+  Eq  sv -> Eq  (sv { svTags = [] })+  Geq sv -> Geq (sv { svTags = [] })+  Leq sv -> Leq (sv { svTags = [] })+  Lt  sv -> Lt  (sv { svTags = [] })+  Gt  sv -> Gt  (sv { svTags = [] })+  And svr1 svr2 -> And (stripRangeTags svr1) (stripRangeTags svr2)+  Or  svr1 svr2 -> Or  (stripRangeTags svr1) (stripRangeTags svr2)+ -- | Create a SemVer with no version tags. semver :: Int -> Int -> Int -> SemVer semver major minor patch = semver' major minor patch []@@ -195,22 +206,77 @@ -- | Returns whether a given semantic version matches a range. -- Note that there are special cases when there are prerelease tags. For -- details see https://github.com/npm/node-semver#prerelease-tags.--- matches :: SemVerRange -> SemVer -> Bool--- matches range version = case (sharedTags range, svTags version) of---   -- This is the simple case, where neither the range nor the version has given---   -- prerelease tags. Then we can just do regular predicate calculus.---   (Nothing, PrereleaseTags []) -> matchesSimple range version---   _ -> undefined---   -- (Just rTags, PrereleaseTags vTags)---   --   | rTags == vTags -> matchesSimple range version---   --   | tuplesOf range /= [toTuple version] -> False---   --   | otherwise -> matchesTags range rTags vTags---   -- (_, _) -> False+matches :: SemVerRange -> SemVer -> Bool+matches range version =+  case (sharedTags range, svTags version) of +    (Nothing, PrereleaseTags vTags)+      -- Neither the range nor the version have prerelease tags+      | null vTags -> matchesSimple range version++      -- If there is no prerelease tag in the range but there is in+      -- the version reject it+      | otherwise  -> False++    -- A range with a prerelease tag can match a version without a+    -- prerelease tag provided it *does* meet the semantic version+    -- tuple's constraint criteria+    (Just _, PrereleaseTags []) ->+      matchesSimple range version++    -- The most important invariant when considering a comparison+    -- between a range with prerelease tags and a version with+    -- prerelease tags is whether the semantic version in both is the+    -- same; if it is not, then we must reject the version.+    --+    -- Note that we could have a conjunction or a disjunction, so we+    -- want to see if our version tuple is in the list of tuples for+    -- the range. However, it would be possible to then match with,+    -- say, the upper-bound version tuple which may be constrained by+    -- a less-than relation. Therefore, if there is an equivalent+    -- range tuple to the version tuple, we want to check if it+    -- satisfies the constraints with the goal of rejecting early.+    --+    -- For example, if we assume a range constraint of "^1.2.3-alpha"+    -- this translates to ">=1.2.3-alpha <2.0.0-alpha". Also assume we+    -- have the version "1.2.3-alpha". In the trivial case, we check+    -- to see if the version's tuple ("1.2.3") is in the set of+    -- version tuples for the range ([ (1.2.3), (2.0.0) ]). We can+    -- clearly see that it is, therefore we proceed with a match check+    -- on the tags.+    --+    -- However, consider matching "2.0.0-alpha" against the range+    -- constraint we've already given. If we only check for membership+    -- of our version tuple ("2.0.0") in the set of range tuples ([+    -- (1.2.3), (2.0.0) ]) then we would get a match, this is not+    -- correct. Thus, if the version tuple is a member of the set of+    -- range tuples we must also check that it satisfies the range+    -- constraints sans prerelease tags.+    (Just rTags, vTags)++      -- Explicit rejection, e.g. "^1.2.3-alpha" must reject+      -- "1.2.4-alpha" and "2.0.0-alpha", anything else is safe to+      -- compare based on tags so we can let it "fall through".+      | versionTuple `notElem` rangeTuple || not (matchesSimple rangeNoTags versionNoTags)+        -> False++      | rTags == vTags+        -> True++      | rTags /= vTags+        -> matchesTags range rTags vTags++  where+    rangeTuple   = tuplesOf range+    versionTuple = toTuple version++    rangeNoTags   = stripRangeTags range+    versionNoTags = version { svTags = [] }+ -- | Simple predicate calculus matching, doing AND and OR combination with -- numerical comparison.-matches :: SemVerRange -> SemVer -> Bool-matches range ver = case range of+matchesSimple :: SemVerRange -> SemVer -> Bool+matchesSimple range ver = case range of   Eq sv -> ver == sv   Gt sv -> ver > sv   Lt sv -> ver < sv@@ -223,18 +289,19 @@  -- | Given a range and two sets of tags, the first being a bound on the second, -- uses the range to compare the tags and see if they match.-matchesTags :: SemVerRange -> [PrereleaseTag] -> [PrereleaseTag] -> Bool-matchesTags range rangeTags verTags = case range of-  Eq _ -> verTags == rangeTags-  Gt _ -> verTags > rangeTags-  Lt _ -> verTags < rangeTags-  Geq _ -> verTags >= rangeTags-  Leq _ -> verTags <= rangeTags-  -- Note that as we're currently doing things, these cases won't get hit.-  And svr1 svr2 -> matchesTags svr1 verTags rangeTags &&-                     matchesTags svr2 verTags rangeTags-  Or svr1 svr2 -> matchesTags svr1 verTags rangeTags ||-                     matchesTags svr2 verTags rangeTags+matchesTags :: SemVerRange -> PrereleaseTags -> PrereleaseTags -> Bool+matchesTags range rangeTags verTags =+  case range of+    Eq _  -> verTags == rangeTags+    Gt _  -> verTags >  rangeTags+    Lt _  -> verTags <  rangeTags+    Geq _ -> verTags >= rangeTags+    Leq _ -> verTags <= rangeTags++    And svr1 svr2 ->+      matchesTags svr1 rangeTags verTags+    Or svr1 svr2 ->+      matchesTags svr1 rangeTags verTags || matchesTags svr2 rangeTags verTags  -- | Gets the highest-matching semver in a range. bestMatch :: SemVerRange -> [SemVer] -> Either String SemVer
tests/Unit.hs view
@@ -12,23 +12,13 @@ module Main (main) where  import ClassyPrelude-import Data.Either+import Data.Either (isRight, isLeft) import Test.Hspec import Test.QuickCheck (property, Arbitrary(..), oneof) import qualified Data.Text as T  import Data.SemVer --- | This instance seems to be missing :(-instance (Arbitrary a, Arbitrary b, Arbitrary c,-          Arbitrary d, Arbitrary e, Arbitrary f)-         => Arbitrary (a, b, c, d, e, f) where-  arbitrary = (,,,,,) <$> arbitrary-                      <*> arbitrary-                      <*> arbitrary-                      <*> arbitrary-                      <*> arbitrary-                      <*> arbitrary -- | Arbitrary semver instance Arbitrary SemVer where   arbitrary = semver' <$> arb <*> arb <*> arb <*> arbitrary where@@ -73,6 +63,9 @@  infixl 1 `shouldBeR` +shouldBeL :: (Show a, Show b, Eq a) => Either a b -> IO ()+shouldBeL x = shouldSatisfy x isLeft+ main :: IO () main = hspec $ do   describe "semver parsing" $ do@@ -138,124 +131,191 @@         `shouldBeR` Geq (semver'' 1 2 3 ["pre"] ["asdf"])                      `And` Lt (semver'' 2 4 3 ["pre"] ["asdf"]) -  it "poo" $ do-    let svNoTags = semver 1 2 3-        svTags = semver' 1 2 3 ["pre"]-        svTags' = semver' 2 4 3 ["pre"]-    svNoTags >= svTags `shouldBe` True-    let range = Geq svTags `And` Lt svTags'-    range `matches` svNoTags `shouldBe` True+    it "should parse semvers with && instead of spaces" $ do+      let expected = Geq (semver 2 0 0) `And` Leq (semver 2 15 0)+      parseSemVerRange ">= 2 && <= 2.14" `shouldBeR` expected++    it "should fail when it's wrong" $ do+      shouldBeL (parseSemVerRange "xyz")+   rangeTests+  cleanTests +-- | These test cases were adapted from+-- https://github.com/npm/node-semver/blob/master/test/clean.js+cleanTests :: Spec+cleanTests = describe "unclean version strings" $ do+  let examples :: [(Text, Maybe Text)] = [+        ("1.2.3", Just "1.2.3"),+        (" 1.2.3 ", Just "1.2.3"),+        (" 1.2.3-4 ", Just "1.2.3-4"),+        (" 1.2.3-pre ", Just "1.2.3-pre"),+        ("  =v1.2.3   ", Just "1.2.3"),+        ("v1.2.3", Just "1.2.3"),+        (" v1.2.3 ", Just "1.2.3"),+        ("\t1.2.3", Just "1.2.3"),+        (">1.2.3", Nothing),+        ("~1.2.3", Nothing),+        ("<=1.2.3", Nothing)+        -- The example below is given in the tests but this doesn't+        -- seem like an error to me, so there.+        -- ("1.2.x", Nothing)+        ]+  forM_ examples $ \(string, result) -> case result of+    Just string' -> do+      it ("should parse " <> show string <> " same as " <> show string') $ do+        parseSemVer string `shouldSatisfy` isRight+        parseSemVer string `shouldBe` parseSemVer string' +    Nothing -> do+      it ("should not parse " <> show string) $ do+        parseSemVer string `shouldSatisfy` isLeft+ -- | These test cases were adapted from--- https://github.com/npm/node-semver/blob/---   d21444a0658224b152ce54965d02dbe0856afb84/test/index.js#L134+-- https://github.com/npm/node-semver/blob/master/test/index.js#L134 rangeTests :: Spec rangeTests = describe "range tests" $ do   -- In each case, the range described in the first element of the   -- tuple should be satisfied by the concrete version described in   -- the second element of the tuple.-  let testCases :: [(Text, Text)] = [-        ("1.0.0 - 2.0.0", "1.2.3"),-        ("^1.2.3+build", "1.2.3"),-        ("^1.2.3+build", "1.3.0"),-        ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),-        ("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),-        ("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),-        ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),-        ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),-        ("1.2.3+asdf - 2.4.3+asdf", "1.2.3"),-        ("1.0.0", "1.0.0"),-        (">=*", "0.2.4"),-        ("", "1.0.0"),-        ("*", "1.2.3"),-        ("*", "v1.2.3"),-        (">=1.0.0", "1.0.0"),-        (">=1.0.0", "1.0.1"),-        (">=1.0.0", "1.1.0"),-        (">1.0.0", "1.0.1"),-        (">1.0.0", "1.1.0"),-        ("<=2.0.0", "2.0.0"),-        ("<=2.0.0", "1.9999.9999"),-        ("<=2.0.0", "0.2.9"),-        ("<2.0.0", "1.9999.9999"),-        ("<2.0.0", "0.2.9"),-        (">= 1.0.0", "1.0.0"),-        (">=  1.0.0", "1.0.1"),-        (">=   1.0.0", "1.1.0"),-        ("> 1.0.0", "1.0.1"),-        (">  1.0.0", "1.1.0"),-        ("<=   2.0.0", "2.0.0"),-        ("<= 2.0.0", "1.9999.9999"),-        ("<=  2.0.0", "0.2.9"),-        ("<    2.0.0", "1.9999.9999"),-        ("<\t2.0.0", "0.2.9"),-        (">=0.1.97", "v0.1.97"),-        (">=0.1.97", "0.1.97"),-        ("0.1.20 || 1.2.4", "1.2.4"),-        (">=0.2.3 || <0.0.1", "0.0.0"),-        (">=0.2.3 || <0.0.1", "0.2.3"),-        (">=0.2.3 || <0.0.1", "0.2.4"),-        ("||", "1.3.4"),-        ("2.x.x", "2.1.3"),-        ("1.2.x", "1.2.3"),-        ("1.2.x || 2.x", "2.1.3"),-        ("1.2.x || 2.x", "1.2.3"),-        ("x", "1.2.3"),-        ("2.*.*", "2.1.3"),-        ("1.2.*", "1.2.3"),-        ("1.2.* || 2.*", "2.1.3"),-        ("1.2.* || 2.*", "1.2.3"),-        ("*", "1.2.3"),-        ("2", "2.1.2"),-        ("2.3", "2.3.1"),-        ("~2.4", "2.4.0"),-        ("~2.4", "2.4.5"),-        ("~>3.2.1", "3.2.2"),-        ("~1", "1.2.3"),-        ("~>1", "1.2.3"),-        ("~> 1", "1.2.3"),-        ("~1.0", "1.0.2"),-        ("~ 1.0", "1.0.2"),-        ("~ 1.0.3", "1.0.12"),-        (">=1", "1.0.0"),-        (">= 1", "1.0.0"),-        ("<1.2", "1.1.1"),-        ("< 1.2", "1.1.1"),-        ("~v0.5.4-pre", "0.5.5"),-        ("~v0.5.4-pre", "0.5.4"),-        ("=0.7.x", "0.7.2"),-        ("<=0.7.x", "0.7.2"),-        (">=0.7.x", "0.7.2"),-        ("<=0.7.x", "0.6.2"),-        ("~1.2.1 >=1.2.3", "1.2.3"),-        ("~1.2.1 =1.2.3", "1.2.3"),-        ("~1.2.1 1.2.3", "1.2.3"),-        ("~1.2.1 >=1.2.3 1.2.3", "1.2.3"),-        ("~1.2.1 1.2.3 >=1.2.3", "1.2.3"),-        ("~1.2.1 1.2.3", "1.2.3"),-        (">=1.2.1 1.2.3", "1.2.3"),-        ("1.2.3 >=1.2.1", "1.2.3"),-        (">=1.2.3 >=1.2.1", "1.2.3"),-        (">=1.2.1 >=1.2.3", "1.2.3"),-        (">=1.2", "1.2.8"),-        ("^1.2.3", "1.8.1"),-        ("^0.1.2", "0.1.2"),-        ("^0.1", "0.1.2"),-        ("^1.2", "1.4.2"),-        ("^1.2 ^1", "1.4.2"),-        ("^1.2.3-alpha", "1.2.3-pre"),-        ("^1.2.0-alpha", "1.2.0-pre"),-        ("^0.0.1-alpha", "0.0.1-beta")+  let testCases :: [(Bool, Text, Text)] = [++        -- Range constraints with pre-release tags require that any+        -- version satisfying the constraint must be equivalent (in+        -- its semantic version tuple) to the minimum of all semantic+        -- versions within the range. In this case the minimum of the+        -- range is "1.2.3" and the version's semantic version tuple+        -- is "1.2.4", therefore it does not satisfy the constraints+        -- of the range given the presence of pre-release tags.+        (False, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.4-pre+asdf"),+        (False, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"),+        (False, ">=0.0.1-alpha <0.2.0-alpha", "0.1.1-alpha"),+        (False, "^0.0.1-alpha", "0.0.4-alpha"),++        -- Range constraints without prerelease tags are very strict+        -- about not admitting versions *with* prerelease tags+        (False, "^0.1.2", "0.1.2-beta1"),+        (False, "^0.1.2", "0.1.4-beta1"),++        -- Despite the numeric quantity, these versions have+        -- prerelease tags and are therefore subjected to the same+        -- invariant checking.+        (False, "^1.2.3-1", "1.8.1-1"),+        (False, "^1.2.3-1", "1.8.1-4"),++        -- If we ever have an exact version tuple match at the top of+        -- a given range then it must satisfy the range constraint!+        --+        -- e.g. "^1.2.3-alpha" translates to ">=1.2.3-alpha+        -- <2.0.0-alpha" and the version to check is "2.0.0-alpha". In+        -- this case the version tuples are equivalent, sans+        -- prerelease tags, but it does not satisfy the upper-bound+        -- less-than relation.+        (False, "^1.2.3-alpha", "2.0.0-alpha"),++        (True,  "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre+asdf"),+        (True, "", "1.0.0"),+        (True, "*", "1.2.3"),+        (True, "*", "1.2.3"),+        (True, "*", "v1.2.3"),+        (True, "0.1.20 || 1.2.4", "1.2.4"),+        (True, "1.0.0 - 2.0.0", "1.2.3"),+        (True, "1.0.0", "1.0.0"),+        (True, "1.2.* || 2.*", "1.2.3"),+        (True, "1.2.* || 2.*", "2.1.3"),+        (True, "1.2.*", "1.2.3"),+        (True, "1.2.3 - 2.4.3", "1.2.4"),+        (True, "1.2.3 >=1.2.1", "1.2.3"),+        (True, "1.2.3+asdf - 2.4.3+asdf", "1.2.3"),+        (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"),+        (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"),+        (True, "1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pred"),+        (True, "1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"),+        (True, "1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"),+        (True, "1.2.x || 2.x", "1.2.3"),+        (True, "1.2.x || 2.x", "2.1.3"),+        (True, "1.2.x", "1.2.3"),+        (True, "2", "2.1.2"),+        (True, "2.*.*", "2.1.3"),+        (True, "2.3", "2.3.1"),+        (True, "2.x.x", "2.1.3"),+        (True, "<    2.0.0", "1.9999.9999"),+        (True, "< 1.2", "1.1.1"),+        (True, "<1.2", "1.1.1"),+        (True, "<2.0.0", "0.2.9"),+        (True, "<2.0.0", "1.9999.9999"),+        (True, "<=   2.0.0", "2.0.0"),+        (True, "<=  2.0.0", "0.2.9"),+        (True, "<= 2.0.0", "1.9999.9999"),+        (True, "<=0.7.x", "0.6.2"),+        (True, "<=0.7.x", "0.7.2"),+        (True, "<=2.0.0", "0.2.9"),+        (True, "<=2.0.0", "1.9999.9999"),+        (True, "<=2.0.0", "2.0.0"),+        (True, "<\t2.0.0", "0.2.9"),+        (True, "=0.7.x", "0.7.2"),+        (True, ">  1.0.0", "1.1.0"),+        (True, "> 1.0.0", "1.0.1"),+        (True, ">1.0.0", "1.0.1"),+        (True, ">1.0.0", "1.1.0"),+        (True, ">=   1.0.0", "1.1.0"),+        (True, ">=  1.0.0", "1.0.1"),+        (True, ">= 1", "1.0.0"),+        (True, ">= 1.0.0", "1.0.0"),+        (True, ">= 4.0.0 <4.1.0-0", "4.0.1"),+        (True, ">=*", "0.2.4"),+        (True, ">=0.1.97", "0.1.97"),+        (True, ">=0.1.97", "v0.1.97"),+        (True, ">=0.2.3 || <0.0.1", "0.0.0"),+        (True, ">=0.2.3 || <0.0.1", "0.2.3"),+        (True, ">=0.2.3 || <0.0.1", "0.2.4"),+        (True, ">=0.7.x", "0.7.2"),+        (True, ">=1", "1.0.0"),+        (True, ">=1.0.0", "1.0.0"),+        (True, ">=1.0.0", "1.0.1"),+        (True, ">=1.0.0", "1.1.0"),+        (True, ">=1.2", "1.2.8"),+        (True, ">=1.2.1 1.2.3", "1.2.3"),+        (True, ">=1.2.1 >=1.2.3", "1.2.3"),+        (True, ">=1.2.3 >=1.2.1", "1.2.3"),+        (True, "^0.0.1-alpha", "0.0.1-beta"),+        (True, "^0.0.1-alpha.1", "0.0.1-alpha.t"),+        (True, "^0.0.1-alpha.1", "0.0.1-alpha.tdff.dddddddddd"),+        (True, "^0.1", "0.1.2"),+        (True, "^0.1.2", "0.1.2"),+        (True, "^1.2 ^1", "1.4.2"),+        (True, "^1.2", "1.4.2"),+        (True, "^1.2.0-alpha", "1.2.0-pre"),+        (True, "^1.2.3", "1.8.1"),+        (True, "^1.2.3+build", "1.2.3"),+        (True, "^1.2.3+build", "1.3.0"),+        (True, "^1.2.3-alpha", "1.2.3-pre"),+        (True, "^1.2.3-alpha.1", "1.2.3-alpha.7"),+        (True, "^1.2.3-boop", "1.2.4"),+        (True, "x", "1.2.3"),+        (True, "||", "1.3.4"),+        (True, "~ 1.0", "1.0.2"),+        (True, "~ 1.0.3", "1.0.12"),+        (True, "~1", "1.2.3"),+        (True, "~1.0", "1.0.2"),+        (True, "~1.2.1 1.2.3 >=1.2.3", "1.2.3"),+        (True, "~1.2.1 1.2.3", "1.2.3"),+        (True, "~1.2.1 1.2.3", "1.2.3"),+        (True, "~1.2.1 =1.2.3", "1.2.3"),+        (True, "~1.2.1 >=1.2.3 1.2.3", "1.2.3"),+        (True, "~1.2.1 >=1.2.3", "1.2.3"),+        (True, "~2.4", "2.4.0"),+        (True, "~2.4", "2.4.5"),+        (True, "~> 1", "1.2.3"),+        (True, "~>1", "1.2.3"),+        (True, "~v0.5.4-pre", "0.5.4"),+        (True, "~v0.5.4-pre", "0.5.5"),+        (True, "~>3.2.1", "3.2.2")         ]-  forM_ testCases $ \(range, version) -> do+  forM_ testCases $ \(expectedMatchBool, range, version) -> do     let fail_ = expectationFailure     it (show version <> " satisfies range " <> show range) $ do       case (parseSemVerRange range, parseSemVer version) of         (Left err, _) -> fail $ "Semver range parse failed: " <> show err         (_, Left err) -> fail $ "Semver parse failed: " <> show err-        (Right range, Right version) -> case matches range version of-          True -> True `shouldBe` True -- return ()-          False -> fail $ "Version " <> show version <> " didn't match range "-                       <> show range --`shouldBe` True+        (Right range, Right version) -> matches range version `shouldBe` expectedMatchBool