Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
"use strict";

const { EventEmitter } = require("events");
const globToRegExp = require("glob-to-regexp");
const LinkResolver = require("./LinkResolver");
const getWatcherManager = require("./getWatcherManager");
const globToRegExp = require("./util/globToRegExp");
const watchEventSource = require("./watchEventSource");

/** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
Expand Down Expand Up @@ -45,6 +45,7 @@ const watchEventSource = require("./watchEventSource");
/** @typedef {`scan (${string})` | "change" | "rename" | `watch ${string}` | `directory-removed ${string}`} EventType */
/** @typedef {{ safeTime: number, timestamp: number, accuracy: number }} Entry */
/** @typedef {{ safeTime: number }} OnlySafeTimeEntry */

// eslint-disable-next-line jsdoc/ts-no-empty-object-type
/** @typedef {{ }} ExistenceOnlyTimeEntry */
/** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null>} TimeInfoEntries */
Expand Down Expand Up @@ -73,8 +74,7 @@ const stringToRegexp = (ignored) => {
if (ignored.length === 0) {
return;
}
const { source } = globToRegExp(ignored, { globstar: true, extended: true });
return `${source.slice(0, -1)}(?:$|\\/)`;
return `^${globToRegExp(ignored)}(?:$|\\/)`;
};

/**
Expand Down Expand Up @@ -568,4 +568,27 @@ class Watchpack extends EventEmitter {
}
}

module.exports = Watchpack;
/**
* @template A
* @template B
* @param {A} obj input a
* @param {B} exports input b
* @returns {A & B} merged
*/
const mergeExports = (obj, exports) => {
const descriptors = Object.getOwnPropertyDescriptors(exports);
Object.defineProperties(obj, descriptors);
return /** @type {A & B} */ (Object.freeze(obj));
};

/** @typedef {typeof Watchpack & { util: { readonly globToRegExp: typeof globToRegExp } }} WatchpackExports */

module.exports = /** @type {WatchpackExports} */ (
mergeExports(Watchpack, {
util: {
get globToRegExp() {
return globToRegExp;
},
},
})
);
142 changes: 142 additions & 0 deletions lib/util/globToRegExp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Haijie Xie @hai-x
*/

"use strict";

// Based on https://github.com/fitzgen/glob-to-regexp (MIT)
// 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;
};
7 changes: 0 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"release": "changeset publish"
},
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"devDependencies": {
Expand Down
Loading
Loading