Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/braces/lib/parse.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
333 lines (267 sloc)
6.75 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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, /* [ */ | |
CHAR_RIGHT_SQUARE_BRACKET, /* ] */ | |
CHAR_DOUBLE_QUOTE, /* " */ | |
CHAR_SINGLE_QUOTE, /* ' */ | |
CHAR_NO_BREAK_SPACE, | |
CHAR_ZERO_WIDTH_NOBREAK_SPACE | |
} = require('./constants'); | |
/** | |
* parse | |
*/ | |
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 block = ast; | |
let prev = ast; | |
let brackets = 0; | |
let length = input.length; | |
let index = 0; | |
let depth = 0; | |
let value; | |
let memo = {}; | |
/** | |
* Helpers | |
*/ | |
const advance = () => input[index++]; | |
const push = node => { | |
if (node.type === 'text' && prev.type === 'dot') { | |
prev.type = 'text'; | |
} | |
if (prev && prev.type === 'text' && node.type === 'text') { | |
prev.value += node.value; | |
return; | |
} | |
block.nodes.push(node); | |
node.parent = block; | |
node.prev = prev; | |
prev = node; | |
return node; | |
}; | |
push({ type: 'bos' }); | |
while (index < length) { | |
block = stack[stack.length - 1]; | |
value = advance(); | |
/** | |
* Invalid chars | |
*/ | |
if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { | |
continue; | |
} | |
/** | |
* Escaped chars | |
*/ | |
if (value === CHAR_BACKSLASH) { | |
push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); | |
continue; | |
} | |
/** | |
* Right square bracket (literal): ']' | |
*/ | |
if (value === CHAR_RIGHT_SQUARE_BRACKET) { | |
push({ type: 'text', value: '\\' + value }); | |
continue; | |
} | |
/** | |
* Left square bracket: '[' | |
*/ | |
if (value === CHAR_LEFT_SQUARE_BRACKET) { | |
brackets++; | |
let closed = true; | |
let next; | |
while (index < length && (next = advance())) { | |
value += next; | |
if (next === CHAR_LEFT_SQUARE_BRACKET) { | |
brackets++; | |
continue; | |
} | |
if (next === CHAR_BACKSLASH) { | |
value += advance(); | |
continue; | |
} | |
if (next === CHAR_RIGHT_SQUARE_BRACKET) { | |
brackets--; | |
if (brackets === 0) { | |
break; | |
} | |
} | |
} | |
push({ type: 'text', value }); | |
continue; | |
} | |
/** | |
* 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) { | |
let open = value; | |
let next; | |
if (options.keepQuotes !== true) { | |
value = ''; | |
} | |
while (index < length && (next = advance())) { | |
if (next === CHAR_BACKSLASH) { | |
value += next + advance(); | |
continue; | |
} | |
if (next === open) { | |
if (options.keepQuotes === true) value += next; | |
break; | |
} | |
value += next; | |
} | |
push({ type: 'text', value }); | |
continue; | |
} | |
/** | |
* Left curly brace: '{' | |
*/ | |
if (value === CHAR_LEFT_CURLY_BRACE) { | |
depth++; | |
let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; | |
let brace = { | |
type: 'brace', | |
open: true, | |
close: false, | |
dollar, | |
depth, | |
commas: 0, | |
ranges: 0, | |
nodes: [] | |
}; | |
block = push(brace); | |
stack.push(block); | |
push({ type: 'open', value }); | |
continue; | |
} | |
/** | |
* Right curly brace: '}' | |
*/ | |
if (value === CHAR_RIGHT_CURLY_BRACE) { | |
if (block.type !== 'brace') { | |
push({ type: 'text', value }); | |
continue; | |
} | |
let type = 'close'; | |
block = stack.pop(); | |
block.close = true; | |
push({ type, value }); | |
depth--; | |
block = stack[stack.length - 1]; | |
continue; | |
} | |
/** | |
* Comma: ',' | |
*/ | |
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; | |
} | |
/** | |
* Dot: '.' | |
*/ | |
if (value === CHAR_DOT && depth > 0 && block.commas === 0) { | |
let siblings = block.nodes; | |
if (depth === 0 || siblings.length === 0) { | |
push({ type: 'text', value }); | |
continue; | |
} | |
if (prev.type === 'dot') { | |
block.range = []; | |
prev.value += value; | |
prev.type = 'range'; | |
if (block.nodes.length !== 3 && block.nodes.length !== 5) { | |
block.invalid = true; | |
block.ranges = 0; | |
prev.type = 'text'; | |
continue; | |
} | |
block.ranges++; | |
block.args = []; | |
continue; | |
} | |
if (prev.type === 'range') { | |
siblings.pop(); | |
let before = siblings[siblings.length - 1]; | |
before.value += prev.value + value; | |
prev = before; | |
block.ranges--; | |
continue; | |
} | |
push({ type: 'dot', value }); | |
continue; | |
} | |
/** | |
* Text | |
*/ | |
push({ type: 'text', value }); | |
} | |
// Mark imbalanced braces and brackets as invalid | |
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; | |
} | |
}); | |
// 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); | |
push({ type: 'eos' }); | |
return ast; | |
}; | |
module.exports = parse; |