From e3446bfc0c91ac42f49da73be3e723b350843fb6 Mon Sep 17 00:00:00 2001 From: Fedor Lukyanov <sleekybadger@gmail.com> Date: Thu, 3 Jan 2019 07:32:08 +0200 Subject: [PATCH] feat: Ruby semver versioning (#3000) --- lib/versioning/semver-ruby/index.js | 83 ++++ lib/versioning/semver-ruby/operator.js | 27 ++ lib/versioning/semver-ruby/range.js | 49 +++ lib/versioning/semver-ruby/strategies/bump.js | 32 ++ .../semver-ruby/strategies/index.js | 9 + lib/versioning/semver-ruby/strategies/pin.js | 1 + .../semver-ruby/strategies/replace.js | 5 + lib/versioning/semver-ruby/version.js | 100 +++++ package.json | 3 +- test/versioning/semver-ruby.spec.js | 412 ++++++++++++++++++ yarn.lock | 18 +- 11 files changed, 728 insertions(+), 11 deletions(-) create mode 100644 lib/versioning/semver-ruby/index.js create mode 100644 lib/versioning/semver-ruby/operator.js create mode 100644 lib/versioning/semver-ruby/range.js create mode 100644 lib/versioning/semver-ruby/strategies/bump.js create mode 100644 lib/versioning/semver-ruby/strategies/index.js create mode 100644 lib/versioning/semver-ruby/strategies/pin.js create mode 100644 lib/versioning/semver-ruby/strategies/replace.js create mode 100644 lib/versioning/semver-ruby/version.js create mode 100644 test/versioning/semver-ruby.spec.js diff --git a/lib/versioning/semver-ruby/index.js b/lib/versioning/semver-ruby/index.js new file mode 100644 index 0000000000..310817d04d --- /dev/null +++ b/lib/versioning/semver-ruby/index.js @@ -0,0 +1,83 @@ +const { + eq, + valid, + gt, + satisfies, + maxSatisfying, + minSatisfying, +} = require('@snyk/ruby-semver'); +const { parse: parseVersion } = require('./version'); +const { parse: parseRange, ltr } = require('./range'); +const { isSingleOperator, isValidOperator } = require('./operator'); +const { pin, bump, replace } = require('./strategies'); + +const equals = (left, right) => eq(left, right); + +const getMajor = version => parseVersion(version).major; +const getMinor = version => parseVersion(version).minor; +const getPatch = version => parseVersion(version).patch; + +const isVersion = version => !!valid(version); +const isGreaterThan = (left, right) => gt(left, right); +const isLessThanRange = (version, range) => ltr(version, range); + +const isSingleVersion = range => { + const { version, operator } = parseRange(range); + + return operator + ? isVersion(version) && isSingleOperator(operator) + : isVersion(version); +}; + +const isStable = version => + parseVersion(version).prerelease ? false : isVersion(version); + +const isValid = range => { + const { version, operator } = parseRange(range); + + return operator + ? isVersion(version) && isValidOperator(operator) + : isVersion(version); +}; + +const matches = (version, range) => satisfies(version, range); +const maxSatisfyingVersion = (versions, range) => + maxSatisfying(versions, range); +const minSatisfyingVersion = (versions, range) => + minSatisfying(versions, range); + +const getNewValue = (currentValue, rangeStrategy, fromVersion, toVersion) => { + switch (rangeStrategy) { + case 'pin': + return pin({ to: toVersion }); + case 'bump': + return bump({ range: currentValue, to: toVersion }); + case 'replace': + return replace({ range: currentValue, to: toVersion }); + // istanbul ignore next + default: + logger.warn(`Unsupported strategy ${rangeStrategy}`); + return null; + } +}; + +const sortVersions = (left, right) => gt(left, right); + +module.exports = { + equals, + getMajor, + getMinor, + getPatch, + isCompatible: isVersion, + isGreaterThan, + isLessThanRange, + isSingleVersion, + isStable, + isValid, + isVersion, + matches, + maxSatisfyingVersion, + minSatisfyingVersion, + getNewValue, + sortVersions, +}; diff --git a/lib/versioning/semver-ruby/operator.js b/lib/versioning/semver-ruby/operator.js new file mode 100644 index 0000000000..9a85b898ba --- /dev/null +++ b/lib/versioning/semver-ruby/operator.js @@ -0,0 +1,27 @@ +const EQUAL = '='; +const NOT_EQUAL = '!='; + +const GT = '>'; +const LT = '<'; + +const GTE = '>='; +const LTE = '<='; +const PGTE = '~>'; + +const SINGLE = [EQUAL]; +const ALL = [EQUAL, NOT_EQUAL, GT, LT, GTE, LTE, PGTE]; + +const isValidOperator = operator => ALL.includes(operator); +const isSingleOperator = operator => SINGLE.includes(operator); + +module.exports = { + EQUAL, + NOT_EQUAL, + GT, + LT, + GTE, + LTE, + PGTE, + isValidOperator, + isSingleOperator, +}; diff --git a/lib/versioning/semver-ruby/range.js b/lib/versioning/semver-ruby/range.js new file mode 100644 index 0000000000..7aa8b6a6cf --- /dev/null +++ b/lib/versioning/semver-ruby/range.js @@ -0,0 +1,49 @@ +const GemVersion = require('@snyk/ruby-semver/lib/ruby/gem-version'); +const GemRequirement = require('@snyk/ruby-semver/lib/ruby/gem-requirement'); +const { EQUAL, NOT_EQUAL, GT, LT, GTE, LTE, PGTE } = require('./operator'); + +const parse = range => { + const regExp = /^([^\d\s]+)?\s?([0-9a-zA-Z-.-]+)$/g; + + const value = (range || '').trim(); + const matches = regExp.exec(value) || {}; + + return { + version: matches[2] || null, + operator: matches[1] || null, + }; +}; + +const ltr = (version, range) => { + const gemVersion = GemVersion.create(version); + const requirements = range.split(',').map(GemRequirement.parse); + + const results = requirements.map(([operator, ver]) => { + switch (operator) { + case GT: + case LT: + return gemVersion.compare(ver) <= 0; + case GTE: + case LTE: + case EQUAL: + case NOT_EQUAL: + return gemVersion.compare(ver) < 0; + case PGTE: + return ( + gemVersion.compare(ver) < 0 && + gemVersion.release().compare(ver.bump()) <= 0 + ); + // istanbul ignore next + default: + logger.warn(`Unsupported operator '${operator}'`); + return null; + } + }); + + return results.reduce((accumulator, value) => accumulator && value, true); +}; + +module.exports = { + parse, + ltr, +}; diff --git a/lib/versioning/semver-ruby/strategies/bump.js b/lib/versioning/semver-ruby/strategies/bump.js new file mode 100644 index 0000000000..28fe77f4c9 --- /dev/null +++ b/lib/versioning/semver-ruby/strategies/bump.js @@ -0,0 +1,32 @@ +const { gte, lte } = require('@snyk/ruby-semver'); +const { EQUAL, NOT_EQUAL, GT, LT, GTE, LTE, PGTE } = require('../operator'); +const { floor, increment, decrement } = require('../version'); +const { parse: parseRange } = require('../range'); + +module.exports = ({ range, to }) => { + const ranges = range.split(',').map(parseRange); + const results = ranges.map(({ operator, version: ver }) => { + switch (operator) { + case null: + return to; + case GT: + return lte(to, ver) ? `${GT} ${ver}` : `${GT} ${decrement(to)}`; + case LT: + return gte(to, ver) ? `${LT} ${increment(ver, to)}` : `${LT} ${ver}`; + case PGTE: + return `${operator} ${floor(to)}`; + case GTE: + case LTE: + case EQUAL: + return `${operator} ${to}`; + case NOT_EQUAL: + return `${NOT_EQUAL} ${ver}`; + // istanbul ignore next + default: + logger.warn(`Unsupported operator '${operator}'`); + return null; + } + }); + + return results.join(', '); +}; diff --git a/lib/versioning/semver-ruby/strategies/index.js b/lib/versioning/semver-ruby/strategies/index.js new file mode 100644 index 0000000000..a1bc4517ce --- /dev/null +++ b/lib/versioning/semver-ruby/strategies/index.js @@ -0,0 +1,9 @@ +const pin = require('./pin'); +const bump = require('./bump'); +const replace = require('./replace'); + +module.exports = { + pin, + bump, + replace, +}; diff --git a/lib/versioning/semver-ruby/strategies/pin.js b/lib/versioning/semver-ruby/strategies/pin.js new file mode 100644 index 0000000000..071862075c --- /dev/null +++ b/lib/versioning/semver-ruby/strategies/pin.js @@ -0,0 +1 @@ +module.exports = ({ to }) => to; diff --git a/lib/versioning/semver-ruby/strategies/replace.js b/lib/versioning/semver-ruby/strategies/replace.js new file mode 100644 index 0000000000..02bffe5664 --- /dev/null +++ b/lib/versioning/semver-ruby/strategies/replace.js @@ -0,0 +1,5 @@ +const { satisfies } = require('@snyk/ruby-semver'); +const bump = require('./bump'); + +module.exports = ({ to, range }) => + satisfies(to, range) ? range : bump({ to, range }); diff --git a/lib/versioning/semver-ruby/version.js b/lib/versioning/semver-ruby/version.js new file mode 100644 index 0000000000..3fcc9c680b --- /dev/null +++ b/lib/versioning/semver-ruby/version.js @@ -0,0 +1,100 @@ +const last = require('lodash/last'); +const GemVersion = require('@snyk/ruby-semver/lib/ruby/gem-version'); +const { diff, major, minor, patch, prerelease } = require('@snyk/ruby-semver'); + +const parse = version => ({ + major: major(version), + minor: minor(version), + patch: patch(version), + prerelease: prerelease(version), +}); + +const adapt = (left, right) => + left + .split('.') + .slice(0, right.split('.').length) + .join('.'); + +const floor = version => + [ + ...GemVersion.create(version) + .release() + .getSegments() + .slice(0, -1), + 0, + ].join('.'); + +// istanbul ignore next +const incrementLastSegment = version => { + const segments = GemVersion.create(version) + .release() + .getSegments(); + const nextLast = parseInt(last(segments), 10) + 1; + + return [...segments.slice(0, -1), nextLast].join('.'); +}; + +// istanbul ignore next +const incrementMajor = (maj, min, ptch, pre) => + min === 0 || ptch === 0 || pre.length === 0 ? maj + 1 : maj; + +// istanbul ignore next +const incrementMinor = (min, ptch, pre) => + ptch === 0 || pre.length === 0 ? min + 1 : min; + +// istanbul ignore next +const incrementPatch = (ptch, pre) => (pre.length === 0 ? ptch + 1 : ptch); + +// istanbul ignore next +const increment = (from, to) => { + const { major: maj, minor: min, patch: ptch, pre } = parse(from); + + let nextVersion; + switch (diff(from, adapt(to, from))) { + case 'major': + nextVersion = [incrementMajor(maj, min, ptch, pre || []), 0, 0].join('.'); + break; + case 'minor': + nextVersion = [maj, incrementMinor(min, ptch, pre || []), 0].join('.'); + break; + case 'patch': + nextVersion = [maj, min, incrementPatch(ptch, pre || [])].join('.'); + break; + case 'prerelease': + nextVersion = [maj, min, ptch].join('.'); + break; + default: + return incrementLastSegment(from); + } + + return increment(nextVersion, to); +}; + +// istanbul ignore next +const decrement = version => { + const segments = GemVersion.create(version) + .release() + .getSegments(); + const nextSegments = segments + .reverse() + .reduce((accumulator, segment, index) => { + if (index === 0) { + return [segment - 1]; + } + + if (accumulator[index - 1] === -1) { + return [...accumulator.slice(0, index - 1), 0, segment - 1]; + } + + return [...accumulator, segment]; + }, []); + + return nextSegments.reverse().join('.'); +}; + +module.exports = { + parse, + floor, + increment, + decrement, +}; diff --git a/package.json b/package.json index 5836fd0e22..86317c8ae8 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,8 @@ "upath": "1.1.0", "validator": "10.9.0", "www-authenticate": "0.6.2", - "yarn": "1.9.4" + "yarn": "1.9.4", + "@snyk/ruby-semver": "2.0.0" }, "devDependencies": { "babel-plugin-transform-object-rest-spread": "6.26.0", diff --git a/test/versioning/semver-ruby.spec.js b/test/versioning/semver-ruby.spec.js new file mode 100644 index 0000000000..a828b0826d --- /dev/null +++ b/test/versioning/semver-ruby.spec.js @@ -0,0 +1,412 @@ +const semverRuby = require('../../lib/versioning/semver-ruby'); + +describe('semverRuby', () => { + describe('.equals', () => { + it('returns true when versions are equal', () => { + expect(semverRuby.equals('1.0.0', '1')).toBe(true); + expect(semverRuby.equals('1.2.0', '1.2')).toBe(true); + expect(semverRuby.equals('1.2.0', '1.2.0')).toBe(true); + expect(semverRuby.equals('1.0.0.rc1', '1.0.0.rc1')).toBe(true); + }); + + it('returns false when versions are different', () => { + expect(semverRuby.equals('1.2.0', '2')).toBe(false); + expect(semverRuby.equals('1.2.0', '1.1')).toBe(false); + expect(semverRuby.equals('1.2.0', '1.2.1')).toBe(false); + expect(semverRuby.equals('1.0.0.rc1', '1.0.0.rc2')).toBe(false); + }); + }); + + describe('.getMajor', () => { + it('returns major segment of version', () => { + expect(semverRuby.getMajor('1')).toEqual(1); + expect(semverRuby.getMajor('1.2')).toEqual(1); + expect(semverRuby.getMajor('1.2.0')).toEqual(1); + expect(semverRuby.getMajor('1.2.0.alpha.4')).toEqual(1); + }); + }); + + describe('.getMinor', () => { + it('returns minor segment of version when it present', () => { + expect(semverRuby.getMinor('1.2')).toEqual(2); + expect(semverRuby.getMinor('1.2.0')).toEqual(2); + expect(semverRuby.getMinor('1.2.0.alpha.4')).toEqual(2); + }); + + it('returns null when minor segment absent', () => { + expect(semverRuby.getMinor('1')).toEqual(null); + }); + }); + + describe('.getPatch', () => { + it('returns patch segment of version when it present', () => { + expect(semverRuby.getPatch('1.2.2')).toEqual(2); + expect(semverRuby.getPatch('1.2.1.alpha.4')).toEqual(1); + }); + + it('returns null when patch segment absent', () => { + expect(semverRuby.getPatch('1')).toEqual(null); + expect(semverRuby.getPatch('1.2')).toEqual(null); + }); + }); + + describe('.isVersion', () => { + it('returns true when version is valid', () => { + expect(semverRuby.isVersion('1')).toBeTruthy(); + expect(semverRuby.isVersion('1.1')).toBeTruthy(); + expect(semverRuby.isVersion('1.1.2')).toBeTruthy(); + expect(semverRuby.isVersion('1.1.2.3')).toBeTruthy(); + expect(semverRuby.isVersion('1.1.2-4')).toBeTruthy(); + expect(semverRuby.isVersion('1.1.2.pre.4')).toBeTruthy(); + }); + + it('returns false when version is invalid', () => { + expect(semverRuby.isVersion()).toBeFalsy(); + expect(semverRuby.isVersion('')).toBeFalsy(); + expect(semverRuby.isVersion(null)).toBeFalsy(); + expect(semverRuby.isVersion('tottally-not-a-version')).toBeFalsy(); + }); + }); + + describe('.isGreaterThan', () => { + it('returns true when version is greater than another', () => { + expect(semverRuby.isGreaterThan('2', '1')).toBeTruthy(); + expect(semverRuby.isGreaterThan('2.2', '2.1')).toBeTruthy(); + expect(semverRuby.isGreaterThan('2.2.1', '2.2.0')).toBeTruthy(); + expect(semverRuby.isGreaterThan('3.0.0.rc2', '3.0.0.rc1')).toBeTruthy(); + expect(semverRuby.isGreaterThan('3.0.0-rc.2', '3.0.0-rc.1')).toBeTruthy(); + expect(semverRuby.isGreaterThan('3.0.0.rc1', '3.0.0.beta')).toBeTruthy(); + expect(semverRuby.isGreaterThan('3.0.0-rc.1', '3.0.0-beta')).toBeTruthy(); + expect( + semverRuby.isGreaterThan('3.0.0.beta', '3.0.0.alpha') + ).toBeTruthy(); + expect( + semverRuby.isGreaterThan('3.0.0-beta', '3.0.0-alpha') + ).toBeTruthy(); + expect(semverRuby.isGreaterThan('5.0.1.rc1', '5.0.1.beta1')).toBeTruthy(); + expect( + semverRuby.isGreaterThan('5.0.1-rc.1', '5.0.1-beta.1') + ).toBeTruthy(); + }); + + it('returns false when version is lower than another', () => { + expect(semverRuby.isGreaterThan('1', '2')).toBeFalsy(); + expect(semverRuby.isGreaterThan('2.1', '2.2')).toBeFalsy(); + expect(semverRuby.isGreaterThan('2.2.0', '2.2.1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0.rc1', '3.0.0.rc2')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0-rc.1', '3.0.0-rc.2')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0.beta', '3.0.0.rc1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0-beta', '3.0.0-rc.1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0.alpha', '3.0.0.beta')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0-alpha', '3.0.0-beta')).toBeFalsy(); + expect(semverRuby.isGreaterThan('5.0.1.beta1', '5.0.1.rc1')).toBeFalsy(); + expect( + semverRuby.isGreaterThan('5.0.1-beta.1', '5.0.1-rc.1') + ).toBeFalsy(); + }); + + it('returns false when versions are equal', () => { + expect(semverRuby.isGreaterThan('1', '1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('2.1', '2.1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('2.2.0', '2.2.0')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0.rc1', '3.0.0.rc1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0-rc.1', '3.0.0-rc.1')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0.beta', '3.0.0.beta')).toBeFalsy(); + expect(semverRuby.isGreaterThan('3.0.0-beta', '3.0.0-beta')).toBeFalsy(); + expect( + semverRuby.isGreaterThan('3.0.0.alpha', '3.0.0.alpha') + ).toBeFalsy(); + expect( + semverRuby.isGreaterThan('3.0.0-alpha', '3.0.0-alpha') + ).toBeFalsy(); + expect( + semverRuby.isGreaterThan('5.0.1.beta1', '5.0.1.beta1') + ).toBeFalsy(); + expect( + semverRuby.isGreaterThan('5.0.1-beta.1', '5.0.1-beta.1') + ).toBeFalsy(); + }); + }); + + describe('.isStable', () => { + it('returns true when version is stable', () => { + expect(semverRuby.isStable('1')).toBeTruthy(); + expect(semverRuby.isStable('1.2')).toBeTruthy(); + expect(semverRuby.isStable('1.2.3')).toBeTruthy(); + }); + + it('returns false when version is prerelease', () => { + expect(semverRuby.isStable('1.2.0-alpha')).toBeFalsy(); + expect(semverRuby.isStable('1.2.0.alpha')).toBeFalsy(); + expect(semverRuby.isStable('1.2.0.alpha1')).toBeFalsy(); + expect(semverRuby.isStable('1.2.0-alpha.1')).toBeFalsy(); + }); + + it('returns false when version is invalid', () => { + expect(semverRuby.isStable()).toBeFalsy(); + expect(semverRuby.isStable('')).toBeFalsy(); + expect(semverRuby.isStable(null)).toBeFalsy(); + expect(semverRuby.isStable('tottally-not-a-version')).toBeFalsy(); + }); + }); + + describe('.sortVersions', () => { + it('sorts versions in an ascending order', () => { + expect( + ['1.2.3-beta', '2.0.1', '1.3.4', '1.2.3'].sort(semverRuby.sortVersions) + ).toEqual(['1.2.3-beta', '1.2.3', '1.3.4', '2.0.1']); + }); + }); + + describe('.minSatisfyingVersion', () => { + it('returns lowest version that matches range', () => { + expect( + semverRuby.minSatisfyingVersion(['2.1.5', '2.1.6'], '~> 2.1') + ).toEqual('2.1.5'); + + expect( + semverRuby.minSatisfyingVersion(['2.1.6', '2.1.5'], '~> 2.1.6') + ).toEqual('2.1.6'); + + expect( + semverRuby.minSatisfyingVersion( + ['4.7.3', '4.7.4', '4.7.5', '4.7.9'], + '~> 4.7, >= 4.7.4' + ) + ).toEqual('4.7.4'); + + expect( + semverRuby.minSatisfyingVersion( + ['2.5.3', '2.5.4', '2.5.5', '2.5.6'], + '~>2.5.3' + ) + ).toEqual('2.5.3'); + + expect( + semverRuby.minSatisfyingVersion( + ['2.1.0', '3.0.0.beta', '2.3', '3.0.0-rc.1', '3.0.0', '3.1.1'], + '~> 3.0' + ) + ).toEqual('3.0.0'); + }); + + it('returns null if version that matches range absent', () => { + expect( + semverRuby.minSatisfyingVersion(['1.2.3', '1.2.4'], '>= 3.5.0') + ).toEqual(null); + }); + }); + + describe('.maxSatisfyingVersion', () => { + it('returns greatest version that matches range', () => { + expect( + semverRuby.maxSatisfyingVersion(['2.1.5', '2.1.6'], '~> 2.1') + ).toEqual('2.1.6'); + + expect( + semverRuby.maxSatisfyingVersion(['2.1.6', '2.1.5'], '~> 2.1.6') + ).toEqual('2.1.6'); + + expect( + semverRuby.maxSatisfyingVersion( + ['4.7.3', '4.7.4', '4.7.5', '4.7.9'], + '~> 4.7, >= 4.7.4' + ) + ).toEqual('4.7.9'); + + expect( + semverRuby.maxSatisfyingVersion( + ['2.5.3', '2.5.4', '2.5.5', '2.5.6'], + '~>2.5.3' + ) + ).toEqual('2.5.6'); + + expect( + semverRuby.maxSatisfyingVersion( + ['2.1.0', '3.0.0.beta', '2.3', '3.0.0-rc.1', '3.0.0', '3.1.1'], + '~> 3.0' + ) + ).toEqual('3.1.1'); + }); + + it('returns null if version that matches range absent', () => { + expect( + semverRuby.maxSatisfyingVersion(['1.2.3', '1.2.4'], '>= 3.5.0') + ).toEqual(null); + }); + }); + + describe('.matches', () => { + it('returns true when version match range', () => { + expect(semverRuby.matches('1.2', '>= 1.2')).toBeTruthy(); + expect(semverRuby.matches('1.2.3', '~> 1.2.1')).toBeTruthy(); + expect(semverRuby.matches('1.2.7', '1.2.7')).toBeTruthy(); + expect(semverRuby.matches('1.1.6', '>= 1.1.5, < 2.0')).toBeTruthy(); + }); + + it('returns false when version not match range', () => { + expect(semverRuby.matches('1.2', '>= 1.3')).toBeFalsy(); + expect(semverRuby.matches('1.3.8', '~> 1.2.1')).toBeFalsy(); + expect(semverRuby.matches('1.3.9', '1.3.8')).toBeFalsy(); + expect(semverRuby.matches('2.0.0', '>= 1.1.5, < 2.0')).toBeFalsy(); + }); + }); + + describe('.isLessThanRange', () => { + it('returns true when version less than range', () => { + expect(semverRuby.isLessThanRange('1.2.2', '< 1.2.2')).toBeTruthy(); + expect( + semverRuby.isLessThanRange('1.1.4', '>= 1.1.5, < 2.0') + ).toBeTruthy(); + expect( + semverRuby.isLessThanRange('1.2.0-alpha', '1.2.0-beta') + ).toBeTruthy(); + expect( + semverRuby.isLessThanRange('1.2.2', '> 1.2.2, ~> 2.0.0') + ).toBeTruthy(); + }); + + it('returns false when version greater or satisfies range', () => { + expect(semverRuby.isLessThanRange('1.2.2', '<= 1.2.2')).toBeFalsy(); + expect( + semverRuby.isLessThanRange('2.0.0', '>= 1.1.5, < 2.0') + ).toBeFalsy(); + expect( + semverRuby.isLessThanRange('1.2.0-beta', '1.2.0-alpha') + ).toBeFalsy(); + expect( + semverRuby.isLessThanRange('2.0.0', '> 1.2.2, ~> 2.0.0') + ).toBeFalsy(); + }); + }); + + describe('.isValid', () => { + it('returns true when version is valid', () => { + expect(semverRuby.isValid('1')).toBeTruthy(); + expect(semverRuby.isValid('1.1')).toBeTruthy(); + expect(semverRuby.isValid('1.1.2')).toBeTruthy(); + expect(semverRuby.isValid('1.2.0.alpha1')).toBeTruthy(); + expect(semverRuby.isValid('1.2.0-alpha.1')).toBeTruthy(); + + expect(semverRuby.isValid('= 1')).toBeTruthy(); + expect(semverRuby.isValid('!= 1.1')).toBeTruthy(); + expect(semverRuby.isValid('> 1.1.2')).toBeTruthy(); + expect(semverRuby.isValid('< 1.0.0-beta')).toBeTruthy(); + expect(semverRuby.isValid('>= 1.0.0.beta')).toBeTruthy(); + expect(semverRuby.isValid('<= 1.2.0.alpha1')).toBeTruthy(); + expect(semverRuby.isValid('~> 1.2.0-alpha.1')).toBeTruthy(); + }); + + it('returns false when version is invalid', () => { + expect(semverRuby.isVersion()).toBeFalsy(); + expect(semverRuby.isVersion('')).toBeFalsy(); + expect(semverRuby.isVersion(null)).toBeFalsy(); + expect(semverRuby.isVersion('tottally-not-a-version')).toBeFalsy(); + + expect(semverRuby.isValid('+ 1')).toBeFalsy(); + expect(semverRuby.isValid('- 1.1')).toBeFalsy(); + expect(semverRuby.isValid('=== 1.1.2')).toBeFalsy(); + expect(semverRuby.isValid('! 1.0.0-beta')).toBeFalsy(); + expect(semverRuby.isValid('& 1.0.0.beta')).toBeFalsy(); + }); + }); + + describe('.isSingleVersion', () => { + it('returns true when version is single', () => { + expect(semverRuby.isSingleVersion('1')).toBeTruthy(); + expect(semverRuby.isSingleVersion('1.2')).toBeTruthy(); + expect(semverRuby.isSingleVersion('1.2.1')).toBeTruthy(); + + expect(semverRuby.isSingleVersion('=1')).toBeTruthy(); + expect(semverRuby.isSingleVersion('=1.2')).toBeTruthy(); + expect(semverRuby.isSingleVersion('=1.2.1')).toBeTruthy(); + + expect(semverRuby.isSingleVersion('= 1')).toBeTruthy(); + expect(semverRuby.isSingleVersion('= 1.2')).toBeTruthy(); + expect(semverRuby.isSingleVersion('= 1.2.1')).toBeTruthy(); + + expect(semverRuby.isSingleVersion('1.2.1.rc1')).toBeTruthy(); + expect(semverRuby.isSingleVersion('1.2.1-rc.1')).toBeTruthy(); + + expect(semverRuby.isSingleVersion('= 1.2.0.alpha')).toBeTruthy(); + expect(semverRuby.isSingleVersion('= 1.2.0-alpha')).toBeTruthy(); + }); + + it('returns false when version is multiple', () => { + expect(semverRuby.isSingleVersion('!= 1')).toBeFalsy(); + expect(semverRuby.isSingleVersion('> 1.2')).toBeFalsy(); + expect(semverRuby.isSingleVersion('< 1.2.1')).toBeFalsy(); + expect(semverRuby.isSingleVersion('>= 1')).toBeFalsy(); + expect(semverRuby.isSingleVersion('<= 1.2')).toBeFalsy(); + expect(semverRuby.isSingleVersion('~> 1.2.1')).toBeFalsy(); + }); + + it('returns false when version is invalid', () => { + expect(semverRuby.isSingleVersion()).toBeFalsy(); + expect(semverRuby.isSingleVersion('')).toBeFalsy(); + expect(semverRuby.isSingleVersion(null)).toBeFalsy(); + expect(semverRuby.isSingleVersion('tottally-not-a-version')).toBeFalsy(); + }); + }); + + describe('.getNewValue', () => { + it('returns correct version for pin strategy', () => { + [ + ['1.2.3', '1.0.3', 'pin', '1.0.3', '1.2.3'], + ['1.2.3', '= 1.0.3', 'pin', '1.0.3', '1.2.3'], + ['1.2.3', '!= 1.0.3', 'pin', '1.0.4', '1.2.3'], + ['1.2.3', '> 1.0.3', 'pin', '1.0.4', '1.2.3'], + ['1.2.3', '< 1.0.3', 'pin', '1.0.2', '1.2.3'], + ['1.2.3', '>= 1.0.3', 'pin', '1.0.4', '1.2.3'], + ['1.2.3', '<= 1.0.3', 'pin', '1.0.3', '1.2.3'], + ['1.2.3', '~> 1.0.3', 'pin', '1.0.4', '1.2.3'], + ['4.7.8', '~> 4.7, >= 4.7.4', 'pin', '4.7.5', '4.7.8'], + ].forEach(([expected, ...params]) => { + expect(semverRuby.getNewValue(...params)).toEqual(expected); + }); + }); + + it('returns correct version for bump strategy', () => { + [ + ['1.2.3', '1.0.3', 'bump', '1.0.3', '1.2.3'], + ['= 1.2.3', '= 1.0.3', 'bump', '1.0.3', '1.2.3'], + ['!= 1.0.3', '!= 1.0.3', 'bump', '1.0.0', '1.2.3'], + ['> 1.2.2', '> 1.0.3', 'bump', '1.0.4', '1.2.3'], + ['< 1.2.4', '< 1.0.3', 'bump', '1.0.0', '1.2.3'], + ['< 1.2.4', '< 1.2.2', 'bump', '1.0.0', '1.2.3'], + ['< 1.2.4', '< 1.2.3', 'bump', '1.0.0', '1.2.3'], + ['< 1.3', '< 1.2', 'bump', '1.0.0', '1.2.3'], + ['< 2', '< 1', 'bump', '0.9.0', '1.2.3'], + ['>= 1.2.3', '>= 1.0.3', 'bump', '1.0.3', '1.2.3'], + ['<= 1.2.3', '<= 1.0.3', 'bump', '1.0.3', '1.2.3'], + ['~> 1.2.0', '~> 1.0.3', 'bump', '1.0.3', '1.2.3'], + ['~> 1.0.0', '~> 1.0.3', 'bump', '1.0.3', '1.0.4'], + ['~> 4.7.0, >= 4.7.9', '~> 4.7, >= 4.7.4', 'bump', '4.7.5', '4.7.9'], + ].forEach(([expected, ...params]) => { + expect(semverRuby.getNewValue(...params)).toEqual(expected); + }); + }); + + it('returns correct version for replace strategy', () => { + [ + ['1.2.3', '1.0.3', 'replace', '1.0.3', '1.2.3'], + ['= 1.2.3', '= 1.0.3', 'replace', '1.0.3', '1.2.3'], + ['!= 1.0.3', '!= 1.0.3', 'replace', '1.0.0', '1.2.3'], + ['< 1.2.4', '< 1.0.3', 'replace', '1.0.0', '1.2.3'], + ['< 1.2.4', '< 1.2.2', 'replace', '1.0.0', '1.2.3'], + ['< 1.2.4', '< 1.2.3', 'replace', '1.0.0', '1.2.3'], + ['< 1.3', '< 1.2', 'replace', '1.0.0', '1.2.3'], + ['< 2', '< 1', 'replace', '0.9.0', '1.2.3'], + ['< 1.2.3', '< 1.2.3', 'replace', '1.0.0', '1.2.2'], + ['>= 1.0.3', '>= 1.0.3', 'replace', '1.0.3', '1.2.3'], + ['<= 1.2.3', '<= 1.0.3', 'replace', '1.0.0', '1.2.3'], + ['<= 1.0.3', '<= 1.0.3', 'replace', '1.0.0', '1.0.2'], + ['~> 1.2.0', '~> 1.0.3', 'replace', '1.0.0', '1.2.3'], + ['~> 1.0.3', '~> 1.0.3', 'replace', '1.0.0', '1.0.4'], + ['~> 4.7, >= 4.7.4', '~> 4.7, >= 4.7.4', 'replace', '1.0.0', '4.7.9'], + ].forEach(([expected, ...params]) => { + expect(semverRuby.getNewValue(...params)).toEqual(expected); + }); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 360ee70a94..929c081c10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -173,6 +173,13 @@ dependencies: symbol-observable "^1.2.0" +"@snyk/ruby-semver@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@snyk/ruby-semver/-/ruby-semver-2.0.0.tgz#2c07734a76cefde4e1574fb1456f963a989c8b95" + integrity sha512-ka2KbiUVI9doltXGGnwx/g+0AK3g5w2JF9gsz+Uparvfjs2/ywDVL0kpCOVFUuO31iPreiplKoWZ/hZsZ4BrFw== + dependencies: + lodash "^4" + "@szmarczak/http-timer@^1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.1.tgz#6402258dfe467532b26649ef076b4d11f74fb612" @@ -5095,7 +5102,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@4.17.11: +lodash@4.17.11, lodash@^4: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -6083,7 +6090,6 @@ npm@6.5.0: cmd-shim "~2.0.2" columnify "~1.5.4" config-chain "^1.1.12" - debuglog "*" detect-indent "~5.0.0" detect-newline "^2.1.0" dezalgo "~1.0.3" @@ -6098,7 +6104,6 @@ npm@6.5.0: has-unicode "~2.0.1" hosted-git-info "^2.7.1" iferr "^1.0.2" - imurmurhash "*" inflight "~1.0.6" inherits "~2.0.3" ini "^1.3.5" @@ -6111,14 +6116,8 @@ npm@6.5.0: libnpx "^10.2.0" lock-verify "^2.0.2" lockfile "^1.0.4" - lodash._baseindexof "*" lodash._baseuniq "~4.6.0" - lodash._bindcallback "*" - lodash._cacheindexof "*" - lodash._createcache "*" - lodash._getnative "*" lodash.clonedeep "~4.5.0" - lodash.restparam "*" lodash.union "~4.6.0" lodash.uniq "~4.5.0" lodash.without "~4.4.0" @@ -6157,7 +6156,6 @@ npm@6.5.0: read-package-json "^2.0.13" read-package-tree "^5.2.1" readable-stream "^2.3.6" - readdir-scoped-modules "*" request "^2.88.0" retry "^0.12.0" rimraf "~2.6.2" -- GitLab