From e9ee0179c5a4a749449da9546ba0c19aeb632296 Mon Sep 17 00:00:00 2001 From: Sergio Zharinov <zharinov@users.noreply.github.com> Date: Fri, 4 Jan 2019 13:11:42 +0400 Subject: [PATCH] feat: maven compare functions (#3020) Adds comparator function, in readiness for full Ruby versioning support later. --- lib/versioning/maven/compare.js | 213 ++++++++++++++++++++++++++++++++ test/versioning/maven.spec.js | 72 +++++++++++ 2 files changed, 285 insertions(+) create mode 100644 lib/versioning/maven/compare.js create mode 100644 test/versioning/maven.spec.js diff --git a/lib/versioning/maven/compare.js b/lib/versioning/maven/compare.js new file mode 100644 index 0000000000..865f1830f0 --- /dev/null +++ b/lib/versioning/maven/compare.js @@ -0,0 +1,213 @@ +const PREFIX_DOT = 'PREFIX_DOT'; +const PREFIX_HYPHEN = 'PREFIX_HYPHEN'; + +const TYPE_NUMBER = 'TYPE_NUMBER'; +const TYPE_QUALIFIER = 'TYPE_QUALIFIER'; + +function iterateChars(str, cb) { + let prev = null; + let next = null; + for (let i = 0; i < str.length; i += 1) { + next = str.charAt(i); + cb(prev, next); + prev = next; + } + cb(prev, null); +} + +function isDigit(char) { + return /^\d$/.test(char); +} + +function isLetter(char) { + return /^[a-z]$/i.test(char); +} + +function isTransition(prevChar, nextChar) { + return ( + (isDigit(prevChar) && isLetter(nextChar)) || + (isLetter(prevChar) && isDigit(nextChar)) + ); +} + +function iterateTokens(versionStr, cb) { + let currentPrefix = PREFIX_HYPHEN; + let currentVal = ''; + + function yieldToken(transition = false) { + const val = currentVal || '0'; + if (/^\d+$/.test(val)) { + cb({ + prefix: currentPrefix, + type: TYPE_NUMBER, + val: parseInt(val, 10), + isTransition: transition, + }); + } else { + cb({ + prefix: currentPrefix, + type: TYPE_QUALIFIER, + val, + isTransition: transition, + }); + } + } + + iterateChars(versionStr, (prevChar, nextChar) => { + if (nextChar === null) { + yieldToken(); + } else if (nextChar === '-') { + yieldToken(); + currentPrefix = PREFIX_HYPHEN; + currentVal = ''; + } else if (nextChar === '.') { + yieldToken(); + currentPrefix = PREFIX_DOT; + currentVal = ''; + } else if (prevChar !== null && isTransition(prevChar, nextChar)) { + yieldToken(true); + currentPrefix = PREFIX_HYPHEN; + currentVal = nextChar; + } else { + currentVal = currentVal.concat(nextChar); + } + }); +} + +function isNull(token) { + const val = token.val; + return val === 0 || val === '' || val === 'final' || val === 'ga'; +} + +function tokenize(versionStr) { + let buf = []; + let result = []; + iterateTokens(versionStr, token => { + if (token.prefix === PREFIX_HYPHEN) { + buf = []; + } + buf.push(token); + if (!isNull(token)) { + result = result.concat(buf); + buf = []; + } + }); + return result; +} + +function nullFor(token) { + return { + prefix: token.prefix, + type: token.prefix === PREFIX_DOT ? TYPE_NUMBER : TYPE_QUALIFIER, + val: token.prefix === PREFIX_DOT ? 0 : '', + }; +} + +function commonOrder(token) { + if (token.prefix === PREFIX_DOT && token.type === TYPE_QUALIFIER) { + return 0; + } + if (token.prefix === PREFIX_HYPHEN && token.type === TYPE_QUALIFIER) { + return 1; + } + if (token.prefix === PREFIX_HYPHEN && token.type === TYPE_NUMBER) { + return 2; + } + return 3; +} + +function qualifierOrder(token) { + const val = token.val; + if (val === 'alpha' || (token.isTransition && val === 'a')) { + return 1; + } + if (val === 'beta' || (token.isTransition && val === 'b')) { + return 2; + } + if (val === 'milestone' || (token.isTransition && val === 'm')) { + return 3; + } + if (val === 'rc' || val === 'cr') { + return 4; + } + if (val === 'snapshot') { + return 5; + } + if (val === '' || val === 'final' || val === 'ga') { + return 6; + } + if (val === 'sp') { + return 7; + } + return null; +} + +function qualifierCmp(left, right) { + const leftOrder = qualifierOrder(left); + const rightOrder = qualifierOrder(right); + if (leftOrder && rightOrder) { + if (leftOrder === rightOrder) { + return 0; + } + if (leftOrder < rightOrder) { + return -1; + } + return 1; + } + if (left.val === right.val) { + return 0; + } + if (left.val < right.val) { + return -1; + } + // istanbul ignore next + return 1; +} + +function tokenCmp(left, right) { + if (left.prefix === right.prefix) { + if (left.type === TYPE_NUMBER && right.type === TYPE_NUMBER) { + if (left.val === right.val) { + return 0; + } + if (left.val < right.val) { + return -1; + } + return 1; + } + if (left.type === TYPE_NUMBER) { + return 1; + } + if (right.type === TYPE_NUMBER) { + return -1; + } + return qualifierCmp(left, right); + } + const leftOrder = commonOrder(left); + const rightOrder = commonOrder(right); + // istanbul ignore if + if (leftOrder === rightOrder) { + return 0; + } + if (leftOrder < rightOrder) { + return -1; + } + return 1; +} + +function compare(left, right) { + const leftTokens = tokenize(left.toLowerCase()); + const rightTokens = tokenize(right.toLowerCase()); + const length = Math.max(leftTokens.length, rightTokens.length); + for (let idx = 0; idx < length; idx += 1) { + const leftToken = leftTokens[idx] || nullFor(rightTokens[idx]); + const rightToken = rightTokens[idx] || nullFor(leftTokens[idx]); + const cmpResult = tokenCmp(leftToken, rightToken); + if (cmpResult !== 0) return cmpResult; + } + return 0; +} + +module.exports = { + compare, +}; diff --git a/test/versioning/maven.spec.js b/test/versioning/maven.spec.js new file mode 100644 index 0000000000..a3ef2208ec --- /dev/null +++ b/test/versioning/maven.spec.js @@ -0,0 +1,72 @@ +const { compare } = require('../../lib/versioning/maven/compare'); + +describe('versioning/maven/compare', () => { + it('returns equality', () => { + expect(compare('1.0.0', '1')).toEqual(0); + expect(compare('1-a1', '1-alpha-1')).toEqual(0); + expect(compare('1-b1', '1-beta-1')).toEqual(0); + expect(compare('1.0.0', '1.ga')).toEqual(0); + expect(compare('1-ga', '1.ga')).toEqual(0); + expect(compare('1-ga-1', '1-1')).toEqual(0); + expect(compare('1.final', '1.0')).toEqual(0); + expect(compare('1', '1.0')).toEqual(0); + expect(compare('1.', '1-')).toEqual(0); + expect(compare('1.0.0-0.0.0', '1-final')).toEqual(0); + expect(compare('1-1.foo-bar1baz-.1', '1-1.foo-bar-1-baz-0.1')).toEqual(0); + expect(compare('1.0ALPHA1', '1.0-a1')).toEqual(0); + expect(compare('1.0Alpha1', '1.0-a1')).toEqual(0); + expect(compare('1.0AlphA1', '1.0-a1')).toEqual(0); + expect(compare('1.0BETA1', '1.0-b1')).toEqual(0); + expect(compare('1.0MILESTONE1', '1.0-m1')).toEqual(0); + expect(compare('1.0RC1', '1.0-cr1')).toEqual(0); + expect(compare('1.0GA', '1.0')).toEqual(0); + expect(compare('1.0FINAL', '1.0')).toEqual(0); + expect(compare('1.0-SNAPSHOT', '1-snapshot')).toEqual(0); + expect(compare('1.0alpha1', '1.0-a1')).toEqual(0); + expect(compare('1.0alpha-1', '1.0-a1')).toEqual(0); + expect(compare('1.0beta1', '1.0-b1')).toEqual(0); + expect(compare('1.0beta-1', '1.0-b1')).toEqual(0); + expect(compare('1.0milestone1', '1.0-m1')).toEqual(0); + expect(compare('1.0milestone-1', '1.0-m1')).toEqual(0); + expect(compare('1.0rc1', '1.0-cr1')).toEqual(0); + expect(compare('1.0rc-1', '1.0-cr1')).toEqual(0); + expect(compare('1.0ga', '1.0')).toEqual(0); + expect(compare('1-0.ga', '1.0')).toEqual(0); + expect(compare('1.0-final', '1.0')).toEqual(0); + expect(compare('1-0-ga', '1.0')).toEqual(0); + expect(compare('1-0-final', '1-0')).toEqual(0); + expect(compare('1-0', '1.0')).toEqual(0); + }); + it('returns less than', () => { + expect(compare('1', '1.1')).toEqual(-1); + expect(compare('1', '2')).toEqual(-1); + expect(compare('1-snapshot', '1')).toEqual(-1); + expect(compare('1', '1-sp')).toEqual(-1); + expect(compare('1-foo2', '1-foo10')).toEqual(-1); + expect(compare('1-m1', '1-milestone-2')).toEqual(-1); + expect(compare('1.foo', '1-foo')).toEqual(-1); + expect(compare('1-foo', '1-1')).toEqual(-1); + expect(compare('1-alpha.1', '1-beta.1')).toEqual(-1); + expect(compare('1-1', '1.1')).toEqual(-1); + expect(compare('1-ga', '1-ap')).toEqual(-1); + expect(compare('1-ga.1', '1-sp.1')).toEqual(-1); + expect(compare('1-sp-1', '1-ga-1')).toEqual(-1); + expect(compare('1-cr1', '1')).toEqual(-1); + }); + it('returns greater than', () => { + expect(compare('1.1', '1')).toEqual(1); + expect(compare('2', '1')).toEqual(1); + expect(compare('1', '1-snapshot')).toEqual(1); + expect(compare('1-sp', '1')).toEqual(1); + expect(compare('1-foo10', '1-foo2')).toEqual(1); + expect(compare('1-milestone-2', '1-m1')).toEqual(1); + expect(compare('1-foo', '1.foo')).toEqual(1); + expect(compare('1-1', '1-foo')).toEqual(1); + expect(compare('1-beta.1', '1-alpha.1')).toEqual(1); + expect(compare('1.1', '1-1')).toEqual(1); + expect(compare('1-sp', '1-ga')).toEqual(1); + expect(compare('1-sp.1', '1-ga.1')).toEqual(1); + expect(compare('1-ga-1', '1-sp-1')).toEqual(1); + expect(compare('1', '1-cr1')).toEqual(1); + }); +}); -- GitLab