Skip to content

Commit 7988f72

Browse files
Support lenient parsers
1 parent 324c685 commit 7988f72

File tree

9 files changed

+204
-79
lines changed

9 files changed

+204
-79
lines changed

ci/src/Foreign/SemVer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
const semver = require("semver");
22

3-
exports.parseRangeImpl = semver.validRange;
3+
exports.parseRangeImpl = (rangeString) =>
4+
semver.validRange(rangeString, { loose: true, includePrerelease: false });

ci/src/Registry/API.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import Registry.Schema (Manifest(..), Metadata, Operation(..), Repo(..), Target(
3232
import Registry.Scripts.LegacyImport.Error (ImportError(..))
3333
import Registry.Scripts.LegacyImport.Manifest as Manifest
3434
import Registry.Types (RawPackageName(..), RawVersion(..))
35-
import Registry.Version (Version)
35+
import Registry.Version (ParseMode(..), Version)
3636
import Registry.Version as Version
3737
import Sunde as Process
3838
import Text.Parsing.StringParser as StringParser
@@ -166,7 +166,7 @@ addOrUpdate { ref, fromBower, packageName } metadata = do
166166
Git _ -> throwWithComment "Legacy packages can only come from GitHub. Aborting."
167167
GitHub { owner, repo } -> pure { owner, repo }
168168

169-
version <- case Version.parseVersion ref of
169+
version <- case Version.parseVersion Lenient ref of
170170
Left _ -> throwWithComment $ "Not a valid registry version: " <> ref
171171
Right result -> pure result
172172

ci/src/Registry/Scripts/LegacyImport.purs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ import Registry.PackageName (PackageName)
2121
import Registry.PackageName as PackageName
2222
import Registry.PackageUpload as Upload
2323
import Registry.RegistryM (Env, runRegistryM)
24-
import Registry.Schema (Repo(..), Manifest(..), Operation(..), Metadata)
24+
import Registry.Schema (Manifest(..), Metadata, Operation(..), Repo(..))
2525
import Registry.Scripts.LegacyImport.Error (APIResource(..), ImportError(..), ManifestError(..), PackageFailures(..), RemoteResource(..), RequestError(..))
2626
import Registry.Scripts.LegacyImport.Manifest as Manifest
2727
import Registry.Scripts.LegacyImport.Process as Process
2828
import Registry.Scripts.LegacyImport.Stats as Stats
2929
import Registry.Types (RawPackageName(..), RawVersion(..))
30-
import Registry.Version (Version)
30+
import Registry.Version (ParseMode(..), Version)
3131
import Registry.Version as Version
3232
import Safe.Coerce (coerce)
3333
import Text.Parsing.StringParser as StringParser
@@ -91,7 +91,7 @@ main = Aff.launchAff_ do
9191
{ addToPackageSet: false -- heh, we don't have package sets until we do this import!
9292
, fromBower: true
9393
, newPackageLocation: manifest.repository
94-
, newRef: Version.raw manifest.version
94+
, newRef: Version.rawVersion manifest.version
9595
, packageName: manifest.name
9696
}
9797
log "\n\n----------------------------------------------------------------------"
@@ -154,7 +154,7 @@ downloadLegacyRegistry = do
154154
Right pname ->
155155
pure pname
156156

157-
packageVersion <- case Version.parseVersion $ un RawVersion tag of
157+
packageVersion <- case Version.parseVersion Lenient $ un RawVersion tag of
158158
Left _ ->
159159
throwError $ ManifestImportError $ NEA.singleton $ BadVersion $ un RawVersion tag
160160
Right version ->

ci/src/Registry/Scripts/LegacyImport/Manifest.purs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import Registry.Scripts.LegacyImport.Process as Process
3333
import Registry.Scripts.LegacyImport.SpagoJson (SpagoJson)
3434
import Registry.Scripts.LegacyImport.SpagoJson as SpagoJson
3535
import Registry.Types (RawPackageName(..), RawVersion(..))
36-
import Registry.Version (Version)
36+
import Registry.Version (ParseMode(..), Version)
3737
import Registry.Version as Version
3838

3939
-- | Attempt to construct the basic fields necessary for a manifest file by reading
@@ -249,8 +249,7 @@ toManifest package repository version manifest = do
249249
Just err -> mkError $ InvalidDependencyNames err
250250

251251
checkDepPair (Tuple packageName versionStr) =
252-
-- TODO: FIXME: Use lenient range parsing
253-
case Version.parseRange versionStr of
252+
case Version.parseRange Lenient versionStr of
254253
Left _ -> Left { dependency: packageName, failedBounds: versionStr }
255254
Right range -> Right $ Tuple (PackageName.print packageName) range
256255

ci/src/Registry/Version.purs

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ module Registry.Version
33
, major
44
, minor
55
, patch
6-
, raw
6+
, rawVersion
77
, printVersion
88
, parseVersion
99
, Range
1010
, greaterThanOrEq
1111
, lessThan
1212
, printRange
13+
, rawRange
14+
, ParseMode(..)
1315
, parseRange
1416
) where
1517

@@ -20,7 +22,9 @@ import Data.Function (on)
2022
import Data.Int as Int
2123
import Data.List as List
2224
import Data.List.NonEmpty as NEL
25+
import Data.String as String
2326
import Data.String.CodeUnits as CodeUnits
27+
import Foreign.SemVer as SemVer
2428
import Registry.Json as Json
2529
import Text.Parsing.StringParser (ParseError, Parser)
2630
import Text.Parsing.StringParser as StringParser
@@ -29,7 +33,13 @@ import Text.Parsing.StringParser.Combinators as StringParser.Combinators
2933

3034
-- | A Registry-compliant version of the form 'X.Y.Z', where each place is a
3135
-- | non-negative integer.
32-
newtype Version = Version { major :: Int, minor :: Int, patch :: Int, raw :: String }
36+
newtype Version = Version
37+
{ major :: Int
38+
, minor :: Int
39+
, patch :: Int
40+
, mode :: ParseMode
41+
, raw :: String
42+
}
3343

3444
derive instance Eq Version
3545

@@ -40,7 +50,7 @@ instance RegistryJson Version where
4050
encode = Json.encode <<< printVersion
4151
decode json = do
4252
string <- Json.decode json
43-
lmap StringParser.printParserError $ parseVersion string
53+
lmap StringParser.printParserError $ parseVersion Strict string
4454

4555
instance Show Version where
4656
show = printVersion
@@ -54,8 +64,8 @@ minor (Version version) = version.minor
5464
patch :: Version -> Int
5565
patch (Version version) = version.patch
5666

57-
raw :: Version -> String
58-
raw (Version version) = version.raw
67+
rawVersion :: Version -> String
68+
rawVersion (Version version) = version.raw
5969

6070
printVersion :: Version -> String
6171
printVersion version = do
@@ -68,15 +78,24 @@ printVersion version = do
6878
, printInt (patch version)
6979
]
7080

