From 4ed704b01ba3fd5f613bc5fca0ff27bc3500894f Mon Sep 17 00:00:00 2001 From: Jon Schlinkert Date: Wed, 3 Apr 2019 11:09:52 -0400 Subject: [PATCH] add unit tests --- appveyor.yml | 25 -- bench/index.js | 20 +- index.js | 43 +--- lib/compile.js | 5 +- lib/expand.js | 58 ++++- lib/parse.js | 78 +++++-- lib/utils.js | 26 ++- package.json | 3 +- test/bash-compiled-ranges.js | 10 +- test/bash-compiled-sets.js | 4 +- test/bash-expanded-ranges.js | 420 ++++++++++++++++++++++----------- test/bash-spec.js | 197 ++++++++++++++++ test/braces.compile.js | 18 +- test/braces.expand.js | 27 ++- test/braces.parse.js | 7 + test/minimatch.js | 28 +++ test/multiples.js | 58 +++++ test/regression.js | 437 +++++++++++++++++++++++++++++++++++ 18 files changed, 1207 insertions(+), 257 deletions(-) delete mode 100644 appveyor.yml create mode 100644 test/bash-spec.js create mode 100644 test/minimatch.js create mode 100644 test/multiples.js create mode 100644 test/regression.js diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 221660f..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Test against this version of Node.js -environment: - matrix: - # node.js - - nodejs_version: "11" - - nodejs_version: "10" - - nodejs_version: "8" - -# Install scripts. (runs after repo cloning) -install: - # Get the latest stable version of Node.js or io.js - - ps: Install-Product node $env:nodejs_version - # install modules - - npm install - -# Post-install test scripts. -test_script: - # Output useful info for debugging. - - node --version - - npm --version - # run tests - - npm test - -# Don't actually build. -build: off diff --git a/bench/index.js b/bench/index.js index f3c8c3f..e4b47bf 100644 --- a/bench/index.js +++ b/bench/index.js @@ -51,21 +51,21 @@ bench.skip = name => { }; bench('expand - set') - .add(' braces', () => braces.expand('foo/{a,b,c}/bar')) + .add(' braces', () => braces.compile('foo/{a,b,c}/bar')) .add('minimatch', () => minimatch.braceExpand('foo/{a,b,c}/bar')) .run(); -bench('expand - nested sets') - .add(' braces', () => braces.expand('foo/{a,b,{x,y,z}}/bar')) - .add('minimatch', () => minimatch.braceExpand('foo/{a,b,{x,y,z}}/bar')) - .run(); - bench('expand - range') - .add(' braces', () => braces.expand('foo/{a..z}/bar')) + .add(' braces', () => braces.compile('foo/{a..z}/bar')) .add('minimatch', () => minimatch.braceExpand('foo/{a..z}/bar')) .run(); -bench('compile regex - set') - .add(' braces', () => braces.makeRe(parse('foo/{a,b,c}/bar'))) - .add('minimatch', () => minimatch.makeRe('foo/{a,b,c}/bar')) +bench('expand - nested sets') + .add(' braces', () => braces.compile('foo/{a,b,{x,y,z}}/bar')) + .add('minimatch', () => minimatch.braceExpand('foo/{a,b,{x,y,z}}/bar')) + .run(); + +bench('expand - nested ranges') + .add(' braces', () => braces.compile('foo/{a,b,{1..25}}/bar')) + .add('minimatch', () => minimatch.braceExpand('foo/{a,b,{1..25}}/bar')) .run(); diff --git a/index.js b/index.js index a3bf6bc..9a05d47 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,9 @@ 'use strict'; -const { MAX_LENGTH } = require('./lib/constants'); const stringify = require('./lib/stringify'); const compile = require('./lib/compile'); const expand = require('./lib/expand'); const parse = require('./lib/parse'); -const toRegex = require('to-regex'); /** * Expand the given pattern or create a regex-compatible string. @@ -53,19 +51,7 @@ const braces = (input, options = {}) => { * @api public */ -braces.parse = (input, options = {}) => { - if (typeof input !== 'string') { - throw new TypeError('Expected a string'); - } - - let opts = options || {}; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - if (input.length > max) { - throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); - } - - return parse(input, options); -}; +braces.parse = (input, options = {}) => parse(input, options); /** * Creates a braces string from an AST, or an AST node. @@ -129,9 +115,16 @@ braces.compile = (input, options = {}) => { braces.expand = (input, options = {}) => { if (typeof input === 'string') { - return expand(braces.parse(input, options), options); + input = braces.parse(input, options); + } + + let result = expand(input, options); + + // filter out duplicates if specified + if (options.nodupes === true) { + result = [...new Set(result)]; } - return expand(input, options); + return result; }; /** @@ -173,21 +166,7 @@ braces.create = (input, options = {}) => { }; /** - * Create a regular expression from the given string `pattern`. - * - * ```js - * const braces = require('braces'); - * console.log(braces.makeRe('foo/id-{200..300}')); - * //=> /^(?:foo/id-(20[0-9]|2[1-9][0-9]|300))$/ - * ``` - * @param {String} `pattern` The pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} - * @api public + * Expose "braces" */ -braces.makeRe = (pattern, options) => { - return toRegex(braces.compile(pattern, options), { strictErrors: false, ...options }); -}; - module.exports = braces; diff --git a/lib/compile.js b/lib/compile.js index 885868b..3e984a4 100644 --- a/lib/compile.js +++ b/lib/compile.js @@ -37,6 +37,7 @@ const compile = (ast, options = {}) => { if (node.nodes && node.ranges > 0) { let args = utils.reduce(node.nodes); let range = fill(...args, { ...options, wrap: false, toRegex: true }); + if (range.length !== 0) { return args.length > 1 && range.length > 1 ? `(${range})` : range; } @@ -54,7 +55,3 @@ const compile = (ast, options = {}) => { }; module.exports = compile; - -const parse = require('./parse'); -console.log(compile(parse('{a,,,,}'), { escapeInvalid: true })); -console.log('(a|)'); diff --git a/lib/expand.js b/lib/expand.js index 088ee43..376c748 100644 --- a/lib/expand.js +++ b/lib/expand.js @@ -1,5 +1,6 @@ 'use strict'; +const fill = require('fill-range'); const stringify = require('./stringify'); const utils = require('./utils'); @@ -30,38 +31,71 @@ const append = (queue = '', stash = '', enclose = false) => { }; const expand = (ast, options = {}) => { + let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; + let walk = (node, parent = {}) => { node.queue = []; + let p = parent; + let q = parent.queue; + + while (p.type !== 'brace' && p.type !== 'root' && p.parent) { + p = p.parent; + q = p.queue; + } + if (node.invalid || node.dollar) { - parent.queue.push(append(parent.queue.pop(), stringify(node, options))); + q.push(append(q.pop(), stringify(node, options))); return; } if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { - parent.queue.push(append(parent.queue.pop(), ['{}'])); + q.push(append(q.pop(), ['{}'])); + return; + } + + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + + if (utils.exceedsLimit(...args, options.step, rangeLimit)) { + throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); + } + + let range = fill(...args, options); + if (range.length === 0) { + range = stringify(node, options); + } + + q.push(append(q.pop(), range)); + node.nodes = []; return; } let enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; + + while (block.type !== 'brace' && block.type !== 'root' && block.parent) { + block = block.parent; + queue = block.queue; + } + for (let i = 0; i < node.nodes.length; i++) { let child = node.nodes[i]; - if (child.type === 'comma') { - node.queue.push(''); - if (i === 1) { - node.queue.push(''); - } + if (child.type === 'comma' && node.type === 'brace') { + if (i === 1) queue.push(''); + queue.push(''); continue; } - if (child.type === 'text') { - node.queue.push(append(node.queue.pop(), child.value)); + if (child.type === 'close') { + q.push(append(q.pop(), queue, enclose)); continue; } - if (child.type === 'close') { - parent.queue.push(append(parent.queue.pop(), node.queue, enclose)); + if (child.value && child.type !== 'open') { + queue.push(append(queue.pop(), child.value)); continue; } @@ -70,7 +104,7 @@ const expand = (ast, options = {}) => { } } - return node.queue; + return queue; }; return utils.flatten(walk(ast)); diff --git a/lib/parse.js b/lib/parse.js index e9d1037..d7d135e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,14 +1,19 @@ 'use strict'; +const stringify = require('./stringify'); + /** * Constants */ const { + MAX_LENGTH, CHAR_BACKSLASH, /* \ */ CHAR_BACKTICK, /* ` */ CHAR_COMMA, /* , */ CHAR_DOT, /* . */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_RIGHT_PARENTHESES, /* ) */ CHAR_LEFT_CURLY_BRACE, /* { */ CHAR_RIGHT_CURLY_BRACE, /* } */ CHAR_LEFT_SQUARE_BRACKET, /* [ */ @@ -24,14 +29,25 @@ const { */ const parse = (input, options = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + let opts = options || {}; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); + } + let ast = { type: 'root', input, nodes: [] }; let stack = [ast]; - let length = input.length; let block = ast; let prev = ast; + let length = input.length; let index = 0; let depth = 0; let value; + let memo = {}; /** * Helpers @@ -49,6 +65,7 @@ const parse = (input, options = {}) => { } block.nodes.push(node); + node.parent = block; node.prev = prev; prev = node; return node; @@ -77,6 +94,15 @@ const parse = (input, options = {}) => { continue; } + /** + * Right square bracket (literal): ']' + */ + + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: 'text', value: '\\' + value }); + continue; + } + /** * Left square bracket: '[' */ @@ -108,7 +134,29 @@ const parse = (input, options = {}) => { } /** - * Left square bracket: '[' + * Parentheses + */ + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: 'paren', nodes: [] }); + stack.push(block); + push({ type: 'text', value }); + continue; + } + + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== 'paren') { + push({ type: 'text', value }); + continue; + } + block = stack.pop(); + push({ type: 'text', value }); + block = stack[stack.length - 1]; + continue; + } + + /** + * Quotes: '|"|` */ if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { @@ -120,7 +168,6 @@ const parse = (input, options = {}) => { } while (index < length && (next = advance())) { - if (next === CHAR_BACKSLASH) { value += next + advance(); continue; @@ -138,15 +185,6 @@ const parse = (input, options = {}) => { continue; } - /** - * Right square bracket (literal): ']' - */ - - if (value === CHAR_RIGHT_SQUARE_BRACKET) { - push({ type: 'text', value: '\\' + value }); - continue; - } - /** * Left curly brace: '{' */ @@ -198,6 +236,12 @@ const parse = (input, options = {}) => { */ if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + let open = block.nodes.shift(); + block.nodes = [open, { type: 'text', value: stringify(block) }]; + } + push({ type: 'comma', value }); block.commas++; continue; @@ -207,7 +251,7 @@ const parse = (input, options = {}) => { * Dot: '.' */ - if (value === CHAR_DOT && depth > 0) { + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { let siblings = block.nodes; if (depth === 0 || siblings.length === 0) { @@ -256,17 +300,21 @@ const parse = (input, options = {}) => { // Mark imbalanced braces and brackets as invalid do { block = stack.pop(); + if (block.type !== 'root') { block.nodes.forEach(node => { if (!node.nodes) { - node.invalid = true; if (node.type === 'open') node.isOpen = true; if (node.type === 'close') node.isClose = true; - node.type = 'text'; + if (!node.nodes) node.type = 'text'; + node.invalid = true; } }); + + // get the location of the block on parent.nodes (block's siblings) let parent = stack[stack.length - 1]; let index = parent.nodes.indexOf(block); + // replace the (invalid) block with it's nodes parent.nodes.splice(index, 1, ...block.nodes); } } while (stack.length > 0); diff --git a/lib/utils.js b/lib/utils.js index cd866e9..e3551a6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,11 +1,31 @@ 'use strict'; +exports.isInteger = num => { + if (typeof num === 'number') { + return Number.isInteger(num); + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isInteger(Number(num)); + } + return false; +}; + /** * Find a node of the given type */ exports.find = (node, type) => node.nodes.find(node => node.type === type); +/** + * Find a node of the given type + */ + +exports.exceedsLimit = (min, max, step = 1, limit) => { + if (limit === false) return false; + if (!exports.isInteger(min) || !exports.isInteger(max)) return false; + return ((Number(max) - Number(min)) / Number(step)) >= limit; +}; + /** * Escape the given node with '\\' before node.value */ @@ -28,7 +48,10 @@ exports.escapeNode = (block, n = 0, type) => { exports.encloseBrace = node => { if (node.type !== 'brace') return false; - if (node.commas >> 0 + node.ranges >> 0 === 0) return true; + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; + return true; + } return false; }; @@ -67,6 +90,7 @@ exports.isOpenOrClose = node => { exports.reduce = nodes => nodes.reduce((acc, node) => { if (node.type === 'text') acc.push(node.value); + if (node.type === 'range') node.type = 'text'; return acc; }, []); diff --git a/package.json b/package.json index e8e8fe2..aeac709 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ } }, "dependencies": { - "fill-range": "^6.0.0", - "to-regex": "^3.0.2" + "fill-range": "^6.0.0" } } diff --git a/test/bash-compiled-ranges.js b/test/bash-compiled-ranges.js index 3f65f47..02ac3bc 100644 --- a/test/bash-compiled-ranges.js +++ b/test/bash-compiled-ranges.js @@ -21,7 +21,7 @@ const equal = (input, expected = bash(input), options) => { * Bash 4.3 unit tests with `braces.compile()` */ -describe('bash tests - braces.compile()', () => { +describe('bash ranges - braces.compile()', () => { const fixtures = [ ['a{b,c{1..100}/{foo,bar}/,h}x/z', {}, 'a(b|c([1-9]|[1-9][0-9]|100)/(foo|bar)/|h)x/z'], ['0{1..9} {10..20}', {}, '0([1-9]) (1[0-9]|20)'], @@ -171,15 +171,15 @@ describe('bash tests - braces.compile()', () => { ['**/{1..5}/a.js', {}, '**/([1-5])/a.js'], ['x{{0..10},braces}y', {}, 'x(([0-9]|10)|braces)y'], - // should handle invalid ranges - ['{0..10,braces}', {}, '{0..10,braces}'], - ['{1..10,braces}', {}, '{1..10,braces}'], + // should handle sets with invalid ranges + ['{0..10,braces}', {}, '(0..10|braces)'], + ['{1..10,braces}', {}, '(1..10|braces)'], ['./\\{x,y}/{a..z..3}/', {}, './{x,y}/(a|d|g|j|m|p|s|v|y)/'], ['{braces,{0..10}}', {}, '(braces|([0-9]|10))'], ['{{0..10},braces}', {}, '(([0-9]|10)|braces)'], ['{{1..10..2},braces}', { bash: false }, '((1|3|5|7|9)|braces)'], - ['{{1..10..2},braces}', {}, '((1|3|5|7|9)|braces)'], + ['{{1..10..2},braces}', {}, '((1|3|5|7|9)|braces)'] ]; fixtures.forEach(arr => { diff --git a/test/bash-compiled-sets.js b/test/bash-compiled-sets.js index b62c6d1..15bff41 100644 --- a/test/bash-compiled-sets.js +++ b/test/bash-compiled-sets.js @@ -21,7 +21,7 @@ const equal = (input, expected = bash(input), options) => { * Bash 4.3 unit tests with `braces.expand()` */ -describe('bash.optimized', () => { +describe('bash sets - braces.compile()', () => { const fixtures = [ ['{a,b,c,d,e}', {}, '(a|b|c|d|e)'], ['a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, 'a/{b,c,d,(x|y)}{e,f}/g'], @@ -160,7 +160,7 @@ describe('bash.optimized', () => { ['a{ ,c{d, },h} ', {}, 'a( |c(d| )|h) '], // see https://github.com/jonschlinkert/microequal/issues/66 - ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)'], + ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)'] ]; let seen = new Map(); diff --git a/test/bash-expanded-ranges.js b/test/bash-expanded-ranges.js index 0fafbaf..4524613 100644 --- a/test/bash-expanded-ranges.js +++ b/test/bash-expanded-ranges.js @@ -21,139 +21,297 @@ const equal = (input, expected = bash(input), options) => { * Bash 4.3 unit tests with `braces.expand()` */ -describe.skip('bash - expanded brace ranges', () => { - it.skip('should throw an error when range exceeds rangeLimit', () => { - assert.throws(() => { - braces.expand('{214748364..2147483649}'); +describe('bash - expanded brace ranges', () => { + describe('large numbers', () => { + it('should expand large numbers', () => { + equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); + }); + + it('should throw an error when range exceeds rangeLimit', () => { + assert.throws(() => braces.expand('{214748364..2147483649}')); + }); + }); + + describe('escaping / invalid ranges', () => { + it('should not try to expand ranges with decimals', () => { + equal('{1.1..2.1}', ['{1.1..2.1}']); + equal('{1.1..~2.1}', ['{1.1..~2.1}']); + }); + + it('should escape invalid ranges:', () => { + equal('{1..0f}', ['{1..0f}']); + equal('{1..10..ff}', ['{1..10..ff}']); + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..10f}', ['{1..10f}']); + equal('{1..20..2f}', ['{1..20..2f}']); + equal('{1..20..f2}', ['{1..20..f2}']); + equal('{1..2f..2}', ['{1..2f..2}']); + equal('{1..ff..2}', ['{1..ff..2}']); + equal('{1..ff}', ['{1..ff}']); + equal('{1.20..2}', ['{1.20..2}']); + }); + + it('weirdly-formed brace expansions -- fixed in post-bash-3.1', () => { + equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + }); + + it('should not expand quoted strings.', () => { + equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); + equal('{"x,x"}', ['{x,x}']); + }); + + it('should escaped outer braces in nested non-sets', () => { + equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); + equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); + }); + + it('should escape imbalanced braces', () => { + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + equal('abc{', ['abc{']); + equal('{abc{', ['{abc{']); + equal('{abc', ['{abc']); + equal('}abc', ['}abc']); + equal('ab{c', ['ab{c']); + equal('ab{c', ['ab{c']); + equal('{{a,b}', ['{a', '{b']); + equal('{a,b}}', ['a}', 'b}']); + equal('abcd{efgh', ['abcd{efgh']); + equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); + equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); + equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); + equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); + equal('f{x,y{{g}h', ['f{x,y{{g}h']); + equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); + equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']); + equal('f{x,y{}g}h', ['fxh', 'fy{}gh']); + equal('z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']); + }); + }); + + describe('positive numeric ranges', () => { + it('should expand numeric ranges', () => { + equal('a{0..3}d', ['a0d', 'a1d', 'a2d', 'a3d']); + equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); + equal('x{3..3}y', ['x3y']); + equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..3}', ['1', '2', '3']); + equal('{1..9}', ['1', '2', '3', '4', '5', '6', '7', '8', '9']); + equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); + equal('{3..3}', ['3']); + equal('{5..8}', ['5', '6', '7', '8']); + }); + }); + + describe('negative ranges', () => { + it('should expand ranges with negative numbers', () => { + equal('{-10..-1}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + equal('{0..-5}', ['0', '-1', '-2', '-3', '-4', '-5']); + equal('{9..-4}', ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']); + }); + }); + + describe('alphabetical ranges', () => { + it('should expand alphabetical ranges', () => { + equal('{a..F}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F']); + equal('0{a..d}0', ['0a0', '0b0', '0c0', '0d0']); + equal('a/{b..d}/e', ['a/b/e', 'a/c/e', 'a/d/e']); + equal('{1..f}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); + equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); + equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); + equal('{a..e}', ['a', 'b', 'c', 'd', 'e']); + equal('{A..E}', ['A', 'B', 'C', 'D', 'E']); + equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{E..A}', ['E', 'D', 'C', 'B', 'A']); + equal('{f..1}', ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); + equal('{f..f}', ['f']); + }); + + it('should expand multiple ranges:', () => { + equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']); + }); + }); + + describe('combo', () => { + it('should expand numerical ranges - positive and negative', () => { + equal('a{01..05}b', ['a01b', 'a02b', 'a03b', 'a04b', 'a05b' ]); + equal('0{1..9}/{10..20}', ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20' ]); + equal('{-10..10}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); }); }); - const fixtures = [ - 'should expand ranges', - - ['a{b,c{1..50}/{foo,bar,baz}/,g}h/i', {}, ['abh/i', 'ac1/bar/h/i', 'ac1/baz/h/i', 'ac1/foo/h/i', 'ac10/bar/h/i', 'ac10/baz/h/i', 'ac10/foo/h/i', 'ac11/bar/h/i', 'ac11/baz/h/i', 'ac11/foo/h/i', 'ac12/bar/h/i', 'ac12/baz/h/i', 'ac12/foo/h/i', 'ac13/bar/h/i', 'ac13/baz/h/i', 'ac13/foo/h/i', 'ac14/bar/h/i', 'ac14/baz/h/i', 'ac14/foo/h/i', 'ac15/bar/h/i', 'ac15/baz/h/i', 'ac15/foo/h/i', 'ac16/bar/h/i', 'ac16/baz/h/i', 'ac16/foo/h/i', 'ac17/bar/h/i', 'ac17/baz/h/i', 'ac17/foo/h/i', 'ac18/bar/h/i', 'ac18/baz/h/i', 'ac18/foo/h/i', 'ac19/bar/h/i', 'ac19/baz/h/i', 'ac19/foo/h/i', 'ac2/bar/h/i', 'ac2/baz/h/i', 'ac2/foo/h/i', 'ac20/bar/h/i', 'ac20/baz/h/i', 'ac20/foo/h/i', 'ac21/bar/h/i', 'ac21/baz/h/i', 'ac21/foo/h/i', 'ac22/bar/h/i', 'ac22/baz/h/i', 'ac22/foo/h/i', 'ac23/bar/h/i', 'ac23/baz/h/i', 'ac23/foo/h/i', 'ac24/bar/h/i', 'ac24/baz/h/i', 'ac24/foo/h/i', 'ac25/bar/h/i', 'ac25/baz/h/i', 'ac25/foo/h/i', 'ac26/bar/h/i', 'ac26/baz/h/i', 'ac26/foo/h/i', 'ac27/bar/h/i', 'ac27/baz/h/i', 'ac27/foo/h/i', 'ac28/bar/h/i', 'ac28/baz/h/i', 'ac28/foo/h/i', 'ac29/bar/h/i', 'ac29/baz/h/i', 'ac29/foo/h/i', 'ac3/bar/h/i', 'ac3/baz/h/i', 'ac3/foo/h/i', 'ac30/bar/h/i', 'ac30/baz/h/i', 'ac30/foo/h/i', 'ac31/bar/h/i', 'ac31/baz/h/i', 'ac31/foo/h/i', 'ac32/bar/h/i', 'ac32/baz/h/i', 'ac32/foo/h/i', 'ac33/bar/h/i', 'ac33/baz/h/i', 'ac33/foo/h/i', 'ac34/bar/h/i', 'ac34/baz/h/i', 'ac34/foo/h/i', 'ac35/bar/h/i', 'ac35/baz/h/i', 'ac35/foo/h/i', 'ac36/bar/h/i', 'ac36/baz/h/i', 'ac36/foo/h/i', 'ac37/bar/h/i', 'ac37/baz/h/i', 'ac37/foo/h/i', 'ac38/bar/h/i', 'ac38/baz/h/i', 'ac38/foo/h/i', 'ac39/bar/h/i', 'ac39/baz/h/i', 'ac39/foo/h/i', 'ac4/bar/h/i', 'ac4/baz/h/i', 'ac4/foo/h/i', 'ac40/bar/h/i', 'ac40/baz/h/i', 'ac40/foo/h/i', 'ac41/bar/h/i', 'ac41/baz/h/i', 'ac41/foo/h/i', 'ac42/bar/h/i', 'ac42/baz/h/i', 'ac42/foo/h/i', 'ac43/bar/h/i', 'ac43/baz/h/i', 'ac43/foo/h/i', 'ac44/bar/h/i', 'ac44/baz/h/i', 'ac44/foo/h/i', 'ac45/bar/h/i', 'ac45/baz/h/i', 'ac45/foo/h/i', 'ac46/bar/h/i', 'ac46/baz/h/i', 'ac46/foo/h/i', 'ac47/bar/h/i', 'ac47/baz/h/i', 'ac47/foo/h/i', 'ac48/bar/h/i', 'ac48/baz/h/i', 'ac48/foo/h/i', 'ac49/bar/h/i', 'ac49/baz/h/i', 'ac49/foo/h/i', 'ac5/bar/h/i', 'ac5/baz/h/i', 'ac5/foo/h/i', 'ac50/bar/h/i', 'ac50/baz/h/i', 'ac50/foo/h/i', 'ac6/bar/h/i', 'ac6/baz/h/i', 'ac6/foo/h/i', 'ac7/bar/h/i', 'ac7/baz/h/i', 'ac7/foo/h/i', 'ac8/bar/h/i', 'ac8/baz/h/i', 'ac8/foo/h/i', 'ac9/bar/h/i', 'ac9/baz/h/i', 'ac9/foo/h/i', 'agh/i'] ], - ['0{1..9} {10..20}', {}, ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20'] ], - ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt'] ], - ['a/{x,{1..5},y}/c{d}e', {}, ['a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/x/c{d}e', 'a/y/c{d}e'] ], - ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], - ['x{10..1}y', {}, ['x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y']], - ['x{3..3}y', {}, ['x3y']], - ['{0..10,braces}', {}, ['0..10', 'braces']], - ['{10..1}', {}, ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9']], - ['{3..3}', {}, ['3']], - ['{5..8}', {}, ['5', '6', '7', '8']], - ['{9..-4}', {}, ['-1', '-2', '-3', '-4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']], - ['{{0..10},braces}', {}, ['0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['**/{1..5}/a.js', {}, ['**/1/a.js', '**/2/a.js', '**/3/a.js', '**/4/a.js', '**/5/a.js']], - ['x{{0..10},braces}y', {}, ['x0y', 'x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'xbracesy'] ], - ['{braces,{0..10}}', {}, ['0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['{{0..10},braces}', {}, ['0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['{{1..10..2},braces}', {}, ['1', '3', '5', '7', '9', 'braces']], - ['{{1..10},braces}', {}, ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['{214748364..2147483649}', { throws: true }, [] ], - ['{2147483645..2147483649}', { throws: true }, [] ], - ['{0..10,braces}', {}, ['0..10', 'braces']], - ['{1..10,braces}', {}, ['1..10', 'braces']], - ['./\\{x,y}/{a..z..3}/', {}, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/'] ], - ['x{{0..10},braces}y', {}, ['x0y', 'x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'xbracesy'] ], - ['{braces,{0..10}}', {}, ['0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['{{0..10},braces}', {}, ['0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['{{1..10..2},braces}', {}, ['1', '3', '5', '7', '9', 'braces']], - ['{{1..10},braces}', {}, ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces']], - ['0{1..9} {10..20}', {}, ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20'] ], - ['{1.1..2.1}', {}, ['{1.1..2.1}']], - ['{1.1..~2.1}', {}, ['{1.1..~2.1}']], - ['{1..0f}', {}, ['{1..0f}']], - ['{1..10..ff}', {}, ['{1..10..ff}']], - ['{1..10.f}', {}, ['{1..10.f}']], - ['{1..10f}', {}, ['{1..10f}']], - ['{1..20..2f}', {}, ['{1..20..2f}']], - ['{1..20..f2}', {}, ['{1..20..f2}']], - ['{1..2f..2}', {}, ['{1..2f..2}']], - ['{1..ff..2}', {}, ['{1..ff..2}']], - ['{1..ff}', {}, ['{1..ff}']], - ['{1.20..2}', {}, ['{1.20..2}']], - ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], - ['x{10..1}y', {}, ['x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y']], - ['x{3..3}y', {}, ['x3y']], - ['{1..10}', {}, ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9']], - ['{1..3}', {}, ['1', '2', '3']], - ['{1..9}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9']], - ['{10..1}y', {}, ['10y', '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y']], - ['{3..3}', {}, ['3']], - ['{5..8}', {}, ['5', '6', '7', '8']], - ['{-10..-1}', {}, ['-1', '-10', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9']], - ['{-20..0}', {}, ['-1', '-10', '-11', '-12', '-13', '-14', '-15', '-16', '-17', '-18', '-19', '-2', '-20', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '0'] ], - ['{0..-5}', {}, ['-1', '-2', '-3', '-4', '-5', '0']], - ['{9..-4}', {}, ['-1', '-2', '-3', '-4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']], - ['0{1..9}/{10..20}', {}, ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20'] ], - ['0{a..d}0', {}, ['0a0', '0b0', '0c0', '0d0']], - ['a/{b..d}/e', {}, ['a/b/e', 'a/c/e', 'a/d/e']], - ['{1..f}', { minimatch: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f'] ], - ['{a..A}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A'] ], - ['{A..a}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a'] ], - ['{a..e}', {}, ['a', 'b', 'c', 'd', 'e']], - ['{A..E}', {}, ['A', 'B', 'C', 'D', 'E']], - ['{a..f}', {}, ['a', 'b', 'c', 'd', 'e', 'f']], - ['{a..z}', {}, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] ], - ['{E..A}', {}, ['A', 'B', 'C', 'D', 'E']], - ['{f..1}', { minimatch: false }, ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1'] ], - ['{f..a}', {}, ['a', 'b', 'c', 'd', 'e', 'f']], - ['{f..f}', {}, ['f']], - ['a/{b..d}/e/{f..h}', {}, ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h'] ], - ['{-10..10}', {}, ['-1', '-10', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9'] ], - ['{2147483645..2147483649}', { minimatch: false }, ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649'] ], - ['{1..10..1}', { optimize: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] ], - ['{1..10..2}', { optimize: false }, ['1', '3', '5', '7', '9'] ], - ['{1..20..20}', { optimize: false }, ['1'] ], - ['{1..20..2}', { optimize: false }, ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19'] ], - ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], - ['{10..1..2}', { optimize: false }, ['10', '8', '6', '4', '2'] ], - ['{100..0..5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0'] ], - ['{2..10..1}', { optimize: false }, ['2', '3', '4', '5', '6', '7', '8', '9', '10'] ], - ['{2..10..2}', { optimize: false }, ['2', '4', '6', '8', '10'] ], - ['{2..10..3}', { optimize: false }, ['2', '5', '8'] ], - ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y'] ], - ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], - ['{-1..-10..-2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9'] ], - ['{-1..-10..2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9'] ], - ['{-10..-2..2}', { optimize: false }, ['-10', '-8', '-6', '-4', '-2'] ], - ['{-2..-10..1}', { optimize: false }, ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10'] ], - ['{-2..-10..2}', { optimize: false }, ['-2', '-4', '-6', '-8', '-10'] ], - ['{-2..-10..3}', { optimize: false }, ['-2', '-5', '-8'] ], - ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0'] ], - ['{-9..9..3}', { optimize: false }, ['-9', '-6', '-3', '0', '3', '6', '9'] ], - ['{10..1..-2}', { optimize: false }, ['10', '8', '6', '4', '2'] ], - ['{100..0..-5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0'] ], - ['{a..e..2}', { optimize: false }, ['a', 'c', 'e'] ], - ['{E..A..2}', { optimize: false }, ['E', 'C', 'A'] ], - ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y'] ], - ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b'] ], - ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b'] ], - ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], - ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], - ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0'] ], - ['../{1..3}/../foo', {}, ['../1/../foo', '../2/../foo', '../3/../foo']], - ['../{2..10..2}/../foo', { optimize: false }, ['../2/../foo', '../4/../foo', '../6/../foo', '../8/../foo', '../10/../foo'] ], - ['../{1..3}/../{a,b,c}/foo', {}, ['../1/../a/foo', '../1/../b/foo', '../1/../c/foo', '../2/../a/foo', '../2/../b/foo', '../2/../c/foo', '../3/../a/foo', '../3/../b/foo', '../3/../c/foo'] ], - ['./{a..z..3}/', { optimize: false }, ['./a/', './d/', './g/', './j/', './m/', './p/', './s/', './v/', './y/'] ], - ['./{"x,y"}/{a..z..3}/', { minimatch: false, optimize: false }, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/'] ], - ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt']], - ['a/{x,{1..5},y}/c{d}e', {}, ['a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/x/c{d}e', 'a/y/c{d}e']] - ]; - - - fixtures.forEach(arr => { - if (typeof arr === 'string') { - return; - } - - let options = { ...arr[1] }; - let pattern = arr[0]; - let expected = arr[2]; - - if (options.skip !== true) { - it('should compile: ' + pattern, () => equal(pattern, expected, options)); - } + describe('steps > positive ranges', () => { + it('should expand ranges using steps:', () => { + equal('{1..10..1}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{1..20..20}', ['1']); + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{2..10..1}', ['2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{2..10..2}', ['2', '4', '6', '8', '10']); + equal('{2..10..3}', ['2', '5', '8']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + }); + + it('should expand positive ranges with negative steps:', () => { + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + }); + }); + + describe('steps > negative ranges', () => { + it('should expand negative ranges using steps:', () => { + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-10..-2..2}', ['-10', '-8', '-6', '-4', '-2']); + equal('{-2..-10..1}', ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-2..-10..2}', ['-2', '-4', '-6', '-8', '-10']); + equal('{-2..-10..3}', ['-2', '-5', '-8']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + }); + }); + + describe('steps > alphabetical ranges', () => { + it('should expand alpha ranges with steps', () => { + equal('{a..e..2}', ['a', 'c', 'e']); + equal('{E..A..2}', ['E', 'C', 'A']); + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + }); + + it('should expand alpha ranges with negative steps', () => { + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + }); + }); + + describe('padding', () => { + it('unwanted zero-padding -- fixed post-bash-4.0', () => { + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + }); + }); + + describe('ranges', () => { + const fixtures = [ + 'should expand ranges', + + ['a{b,c{1..50}/{d,e,f}/,g}h/i', {}, ['abh/i', 'ac1/d/h/i', 'ac1/e/h/i', 'ac1/f/h/i', 'ac2/d/h/i', 'ac2/e/h/i', 'ac2/f/h/i', 'ac3/d/h/i', 'ac3/e/h/i', 'ac3/f/h/i', 'ac4/d/h/i', 'ac4/e/h/i', 'ac4/f/h/i', 'ac5/d/h/i', 'ac5/e/h/i', 'ac5/f/h/i', 'ac6/d/h/i', 'ac6/e/h/i', 'ac6/f/h/i', 'ac7/d/h/i', 'ac7/e/h/i', 'ac7/f/h/i', 'ac8/d/h/i', 'ac8/e/h/i', 'ac8/f/h/i', 'ac9/d/h/i', 'ac9/e/h/i', 'ac9/f/h/i', 'ac10/d/h/i', 'ac10/e/h/i', 'ac10/f/h/i', 'ac11/d/h/i', 'ac11/e/h/i', 'ac11/f/h/i', 'ac12/d/h/i', 'ac12/e/h/i', 'ac12/f/h/i', 'ac13/d/h/i', 'ac13/e/h/i', 'ac13/f/h/i', 'ac14/d/h/i', 'ac14/e/h/i', 'ac14/f/h/i', 'ac15/d/h/i', 'ac15/e/h/i', 'ac15/f/h/i', 'ac16/d/h/i', 'ac16/e/h/i', 'ac16/f/h/i', 'ac17/d/h/i', 'ac17/e/h/i', 'ac17/f/h/i', 'ac18/d/h/i', 'ac18/e/h/i', 'ac18/f/h/i', 'ac19/d/h/i', 'ac19/e/h/i', 'ac19/f/h/i', 'ac20/d/h/i', 'ac20/e/h/i', 'ac20/f/h/i', 'ac21/d/h/i', 'ac21/e/h/i', 'ac21/f/h/i', 'ac22/d/h/i', 'ac22/e/h/i', 'ac22/f/h/i', 'ac23/d/h/i', 'ac23/e/h/i', 'ac23/f/h/i', 'ac24/d/h/i', 'ac24/e/h/i', 'ac24/f/h/i', 'ac25/d/h/i', 'ac25/e/h/i', 'ac25/f/h/i', 'ac26/d/h/i', 'ac26/e/h/i', 'ac26/f/h/i', 'ac27/d/h/i', 'ac27/e/h/i', 'ac27/f/h/i', 'ac28/d/h/i', 'ac28/e/h/i', 'ac28/f/h/i', 'ac29/d/h/i', 'ac29/e/h/i', 'ac29/f/h/i', 'ac30/d/h/i', 'ac30/e/h/i', 'ac30/f/h/i', 'ac31/d/h/i', 'ac31/e/h/i', 'ac31/f/h/i', 'ac32/d/h/i', 'ac32/e/h/i', 'ac32/f/h/i', 'ac33/d/h/i', 'ac33/e/h/i', 'ac33/f/h/i', 'ac34/d/h/i', 'ac34/e/h/i', 'ac34/f/h/i', 'ac35/d/h/i', 'ac35/e/h/i', 'ac35/f/h/i', 'ac36/d/h/i', 'ac36/e/h/i', 'ac36/f/h/i', 'ac37/d/h/i', 'ac37/e/h/i', 'ac37/f/h/i', 'ac38/d/h/i', 'ac38/e/h/i', 'ac38/f/h/i', 'ac39/d/h/i', 'ac39/e/h/i', 'ac39/f/h/i', 'ac40/d/h/i', 'ac40/e/h/i', 'ac40/f/h/i', 'ac41/d/h/i', 'ac41/e/h/i', 'ac41/f/h/i', 'ac42/d/h/i', 'ac42/e/h/i', 'ac42/f/h/i', 'ac43/d/h/i', 'ac43/e/h/i', 'ac43/f/h/i', 'ac44/d/h/i', 'ac44/e/h/i', 'ac44/f/h/i', 'ac45/d/h/i', 'ac45/e/h/i', 'ac45/f/h/i', 'ac46/d/h/i', 'ac46/e/h/i', 'ac46/f/h/i', 'ac47/d/h/i', 'ac47/e/h/i', 'ac47/f/h/i', 'ac48/d/h/i', 'ac48/e/h/i', 'ac48/f/h/i', 'ac49/d/h/i', 'ac49/e/h/i', 'ac49/f/h/i', 'ac50/d/h/i', 'ac50/e/h/i', 'ac50/f/h/i', 'agh/i'] ], + ['0{1..9} {10..20}', {}, ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20'] ], + ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], + ['x{10..1}y', {}, ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']], + ['x{3..3}y', {}, ['x3y']], + ['{0..10,braces}', {}, ['0..10', 'braces']], + ['{3..3}', {}, ['3']], + ['{5..8}', {}, ['5', '6', '7', '8']], + ['**/{1..5}/a.js', {}, ['**/1/a.js', '**/2/a.js', '**/3/a.js', '**/4/a.js', '**/5/a.js']], + ['{braces,{0..10}}', {}, ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['./\\{x,y}/{a..z..3}/', {}, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/'] ], + ['x{{0..10},braces}y', {}, ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy'] ], + ['{braces,{0..10}}', {}, ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{{0..10},braces}', {}, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']], + ['{{1..10..2},braces}', {}, ['1', '3', '5', '7', '9', 'braces']], + ['{{1..10},braces}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']], + ['{1.1..2.1}', {}, ['{1.1..2.1}']], + ['{1.1..~2.1}', {}, ['{1.1..~2.1}']], + ['{1..0f}', {}, ['{1..0f}']], + ['{1..10..ff}', {}, ['{1..10..ff}']], + ['{1..10.f}', {}, ['{1..10.f}']], + ['{1..10f}', {}, ['{1..10f}']], + ['{1..20..2f}', {}, ['{1..20..2f}']], + ['{1..20..f2}', {}, ['{1..20..f2}']], + ['{1..2f..2}', {}, ['{1..2f..2}']], + ['{1..ff..2}', {}, ['{1..ff..2}']], + ['{1..ff}', {}, ['{1..ff}']], + ['{1.20..2}', {}, ['{1.20..2}']], + ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], + ['x{10..1}y', {}, ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']], + ['x{3..3}y', {}, ['x3y']], + ['{1..10}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..3}', {}, ['1', '2', '3']], + ['{1..9}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9']], + ['{10..1}y', {}, ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']], + ['{3..3}', {}, ['3']], + ['{5..8}', {}, ['5', '6', '7', '8']], + ['{-10..-1}', {}, ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']], + ['{-20..0}', {}, ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0'] ], + ['{0..-5}', {}, ['0', '-1', '-2', '-3', '-4', '-5']], + ['{9..-4}', {}, ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']], + ['0{1..9}/{10..20}', {}, ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20'] ], + ['0{a..d}0', {}, ['0a0', '0b0', '0c0', '0d0']], + ['a/{b..d}/e', {}, ['a/b/e', 'a/c/e', 'a/d/e']], + ['{1..f}', { minimatch: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f'] ], + ['{a..A}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A'] ], + ['{A..a}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a'] ], + ['{a..e}', {}, ['a', 'b', 'c', 'd', 'e']], + ['{A..E}', {}, ['A', 'B', 'C', 'D', 'E']], + ['{a..f}', {}, ['a', 'b', 'c', 'd', 'e', 'f']], + ['{a..z}', {}, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] ], + ['{E..A}', {}, ['E', 'D', 'C', 'B', 'A']], + ['{f..1}', { minimatch: false }, ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1'] ], + ['{f..a}', {}, ['f', 'e', 'd', 'c', 'b', 'a']], + ['{f..f}', {}, ['f']], + ['a/{b..d}/e/{f..h}', {}, ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h'] ], + ['{-10..10}', {}, ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] ], + ['{1..10..1}', { optimize: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] ], + ['{1..10..2}', { optimize: false }, ['1', '3', '5', '7', '9'] ], + ['{1..20..20}', { optimize: false }, ['1'] ], + ['{1..20..2}', { optimize: false }, ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19'] ], + ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], + ['{10..1..2}', { optimize: false }, ['10', '8', '6', '4', '2'] ], + ['{100..0..5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0'] ], + ['{2..10..1}', { optimize: false }, ['2', '3', '4', '5', '6', '7', '8', '9', '10'] ], + ['{2..10..2}', { optimize: false }, ['2', '4', '6', '8', '10'] ], + ['{2..10..3}', { optimize: false }, ['2', '5', '8'] ], + ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y'] ], + ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], + ['{-1..-10..-2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9'] ], + ['{-1..-10..2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9'] ], + ['{-10..-2..2}', { optimize: false }, ['-10', '-8', '-6', '-4', '-2'] ], + ['{-2..-10..1}', { optimize: false }, ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10'] ], + ['{-2..-10..2}', { optimize: false }, ['-2', '-4', '-6', '-8', '-10'] ], + ['{-2..-10..3}', { optimize: false }, ['-2', '-5', '-8'] ], + ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0'] ], + ['{-9..9..3}', { optimize: false }, ['-9', '-6', '-3', '0', '3', '6', '9'] ], + ['{10..1..-2}', { optimize: false }, ['10', '8', '6', '4', '2'] ], + ['{100..0..-5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0'] ], + ['{a..e..2}', { optimize: false }, ['a', 'c', 'e'] ], + ['{E..A..2}', { optimize: false }, ['E', 'C', 'A'] ], + ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y'] ], + ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b'] ], + ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b'] ], + ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], + ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0'] ], + ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0'] ], + ['../{1..3}/../foo', {}, ['../1/../foo', '../2/../foo', '../3/../foo']], + ['../{2..10..2}/../foo', { optimize: false }, ['../2/../foo', '../4/../foo', '../6/../foo', '../8/../foo', '../10/../foo'] ], + ['../{1..3}/../{a,b,c}/foo', {}, ['../1/../a/foo', '../1/../b/foo', '../1/../c/foo', '../2/../a/foo', '../2/../b/foo', '../2/../c/foo', '../3/../a/foo', '../3/../b/foo', '../3/../c/foo'] ], + ['./{a..z..3}/', { optimize: false }, ['./a/', './d/', './g/', './j/', './m/', './p/', './s/', './v/', './y/'] ], + ['./{"x,y"}/{a..z..3}/', { minimatch: false, optimize: false }, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/'] ], + ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt']], + ['a/{x,{1..5},y}/c{d}e', {}, ['a/x/c{d}e', 'a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/y/c{d}e']] + ]; + + fixtures.forEach(arr => { + if (typeof arr === 'string') { + return; + } + + let options = { ...arr[1] }; + let pattern = arr[0]; + let expected = arr[2]; + + if (options.skip !== true) { + it('should compile: ' + pattern, () => equal(pattern, expected, options)); + } + }); }); }); diff --git a/test/bash-spec.js b/test/bash-spec.js new file mode 100644 index 0000000..3cb6f6f --- /dev/null +++ b/test/bash-spec.js @@ -0,0 +1,197 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp.spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +/** + * Bash 4.3 unit tests + */ + +describe('bash', () => { + var fixtures = [ + [ '{1\\.2}', {}, [ '{1.2}' ] ], + [ '{1\\.2}', { keepEscaping: true }, [ '{1\\.2}' ] ], + [ '{"x,x"}', {}, [ '{x,x}' ] ], + [ '{x","x}', {}, [ '{x,x}' ] ], + [ '\'{x,x}\'', {}, [ '{x,x}' ] ], + [ '{x`,`x}', {}, [ '{x,x}' ] ], + [ '{x`,`x}', { keepQuotes: true }, [ '{x`,`x}' ] ], + [ '\'{a,b}{{a,b},a,b}\'', {}, [ '{a,b}{{a,b},a,b}' ] ], + [ 'A{b,{d,e},{f,g}}Z', {}, [ 'AbZ', 'AdZ', 'AeZ', 'AfZ', 'AgZ' ] ], + [ 'PRE-{a,b}{{a,b},a,b}-POST', {}, [ 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-ba-POST', 'PRE-bb-POST', 'PRE-ba-POST', 'PRE-bb-POST' ] ], + [ '\\{a,b}{{a,b},a,b}', {}, [ '{a,b}a', '{a,b}b', '{a,b}a', '{a,b}b' ] ], + [ '{{a,b}', {}, [ '{a', '{b' ] ], + [ '{a,b}}', {}, [ 'a}', 'b}' ] ], + [ '{,}', {}, ['', ''] ], + [ 'a{,}', {}, [ 'a', 'a' ] ], + [ '{,}b', {}, [ 'b', 'b' ] ], + [ 'a{,}b', {}, [ 'ab', 'ab' ] ], + [ 'a{b}c', {}, [ 'a{b}c' ] ], + [ 'a{1..5}b', {}, [ 'a1b', 'a2b', 'a3b', 'a4b', 'a5b' ] ], + [ 'a{01..5}b', {}, [ 'a01b', 'a02b', 'a03b', 'a04b', 'a05b' ] ], + [ 'a{-01..5}b', {}, [ 'a-01b', 'a000b', 'a001b', 'a002b', 'a003b', 'a004b', 'a005b' ] ], + [ 'a{-01..5..3}b', {}, [ 'a-01b', 'a002b', 'a005b' ] ], + [ 'a{001..9}b', {}, [ 'a001b', 'a002b', 'a003b', 'a004b', 'a005b', 'a006b', 'a007b', 'a008b', 'a009b' ] ], + [ 'a{b,c{d,e},{f,g}h}x{y,z', {}, [ 'abx{y,z', 'acdx{y,z', 'acex{y,z', 'afhx{y,z', 'aghx{y,z' ] ], + [ 'a{b,c{d,e},{f,g}h}x{y,z\\}', {}, [ 'abx{y,z}', 'acdx{y,z}', 'acex{y,z}', 'afhx{y,z}', 'aghx{y,z}' ] ], + [ 'a{b,c{d,e},{f,g}h}x{y,z}', {}, [ 'abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz' ] ], + [ 'a{b{c{d,e}f{x,y{{g}h', {}, [ 'a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h' ] ], + [ 'a{b{c{d,e}f{x,y{}g}h', {}, [ 'a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh' ] ], + [ 'a{b{c{d,e}f{x,y}}g}h', {}, [ 'a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h' ] ], + [ 'a{b{c{d,e}f}g}h', {}, [ 'a{b{cdf}g}h', 'a{b{cef}g}h' ] ], + [ 'a{{x,y},z}b', {}, [ 'axb', 'ayb', 'azb' ] ], + [ 'f{x,y{g,z}}h', {}, [ 'fxh', 'fygh', 'fyzh' ] ], + [ 'f{x,y{{g,z}}h', {}, [ 'f{x,y{g}h', 'f{x,y{z}h' ] ], + [ 'f{x,y{{g,z}}h}', {}, [ 'fx', 'fy{g}h', 'fy{z}h' ] ], + [ 'f{x,y{{g}h', {}, [ 'f{x,y{{g}h' ] ], + [ 'f{x,y{{g}}h', {}, [ 'f{x,y{{g}}h' ] ], + [ 'f{x,y{}g}h', {}, [ 'fxh', 'fy{}gh' ] ], + [ 'z{a,b{,c}d', {}, [ 'z{a,bd', 'z{a,bcd' ] ], + [ 'z{a,b},c}d', {}, [ 'za,c}d', 'zb,c}d' ] ], + [ '{-01..5}', {}, [ '-01', '000', '001', '002', '003', '004', '005' ] ], + [ '{-05..100..5}', {}, [ '-05', '000', '005', '010', '015', '020', '025', '030', '035', '040', '045', '050', '055', '060', '065', '070', '075', '080', '085', '090', '095', '100' ] ], + [ '{-05..100}', {}, [ '-05', '-04', '-03', '-02', '-01', '000', '001', '002', '003', '004', '005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100' ] ], + [ '{0..5..2}', {}, [ '0', '2', '4' ] ], + [ '{0001..05..2}', {}, [ '0001', '0003', '0005' ] ], + [ '{0001..-5..2}', {}, [ '0001', '-001', '-003', '-005' ] ], + [ '{0001..-5..-2}', {}, [ '0001', '-001', '-003', '-005' ] ], + [ '{0001..5..-2}', {}, [ '0001', '0003', '0005' ] ], + [ '{01..5}', {}, [ '01', '02', '03', '04', '05' ] ], + [ '{1..05}', {}, [ '01', '02', '03', '04', '05' ] ], + [ '{1..05..3}', {}, [ '01', '04' ] ], + [ '{05..100}', {}, [ '005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100' ] ], + [ '{0a..0z}', {}, [ '{0a..0z}' ] ], + [ '{a,b\\}c,d}', {}, [ 'a', 'b}c', 'd' ] ], + [ '{a,b{c,d}', {}, [ '{a,bc', '{a,bd' ] ], + [ '{a,b}c,d}', {}, [ 'ac,d}', 'bc,d}' ] ], + [ '{a..F}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F' ] ], + [ '{A..f}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f' ] ], + [ '{a..Z}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z' ] ], + [ '{A..z}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ] ], + [ '{z..A}', {}, [ 'z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A' ] ], + [ '{Z..a}', {}, [ 'Z', '[', '\\', ']', '^', '_', '`', 'a' ] ], + [ '{a..F..2}', {}, [ 'a', '_', ']', '[', 'Y', 'W', 'U', 'S', 'Q', 'O', 'M', 'K', 'I', 'G' ] ], + [ '{A..f..02}', {}, [ 'A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y', '[', ']', '_', 'a', 'c', 'e' ] ], + [ '{a..Z..5}', {}, [ 'a', '\\' ] ], + [ 'd{a..Z..5}b', {}, [ 'dab', 'd\\b' ] ], + [ '{A..z..10}', {}, [ 'A', 'K', 'U', '_', 'i', 's' ] ], + [ '{z..A..-2}', {}, [ 'z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b', '`', '^', '\\', 'Z', 'X', 'V', 'T', 'R', 'P', 'N', 'L', 'J', 'H', 'F', 'D', 'B' ] ], + [ '{Z..a..20}', {}, [ 'Z' ] ], + [ '{a{,b}', {}, [ '{a', '{ab' ] ], + [ '{a\\},b}', {}, [ 'a}', 'b' ] ], + [ '{x,y{,}g}', {}, [ 'x', 'yg', 'yg' ] ], + [ '{x,y{}g}', {}, [ 'x', 'y{}g' ] ], + [ '{{a,b}', {}, [ '{a', '{b' ] ], + [ '{{a,b},c}', {}, [ 'a', 'b', 'c' ] ], + [ '{{a,b}c}', {}, [ '{ac}', '{bc}' ] ], + [ '{{a,b},}', {}, [ 'a', 'b', '' ] ], + [ 'X{{a,b},}X', {}, [ 'XaX', 'XbX', 'XX' ] ], + [ '{{a,b},}c', {}, [ 'ac', 'bc', 'c' ] ], + [ '{{a,b}.}', {}, [ '{a.}', '{b.}' ] ], + [ '{{a,b}}', {}, [ '{a}', '{b}' ] ], + [ 'X{a..#}X', {}, [ 'X{a..#}X' ] ], + [ '{-10..00}', {}, [ '-10', '-09', '-08', '-07', '-06', '-05', '-04', '-03', '-02', '-01', '000' ] ], + [ '{a,\\\\{a,b}c}', {}, [ 'a', '\\ac', '\\bc' ] ], + [ '{a,\\{a,b}c}', {}, [ 'ac}', '{ac}', 'bc}' ] ], + [ 'a,\\{b,c}', {}, [ 'a,{b,c}' ] ], + [ '{-10.\\.00}', {}, [ '{-10..00}' ] ], + [ 'ff{c,b,a}', {}, [ 'ffc', 'ffb', 'ffa' ] ], + [ 'f{d,e,f}g', {}, [ 'fdg', 'feg', 'ffg' ] ], + [ '{l,n,m}xyz', {}, [ 'lxyz', 'nxyz', 'mxyz' ] ], + [ '{abc\\,def}', {}, [ '{abc,def}' ] ], + [ '{abc}', {}, [ '{abc}' ] ], + [ '{x\\,y,\\{abc\\},trie}', {}, [ 'x,y', '{abc}', 'trie' ] ], + [ '{}', {}, [ '{}' ] ], + [ '{ }', {}, [ '{ }' ] ], + [ '}', {}, [ '}' ] ], + [ '{', {}, [ '{' ] ], + [ 'abcd{efgh', {}, [ 'abcd{efgh' ] ], + [ 'foo {1,2} bar', {}, [ 'foo 1 bar', 'foo 2 bar' ] ], + [ '"${var}"{x,y}', {}, [ '${var}x', '${var}y' ] ], + [ '{1..10}', {}, [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] ], + [ '{0..10,braces}', {}, [ '0..10', 'braces' ] ], + [ '{{0..10},braces}', {}, [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces' ] ], + [ 'x{{0..10},braces}y', {}, [ 'x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy' ] ], + [ '{3..3}', {}, [ '3' ] ], + [ 'x{3..3}y', {}, [ 'x3y' ] ], + [ '{10..1}', {}, [ '10', '9', '8', '7', '6', '5', '4', '3', '2', '1' ] ], + [ '{10..1}y', {}, [ '10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y' ] ], + [ 'x{10..1}y', {}, [ 'x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y' ] ], + [ '{a..f}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f' ] ], + [ '{f..a}', {}, [ 'f', 'e', 'd', 'c', 'b', 'a' ] ], + [ '{a..A}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A' ] ], + [ '{A..a}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a' ] ], + [ '{f..f}', {}, [ 'f' ] ], + [ '0{1..9} {10..20}', {}, [ '01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20' ] ], + [ '{-1..-10}', {}, [ '-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10' ] ], + [ '{-20..0}', {}, [ '-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0' ] ], + [ 'a-{b{d,e}}-c', {}, [ 'a-{bd}-c', 'a-{be}-c' ] ], + [ 'a-{bdef-{g,i}-c', {}, [ 'a-{bdef-g-c', 'a-{bdef-i-c' ] ], + [ '{"klklkl"}{1,2,3}', {}, [ '{klklkl}1', '{klklkl}2', '{klklkl}3' ] ], + [ '{"x,x"}', {}, [ '{x,x}' ] ], + [ '{klklkl}{1,2,3}', {}, [ '{klklkl}1', '{klklkl}2', '{klklkl}3' ] ], + [ '{1..10..2}', {}, [ '1', '3', '5', '7', '9' ] ], + [ '{-1..-10..2}', {}, [ '-1', '-3', '-5', '-7', '-9' ] ], + [ '{-1..-10..-2}', {}, [ '-1', '-3', '-5', '-7', '-9' ] ], + [ '{10..1..-2}', {}, [ '10', '8', '6', '4', '2' ] ], + [ '{10..1..2}', {}, [ '10', '8', '6', '4', '2' ] ], + [ '{1..20..2}', {}, [ '1', '3', '5', '7', '9', '11', '13', '15', '17', '19' ] ], + [ '{1..20..20}', {}, [ '1' ] ], + [ '{100..0..5}', {}, [ '100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0' ] ], + [ '{100..0..-5}', {}, [ '100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0' ] ], + [ '{a..z}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ] ], + [ '{a..z..2}', {}, [ 'a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y' ] ], + [ '{z..a..-2}', {}, [ 'z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b' ] ], + [ '{2147483645..2147483649}', {}, [ '2147483645', '2147483646', '2147483647', '2147483648', '2147483649' ] ], + [ '{10..0..2}', {}, [ '10', '8', '6', '4', '2', '0' ] ], + [ '{10..0..-2}', {}, [ '10', '8', '6', '4', '2', '0' ] ], + [ '{-50..-0..5}', {}, [ '-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0' ] ], + [ '{1..10.f}', {}, [ '{1..10.f}' ] ], + [ '{1..ff}', {}, [ '{1..ff}' ] ], + [ '{1..10..ff}', {}, [ '{1..10..ff}' ] ], + [ '{1.20..2}', {}, [ '{1.20..2}' ] ], + [ '{1..20..f2}', {}, [ '{1..20..f2}' ] ], + [ '{1..20..2f}', {}, [ '{1..20..2f}' ] ], + [ '{1..2f..2}', {}, [ '{1..2f..2}' ] ], + [ '{1..ff..2}', {}, [ '{1..ff..2}' ] ], + [ '{1..ff}', {}, [ '{1..ff}' ] ], + [ '{1..0f}', {}, [ '{1..0f}' ] ], + [ '{1..10f}', {}, [ '{1..10f}' ] ], + [ '{1..10.f}', {}, [ '{1..10.f}' ] ], + [ '{},b}.h', {}, [ '{},b}.h' ] ], + [ 'y{\\},a}x', {}, [ 'y}x', 'yax' ] ], + [ '{}a,b}c', {}, [ '{}a,b}c' ] ] + ]; + + fixtures.forEach(arr => { + if (typeof arr === 'string') { + return; + } + + let options = { ...arr[1] }; + let pattern = arr[0]; + let expected = arr[2]; + + if (options.skip === true) { + return; + } + + it('should compile: ' + pattern, () => { + equal(pattern, expected, options); + }); + }); +}); diff --git a/test/braces.compile.js b/test/braces.compile.js index aa747be..f1b8395 100644 --- a/test/braces.compile.js +++ b/test/braces.compile.js @@ -6,6 +6,12 @@ const compile = require('../lib/compile'); const parse = require('../lib/parse'); describe('braces.compile()', () => { + describe('errors', () => { + it('should throw an error when invalid args are passed', () => { + assert.throws(() => compile()); + }); + }); + describe('invalid characters', () => { it('should escape invalid bracket characters', () => { assert.equal(compile(parse(']{a,b,c}')), '\\](a|b|c)'); @@ -28,6 +34,11 @@ describe('braces.compile()', () => { assert.equal(compile(parse('{a...b}'), { escapeInvalid: true }), '\\{a...b\\}'); }); + it('should expand brace patterns with both sets and ranges', () => { + assert.equal(compile(parse('{a..e,z}')), '(a..e|z)'); + assert.equal(compile(parse('{a..e,a..z}')), '(a..e|a..z)'); + }); + it('should escape braces with too many range expressions', () => { assert.equal(compile(parse('{a..e..x..z}')), '{a..e..x..z}'); assert.equal(compile(parse('{a..e..x..z}'), { escapeInvalid: true }), '\\{a..e..x..z\\}'); @@ -40,13 +51,6 @@ describe('braces.compile()', () => { assert.equal(compile(parse(']{a/b'), { escapeInvalid: true }), '\\]\\{a/b'); }); - it('should escape brace patterns with both sets and ranges', () => { - assert.equal(compile(parse('{a..e,z}')), '{a..e,z}'); - assert.equal(compile(parse('{a..e,a..z}')), '{a..e,a..z}'); - assert.equal(compile(parse('{a..e,z}'), { escapeInvalid: true }), '\\{a..e,z\\}'); - assert.equal(compile(parse('{a..e,a..z}'), { escapeInvalid: true }), '\\{a..e,a..z\\}'); - }); - it('should escape non-brace patterns (no sets or ranges)', () => { assert.equal(compile(parse(']{a/b}')), '\\]{a/b}'); assert.equal(compile(parse(']{a/b}'), { escapeInvalid: true }), '\\]\\{a/b\\}'); diff --git a/test/braces.expand.js b/test/braces.expand.js index 9232c98..23cd4df 100644 --- a/test/braces.expand.js +++ b/test/braces.expand.js @@ -19,19 +19,24 @@ const equal = (input, expected = bash(input), options) => { assert.deepEqual(braces.expand(input, options), expected); }; -/** - * Most of the unit tests from brace-expansion v1.1.6 - * https://github.com/juliangruber/brace-expansion - */ - describe('unit tests from brace-expand', () => { + describe('extglobs', () => { + it('should split on commas when braces are inside extglobs', () => { + equal('*(a|{b|c,d})', ['*(a|b|c)', '*(a|d)']); + }); + + it('should not split on commas in extglobs when inside braces', () => { + equal('{a,@(b,c)}', ['a', '@(b,c)']); + equal('{a,*(b|c,d)}', ['a', '*(b|c,d)']); + }); + }); + describe('expand', () => { it('should expand an AST', () => { - let actual = expand(parse('a/{b,c}/d')); - assert.deepEqual(actual, ['a/b/d', 'a/c/d']); + assert.deepEqual(expand(parse('a/{b,c}/d')), ['a/b/d', 'a/c/d']); }); - it('should support expanded nested empty sets', function() { + it('should support expanded nested empty sets', () => { equal('{\`foo,bar\`}', ['{`foo,bar`}'], { keepQuotes: true }); equal('{\\`foo,bar\\`}', ['`foo', 'bar`'], { keepQuotes: true }); equal('{`foo\,bar`}', ['{`foo,bar`}'], { keepQuotes: true }); @@ -73,7 +78,7 @@ describe('unit tests from brace-expand', () => { equal('{,{,a}b}', ['', 'b', 'ab']); equal('{,b}', ['', 'b']); equal('{,b{,a}}', ['', 'b', 'ba']); - equal('{b,{,a}}', ['b','', 'a']); + equal('{b,{,a}}', ['b', '', 'a']); equal('{,b}{,d}', ['', 'd', 'b', 'bd']); equal('{a,b}{,d}', ['a', 'ad', 'b', 'bd']); }); @@ -84,7 +89,7 @@ describe('unit tests from brace-expand', () => { * https://github.com/juliangruber/brace-expansion */ - describe.skip('brace expansion unit tests from brace-expand', () => { + describe('brace expansion unit tests from brace-expand', () => { describe('sequences', () => { it('numeric sequences', () => { equal('a{1..2}b{2..3}c', ['a1b2c', 'a1b3c', 'a2b2c', 'a2b3c']); @@ -141,7 +146,7 @@ describe('unit tests from brace-expand', () => { describe('nested', () => { it('should support nested sets', () => { equal('{a,b{1..3},c}', ['a', 'b1', 'b2', 'b3', 'c']); - equal('{{A..E},{a..e}}', ['a', 'b', 'c', 'd', 'e', 'A', 'B', 'C', 'D', 'E']); + equal('{{A..E},{a..e}}', ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']); equal('ppp{,config,oe{,conf}}', ['ppp', 'pppconfig', 'pppoe', 'pppoeconf']); }); }); diff --git a/test/braces.parse.js b/test/braces.parse.js index d0e9a8d..b814558 100644 --- a/test/braces.parse.js +++ b/test/braces.parse.js @@ -5,6 +5,13 @@ const assert = require('assert').strict; const parse = require('../lib/parse'); describe('braces.parse()', () => { + describe('errors', () => { + it('should throw an error when string exceeds max safe length', () => { + let MAX_LENGTH = 1024 * 64; + assert.throws(() => parse('.'.repeat(MAX_LENGTH + 2))); + }); + }); + describe('valid', () => { it('should return an AST', () => { let ast = parse('a/{b,c}/d'); diff --git a/test/minimatch.js b/test/minimatch.js new file mode 100644 index 0000000..72a1182 --- /dev/null +++ b/test/minimatch.js @@ -0,0 +1,28 @@ +'use strict'; + +const assert = require('assert').strict; +const braces = require('..'); + +/** + * minimatch v3.0.3 unit tests + */ + +describe('brace expansion', () => { + const units = [ + ['a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + ['a{1..5}b', ['a1b', 'a2b', 'a3b', 'a4b', 'a5b'] ], + ['a{b}c', ['a{b}c']], + ['a{00..05}b', ['a00b', 'a01b', 'a02b', 'a03b', 'a04b', 'a05b'] ], + ['z{a,b},c}d', ['za,c}d', 'zb,c}d']], + ['z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']], + ['a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']], + ['a{b{c{d,e}f{x,y}}g}h', ['a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h'] ], + ['a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh'] ] + ]; + + units.forEach(unit => { + it('should expand: ' + unit[0], () => { + assert.deepEqual(braces.expand(unit[0]), unit[1], unit[0]); + }); + }); +}); diff --git a/test/multiples.js b/test/multiples.js new file mode 100644 index 0000000..e433aa4 --- /dev/null +++ b/test/multiples.js @@ -0,0 +1,58 @@ +'use strict'; + +const assert = require('assert').strict; +const braces = require('..'); + +const equal = (input, expected, options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +describe('multiples', () => { + const patterns = [ + ['-v{,,,,}', ['-v', '-v', '-v', '-v', '-v']], + ['-v{,,,,}{,}', ['-v', '-v', '-v', '-v', '-v', '-v', '-v', '-v', '-v', '-v']], + ['a/b{,}', ['a/b', 'a/b']], + ['a/{,}/b', ['a//b', 'a//b']], + ['a/{,}{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']], + ['a/{a,b,{,}{,}{,},c}/b', ['a/a/b', 'a/b/b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a/c/b']], + ['a/{a,b,{,},c}/b', ['a/a/b', 'a/b/b', 'a//b', 'a//b', 'a/c/b']], + ['a/{a,b,{,}{,}{,}}/b', ['a/a/b', 'a/b/b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b']], + ['a/{b,cz{,}}/{d{,},ef}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef']], + ['a/{b,cz}{,}/{d{,},ef}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef']], + ['a/{b,c{,}}', ['a/b', 'a/c', 'a/c']], + ['a/{b,c{,}}/{,}', ['a/b/', 'a/b/', 'a/c/', 'a/c/', 'a/c/', 'a/c/']], + ['a/{b,c}/{,}', ['a/b/', 'a/b/', 'a/c/', 'a/c/']], + ['a/{b,c}{,}/d{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d']], + ['a/{b,c}{,}/{d,e{,}}', ['a/b/d', 'a/b/e', 'a/b/e', 'a/b/d', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/d', 'a/c/e', 'a/c/e']], + ['a/{b,c}{,}/{d,e}{,}', ['a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e']], + ['a/{b,c}{,}/{d{,},e}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e']], + ['a/{c,d}/{x,y{,}}/e', ['a/c/x/e', 'a/c/y/e', 'a/c/y/e', 'a/d/x/e', 'a/d/y/e', 'a/d/y/e']], + ['a/{c,d}{,}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']], + ['a{,,,,,}', ['a', 'a', 'a', 'a', 'a', 'a']], + ['a{,,,,,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], + ['a{,,,,,}{,,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], + ['a{,,,,,}{,,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], + ['a{,,,,}', ['a', 'a', 'a', 'a', 'a']], + ['a{,,,}', ['a', 'a', 'a', 'a']], + ['a{,,}', ['a', 'a', 'a']], + ['a{,,}{,,}{,,}{,}/b', ['a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b']], + ['a{,,}{,}', ['a', 'a', 'a', 'a', 'a', 'a']], + ['a{,}', ['a', 'a']], + ['a{,}/{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']], + ['a{,}b', ['ab', 'ab']], + ['a{,}{,}', ['a', 'a', 'a', 'a']], + ['a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], + ['a{,}{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], + ['one/{a{,}{,}}/{b/c{,,}{,}{,,}{,}}/two', ['one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two']], + ['{,}', ['', '']], + ['{,}a/{,}', ['a/', 'a/', 'a/', 'a/']], + ['{,}{,}', ['', '', '', '']], + ['{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']], + ['{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']], + ['{a{,,}b{,}}', ['{ab}', '{ab}', '{ab}', '{ab}', '{ab}', '{ab}']] + ]; + + patterns.forEach(pattern => { + it('should expand: ' + pattern[0], () => equal(pattern[0], pattern[1])); + }); +}); diff --git a/test/regression.js b/test/regression.js new file mode 100644 index 0000000..c9529a9 --- /dev/null +++ b/test/regression.js @@ -0,0 +1,437 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const braces = require('..'); + +const equal = (input, expected, options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +describe('braces tests from 1.8.5', () => { + it('braces', () => { + equal('ff{c,b,a}', ['ffc', 'ffb', 'ffa']); + equal('f{d,e,f}g', ['fdg', 'feg', 'ffg']); + equal('{l,n,m}xyz', ['lxyz', 'nxyz', 'mxyz']); + equal('{abc\\,d,ef}', ['abc,d', 'ef']); + equal('{abc}', ['{abc}']); + + equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); + equal('{x,y,\\{a,b,c}}', ['x}', 'y}', '{a}', 'b}', 'c}']); + equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); + + equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']); + + equal('{}', ['{}']); + equal('{ }', ['{ }']); + equal('}', ['}']); + equal('{', ['{']); + equal('abcd{efgh', ['abcd{efgh']); + + equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); + }); + + it('new sequence brace operators', () => { + equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{0..10,braces}', ['0..10', 'braces']); + equal('{braces,{0..10}}', ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); + equal('x{{0..10},braces}y', ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']); + }); + + it('ranges', () => { + equal('{3..3}', ['3']); + equal('x{3..3}y', ['x3y']); + equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); + equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); + equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); + equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); + + equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); + equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); + + equal('{f..f}', ['f']); + equal('0{1..9} {10..20}', ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20']); + }); + + it('mixes are incorrectly-formed brace expansions', () => { + // the first one is valid, but Bash fails on it + equal('{1..f}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); + equal('{f..1}', ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + }); + + it('do negative numbers work?', () => { + equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + }); + + it('weirdly-formed brace expansions -- fixed in post-bash-3.1', () => { + equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); + + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + + equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); + equal('{"x,x"}', ['{x,x}']); + }); + + it('numerical ranges with steps', () => { + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{1..20..20}', ['1']); + + equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + }); + + it('alpha ranges with steps', () => { + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + }); + + it('make sure brace expansion handles ints > 2**31 - 1 using intmax_t', () => { + equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); + }); + + it('unwanted zero-padding -- fixed post-bash-4.0', () => { + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + }); + + it('bad', () => { + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..ff}', ['{1..ff}']); + equal('{1..10..ff}', ['{1..10..ff}']); + equal('{1.20..2}', ['{1.20..2}']); + equal('{1..20..f2}', ['{1..20..f2}']); + equal('{1..20..2f}', ['{1..20..2f}']); + equal('{1..2f..2}', ['{1..2f..2}']); + equal('{1..ff..2}', ['{1..ff..2}']); + equal('{1..ff}', ['{1..ff}']); + equal('{1..0f}', ['{1..0f}']); + equal('{1..10f}', ['{1..10f}']); + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..10.f}', ['{1..10.f}']); + }); +}); + +describe('bash tests', () => { + describe('brace expansion', () => { + it('should return an empty array when no braces are found', () => { + equal('', []); + }); + + it('should expand emty sets', () => { + equal('a{,}', ['a', 'a']); + equal('{,}b', ['b', 'b']); + equal('a{,}b', ['ab', 'ab']); + equal('a{,}', ['a', 'a']); + equal('a{,}{,}', ['a', 'a', 'a', 'a']); + equal('a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); + equal('{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']); + equal('a{,}/{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']); + }); + + it('should eliminate dupes in repeated strings', () => { + equal('a{,}', ['a'], { nodupes: true }); + equal('a{,}{,}', ['a'], { nodupes: true }); + equal('a{,}{,}{,}', ['a'], { nodupes: true }); + equal('{a,b{,}{,}{,}}', ['a', 'b'], { nodupes: true }); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], { nodupes: true }); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], { nodupes: true }); + }); + + it('should work with no braces', () => { + equal('abc', ['abc']); + }); + + it('should work with no commas', () => { + equal('a{b}c', ['a{b}c']); + }); + + it('should work with no commas in `bash` mode', () => { + equal('a{b}c', ['a{b}c']); + }); + + it('should handle spaces', () => { + equal('a{ ,c{d, },h}x', ['a x', 'acdx', 'ac x', 'ahx']); + equal('a{ ,c{d, },h} ', [ 'a ', 'acd ', 'ac ', 'ah ' ]); + + // see https://github.com/jonschlinkert/micromatch/issues/66 + equal('/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', [ + '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html', + '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs' + ]); + }); + + it('should handle empty braces', () => { + equal('{ }', ['{ }']); + equal('{}', ['{}']); + equal('}', ['}']); + equal('{', ['{']); + equal('{,}', ['', '']); + }); + + it('should handle imbalanced braces', () => { + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + equal('abc{', ['abc{']); + equal('{abc{', ['{abc{']); + equal('{abc', ['{abc']); + equal('}abc', ['}abc']); + equal('ab{c', ['ab{c']); + equal('ab{c', ['ab{c']); + equal('{{a,b}', ['{a', '{b']); + equal('{a,b}}', ['a}', 'b}']); + equal('abcd{efgh', ['abcd{efgh']); + equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); + equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); + equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); + equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); + equal('f{x,y{{g}h', ['f{x,y{{g}h']); + equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); + equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']); + equal('f{x,y{}g}h', ['fxh', 'fy{}gh']); + equal('z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']); + }); + + it('should handle invalid braces in `bash mode`:', () => { + equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); + equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); + equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); + equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); + equal('f{x,y{{g}h', ['f{x,y{{g}h']); + equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); + }); + + it('should return invalid braces:', () => { + equal('{0..10,braces}', ['0..10', 'braces']); + }); + + it('should not expand quoted strings.', () => { + equal('{"x,x"}', ['{x,x}']); + equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); + }); + + it('should work with one value', () => { + equal('a{b}c', ['a{b}c']); + equal('a/b/c{d}e', ['a/b/c{d}e']); + }); + + it('should work with one value in `bash` mode', () => { + equal('a{b}c', ['a{b}c']); + equal('a/b/c{d}e', ['a/b/c{d}e']); + }); + + it('should work with nested non-sets', () => { + equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); + equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); + equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); + }); + + it('should work with nested non-sets in `bash` mode', () => { + equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); + equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); + }); + + it('should not expand dots with leading slashes (escaped or paths).', () => { + equal('a{b,c/*/../d}e', ['abe', 'ac/*/../de']); + equal('a{b,b,c/../b}d', ['abd', 'abd', 'ac/../bd']); + }); + + it('should work with commas.', () => { + equal('a{b,}c', ['abc', 'ac']); + equal('a{,b}c', ['ac', 'abc']); + }); + + it('should expand sets', () => { + equal('a/{x,y}/cde', ['a/x/cde', 'a/y/cde']); + equal('a/b/c/{x,y}', ['a/b/c/x', 'a/b/c/y']); + equal('ff{c,b,a}', ['ffc', 'ffb', 'ffa']); + equal('f{d,e,f}g', ['fdg', 'feg', 'ffg']); + equal('{l,n,m}xyz', ['lxyz', 'nxyz', 'mxyz']); + equal('{x,y,{abc},trie}', ['x', 'y', '{abc}', 'trie']); + }); + + it('should expand multiple sets', () => { + equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); + equal('a{b,c}d{e,f}g', ['abdeg', 'abdfg', 'acdeg', 'acdfg']); + equal('a/{x,y}/c{d,e}f.{md,txt}', ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']); + }); + + it('should expand nested sets', () => { + equal('a/{b,c,{d,e}}/g', ['a/b/g', 'a/c/g', 'a/d/g', 'a/e/g']); + equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); + equal('{a,b}{{a,b},a,b}', ['aa', 'ab', 'aa', 'ab', 'ba', 'bb', 'ba', 'bb']); + equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']); + equal('a{b,c{d,e}f}g', ['abg', 'acdfg', 'acefg']); + equal('a{{x,y},z}b', ['axb', 'ayb', 'azb']); + equal('f{x,y{g,z}}h', ['fxh', 'fygh', 'fyzh']); + equal('a{b,c{d,e},h}x/z', ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']); + equal('a{b,c{d,e},h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']); + equal('a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']); + equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); + }); + + it('should expand with globs.', () => { + equal('a/b/{d,e}/*.js', ['a/b/d/*.js', 'a/b/e/*.js']); + equal('a/**/c/{d,e}/f*.js', ['a/**/c/d/f*.js', 'a/**/c/e/f*.js']); + equal('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt']); + }); + + it('should expand with extglobs.', () => { + equal('a/b/{d,e,[1-5]}/*.js', ['a/b/d/*.js', 'a/b/e/*.js', 'a/b/[1-5]/*.js']); + }); + }); + + describe('escaping:', () => { + it('should not expand strings with es6/bash-like variables.', () => { + equal('abc/${ddd}/xyz', ['abc/${ddd}/xyz']); + equal('a${b}c', ['a${b}c']); + equal('a/{${b},c}/d', ['a/${b}/d', 'a/c/d']); + equal('a${b,d}/{foo,bar}c', ['a${b,d}/fooc', 'a${b,d}/barc']); + }); + + it('should not expand escaped commas.', () => { + equal('a{b\\,c}d', ['a{b,c}d']); + equal('a{b\\,c\\,d}e', ['a{b,c,d}e']); + equal('{abc\\,def}', ['{abc,def}']); + equal('{abc\\,def,ghi}', ['abc,def', 'ghi']); + equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); + }); + + it('should return sets with escaped commas in `bash` mode.', () => { + equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); + }); + + it('should not expand escaped braces.', () => { + equal('{a,b\\}c,d}', ['a', 'b}c', 'd']); + equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); + equal('a/{b,\\{a,b,c,d,e}/d', ['a/b/d', 'a/{a/d', 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d']); + equal('a/\\{b,c}/{d,e}/f', ['a/{b,c}/d/f', 'a/{b,c}/e/f']); + }); + + it('should not expand escaped braces or commas.', () => { + equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); + }); + }); +}); + +describe('range expansion', () => { + it('should expand numerical ranges', () => { + equal('a{0..3}d', ['a0d', 'a1d', 'a2d', 'a3d']); + equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); + equal('x{3..3}y', ['x3y']); + equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..3}', ['1', '2', '3']); + equal('{1..9}', ['1', '2', '3', '4', '5', '6', '7', '8', '9']); + equal('{3..3}', ['3']); + equal('{5..8}', ['5', '6', '7', '8']); + }); + + it('should expand alphabetical ranges', () => { + equal('0{a..d}0', ['0a0', '0b0', '0c0', '0d0']); + equal('a/{b..d}/e', ['a/b/e', 'a/c/e', 'a/d/e']); + equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); + equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); + equal('{a..e}', ['a', 'b', 'c', 'd', 'e']); + equal('{A..E}', ['A', 'B', 'C', 'D', 'E']); + equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{E..A}', ['E', 'D', 'C', 'B', 'A']); + equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); + equal('{f..f}', ['f']); + }); + + it('should use steps with alphabetical ranges', () => { + equal('{a..e..2}', ['a', 'c', 'e']); + equal('{E..A..2}', ['E', 'C', 'A']); + }); + + it('should not try to expand ranges with decimals', () => { + equal('{1.1..2.1}', ['{1.1..2.1}']); + }); + + it('should expand negative ranges', () => { + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + equal('{-10..-1}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']); + equal('{0..-5}', ['0', '-1', '-2', '-3', '-4', '-5']); + equal('{9..-4}', ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']); + }); + + it('should expand multiple ranges:', () => { + equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']); + }); + + it('should work with dots in file paths', () => { + equal('../{1..3}/../foo', ['../1/../foo', '../2/../foo', '../3/../foo']); + }); + + it('should expand ranges using steps:', () => { + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{1..20..20}', ['1']); + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); + equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + equal('{1..10..1}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{1..20..20}', ['1']); + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + equal('{2..10..1}', ['2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{2..10..2}', ['2', '4', '6', '8', '10']); + equal('{2..10..3}', ['2', '5', '8']); + }); + + it('should expand negative ranges using steps:', () => { + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-10..-2..2}', ['-10', '-8', '-6', '-4', '-2']); + equal('{-2..-10..1}', ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-2..-10..2}', ['-2', '-4', '-6', '-8', '-10']); + equal('{-2..-10..3}', ['-2', '-5', '-8']); + equal('{-9..9..3}', ['-9', '-6', '-3', '0', '3', '6', '9']); + }); + + it('should expand mixed ranges and sets:', () => { + equal('x{{0..10},braces}y', ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']); + equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); + equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); + }); + + it('should return invalid ranges:', () => { + equal('{1.20..2}', ['{1.20..2}']); + equal('{1..0f}', ['{1..0f}']); + equal('{1..10..ff}', ['{1..10..ff}']); + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..10f}', ['{1..10f}']); + equal('{1..20..2f}', ['{1..20..2f}']); + equal('{1..20..f2}', ['{1..20..f2}']); + equal('{1..2f..2}', ['{1..2f..2}']); + equal('{1..ff..2}', ['{1..ff..2}']); + equal('{1..ff}', ['{1..ff}']); + }); +});