From 54480ecc051bb76444b395361d803aae700ea0d2 Mon Sep 17 00:00:00 2001 From: Pepijn Van Eeckhoudt Date: Tue, 14 Apr 2020 19:28:44 +0200 Subject: [PATCH 1/3] Ensure a less-than(-equal) filter is always passed to the metadata server If a less-than(-equal) filter is not provided level-sublevel will add a default one which uses '\uffff' as upperbound. This incorrectly filters out all codepoints outside the BMP. By always passing an explicit filter this default behaviour is avoided. Signed-off-by: Pepijn Van Eeckhoudt --- lib/algos/list/MPU.js | 4 +++- lib/algos/list/delimiter.js | 4 +++- lib/algos/list/delimiterVersions.js | 4 +++- lib/algos/list/tools.js | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/algos/list/MPU.js b/lib/algos/list/MPU.js index 1db7166f1..4fbd1a2f5 100644 --- a/lib/algos/list/MPU.js +++ b/lib/algos/list/MPU.js @@ -1,6 +1,6 @@ 'use strict'; // eslint-disable-line strict -const { inc, checkLimit, FILTER_END, FILTER_ACCEPT } = require('./tools'); +const { inc, checkLimit, FILTER_END, FILTER_ACCEPT, UNICODE_MAX } = require('./tools'); const DEFAULT_MAX_KEYS = 1000; function numberDefault(num, defaultNum) { @@ -53,6 +53,8 @@ class MultipartUploads { params.gte = this.params.prefix; } params.lt = inc(this.params.prefix); + } else { + params.lte = UNICODE_MAX; } return params; } diff --git a/lib/algos/list/delimiter.js b/lib/algos/list/delimiter.js index 97efa202e..80461ca55 100644 --- a/lib/algos/list/delimiter.js +++ b/lib/algos/list/delimiter.js @@ -1,7 +1,7 @@ 'use strict'; // eslint-disable-line strict const Extension = require('./Extension').default; -const { inc, FILTER_END, FILTER_ACCEPT, FILTER_SKIP } = require('./tools'); +const { inc, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, UNICODE_MAX } = require('./tools'); /** * Find the next delimiter in the path @@ -106,6 +106,8 @@ class Delimiter extends Extension { if (this.prefix) { params.gte = this.prefix; params.lt = inc(this.prefix); + } else { + params.lte = UNICODE_MAX; } const startVal = this[this.continueMarker] || this[this.startMarker]; if (startVal) { diff --git a/lib/algos/list/delimiterVersions.js b/lib/algos/list/delimiterVersions.js index e049e9f4c..f6972322f 100644 --- a/lib/algos/list/delimiterVersions.js +++ b/lib/algos/list/delimiterVersions.js @@ -3,7 +3,7 @@ const Delimiter = require('./delimiter').Delimiter; const Version = require('../../versioning/Version').Version; const VSConst = require('../../versioning/constants').VersioningConstants; -const { inc, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = +const { inc, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE, UNICODE_MAX } = require('./tools'); const VID_SEP = VSConst.VersionId.Separator; @@ -43,6 +43,8 @@ class DelimiterVersions extends Delimiter { if (this.parameters.prefix) { params.gte = this.parameters.prefix; params.lt = inc(this.parameters.prefix); + } else { + params.lte = UNICODE_MAX; } if (this.parameters.keyMarker) { if (params.gte && params.gte > this.parameters.keyMarker) { diff --git a/lib/algos/list/tools.js b/lib/algos/list/tools.js index 9239a6d55..d3503543e 100644 --- a/lib/algos/list/tools.js +++ b/lib/algos/list/tools.js @@ -3,6 +3,7 @@ const SKIP_NONE = undefined; // to be inline with the values of NextMarker const FILTER_ACCEPT = 1; const FILTER_SKIP = 0; const FILTER_END = -1; +const UNICODE_MAX = String.fromCodePoint(0x10FFFF); /** * This function check if number is valid @@ -38,4 +39,5 @@ module.exports = { FILTER_END, FILTER_SKIP, FILTER_ACCEPT, + UNICODE_MAX }; From 769d184842f53a913b9544d7df0e9151ef1909d8 Mon Sep 17 00:00:00 2001 From: Pepijn Van Eeckhoudt Date: Tue, 14 Apr 2020 20:36:20 +0200 Subject: [PATCH 2/3] Use UTF-8 lexicographic order when filtering out of order keys The default String comparison operators compare Strings using UTF-16 binary ordering. Using this ordering codepoints in the BMP region after the UTF-16 surrogate pair region are incorrectly considered to be greater than codepoints outside the BMP. Note that the default String <= is still used under the assumption that this type of keys will be rare and the <= will be faster than utf8Compare. This way the extra cost of utf8Compare only applies in the rare cases where these ordering issues do occur. Signed-off-by: Pepijn Van Eeckhoudt --- lib/algos/list/MPU.js | 4 +- lib/algos/list/delimiter.js | 7 +-- lib/algos/list/delimiterMaster.js | 7 +-- lib/algos/list/tools.js | 37 +++++++++++++ tests/unit/algos/list/delimiter.js | 87 +++++++++++++++++++++--------- 5 files changed, 108 insertions(+), 34 deletions(-) diff --git a/lib/algos/list/MPU.js b/lib/algos/list/MPU.js index 4fbd1a2f5..4a475c018 100644 --- a/lib/algos/list/MPU.js +++ b/lib/algos/list/MPU.js @@ -1,6 +1,6 @@ 'use strict'; // eslint-disable-line strict -const { inc, checkLimit, FILTER_END, FILTER_ACCEPT, UNICODE_MAX } = require('./tools'); +const { inc, checkLimit, utf8Compare, FILTER_END, FILTER_ACCEPT, UNICODE_MAX } = require('./tools'); const DEFAULT_MAX_KEYS = 1000; function numberDefault(num, defaultNum) { @@ -48,7 +48,7 @@ class MultipartUploads { params.gt = inc(params.gt); } if (this.params.prefix) { - if (params.gt === undefined || this.params.prefix > params.gt) { + if (params.gt === undefined || utf8Compare(this.params.prefix, params.gt) > 0) { delete params.gt; params.gte = this.params.prefix; } diff --git a/lib/algos/list/delimiter.js b/lib/algos/list/delimiter.js index 80461ca55..32cb0bb60 100644 --- a/lib/algos/list/delimiter.js +++ b/lib/algos/list/delimiter.js @@ -1,7 +1,7 @@ 'use strict'; // eslint-disable-line strict const Extension = require('./Extension').default; -const { inc, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, UNICODE_MAX } = require('./tools'); +const { inc, utf8Compare, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, UNICODE_MAX } = require('./tools'); /** * Find the next delimiter in the path @@ -111,7 +111,7 @@ class Delimiter extends Extension { } const startVal = this[this.continueMarker] || this[this.startMarker]; if (startVal) { - if (params.gte && params.gte > startVal) { + if (params.gte && utf8Compare(params.gte, startVal) > 0) { return params; } delete params.gte; @@ -169,7 +169,8 @@ class Delimiter extends Extension { if ((this.prefix && !key.startsWith(this.prefix)) || (this.alphabeticalOrder && typeof this[this.nextContinueMarker] === 'string' - && key <= this[this.nextContinueMarker])) { + && key <= this[this.nextContinueMarker] + && utf8Compare(key, this[this.nextContinueMarker]) <= 0)) { return FILTER_SKIP; } if (this.delimiter) { diff --git a/lib/algos/list/delimiterMaster.js b/lib/algos/list/delimiterMaster.js index fbeb8ccb5..42ff43789 100644 --- a/lib/algos/list/delimiterMaster.js +++ b/lib/algos/list/delimiterMaster.js @@ -3,7 +3,7 @@ const Delimiter = require('./delimiter').Delimiter; const Version = require('../../versioning/Version').Version; const VSConst = require('../../versioning/constants').VersioningConstants; -const { FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = require('./tools'); +const { utf8Compare, FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = require('./tools'); const VID_SEP = VSConst.VersionId.Separator; @@ -49,8 +49,9 @@ class DelimiterMaster extends Delimiter { /* Skip keys not starting with the prefix or not alphabetically * ordered. */ if ((this.prefix && !key.startsWith(this.prefix)) - || (typeof this[this.nextContinueMarker] === 'string' && - key <= this[this.nextContinueMarker])) { + || (typeof this[this.nextContinueMarker] === 'string' + && key <= this[this.nextContinueMarker] + && utf8Compare(key, this[this.nextContinueMarker]) <= 0)) { return FILTER_SKIP; } diff --git a/lib/algos/list/tools.js b/lib/algos/list/tools.js index d3503543e..6e2c1bbe6 100644 --- a/lib/algos/list/tools.js +++ b/lib/algos/list/tools.js @@ -32,9 +32,46 @@ function inc(str) { String.fromCharCode(str.charCodeAt(str.length - 1) + 1)) : str; } +/* + * Compares two Strings, s1 and s2, using UTF-8 lexicographic ordering. + * @function + * @param {String} s1 the first string to compare + * @param {String} s2 the second string to compare + * @return {number} -1, 0, or 1 if s1 is less than, equal or greater than s2 respectively + */ +function utf8Compare(s1, s2) { + const l1 = s1.length; + const l2 = s2.length; + const l = Math.min(l1, l2) + + for (let i = 0; i < l; i++) { + let cp1 = s1.codePointAt(i); + let cp2 = s2.codePointAt(i); + + if (cp1 < cp2) { + return -1; + } else if (cp1 > cp2) { + return 1; + } + + if (cp1 > 0xFFFF) { + i++; + } + } + + if (l1 < l2) { + return -1; + } else if (l1 > l2) { + return 1; + } else { + return 0; + } +} + module.exports = { checkLimit, inc, + utf8Compare, SKIP_NONE, FILTER_END, FILTER_SKIP, diff --git a/tests/unit/algos/list/delimiter.js b/tests/unit/algos/list/delimiter.js index 382b360f7..d0f7392c5 100644 --- a/tests/unit/algos/list/delimiter.js +++ b/tests/unit/algos/list/delimiter.js @@ -5,6 +5,7 @@ const Delimiter = require('../../../../lib/algos/list/delimiter').Delimiter; const DelimiterMaster = require('../../../../lib/algos/list/delimiterMaster').DelimiterMaster; +const { utf8Compare } = require('../../../../lib/algos/list/tools'); const Werelogs = require('werelogs').Logger; const logger = new Werelogs('listTest'); const performListing = require('../../../utils/performListing'); @@ -37,6 +38,11 @@ const data = [ { key: 'notes/year.txt', value }, { key: 'notes/yore.rs', value }, { key: 'notes/zaphod/Beeblebrox.txt', value }, + { key: 'utf8order/a', value }, + { key: 'utf8order/\uD7FB', value }, + { key: 'utf8order/\uFB80', value }, + { key: 'utf8order/\uD83D\uDC4F', value }, + { key: 'utf8order/\uD83D\uDC50', value }, ]; const dataVersioned = [ { key: 'Pâtisserie=中文-español-English', value }, @@ -72,6 +78,11 @@ const dataVersioned = [ { key: 'notes/year.txt', value }, { key: 'notes/yore.rs', value }, { key: 'notes/zaphod/Beeblebrox.txt', value }, + { key: 'utf8order/a', value }, + { key: 'utf8order/\uD7FB', value }, + { key: 'utf8order/\uFB80', value }, + { key: 'utf8order/\uD83D\uDC4F', value }, + { key: 'utf8order/\uD83D\uDC50', value }, ]; const nonAlphabeticalData = [ { key: 'zzz', value }, @@ -83,6 +94,10 @@ const receivedNonAlphaData = nonAlphabeticalData.map( item => ({ key: item.key, value: item.value }) ); +function createUtf8GreaterThanFilter(startAfter) { + return (e) => utf8Compare(e.key, startAfter) > 0; +} + const tests = [ new Test('all elements', {}, { Contents: receivedData, @@ -100,12 +115,17 @@ const tests = [ receivedData[7], receivedData[8], receivedData[9], + receivedData[10], + receivedData[11], + receivedData[12], + receivedData[13], + receivedData[14], ], CommonPrefixes: [], Delimiter: undefined, IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter(receivedData[4].key)), new Test('with bad marker', { marker: 'zzzz', delimiter: '/', @@ -115,7 +135,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('zzzz')), new Test('with makKeys', { maxKeys: 3, }, { @@ -140,7 +160,7 @@ const tests = [ Contents: [ receivedData[0], ], - CommonPrefixes: ['notes/'], + CommonPrefixes: ['notes/', 'utf8order/'], Delimiter: '/', IsTruncated: false, NextMarker: undefined, @@ -156,6 +176,11 @@ const tests = [ receivedData[7], receivedData[8], receivedData[9], + receivedData[10], + receivedData[11], + receivedData[12], + receivedData[13], + receivedData[14], ], CommonPrefixes: ['notes/summer'], Delimiter: 'notes/summer', @@ -172,7 +197,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/summer0')), new Test('delimiter and prefix (related to #147)', { delimiter: '/', prefix: 'notes/', @@ -204,7 +229,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/year.txt')), new Test('all parameters 1/3', { delimiter: '/', prefix: 'notes/', @@ -216,7 +241,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextMarker: 'notes/spring/', - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/')), new Test('all parameters 2/3', { delimiter: '/', @@ -229,7 +254,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextMarker: 'notes/summer/', - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/spring/')), new Test('all parameters 3/3', { delimiter: '/', @@ -244,7 +269,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextMarker: 'notes/year.txt', - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/summer/')), new Test('all parameters 4/3', { delimiter: '/', @@ -259,7 +284,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextMarker: 'notes/yore.rs', - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/year.txt')), new Test('all parameters 5/3', { delimiter: '/', @@ -272,7 +297,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.marker), + }, createUtf8GreaterThanFilter('notes/yore.rs')), new Test('all elements v2', { v2: true, @@ -293,12 +318,17 @@ const tests = [ receivedData[7], receivedData[8], receivedData[9], + receivedData[10], + receivedData[11], + receivedData[12], + receivedData[13], + receivedData[14], ], CommonPrefixes: [], Delimiter: undefined, IsTruncated: false, NextContinuationToken: undefined, - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter(receivedData[4].key)), new Test('with bad startAfter', { startAfter: 'zzzz', delimiter: '/', @@ -309,7 +339,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextContinuationToken: undefined, - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter('zzzz')), new Test('with valid continuationToken', { continuationToken: receivedData[4].key, v2: true, @@ -320,12 +350,17 @@ const tests = [ receivedData[7], receivedData[8], receivedData[9], + receivedData[10], + receivedData[11], + receivedData[12], + receivedData[13], + receivedData[14], ], CommonPrefixes: [], Delimiter: undefined, IsTruncated: false, NextContinuationToken: undefined, - }, (e, input) => e.key > input.continuationToken), + }, createUtf8GreaterThanFilter(receivedData[4].key)), new Test('with bad continuationToken', { continuationToken: 'zzzz', delimiter: '/', @@ -336,7 +371,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextContinuationToken: undefined, - }, (e, input) => e.key > input.continuationToken), + }, createUtf8GreaterThanFilter('zzzz')), new Test('bad startAfter and good prefix', { delimiter: '/', prefix: 'notes/summer/', @@ -347,7 +382,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter('notes/summer0')), new Test('bad continuation token and good prefix', { delimiter: '/', prefix: 'notes/summer/', @@ -358,7 +393,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextMarker: undefined, - }, (e, input) => e.key > input.continuationToken), + }, createUtf8GreaterThanFilter('notes/summer0')), new Test('no delimiter v2', { startAfter: 'notes/year.txt', @@ -372,7 +407,7 @@ const tests = [ Delimiter: undefined, IsTruncated: true, NextContinuationToken: 'notes/yore.rs', - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter('notes/year.txt')), new Test('all parameters v2 1/6', { delimiter: '/', @@ -386,7 +421,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextContinuationToken: 'notes/spring/', - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter('notes/')), new Test('all parameters v2 2/6', { delimiter: '/', @@ -400,7 +435,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextContinuationToken: 'notes/summer/', - }, (e, input) => e.key > input.continuationToken), + }, createUtf8GreaterThanFilter('notes/spring/')), new Test('all parameters v2 3/5', { delimiter: '/', @@ -416,7 +451,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextContinuationToken: 'notes/year.txt', - }, (e, input) => e.key > input.continuationToken), + }, createUtf8GreaterThanFilter('notes/summer/')), new Test('all parameters v2 4/5', { delimiter: '/', @@ -432,7 +467,7 @@ const tests = [ Delimiter: '/', IsTruncated: true, NextContinuationToken: 'notes/yore.rs', - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter('notes/year.txt')), new Test('all parameters v2 5/5', { delimiter: '/', @@ -446,7 +481,7 @@ const tests = [ Delimiter: '/', IsTruncated: false, NextContinuationToken: undefined, - }, (e, input) => e.key > input.startAfter), + }, createUtf8GreaterThanFilter('notes/yore.rs')), ]; @@ -496,7 +531,7 @@ describe('Delimiter listing algorithm', () => { tests.forEach(test => { it(`Should list ${test.name}`, done => { // Simulate skip scan done by LevelDB - const d = data.filter(e => test.filter(e, test.input)); + const d = data.filter(e => test.filter(e)); const res = performListing(d, Delimiter, test.input, logger); assert.deepStrictEqual(res, test.output); done(); @@ -506,7 +541,7 @@ describe('Delimiter listing algorithm', () => { tests.forEach(test => { it(`Should list master versions ${test.name}`, done => { // Simulate skip scan done by LevelDB - const d = dataVersioned.filter(e => test.filter(e, test.input)); + const d = dataVersioned.filter(e => test.filter(e)); const res = performListing(d, DelimiterMaster, test.input, logger); assert.deepStrictEqual(res, test.output); done(); @@ -527,7 +562,7 @@ describe('Delimiter listing algorithm', () => { IsTruncated: false, NextMarker: undefined, }); - let d = nonAlphabeticalData.filter(e => test.filter(e, test.input)); + let d = nonAlphabeticalData.filter(e => test.filter(e)); let res = performListing(d, Delimiter, test.input, logger); assert.deepStrictEqual(res, test.output); @@ -544,7 +579,7 @@ describe('Delimiter listing algorithm', () => { IsTruncated: false, NextMarker: undefined, }); - d = nonAlphabeticalData.filter(e => test.filter(e, test.input)); + d = nonAlphabeticalData.filter(e => test.filter(e)); res = performListing(d, Delimiter, test.input, logger); assert.deepStrictEqual(res, test.output); }); From 5924b0af40871a93f3314b09e0cf99237d7781f6 Mon Sep 17 00:00:00 2001 From: Pepijn Van Eeckhoudt Date: Thu, 16 Apr 2020 09:04:37 +0200 Subject: [PATCH 3/3] Fix eslint errors Signed-off-by: Pepijn Van Eeckhoudt --- lib/algos/list/MPU.js | 8 ++++++-- lib/algos/list/delimiter.js | 5 ++++- lib/algos/list/delimiterMaster.js | 5 ++++- lib/algos/list/tools.js | 19 +++++++------------ tests/unit/algos/list/delimiter.js | 2 +- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/algos/list/MPU.js b/lib/algos/list/MPU.js index 4a475c018..3469155e8 100644 --- a/lib/algos/list/MPU.js +++ b/lib/algos/list/MPU.js @@ -1,6 +1,9 @@ 'use strict'; // eslint-disable-line strict -const { inc, checkLimit, utf8Compare, FILTER_END, FILTER_ACCEPT, UNICODE_MAX } = require('./tools'); +const { + inc, checkLimit, utf8Compare, + FILTER_END, FILTER_ACCEPT, UNICODE_MAX, +} = require('./tools'); const DEFAULT_MAX_KEYS = 1000; function numberDefault(num, defaultNum) { @@ -48,7 +51,8 @@ class MultipartUploads { params.gt = inc(params.gt); } if (this.params.prefix) { - if (params.gt === undefined || utf8Compare(this.params.prefix, params.gt) > 0) { + if (params.gt === undefined + || utf8Compare(this.params.prefix, params.gt) > 0) { delete params.gt; params.gte = this.params.prefix; } diff --git a/lib/algos/list/delimiter.js b/lib/algos/list/delimiter.js index 32cb0bb60..6545657b3 100644 --- a/lib/algos/list/delimiter.js +++ b/lib/algos/list/delimiter.js @@ -1,7 +1,10 @@ 'use strict'; // eslint-disable-line strict const Extension = require('./Extension').default; -const { inc, utf8Compare, FILTER_END, FILTER_ACCEPT, FILTER_SKIP, UNICODE_MAX } = require('./tools'); +const { + inc, utf8Compare, + FILTER_END, FILTER_ACCEPT, FILTER_SKIP, UNICODE_MAX, +} = require('./tools'); /** * Find the next delimiter in the path diff --git a/lib/algos/list/delimiterMaster.js b/lib/algos/list/delimiterMaster.js index 42ff43789..30920c362 100644 --- a/lib/algos/list/delimiterMaster.js +++ b/lib/algos/list/delimiterMaster.js @@ -3,7 +3,10 @@ const Delimiter = require('./delimiter').Delimiter; const Version = require('../../versioning/Version').Version; const VSConst = require('../../versioning/constants').VersioningConstants; -const { utf8Compare, FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE } = require('./tools'); +const { + utf8Compare, + FILTER_ACCEPT, FILTER_SKIP, SKIP_NONE, +} = require('./tools'); const VID_SEP = VSConst.VersionId.Separator; diff --git a/lib/algos/list/tools.js b/lib/algos/list/tools.js index 6e2c1bbe6..21cfa2524 100644 --- a/lib/algos/list/tools.js +++ b/lib/algos/list/tools.js @@ -37,16 +37,17 @@ function inc(str) { * @function * @param {String} s1 the first string to compare * @param {String} s2 the second string to compare - * @return {number} -1, 0, or 1 if s1 is less than, equal or greater than s2 respectively + * @return {number} -1, 0, or 1 if s1 is less than, equal or greater than s2 + * respectively */ function utf8Compare(s1, s2) { const l1 = s1.length; const l2 = s2.length; - const l = Math.min(l1, l2) + const l = Math.min(l1, l2); for (let i = 0; i < l; i++) { - let cp1 = s1.codePointAt(i); - let cp2 = s2.codePointAt(i); + const cp1 = s1.codePointAt(i); + const cp2 = s2.codePointAt(i); if (cp1 < cp2) { return -1; @@ -59,13 +60,7 @@ function utf8Compare(s1, s2) { } } - if (l1 < l2) { - return -1; - } else if (l1 > l2) { - return 1; - } else { - return 0; - } + return Math.sign(l1 - l2); } module.exports = { @@ -76,5 +71,5 @@ module.exports = { FILTER_END, FILTER_SKIP, FILTER_ACCEPT, - UNICODE_MAX + UNICODE_MAX, }; diff --git a/tests/unit/algos/list/delimiter.js b/tests/unit/algos/list/delimiter.js index d0f7392c5..84463f569 100644 --- a/tests/unit/algos/list/delimiter.js +++ b/tests/unit/algos/list/delimiter.js @@ -95,7 +95,7 @@ const receivedNonAlphaData = nonAlphabeticalData.map( ); function createUtf8GreaterThanFilter(startAfter) { - return (e) => utf8Compare(e.key, startAfter) > 0; + return e => utf8Compare(e.key, startAfter) > 0; } const tests = [