71-
parseVersion :: String -> Either ParseError Version
72-
parseVersion input = flip StringParser.runParser input do
81+
parseVersion :: ParseMode -> String -> Either ParseError Version
82+
parseVersion mode input = flip StringParser.runParser input do
83+
-- We allow leading whitespace and the commonly-used 'v' character prefix in
84+
-- lenient mode.
85+
when (mode == Lenient) do
86+
_ <- StringParser.CodeUnits.whiteSpace
87+
_ <- StringParser.Combinators.optional $ StringParser.CodeUnits.char 'v'
88+
pure unit
7389
major' <- nonNegativeInt
7490
_ <- StringParser.CodeUnits.char '.'
7591
minor' <- nonNegativeInt
7692
_ <- StringParser.CodeUnits.char '.'
7793
patch' <- nonNegativeInt
94+
when (mode == Lenient) do
95+
_ <- StringParser.CodeUnits.whiteSpace
96+
pure unit
7897
StringParser.CodeUnits.eof
79-
pure $ Version { major: major', minor: minor', patch: patch', raw: input }
98+
pure $ Version { major: major', minor: minor', patch: patch', mode, raw: input }
8099
where
81100
nonNegativeInt :: Parser Int
82101
nonNegativeInt = do
@@ -86,24 +105,29 @@ parseVersion input = flip StringParser.runParser input do
86105
digitString = CodeUnits.fromCharArray $ Array.fromFoldable digitChars
87106
failInteger = StringParser.fail $ "Invalid 32-bit integer: " <> digitString
88107
integer <- maybe failInteger pure $ Int.fromString digitString
89-
-- We do not accept leading zeros in versions
90-
when (zeroCount > 1 || (zeroCount == 1 && integer /= 0)) do
108+
-- We do not accept leading zeros in versions unless we are in lenient mode
109+
when (mode == Strict && (zeroCount > 1 || (zeroCount == 1 && integer /= 0))) do
91110
StringParser.fail $ "Leading zeros are not allowed: " <> digitString
92111
when (integer < 0) do
93112
StringParser.fail $ "Invalid non-negative integer: " <> show integer
94113
pure integer
95114

96115
-- | A Registry-compliant version range of the form '>=X.Y.Z <X.Y.Z', where the
97116
-- | left-hand version is less than the right-hand version.
98-
newtype Range = Range { lhs :: Version, rhs :: Version }
117+
newtype Range = Range
118+
{ lhs :: Version
119+
, rhs :: Version
120+
, mode :: ParseMode
121+
, raw :: String
122+
}
99123

100124
derive instance Eq Range
101125

102126
instance RegistryJson Range where
103127
encode = Json.encode <<< printRange
104128
decode json = do
105129
string <- Json.decode json
106-
lmap StringParser.printParserError $ parseRange string
130+
lmap StringParser.printParserError $ parseRange Strict string
107131

108132
instance Show Range where
109133
show = printRange
@@ -114,6 +138,9 @@ greaterThanOrEq (Range range) = range.lhs
114138
lessThan :: Range -> Version
115139
lessThan (Range range) = range.rhs
116140

141+
rawRange :: Range -> String
142+
rawRange (Range range) = range.raw
143+
117144
printRange :: Range -> String
118145
printRange range =
119146
Array.fold
@@ -123,25 +150,38 @@ printRange range =
123150
, printVersion (lessThan range)
124151
]
125152

126-
parseRange :: String -> Either ParseError Range
127-
parseRange = StringParser.runParser do
128-
_ <- StringParser.CodeUnits.string ">="
129-
lhs <- toVersion =<< map toString charsUntilSpace
130-
_ <- StringParser.CodeUnits.char '<'
131-
rhs <- toVersion =<< map toString chars
132-
StringParser.CodeUnits.eof
133-
when (lhs >= rhs) do
134-
StringParser.fail $ Array.fold
135-
[ "Left-hand version ("
136-
, printVersion lhs
137-
, ") must be less than right-hand version ("
138-
, printVersion rhs
139-
, ")"
140-
]
141-
pure $ Range { lhs, rhs }
153+
parseRange :: ParseMode -> String -> Either ParseError Range
154+
parseRange mode input = do
155+
let
156+
strictParser :: Parser Range
157+
strictParser = do
158+
_ <- StringParser.CodeUnits.string ">="
159+
lhs <- toVersion =<< map toString charsUntilSpace
160+
_ <- StringParser.CodeUnits.char '<'
161+
rhs <- toVersion =<< map toString chars
162+
StringParser.CodeUnits.eof
163+
when (lhs >= rhs) do
164+
StringParser.fail $ Array.fold
165+
[ "Left-hand version ("
166+
, printVersion lhs
167+
, ") must be less than right-hand version ("
168+
, printVersion rhs
169+
, ")"
170+
]
171+
pure $ Range { lhs, rhs, mode, raw: input }
172+
173+
case mode of
174+
Lenient -> case SemVer.parseRange input of
175+
Nothing ->
176+
Left { pos: 0, error: "Unable to parse SemVer range in lenient mode." }
177+
Just parsed -> do
178+
let trimPrereleaseGuards = String.replaceAll (String.Pattern "-0") (String.Replacement "")
179+
StringParser.runParser strictParser $ trimPrereleaseGuards parsed
180+
Strict ->
181+
StringParser.runParser strictParser input
142182
where
143183
toVersion string =
144-
case parseVersion string of
184+
case parseVersion mode string of
145185
Left { error } ->
146186
StringParser.fail error
147187
Right parsed ->
@@ -158,3 +198,7 @@ parseRange = StringParser.runParser do
158198
StringParser.Combinators.manyTill
159199
StringParser.CodeUnits.anyChar
160200
(StringParser.CodeUnits.char ' ')
201+
202+
data ParseMode = Lenient | Strict
203+
204+
derive instance Eq ParseMode

ci/test/Main.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Registry.Json as Json
1616
import Registry.PackageName as PackageName
1717
import Registry.Schema (Operation(..), Repo(..), Manifest(..))
1818
import Registry.Scripts.LegacyImport.Bowerfile (Bowerfile(..))
19-
import Registry.Version (raw) as Version
19+
import Registry.Version (rawVersion) as Version
2020
import Safe.Coerce (coerce)
2121
import Test.Foreign.Jsonic (jsonic)
2222
import Test.Foreign.Licensee (licensee)
@@ -91,7 +91,7 @@ manifestEncoding :: Spec.Spec Unit
9191
manifestEncoding = do
9292
let
9393
roundTrip (Manifest manifest) =
94-
Spec.it (PackageName.print manifest.name <> " " <> Version.raw manifest.version) do
94+
Spec.it (PackageName.print manifest.name <> " " <> Version.rawVersion manifest.version) do
9595
Json.roundtrip manifest `Assert.shouldContain` manifest
9696

9797
roundTrip Fixtures.ab.v1a

ci/test/Registry/Scripts/LegacyImport/Stats.purs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Registry.Scripts.LegacyImport.Process (ProcessedPackageVersions)
1616
import Registry.Scripts.LegacyImport.Stats (ErrorCounts(..))
1717
import Registry.Scripts.LegacyImport.Stats as Stats
1818
import Registry.Types (RawPackageName(..), RawVersion(..))
19-
import Registry.Version (Version)
19+
import Registry.Version (ParseMode(..), Version)
2020
import Registry.Version as Version
2121
import Test.Spec as Spec
2222
import Test.Spec.Assertions as Assert
@@ -84,14 +84,17 @@ exampleFailures = PackageFailures $ Map.fromFoldable
8484
exampleStats :: Stats.Stats
8585
exampleStats = Stats.errorStats mockStats
8686
where
87+
unsafeFromRight :: forall e a. Either e a -> a
88+
unsafeFromRight = fromRight' (\_ -> unsafeCrashWith "Unexpected Left")
89+
8790
mockLicense :: License
88-
mockLicense = unsafePartial $ fromJust $ hush $ License.parse "MIT"
91+
mockLicense = unsafeFromRight $ License.parse "MIT"
8992

9093
mockPackageName :: PackageName
91-
mockPackageName = unsafePartial $ fromJust $ hush $ PackageName.parse "foobarbaz"
94+
mockPackageName = unsafeFromRight $ PackageName.parse "foobarbaz"
9295

9396
mockVersion :: Version
94-
mockVersion = unsafePartial $ fromRight' (\_ -> unsafeCrashWith "not a valid version") $ Version.parseVersion "0.0.0"
97+
mockVersion = unsafeFromRight $ Version.parseVersion Strict "0.0.0"
9598

9699
mockStats =
97100
{ failures: examplePackageResults.failures

0 commit comments

Comments
 (0)