Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions classes/range.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class Range {
debug('hyphen replace', range)

// `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace)
range = trimComparatorWhitespace(range)
debug('comparator trim', range)

// `~ 1.2.3` => `~1.2.3`
Expand Down Expand Up @@ -228,7 +228,6 @@ const {
safeRe: re,
src,
t,
comparatorTrimReplace,
tildeTrimReplace,
caretTrimReplace,
} = require('../internal/re')
Expand All @@ -237,6 +236,77 @@ const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants')
// unbounded global build-metadata stripper used by parseRange
const BUILDSTRIPRE = new RegExp(src[t.BUILD], 'g')

const isVersionPrefix = char =>
char === 'v' ||
char === '=' ||
char === ' '

const isVersionStart = char =>
(char >= '0' && char <= '9') ||
char === 'x' ||
char === 'X' ||
char === '*'

// Normalize comparator whitespace without running the unanchored
// COMPARATORTRIM regex against the full range.
const trimComparatorWhitespace = range => {
const removals = []

for (let versionStart = 0; versionStart < range.length; versionStart++) {
if (!isVersionStart(range[versionStart])) {
continue
}

let prefixStart = versionStart
while (prefixStart > 0 && isVersionPrefix(range[prefixStart - 1])) {
prefixStart--
}

let operatorEnd
if (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format: Any particular reason why we're using this formatting? It's really hard to read.

prefixStart > 0 &&
(range[prefixStart - 1] === '<' || range[prefixStart - 1] === '>')
) {
operatorEnd = prefixStart
if (range[operatorEnd] === '=') {
operatorEnd++
}
} else {
operatorEnd = prefixStart
if (range[operatorEnd] === ' ') {
operatorEnd++
}
if (range[operatorEnd] !== '=') {
if (
range[prefixStart] !== ' ' ||
range[prefixStart + 1] !== ' '
) {
continue
}
operatorEnd = prefixStart + 1
} else {
operatorEnd++
}
}

if (range[operatorEnd] === ' ') {
removals.push([operatorEnd, operatorEnd + 1])
}
}

if (!removals.length) {
return range
}

let result = ''
let position = 0
for (const [start, end] of removals) {
result += range.slice(position, start)
position = end
}
return result + range.slice(position)
}

const isNullSet = c => c.value === '<0.0.0-0'
const isAny = c => c.value === ''

Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/range-parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ module.exports = [
['<= 2.0.0', '<=2.0.0'],
['< 2.0.0', '<2.0.0'],
['<\t2.0.0', '<2.0.0'],
['1.2.3 = 1.2.3', '1.2.3'],
['== 1', null],
['>v= 1.2.3', '1.2.3', { loose: true }],
['> +foo 1.2.3', null],
['> +foo 1.2.3', '1.2.3', { loose: true }],
['1 +a +b = 2', null],
['~ +build 1', '>=1.0.0 <2.0.0-0'],
['~> +build 1', '>=1.0.0 <2.0.0-0'],
['^ +build 1', '>=1.0.0 <2.0.0-0'],
['>=0.1.97', '>=0.1.97'],
['0.1.20 || 1.2.4', '0.1.20||1.2.4'],
['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1'],
Expand All @@ -41,10 +50,16 @@ module.exports = [
['1.x.5', null],
['1.*.5', null],
['1.x.5 || 2.x', null],
['> invalid', null],
['x.1', null],
['x.1.2', null],
['x.x.1', null],
['x', '*'],
['vvv1', '>=1.0.0 <2.0.0-0'],
['>= vvv1', '>=1.0.0'],
['vvv1.2.3', '1.2.3', { loose: true }],
['===1.2.3', '1.2.3', { loose: true }],
['v=1.2.3', '1.2.3', { loose: true }],
['2.*.*', '>=2.0.0 <3.0.0-0'],
['1.2.*', '>=1.2.0 <1.3.0-0'],
['1.2.* || 2.*', '>=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0'],
Expand Down
21 changes: 21 additions & 0 deletions test/integration/whitespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ const validRange = require('../../ranges/valid')
const minVersion = require('../../ranges/min-version')
const minSatisfying = require('../../ranges/min-satisfying')
const maxSatisfying = require('../../ranges/max-satisfying')
const satisfies = require('../../functions/satisfies')

const wsMedium = ' '.repeat(125)
const wsLarge = ' '.repeat(500000)
const zeroLarge = '0'.repeat(500000)
const versionPrefixLarge = 'v'.repeat(500000)

test('range with whitespace', (t) => {
// a range with these extra characters would take a few minutes to process if
Expand All @@ -23,6 +25,7 @@ test('range with whitespace', (t) => {
t.equal(minVersion(r).version, '1.2.3')
t.equal(minSatisfying(['1.2.3'], r), '1.2.3')
t.equal(maxSatisfying(['1.2.3'], r), '1.2.3')
t.throws(() => new Range('> invalid'))
t.end()
})

Expand All @@ -36,6 +39,24 @@ test('range with 0', (t) => {
t.end()
})

test('range with repeated version prefix', (t) => {
t.throws(() => new Range(versionPrefixLarge))
t.equal(validRange(versionPrefixLarge), null)
t.equal(satisfies('1.2.3', versionPrefixLarge), false)
t.throws(() => minVersion(versionPrefixLarge))
t.equal(minSatisfying(['1.2.3'], versionPrefixLarge), null)
t.equal(maxSatisfying(['1.2.3'], versionPrefixLarge), null)

const r = `>= ${versionPrefixLarge}1`
t.equal(new Range(r).range, '>=1.0.0')
t.equal(validRange(r), '>=1.0.0')
t.equal(satisfies('1.2.3', r), true)
t.equal(minVersion(r).version, '1.0.0')
t.equal(minSatisfying(['1.2.3'], r), '1.2.3')
t.equal(maxSatisfying(['1.2.3'], r), '1.2.3')
t.end()
})

test('semver version', (t) => {
const v = `${wsMedium}1.2.3${wsMedium}`
const tooLong = `${wsLarge}1.2.3${wsLarge}`
Expand Down
Loading