Source code
Revision control
Copy as Markdown
Other Tools
/*
Author Haijie Xie @hai-x
*/
"use strict";
// Specialized for watchpack: `extended` and `globstar` always enabled.
// Returns the regexp source without `^`/`$` anchors.
const CC_EXCLAMATION = 33; // "!"
const CC_DOLLAR = 36; // "$"
const CC_LEFT_PARENTHESIS = 40; // "("
const CC_RIGHT_PARENTHESIS = 41; // ")"
const CC_ASTERISK = 42; // "*"
const CC_PLUS = 43; // "+"
const CC_COMMA = 44; // ","
const CC_DOT = 46; // "."
const CC_SLASH = 47; // "/"
const CC_EQUAL = 61; // "="
const CC_QUESTION_MARK = 63; // "?"
const CC_LEFT_BRACKET = 91; // "["
const CC_RIGHT_BRACKET = 93; // "]"
const CC_CARET = 94; // "^"
const CC_LEFT_BRACE = 123; // "{"
const CC_PIPE = 124; // "|"
const CC_RIGHT_BRACE = 125; // "}"
/**
* @param {string} glob glob pattern
* @returns {string} regexp source without anchors
*/
module.exports = (glob) => {
if (typeof glob !== "string") {
throw new TypeError("Expected a string");
}
const len = glob.length;
let reStr = "";
let inGroup = false;
// Start of the current run of literal characters, copied with one slice
let literalStart = 0;
for (let i = 0; i < len; i++) {
const cc = glob.charCodeAt(i);
const tokenStart = i;
let mapped;
switch (cc) {
case CC_SLASH:
mapped = "\\/";
break;
case CC_DOLLAR:
mapped = "\\$";
break;
case CC_CARET:
mapped = "\\^";
break;
case CC_PLUS:
mapped = "\\+";
break;
case CC_DOT:
mapped = "\\.";
break;
case CC_LEFT_PARENTHESIS:
mapped = "\\(";
break;
case CC_RIGHT_PARENTHESIS:
mapped = "\\)";
break;
case CC_EQUAL:
mapped = "\\=";
break;
case CC_EXCLAMATION:
mapped = "\\!";
break;
case CC_PIPE:
mapped = "\\|";
break;
case CC_QUESTION_MARK:
mapped = ".";
break;
case CC_LEFT_BRACKET:
mapped = "[";
break;
case CC_RIGHT_BRACKET:
mapped = "]";
break;
case CC_LEFT_BRACE:
inGroup = true;
mapped = "(";
break;
case CC_RIGHT_BRACE:
inGroup = false;
mapped = ")";
break;
case CC_COMMA:
mapped = inGroup ? "|" : "\\,";
break;
case CC_ASTERISK: {
const atStart = i === 0;
const afterSlash = !atStart && glob.charCodeAt(i - 1) === CC_SLASH;
let starCount = 1;
while (i + 1 < len && glob.charCodeAt(i + 1) === CC_ASTERISK) {
starCount++;
i++;
}
const atEnd = i + 1 === len;
const beforeSlash = !atEnd && glob.charCodeAt(i + 1) === CC_SLASH;
if (
starCount > 1 &&
(atStart || afterSlash) &&
(atEnd || beforeSlash)
) {
// Globstar segment, matches zero or more path segments
mapped = "((?:[^/]*(?:\\/|$))*)";
i++; // Move over the "/"
} else {
// Not a globstar, matches one path segment
mapped = "([^/]*)";
}
break;
}
default:
// Literal character, extend the current run
continue;
}
if (literalStart < tokenStart) {
reStr += glob.slice(literalStart, tokenStart);
}
reStr += mapped;
literalStart = i + 1;
}
if (literalStart < len) {
reStr += literalStart === 0 ? glob : glob.slice(literalStart);
}
return reStr;
};