'use strict'; const utils = require('./utils'); const { CHAR_ASTERISK, /* * */ CHAR_AT, /* @ */ CHAR_BACKWARD_SLASH, /* \ */ CHAR_COMMA, /* , */ CHAR_DOT, /* . */ CHAR_EXCLAMATION_MARK, /* ! */ CHAR_FORWARD_SLASH, /* / */ CHAR_LEFT_CURLY_BRACE, /* { */ CHAR_LEFT_PARENTHESES, /* ( */ CHAR_LEFT_SQUARE_BRACKET, /* [ */ CHAR_PLUS, /* + */ CHAR_QUESTION_MARK, /* ? */ CHAR_RIGHT_CURLY_BRACE, /* } */ CHAR_RIGHT_PARENTHESES, /* ) */ CHAR_RIGHT_SQUARE_BRACKET /* ] */ } = require('./constants'); const isPathSeparator = code => { return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; }; const depth = token => { if (token.isPrefix !== true) { token.depth = token.isGlobstar ? Infinity : 1; } }; /** * Quickly scans a glob pattern and returns an object with a handful of * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), * `glob` (the actual pattern), `negated` (true if the path starts with `!` but not * with `!(`) and `negatedExtglob` (true if the path starts with `!(`). * * ```js * const pm = require('picomatch'); * console.log(pm.scan('foo/bar/*.js')); * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } * ``` * @param {String} `str` * @param {Object} `options` * @return {Object} Returns an object with tokens and regex source string. * @api public */ const scan = (input, options) => { const opts = options || {}; const length = input.length - 1; const scanToEnd = opts.parts === true || opts.scanToEnd === true; const slashes = []; const tokens = []; const parts = []; let str = input; let index = -1; let start = 0; let lastIndex = 0; let isBrace = false; let isBracket = false; let isGlob = false; let isExtglob = false; let isGlobstar = false; let braceEscaped = false; let backslashes = false; let negated = false; let negatedExtglob = false; let finished = false; let braces = 0; let prev; let code; let token = { value: '', depth: 0, isGlob: false }; const eos = () => index >= length; const peek = () => str.charCodeAt(index + 1); const advance = () => { prev = code; return str.charCodeAt(++index); }; while (index < length) { code = advance(); let next; if (code === CHAR_BACKWARD_SLASH) { backslashes = token.backslashes = true; code = advance(); if (code === CHAR_LEFT_CURLY_BRACE) { braceEscaped = true; } continue; } if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { braces++; while (eos() !== true && (code = advance())) { if (code === CHAR_BACKWARD_SLASH) { backslashes = token.backslashes = true; advance(); continue; } if (code === CHAR_LEFT_CURLY_BRACE) { braces++; continue; } if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) { isBrace = token.isBrace = true; isGlob = token.isGlob = true; finished = true; if (scanToEnd === true) { continue; } break; } if (braceEscaped !== true && code === CHAR_COMMA) { isBrace = token.isBrace = true; isGlob = token.isGlob = true; finished = true; if (scanToEnd === true) { continue; } break; } if (code === CHAR_RIGHT_CURLY_BRACE) { braces--; if (braces === 0) { braceEscaped = false; isBrace = token.isBrace = true; finished = true; break; } } } if (scanToEnd === true) { continue; } break; } if (code === CHAR_FORWARD_SLASH) { slashes.push(index); tokens.push(token); token = { value: '', depth: 0, isGlob: false }; if (finished === true) continue; if (prev === CHAR_DOT && index === (start + 1)) { start += 2; continue; } lastIndex = index + 1; continue; } if (opts.noext !== true) { const isExtglobChar = code === CHAR_PLUS || code === CHAR_AT || code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK || code === CHAR_EXCLAMATION_MARK; if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) { isGlob = token.isGlob = true; isExtglob = token.isExtglob = true; finished = true; if (code === CHAR_EXCLAMATION_MARK && index === start) { negatedExtglob = true; } if (scanToEnd === true) { while (eos() !== true && (code = advance())) { if (code === CHAR_BACKWARD_SLASH) { backslashes = token.backslashes = true; code = advance(); continue; } if (code === CHAR_RIGHT_PARENTHESES) { isGlob = token.isGlob = true; finished = true; break; } } continue; } break; } } if (code === CHAR_ASTERISK) { if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true; isGlob = token.isGlob = true; finished = true; if (scanToEnd === true) { continue; } break; } if (code === CHAR_QUESTION_MARK) { isGlob = token.isGlob = true; finished = true; if (scanToEnd === true) { continue; } break; } if (code === CHAR_LEFT_SQUARE_BRACKET) { while (eos() !== true && (next = advance())) { if (next === CHAR_BACKWARD_SLASH) { backslashes = token.backslashes = true; advance(); continue; } if (next === CHAR_RIGHT_SQUARE_BRACKET) { isBracket = token.isBracket = true; isGlob = token.isGlob = true; finished = true; break; } } if (scanToEnd === true) { continue; } break; } if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) { negated = token.negated = true; start++; continue; } if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { isGlob = token.isGlob = true; if (scanToEnd === true) { while (eos() !== true && (code = advance())) { if (code === CHAR_LEFT_PARENTHESES) { backslashes = token.backslashes = true; code = advance(); continue; } if (code === CHAR_RIGHT_PARENTHESES) { finished = true; break; } } continue; } break; } if (isGlob === true) { finished = true; if (scanToEnd === true) { continue; } break; } } if (opts.noext === true) { isExtglob = false; isGlob = false; } let base = str; let prefix = ''; let glob = ''; if (start > 0) { prefix = str.slice(0, start); str = str.slice(start); lastIndex -= start; } if (base && isGlob === true && lastIndex > 0) { base = str.slice(0, lastIndex); glob = str.slice(lastIndex); } else if (isGlob === true) { base = ''; glob = str; } else { base = str; } if (base && base !== '' && base !== '/' && base !== str) { if (isPathSeparator(base.charCodeAt(base.length - 1))) { base = base.slice(0, -1); } } if (opts.unescape === true) { if (glob) glob = utils.removeBackslashes(glob); if (base && backslashes === true) { base = utils.removeBackslashes(base); } } const state = { prefix, input, start, base, glob, isBrace, isBracket, isGlob, isExtglob, isGlobstar, negated, negatedExtglob }; if (opts.tokens === true) { state.maxDepth = 0; if (!isPathSeparator(code)) { tokens.push(token); } state.tokens = tokens; } if (opts.parts === true || opts.tokens === true) { let prevIndex; for (let idx = 0; idx < slashes.length; idx++) { const n = prevIndex ? prevIndex + 1 : start; const i = slashes[idx]; const value = input.slice(n, i); if (opts.tokens) { if (idx === 0 && start !== 0) { tokens[idx].isPrefix = true; tokens[idx].value = prefix; } else { tokens[idx].value = value; } depth(tokens[idx]); state.maxDepth += tokens[idx].depth; } if (idx !== 0 || value !== '') { parts.push(value); } prevIndex = i; } if (prevIndex && prevIndex + 1 < input.length) { const value = input.slice(prevIndex + 1); parts.push(value); if (opts.tokens) { tokens[tokens.length - 1].value = value; depth(tokens[tokens.length - 1]); state.maxDepth += tokens[tokens.length - 1].depth; } } state.slashes = slashes; state.parts = parts; } return state; }; module.exports = scan;