diff --git a/README.md b/README.md index 67f2689..363ad11 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,18 @@ console.log(braces.expand('a{b}c')); console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error ``` +### options.maxSymbols + +**Type**: `Number` + +**Default**: `1024` + +**Description**: Limit the count of unique symbols the input string. + +```js +console.log(braces('a/{b,c}/d', { maxSymbols: 2 })); //=> throws an error +``` + ### options.expand **Type**: `Boolean` diff --git a/lib/constants.js b/lib/constants.js index a937943..3280bea 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -2,6 +2,7 @@ module.exports = { MAX_LENGTH: 1024 * 64, + MAX_SYMBOLS: 1024, // Digits CHAR_0: '0', /* 0 */ diff --git a/lib/parse.js b/lib/parse.js index 145ea26..7a724b8 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,6 +1,7 @@ 'use strict'; const stringify = require('./stringify'); +const {isCorrectBraces, validateInput} = require('./validate-input'); /** * Constants @@ -8,6 +9,7 @@ const stringify = require('./stringify'); const { MAX_LENGTH, + MAX_SYMBOLS, CHAR_BACKSLASH, /* \ */ CHAR_BACKTICK, /* ` */ CHAR_COMMA, /* , */ @@ -34,6 +36,11 @@ const parse = (input, options = {}) => { } let opts = options || {}; + + validateInput(input, { + maxSymbols: opts.maxSymbols || MAX_SYMBOLS, + }); + 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})`); @@ -304,30 +311,43 @@ const parse = (input, options = {}) => { push({ type: 'text', value }); } + flattenBlocks(stack) + markImbalancedBraces(ast); + push({ type: 'eos' }); + + return ast; +}; + +module.exports = parse; + +function markImbalancedBraces({nodes}) { // Mark imbalanced braces and brackets as invalid + for (const node of nodes) { + if (!node.nodes && !node.invalid) { + if (node.type === 'open') node.isOpen = true; + if (node.type === 'close') node.isClose = true; + if (!node.nodes) node.type = 'text'; + + node.invalid = true; + } + + delete node.parent; + delete node.prev; + } +} + +function flattenBlocks(stack) { + let block; do { block = stack.pop(); - if (block.type !== 'root') { - block.nodes.forEach(node => { - if (!node.nodes) { - if (node.type === 'open') node.isOpen = true; - if (node.type === 'close') node.isClose = true; - if (!node.nodes) node.type = 'text'; - node.invalid = true; - } - }); + if (block.type === 'root') + continue; - // 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); - } + // get the location of the block on parent.nodes (block's siblings) + let parent = stack.at(-1); + let index = parent.nodes.indexOf(block); + // replace the (invalid) block with its nodes + parent.nodes.splice(index, 1, ...block.nodes); } while (stack.length > 0); - - push({ type: 'eos' }); - return ast; -}; - -module.exports = parse; +} diff --git a/lib/validate-input.js b/lib/validate-input.js new file mode 100644 index 0000000..0f987b6 --- /dev/null +++ b/lib/validate-input.js @@ -0,0 +1,12 @@ +module.exports.validateInput = (line, {maxSymbols}) => { + const symbols = {}; + + for (const current of line) { + symbols[current] = (symbols[current] || 0) + 1; + } + + for (const [value, count] of Object.entries(symbols)) { + if (count > maxSymbols) + throw SyntaxError(`To many symbols '${value}'. Maximum: ${maxSymbols} allowed. Received: ${count}`); + } +}; diff --git a/test/braces.parse.js b/test/braces.parse.js index b814558..09a2361 100644 --- a/test/braces.parse.js +++ b/test/braces.parse.js @@ -10,6 +10,16 @@ describe('braces.parse()', () => { let MAX_LENGTH = 1024 * 64; assert.throws(() => parse('.'.repeat(MAX_LENGTH + 2))); }); + it('should throw an error when symbols exceeds max symbols count default', () => { + let SYMBOLS= 1024; + assert.throws(() => parse('.'.repeat(MAX_SYMBOLS * 2))); + }); + it('should throw an error when symbols exceeds max symbols count ', () => { + let SYMBOLS= 2; + assert.throws(() => parse('...', { + maxSymbols: 2, + })); + }); }); describe('valid', () => {