From 4ab0d9dfd3e4690c661ae4feea994c0667b78500 Mon Sep 17 00:00:00 2001 From: Denis Demchenko Date: Mon, 2 Jul 2018 23:30:48 +0300 Subject: [PATCH] Get rid of semver and use old comparison which is more applicable --- src/parser.js | 24 +++++++++----- src/utils.js | 81 +++++++++++++++++++++++++++++++++++++++++++++ test/unit/parser.js | 10 ++++-- test/unit/utils.js | 33 ++++++++++++++++++ 4 files changed, 137 insertions(+), 11 deletions(-) diff --git a/src/parser.js b/src/parser.js index c43ddbe..2b961c7 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,8 +1,8 @@ -import semver from 'semver'; import browserParsersList from './parser-browsers'; import osParsersList from './parser-os'; import platformParsersList from './parser-platforms'; import enginesParsersList from './parser-engines'; +import { compareVersions } from './utils'; class Parser { /** @@ -303,18 +303,14 @@ class Parser { * // or with platforms * if (browser.check({desktop: { chrome: '>118.01.1322' } })) */ - semverCheck(checkTree) { - const thisVersion = this.getBrowser().version; - if (!semver.valid(semver.coerce(thisVersion))) { - throw new Error(`Version of current browser doesn't seem applicable: ${thisVersion}`); - } + check(checkTree) { const keysToProcess = Object.keys(checkTree); return keysToProcess.some((browserAttribute) => { const objectOrVersion = checkTree[browserAttribute]; if (typeof objectOrVersion === 'object') { return (this.isOs(browserAttribute) || this.isPlatform(browserAttribute)) - && this.semverCheck(objectOrVersion); + && this.check(objectOrVersion); } return this.isBrowser(browserAttribute) && this.satisfies(objectOrVersion); @@ -326,7 +322,19 @@ class Parser { } satisfies(version) { - return semver.satisfies(semver.coerce(this.getBrowser().version), version); + let expectedResult = 0; + let comparableVersion = version; + + if (version[0] === '>') { + expectedResult = 1; + comparableVersion = version.substr(1); + } else if (version[0] === '<') { + expectedResult = -1; + comparableVersion = version.substr(1); + } else if (version[0] === '=') { + comparableVersion = version.substr(1); + } + return compareVersions(this.getBrowserVersion(), comparableVersion) === expectedResult; } isOs(osName) { diff --git a/src/utils.js b/src/utils.js index b974c7f..666a597 100644 --- a/src/utils.js +++ b/src/utils.js @@ -50,6 +50,87 @@ class Utils { default: return undefined; } } + + /** + * Get version precisions count + * + * @example + * getVersionPrecision("1.10.3") // 3 + * + * @param {string} version + * @return {number} + */ + static getVersionPrecision(version) { + return version.split('.').length; + } + + /** + * Calculate browser version weight + * + * @example + * compareVersions(['1.10.2.1', '1.8.2.1.90']) // 1 + * compareVersions(['1.010.2.1', '1.09.2.1.90']); // 1 + * compareVersions(['1.10.2.1', '1.10.2.1']); // 0 + * compareVersions(['1.10.2.1', '1.0800.2']); // -1 + * + * @param {String} versionA versions versions to compare + * @param {String} versionB versions versions to compare + * @return {Number} comparison result: -1 when versionA is lower, + * 1 when versionA is bigger, 0 when both equal + */ + static compareVersions(versionA, versionB) { + // 1) get common precision for both versions, for example for "10.0" and "9" it should be 2 + let precision = Math.max( + Utils.getVersionPrecision(versionA), + Utils.getVersionPrecision(versionB), + ); + + const chunks = Utils.map([versionA, versionB], (version) => { + const delta = precision - Utils.getVersionPrecision(version); + + // 2) "9" -> "9.0" (for precision = 2) + const _version = version + new Array(delta + 1).join('.0'); + + // 3) "9.0" -> ["000000000"", "000000009"] + return Utils.map(_version.split('.'), chunk => new Array(20 - chunk.length).join('0') + chunk).reverse(); + }); + + // iterate in reverse order by reversed chunks array + precision -= 1; + while (precision >= 0) { + // 4) compare: "000000009" > "000000010" = false (but "9" > "10" = true) + if (chunks[0][precision] > chunks[1][precision]) { + return 1; + } else if (chunks[0][precision] === chunks[1][precision]) { + if (precision === 0) { + // all version chunks are same + return 0; + } + } else { + return -1; + } + precision -= 1; + } + } + + /** + * Array::map polyfill + * + * @param {Array} arr + * @param {Function} iterator + * @return {Array} + */ + static map(arr, iterator) { + const result = []; + let i; + if (Array.prototype.map) { + return Array.prototype.map.call(arr, iterator); + } + for (i = 0; i < arr.length; i += 1) { + result.push(iterator(arr[i])); + } + return result; + } } module.exports = Utils; diff --git a/test/unit/parser.js b/test/unit/parser.js index a5dbe11..33443a7 100644 --- a/test/unit/parser.js +++ b/test/unit/parser.js @@ -58,13 +58,17 @@ test('Skip parsing shouldn\'t parse', (t) => { }); test('Parser.check should make simple check', (t) => { - t.is(parser.semverCheck({ opera: '>42' }), true); + t.is(parser.check({ opera: '>42' }), true); }); test('Parser.check should make simple check', (t) => { - t.is(parser.semverCheck({ + t.is(parser.check({ macos: { - opera: '>42', + safari: '>11', }, + ios: { + safari: '>10', + }, + opera: '>42', }), true); }); diff --git a/test/unit/utils.js b/test/unit/utils.js index 59919a4..0240976 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -2,6 +2,7 @@ import test from 'ava'; import { getFirstMatch, getWindowsVersionName, + compareVersions, } from '../../src/utils'; test('getFirstMatch', (t) => { @@ -13,3 +14,35 @@ test('getWindowsVersionName', (t) => { t.is(getWindowsVersionName('NT 5.0'), '2000'); t.is(getWindowsVersionName('XXX'), void 0); }); + +test('compareVersions', (t) => { + const comparisionsTasks = [ + ['9.0', '10', -1], + ['11', '10', 1], + ['1.10.2.1', '1.8.2.1.90', 1], + ['1.010.2.1', '1.08.2.1.90', 1], + ['1.10.2.1', '1.10.2.1', 0], + ['1.10.2.1', '1.0800.2', -1], + ['1.0.0-alpha', '1.0.0-alpha.1', -1], + ['1.0.0-alpha.1', '1.0.0-alpha.beta', -1], + ['1.0.0-alpha.beta', '1.0.0-beta', -1], + ['1.0.0-beta', '1.0.0-beta.2', -1], + ['1.0.0-beta.11', '1.0.0-rc.1', -1], + ['1.0.0-rc.1', '1.0.0', -1], + ]; + + comparisionsTasks.forEach((testingParams) => { + const versionA = testingParams[0]; + const versionB = testingParams[1]; + const result = testingParams[2]; + let matching = ' == '; + + if (result > 0) { + matching = ' > '; + } else if (result < 0) { + matching = ' < '; + } + + t.is(compareVersions(versionA, versionB), result, `version ${versionA} should be ${matching} version ${versionB}`); + }); +});