You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

cspell-glob

Package Overview
Dependencies
Maintainers
1
Versions
308
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cspell-glob - npm Package Compare versions

Comparing version
9.6.0
to
9.6.1
+202
-3
dist/index.d.ts

@@ -1,4 +0,203 @@

export { fileOrGlobToGlob, isGlobPatternNormalized, isGlobPatternWithOptionalRoot, isGlobPatternWithRoot, normalizeGlobPatterns, NormalizeOptions, workaroundPicomatchBug, } from './globHelper.js';
export { GlobMatcher, GlobMatchOptions } from './GlobMatcher.js';
export * from './GlobMatcherTypes.js';
//#region src/GlobMatcherTypes.d.ts
interface PathInterface {
normalize(p: string): string;
join(...paths: string[]): string;
resolve(...paths: string[]): string;
relative(from: string, to: string): string;
isAbsolute(p: string): boolean;
parse(p: string): {
root: string;
dir: string;
base: string;
ext: string;
name: string;
};
sep: string;
}
type GlobMatch = GlobMatchRule | GlobMatchNoRule;
interface GlobMatchRule {
matched: boolean;
glob: string;
root: string;
pattern: GlobPatternWithRoot;
index: number;
isNeg: boolean;
}
interface GlobMatchNoRule {
matched: false;
}
type GlobPattern = SimpleGlobPattern | GlobPatternWithRoot | GlobPatternWithOptionalRoot;
type SimpleGlobPattern = string;
interface GlobPatternWithOptionalRoot {
/**
* a glob pattern
*/
glob: string;
/**
* The root from which the glob pattern is relative.
* @default: options.root
*/
root?: string | undefined;
/**
* Optional value useful for tracing which file a glob pattern was defined in.
*/
source?: string | undefined;
/**
* Optional line number in the source
*/
line?: number | undefined;
}
interface GlobPatternWithRoot extends GlobPatternWithOptionalRoot {
root: string;
/**
* Global patterns do not need to be relative to the root.
* Note: Some patterns start with `**` but they are tied to the root. In this case, `isGlobalPattern` is `false`.
*/
isGlobalPattern: boolean;
}
interface GlobPatternNormalized extends GlobPatternWithRoot {
/** the original glob pattern before it was normalized */
rawGlob: string;
/** the original root */
rawRoot: string | undefined;
}
//#endregion
//#region src/globHelper.d.ts
/**
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
* @param fileOrGlob - file (with absolute path) or glob.
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
* @param path - optional node path methods - used for testing
*/
declare function fileOrGlobToGlob(fileOrGlob: string | GlobPattern, root: string, path?: PathInterface): GlobPatternWithRoot;
declare function isGlobPatternWithOptionalRoot(g: GlobPattern): g is GlobPatternWithOptionalRoot;
declare function isGlobPatternWithRoot(g: GlobPattern): g is GlobPatternWithRoot;
declare function isGlobPatternNormalized(g: GlobPattern | GlobPatternNormalized): g is GlobPatternNormalized;
interface NormalizeOptions {
/**
* Indicates that the glob should be modified to match nested patterns.
*
* Example: `node_modules` becomes `**​/node_modules/​**`, `**​/node_modules`, and `node_modules/​**`
*/
nested: boolean;
/**
* This is the root to use for the glob if the glob does not already contain one.
*/
root: string;
/**
* This is the replacement for `${cwd}` in either the root or in the glob.
*/
cwd?: string;
/**
* Optional path interface for working with paths.
*/
nodePath?: PathInterface;
}
/**
*
* @param patterns - glob patterns to normalize.
* @param options - Normalization options.
*/
declare function normalizeGlobPatterns(patterns: GlobPattern[], options: NormalizeOptions): GlobPatternNormalized[];
declare function workaroundPicomatchBug(glob: string): string;
//#endregion
//#region src/GlobMatcher.d.ts
type Optional<T> = { [P in keyof T]?: T[P] | undefined };
type GlobMatchOptions = Optional<NormalizedGlobMatchOptions>;
type MatcherMode = 'exclude' | 'include';
interface NormalizedGlobMatchOptions {
/**
* The matcher has two modes (`include` or `exclude`) that impact how globs behave.
*
* `include` - designed for searching for file. By default it matches a sub-set of file.
* In include mode, the globs need to be more explicit to match.
* - `dot` is by default false.
* - `nested` is by default false.
*
* `exclude` - designed to emulate `.gitignore`. By default it matches a larger range of files.
* - `dot` is by default true.
* - `nested` is by default true.
*
* @default: 'exclude'
*/
mode: MatcherMode;
/**
* The default directory from which a glob is relative.
* Any globs that are not relative to the root will ignored.
* @default: process.cwd()
*/
root: string;
/**
* The directory to use as the current working directory.
* @default: process.cwd();
*/
cwd: string;
/**
* Allows matching against directories with a leading `.`.
*
* @default: mode == 'exclude'
*/
dot: boolean;
/**
* Allows matching against nested directories or files without needing to add `**`
*
* @default: mode == 'exclude'
*/
nested: boolean;
/**
* Mostly used for testing purposes. It allows explicitly specifying `path.win32` or `path.posix`.
*
* @default: require('path')
*/
nodePath: PathInterface;
/**
* Disable brace matching, so that `{a,b}` and `{1..3}` would be treated as literal characters.
*
* @default false
*/
nobrace?: boolean | undefined;
}
declare class GlobMatcher {
/**
* @param filename full path of file to match against.
* @returns a GlobMatch - information about the match.
*/
readonly matchEx: (filename: string) => GlobMatch;
readonly path: PathInterface;
readonly patterns: GlobPatternWithRoot[];
readonly patternsNormalizedToRoot: GlobPatternNormalized[];
/**
* path or href of the root directory.
*/
readonly root: string;
readonly dot: boolean;
readonly options: NormalizedGlobMatchOptions;
/**
* Instance ID
*/
readonly id: number;
/**
* Construct a `.gitignore` emulator
* @param patterns - the contents of a `.gitignore` style file or an array of individual glob rules.
* @param root - the working directory
*/
constructor(patterns: GlobPattern | GlobPattern[], root?: string | URL, nodePath?: PathInterface);
/**
* Construct a `.gitignore` emulator
* @param patterns - the contents of a `.gitignore` style file or an array of individual glob rules.
* @param options - to set the root and other options
*/
constructor(patterns: GlobPattern | GlobPattern[], options?: GlobMatchOptions);
constructor(patterns: GlobPattern | GlobPattern[], rootOrOptions?: string | URL | GlobMatchOptions);
/**
* Check to see if a filename matches any of the globs.
* If filename is relative, it is considered relative to the root.
* If filename is absolute and contained within the root, it will be made relative before being tested for a glob match.
* If filename is absolute and not contained within the root, it will be tested as is.
* @param filename full path of the file to check.
*/
match(filename: string): boolean;
}
//#endregion
export { GlobMatch, GlobMatchNoRule, type GlobMatchOptions, GlobMatchRule, GlobMatcher, GlobPattern, GlobPatternNormalized, GlobPatternWithOptionalRoot, GlobPatternWithRoot, type NormalizeOptions, PathInterface, SimpleGlobPattern, fileOrGlobToGlob, isGlobPatternNormalized, isGlobPatternWithOptionalRoot, isGlobPatternWithRoot, normalizeGlobPatterns, workaroundPicomatchBug };
//# sourceMappingURL=index.d.ts.map

@@ -1,4 +0,556 @@

export { fileOrGlobToGlob, isGlobPatternNormalized, isGlobPatternWithOptionalRoot, isGlobPatternWithRoot, normalizeGlobPatterns, workaroundPicomatchBug, } from './globHelper.js';
export { GlobMatcher } from './GlobMatcher.js';
export * from './GlobMatcherTypes.js';
import * as Path from "node:path";
import { FileUrlBuilder } from "@cspell/url";
import pm from "picomatch";
//#region src/globHelper.ts
const { posix } = Path;
/** test for glob patterns starting with `**` */
const isGlobalPatternRegExp = /^!*[*]{2}/;
const hasGlobCharactersRegExp = /[*?{}[\]]/;
const fileUrlBuilder = new FileUrlBuilder();
const GlobPlaceHolders = { cwd: "${cwd}" };
const GlobPatterns = {
suffixAny: "/**",
suffixDir: "/**/*",
prefixAny: "**/"
};
let cacheCalls = 0;
let cacheMisses = 0;
let cachePath = Path;
let cacheRoot = "<>";
const cache = /* @__PURE__ */ new Map();
/**
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
* @param fileOrGlob - file (with absolute path) or glob.
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
* @param path - optional node path methods - used for testing
*/
function fileOrGlobToGlob(fileOrGlob, root, path = Path) {
if (cacheRoot !== root || cachePath !== path) {
cache.clear();
cacheCalls = 0;
cacheMisses = 0;
cacheRoot = root;
cachePath = path;
}
++cacheCalls;
const found = cache.get(fileOrGlob);
if (found) return found;
++cacheMisses;
const pattern = _fileOrGlobToGlob(fileOrGlob, root, path);
cache.set(fileOrGlob, pattern);
return pattern;
}
/**
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
* @param fileOrGlob - file (with absolute path) or glob.
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
* @param path - optional node path methods - used for testing
*/
function _fileOrGlobToGlob(fileOrGlob, root, path = Path) {
const toForwardSlash = path.sep === "\\" ? (p) => p.replaceAll("\\", "/") : (p) => p;
const builder = urlBuilder(path);
fileOrGlob = typeof fileOrGlob === "string" ? toForwardSlash(fileOrGlob) : fileOrGlob;
const rootUrl = builder.toFileDirURL(root);
root = builder.urlToFilePathOrHref(rootUrl);
return toGlobPatternWithRoot(fileOrGlob, root, builder);
}
function toGlobPatternWithRoot(glob, root, builder) {
function toPattern() {
if (isGlobPatternWithRoot(glob)) return fixPatternRoot({ ...glob }, builder);
const rootUrl = builder.toFileDirURL(root);
if (typeof glob === "string") return filePathOrGlobToGlob(glob, rootUrl, builder);
const pattern$1 = {
isGlobalPattern: isGlobalGlob(glob.glob),
...glob,
root: glob.root ?? root
};
fixPatternRoot(pattern$1, builder);
fixPatternGlob(pattern$1, builder);
return pattern$1;
}
const pattern = toPattern();
if (pattern.glob.startsWith(GlobPlaceHolders.cwd)) {
pattern.root = GlobPlaceHolders.cwd;
pattern.glob = pattern.glob.replace(GlobPlaceHolders.cwd, "");
}
return pattern;
}
function isGlobPatternWithOptionalRoot(g) {
return typeof g !== "string" && typeof g.glob === "string";
}
function isGlobPatternWithRoot(g) {
if (typeof g === "string") return false;
return typeof g.root === "string" && "isGlobalPattern" in g;
}
function isGlobPatternNormalized(g) {
if (!isGlobPatternWithRoot(g)) return false;
const gr = g;
return "rawGlob" in gr && "rawRoot" in gr && typeof gr.rawGlob === "string";
}
function isGlobPatternNormalizedToRoot(g, options) {
if (!isGlobPatternNormalized(g)) return false;
return g.root === options.root;
}
function urlBuilder(path = Path) {
return path === Path ? fileUrlBuilder : new FileUrlBuilder({ path });
}
/**
* @param pattern glob pattern
* @param nested when true add `**​/<glob>/​**`
* @returns the set of matching globs.
*/
function normalizePattern(pattern, nested) {
pattern = pattern.replace(/^(!!)+/, "");
const isNeg = pattern.startsWith("!");
const prefix = isNeg ? "!" : "";
pattern = isNeg ? pattern.slice(1) : pattern;
return (nested ? normalizePatternNested(pattern) : normalizePatternGeneral(pattern)).map((p) => prefix + p);
}
function normalizePatternNested(pattern) {
if (!pattern.includes("/")) {
if (pattern === "**") return ["**"];
return ["**/" + pattern, "**/" + pattern + "/**"];
}
const hasLeadingSlash = pattern.startsWith("/");
pattern = hasLeadingSlash ? pattern.slice(1) : pattern;
if (pattern.endsWith("/")) return hasLeadingSlash || pattern.slice(0, -1).includes("/") ? [pattern + "**/*"] : ["**/" + pattern + "**/*"];
if (pattern.endsWith("**")) return [pattern];
return [pattern, pattern + "/**"];
}
function normalizePatternGeneral(pattern) {
pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
pattern = pattern.endsWith("/") ? pattern + "**/*" : pattern;
return [pattern];
}
/**
*
* @param patterns - glob patterns to normalize.
* @param options - Normalization options.
*/
function normalizeGlobPatterns(patterns, options) {
function* normalize() {
for (const glob of patterns) {
if (isGlobPatternNormalized(glob)) {
yield isGlobPatternNormalizedToRoot(glob, options) ? glob : normalizeGlobToRoot(glob, options.root, options.nodePath || Path);
continue;
}
yield* normalizeGlobPattern(glob, options);
}
}
return [...normalize()];
}
function normalizeGlobPattern(g, options) {
const { root, nodePath: path = Path, nested } = options;
const builder = urlBuilder(path);
const cwd = options.cwd ?? path.resolve();
const cwdUrl = builder.toFileDirURL(cwd);
const rootUrl = builder.toFileDirURL(root, cwdUrl);
const gIsGlobalPattern = isGlobPatternWithRoot(g) ? g.isGlobalPattern : void 0;
g = !isGlobPatternWithOptionalRoot(g) ? { glob: g } : g;
const gr = {
...g,
root: g.root ?? root
};
const rawRoot = gr.root;
const rawGlob = g.glob;
gr.glob = trimGlob(g.glob);
if (gr.glob.startsWith(GlobPlaceHolders.cwd)) {
gr.glob = gr.glob.replace(GlobPlaceHolders.cwd, "");
gr.root = GlobPlaceHolders.cwd;
}
if (gr.root.startsWith(GlobPlaceHolders.cwd)) {
const relRoot = gr.root.replace(GlobPlaceHolders.cwd, "./");
const r = builder.toFileDirURL(relRoot, cwdUrl);
r.pathname = posix.normalize(r.pathname);
gr.root = builder.urlToFilePathOrHref(r);
}
const isGlobalPattern = gIsGlobalPattern ?? isGlobalGlob(gr.glob);
gr.root = builder.urlToFilePathOrHref(builder.toFileDirURL(gr.root, rootUrl));
return normalizePattern(gr.glob, nested).map((glob) => ({
...gr,
glob,
rawGlob,
rawRoot,
isGlobalPattern
}));
}
/**
* Try to adjust the root of a glob to match a new root. If it is not possible, the original glob is returned.
* Note: this does NOT generate absolutely correct glob patterns. The results are intended to be used as a
* first pass only filter. Followed by testing against the original glob/root pair.
* @param glob - glob to map
* @param root - new root to use if possible
* @param path - Node Path modules to use (testing only)
*/
function normalizeGlobToRoot(glob, root, path) {
const builder = urlBuilder(path);
glob = { ...glob };
fixPatternRoot(glob, builder);
const rootURL = builder.toFileDirURL(root);
root = builder.urlToFilePathOrHref(rootURL);
if (glob.root === root) return glob;
const globRootUrl = builder.toFileDirURL(glob.root);
const relFromRootToGlob = builder.relative(rootURL, globRootUrl);
if (!relFromRootToGlob) return glob;
if (glob.isGlobalPattern) return {
...glob,
root
};
const relFromGlobToRoot = builder.relative(globRootUrl, rootURL);
const globIsUnderRoot = isRelativeValueNested(relFromRootToGlob);
const rootIsUnderGlob = isRelativeValueNested(relFromGlobToRoot);
if (!globIsUnderRoot && !rootIsUnderGlob) return glob;
const isNeg = glob.glob.startsWith("!");
const g = isNeg ? glob.glob.slice(1) : glob.glob;
const prefix = isNeg ? "!" : "";
if (globIsUnderRoot) {
const relGlob = relFromRootToGlob;
return {
...glob,
glob: prefix + posix.join(relGlob, g),
root
};
}
const rebasedGlob = rebaseGlob(g, nRel(relFromRootToGlob), nRel(relFromGlobToRoot));
return rebasedGlob ? {
...glob,
glob: prefix + rebasedGlob,
root
} : glob;
}
function nRel(rel) {
return rel.endsWith("/") ? rel : rel + "/";
}
function isRelativeValueNested(rel) {
return !rel || !(rel === ".." || rel.startsWith("../") || rel.startsWith("/"));
}
/**
* Rebase a glob string to a new root.
* @param glob - glob string
* @param fromRootToGlob - relative path from root to globRoot
* @param fromGlobToRoot - relative path from globRoot to root
*/
function rebaseGlob(glob, fromRootToGlob, fromGlobToRoot) {
if (!fromGlobToRoot || fromGlobToRoot === "/") return glob;
if (fromRootToGlob.startsWith("../") && !fromGlobToRoot.startsWith("../") && glob.startsWith("**")) return glob;
fromRootToGlob = nRel(fromRootToGlob);
fromGlobToRoot = nRel(fromGlobToRoot);
const relToParts = fromRootToGlob.split("/");
const relFromParts = fromGlobToRoot.split("/");
if (glob.startsWith(fromGlobToRoot) && fromRootToGlob === "../".repeat(relToParts.length - 1)) return glob.slice(fromGlobToRoot.length);
const lastRelIdx = relToParts.findIndex((s) => s !== "..");
const lastRel = lastRelIdx < 0 ? relToParts.length : lastRelIdx;
const globParts = [...relToParts.slice(lastRel).filter((a) => a), ...glob.split("/")];
relToParts.length = lastRel;
if (fromRootToGlob.startsWith("../") && relFromParts.length !== relToParts.length + 1) return fromRootToGlob + (glob.startsWith("/") ? glob.slice(1) : glob);
for (let i = 0; i < relFromParts.length && i < globParts.length; ++i) {
const relSeg = relFromParts[i];
const globSeg = globParts[i];
if (!relSeg || globSeg === "**") return globParts.slice(i).join("/");
if (relSeg !== globSeg && globSeg !== "*") break;
}
return fromRootToGlob + (glob.startsWith("/") ? glob.slice(1) : glob);
}
/**
* Trims any trailing spaces, tabs, line-feeds, new-lines, and comments
* @param glob - glob string
* @returns trimmed glob
*/
function trimGlob(glob) {
glob = globRemoveComment(glob);
glob = trimGlobLeft(glob);
glob = trimGlobRight(glob);
return glob;
}
function globRemoveComment(glob) {
return glob.replace(/(?<=^|\s)#.*/, "");
}
const spaces = {
" ": true,
" ": true,
"\n": true,
"\r": true
};
/**
* Trim any trailing spaces, tabs, line-feeds, or new-lines
* Handles a trailing \<space>
* @param glob - glob string
* @returns glob string with space to the right removed.
*/
function trimGlobRight(glob) {
let i = glob.length - 1;
while (i >= 0 && glob[i] in spaces) --i;
if (glob[i] === "\\") ++i;
++i;
return i ? glob.slice(0, i) : "";
}
/**
* Trim any leading spaces, tabs, line-feeds, or new-lines
* @param glob - any string
* @returns string with leading spaces removed.
*/
function trimGlobLeft(glob) {
return glob.trimStart();
}
/**
* Test if a glob pattern has a leading `**`.
* @param glob - the glob
* @returns true if the glob pattern starts with `**`
*/
function isGlobalGlob(glob) {
return isGlobalPatternRegExp.test(glob);
}
function hasGlobCharacters(glob) {
return hasGlobCharactersRegExp.test(glob);
}
function isGlobPart(part) {
if (part === GlobPlaceHolders.cwd) return false;
return hasGlobCharacters(part);
}
/**
* Split a glob into a path and a glob portion.
* The path portion does not contain any glob characters.
* Path might be empty. The glob portion should always be non-empty.
* @param glob - glob string pattern
* @returns
*/
function splitGlob(glob) {
const parts = glob.split("/");
const p = parts.findIndex(isGlobPart);
const s = p < 0 ? parts.length - 1 : p;
return createSplitGlob(s ? parts.slice(0, s).join("/") + "/" : void 0, parts.slice(s).join("/"));
}
/**
* Split a glob into a path and a glob portion.
* The path portion does not contain any glob characters.
* Path might be empty. The glob portion should always be non-empty.
* @param glob - glob string pattern
* @param relOnly - Indicates that only `..` and `.` path segments are considered for the path.
* @returns
*/
function splitGlobRel(glob) {
const parts = glob.split("/");
if (!parts.includes("..") && !parts.includes(".")) return {
path: void 0,
glob
};
const firstGlobPartIdx = parts.findIndex(isGlobPart);
const lastRelIdx = Math.max(parts.lastIndexOf(".."), parts.lastIndexOf("."));
const p = firstGlobPartIdx >= 0 ? Math.min(firstGlobPartIdx, lastRelIdx + 1) : lastRelIdx + 1;
const s = p < 0 ? parts.length - 1 : p;
return createSplitGlob(s ? parts.slice(0, s).join("/") + "/" : void 0, parts.slice(s).join("/"));
}
function createSplitGlob(path, glob) {
glob = path ? "/" + glob : glob;
glob = glob.startsWith("/**") ? glob.slice(1) : glob;
return {
path,
glob
};
}
function rootToUrl(root, builder) {
if (root.startsWith(GlobPlaceHolders.cwd)) return new URL(builder.normalizeFilePathForUrl(root.replace(GlobPlaceHolders.cwd, ".")), builder.cwd);
return builder.toFileDirURL(root);
}
function fixPatternRoot(glob, builder) {
if (glob.root.startsWith(GlobPlaceHolders.cwd)) return glob;
glob.root = builder.urlToFilePathOrHref(rootToUrl(glob.root, builder));
return glob;
}
/**
* Adjust the glob pattern in case it is a file or a relative glob.
* @param glob
* @param builder
* @returns
*/
function fixPatternGlob(glob, builder) {
const rootURL = builder.toFileURL(glob.root);
const split = splitGlobRel(glob.glob);
glob.glob = split.glob;
if (split.path !== void 0) {
const relRootPath = split.path.startsWith("/") ? "." + split.path : split.path;
glob.root = builder.urlToFilePathOrHref(builder.toFileDirURL(relRootPath, glob.root));
}
fixPatternRelativeToRoot(glob, rootURL, builder);
}
function fixPatternRelativeToRoot(glob, root, builder) {
if (glob.root.startsWith(GlobPlaceHolders.cwd)) return;
const rel = builder.relative(root, builder.toFileDirURL(glob.root));
if (rel.startsWith("/") || rel.startsWith("../")) return;
glob.root = builder.urlToFilePathOrHref(root);
glob.glob = rel + glob.glob;
}
function filePathOrGlobToGlob(filePathOrGlob, root, builder) {
const isGlobalPattern = isGlobalGlob(filePathOrGlob);
const { path, glob } = builder.isAbsolute(filePathOrGlob) ? splitGlob(filePathOrGlob) : splitGlobRel(filePathOrGlob);
const url = builder.toFileDirURL(path || "./", root);
return {
root: builder.urlToFilePathOrHref(url),
glob,
isGlobalPattern
};
}
function workaroundPicomatchBug(glob) {
const obj = {};
return glob.split("/").map((s) => obj[s] ? `{${s},${s}}` : s).join("/");
}
//#endregion
//#region src/GlobMatcher.ts
let idGlobMatcher = 0;
var GlobMatcher = class {
/**
* @param filename full path of file to match against.
* @returns a GlobMatch - information about the match.
*/
matchEx;
path;
patterns;
patternsNormalizedToRoot;
/**
* path or href of the root directory.
*/
root;
dot;
options;
/**
* Instance ID
*/
id;
constructor(patterns, rootOrOptions, _nodePath) {
this.id = idGlobMatcher++;
const options = typeof rootOrOptions === "string" || rootOrOptions instanceof URL ? { root: rootOrOptions.toString() } : rootOrOptions ?? {};
const mode = options.mode ?? "exclude";
const isExcludeMode = mode !== "include";
const nodePath = options.nodePath ?? _nodePath ?? Path;
this.path = nodePath;
const cwd = options.cwd ?? nodePath.resolve();
const dot = options.dot ?? isExcludeMode;
const nested = options.nested ?? isExcludeMode;
const nobrace = options.nobrace;
const root = options.root ?? nodePath.resolve();
const builder = new FileUrlBuilder({ path: nodePath });
const rootURL = builder.toFileDirURL(root);
const normalizedRoot = builder.urlToFilePathOrHref(rootURL);
this.options = {
root: normalizedRoot,
dot,
nodePath,
nested,
mode,
nobrace,
cwd
};
patterns = Array.isArray(patterns) ? patterns : typeof patterns === "string" ? patterns.split(/\r?\n/g) : [patterns];
const globPatterns = normalizeGlobPatterns(patterns, this.options);
this.patternsNormalizedToRoot = globPatterns.map((g) => normalizeGlobToRoot(g, normalizedRoot, nodePath)).filter((g) => builder.relative(builder.toFileDirURL(g.root), rootURL) === "");
this.patterns = globPatterns;
this.root = normalizedRoot;
this.dot = dot;
this.matchEx = buildMatcherFn(this.id, this.patterns, this.options);
}
/**
* Check to see if a filename matches any of the globs.
* If filename is relative, it is considered relative to the root.
* If filename is absolute and contained within the root, it will be made relative before being tested for a glob match.
* If filename is absolute and not contained within the root, it will be tested as is.
* @param filename full path of the file to check.
*/
match(filename) {
return this.matchEx(filename).matched;
}
};
/**
* This function attempts to emulate .gitignore functionality as much as possible.
*
* The resulting matcher function: (filename: string) => GlobMatch
*
* If filename is relative, it is considered relative to the root.
* If filename is absolute and contained within the root, it will be made relative before being tested for a glob match.
* If filename is absolute and not contained within the root, it will return a GlobMatchNoRule.
*
* @param patterns - the contents of a .gitignore style file or an array of individual glob rules.
* @param options - defines root and other options
* @returns a function given a filename returns true if it matches.
*/
function buildMatcherFn(_id, patterns, options) {
const { nodePath, dot, nobrace } = options;
const builder = new FileUrlBuilder({ path: nodePath });
const makeReOptions = {
dot,
nobrace
};
const suffixDir = GlobPatterns.suffixDir;
const rules = patterns.map((pattern, index) => ({
pattern,
index
})).filter((r) => !!r.pattern.glob).filter((r) => !r.pattern.glob.startsWith("#")).map(({ pattern, index }) => {
const matchNeg = pattern.glob.match(/^!/);
const glob = pattern.glob.replace(/^!/, "");
const isNeg = matchNeg && matchNeg[0].length & 1 && true || false;
const reg = pm.makeRe(workaroundPicomatchBug(glob), makeReOptions);
return {
pattern,
index,
isNeg,
fn: pattern.glob.endsWith(suffixDir) ? (filename) => {
return reg.test(filename) || filename.endsWith("/") && reg.test(filename + " ");
} : (filename) => {
return reg.test(filename);
},
reg
};
});
const negRules = rules.filter((r) => r.isNeg);
const posRules = rules.filter((r) => !r.isNeg);
const mapRoots = /* @__PURE__ */ new Map();
const fn = (filename) => {
const fileUrl = builder.toFileURL(filename);
const relFilePathname = builder.relative(new URL("file:///"), fileUrl);
let lastRoot = new URL("placeHolder://");
let lastRel = "";
function rootToUrl$1(root) {
const found = mapRoots.get(root);
if (found) return found;
const url = builder.toFileDirURL(root);
mapRoots.set(root, url);
return url;
}
function relativeToRoot(root) {
if (root.href !== lastRoot.href) {
lastRoot = root;
lastRel = builder.relative(root, fileUrl);
}
return lastRel;
}
function testRules(rules$1, matched) {
for (const rule of rules$1) {
const pattern = rule.pattern;
const root = pattern.root;
const rootURL = rootToUrl$1(root);
const isRelPat = !pattern.isGlobalPattern;
let fname = relFilePathname;
if (isRelPat) {
const relPathToFile = relativeToRoot(rootURL);
if (!isRelativeValueNested(relPathToFile)) continue;
fname = relPathToFile;
}
if (rule.fn(fname)) return {
matched,
glob: pattern.glob,
root,
pattern,
index: rule.index,
isNeg: rule.isNeg
};
}
}
return testRules(negRules, false) || testRules(posRules, true) || { matched: false };
};
return fn;
}
//#endregion
export { GlobMatcher, fileOrGlobToGlob, isGlobPatternNormalized, isGlobPatternWithOptionalRoot, isGlobPatternWithRoot, normalizeGlobPatterns, workaroundPicomatchBug };
//# sourceMappingURL=index.js.map
+8
-8

@@ -7,3 +7,3 @@ {

},
"version": "9.6.0",
"version": "9.6.1",
"description": "Glob matcher for cspell",

@@ -40,11 +40,11 @@ "keywords": [

"clean": "shx rm -rf dist temp coverage \"*.tsbuildInfo\"",
"build": "tsc -p .",
"build": "tsdown",
"build:check": "tsc -p .",
"clean-build": "pnpm run clean && pnpm run build",
"coverage": "vitest run --coverage",
"test:perf": "insight --file \"dist/perf/**/*.perf.{mjs,js}\" -t 1000",
"test:perf:ts": "insight --register ts-node/esm --file \"src/perf/**/*.perf.{mts,ts}\" -t 1000",
"test:perf": "insight --file \"src/perf/**/*.perf.{mts,ts}\" -t 1000",
"test:perf:prof": "NODE_ENV=production node --cpu-prof ../../node_modules/perf-insight/bin.mjs --file \"dist/perf/**/*.perf.{mjs,js}\" -t 1000",
"test:watch": "vitest",
"test": "vitest run",
"watch": "tsc -p . -w"
"test": "pnpm build:check && vitest run",
"watch": "tsdown -w"
},

@@ -63,3 +63,3 @@ "repository": {

"dependencies": {
"@cspell/url": "9.6.0",
"@cspell/url": "9.6.1",
"picomatch": "^4.0.3"

@@ -72,3 +72,3 @@ },

},
"gitHead": "163793ddf2a0ad90bc7c90351698a106003297af"
"gitHead": "666fb79096d25c53af9519cad07030e7aca597e1"
}
import type { GlobPattern, GlobPatternNormalized, GlobPatternWithOptionalRoot, GlobPatternWithRoot, PathInterface } from './GlobMatcherTypes.js';
export declare const GlobPlaceHolders: {
cwd: string;
};
export declare const GlobPatterns: {
suffixAny: string;
/**
* Use as as suffix for a directory. Example `node_modules/` becomes `node_modules/**​/*`.
*/
suffixDir: string;
prefixAny: string;
};
/**
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
* @param fileOrGlob - file (with absolute path) or glob.
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
* @param path - optional node path methods - used for testing
*/
export declare function fileOrGlobToGlob(fileOrGlob: string | GlobPattern, root: string, path?: PathInterface): GlobPatternWithRoot;
export declare function isGlobPatternWithOptionalRoot(g: GlobPattern): g is GlobPatternWithOptionalRoot;
export declare function isGlobPatternWithRoot(g: GlobPattern): g is GlobPatternWithRoot;
export declare function isGlobPatternNormalized(g: GlobPattern | GlobPatternNormalized): g is GlobPatternNormalized;
export declare function isGlobPatternNormalizedToRoot(g: GlobPattern | GlobPatternNormalized, options: NormalizeOptions): g is GlobPatternNormalized;
export interface NormalizeOptions {
/**
* Indicates that the glob should be modified to match nested patterns.
*
* Example: `node_modules` becomes `**​/node_modules/​**`, `**​/node_modules`, and `node_modules/​**`
*/
nested: boolean;
/**
* This is the root to use for the glob if the glob does not already contain one.
*/
root: string;
/**
* This is the replacement for `${cwd}` in either the root or in the glob.
*/
cwd?: string;
/**
* Optional path interface for working with paths.
*/
nodePath?: PathInterface;
}
/**
*
* @param patterns - glob patterns to normalize.
* @param options - Normalization options.
*/
export declare function normalizeGlobPatterns(patterns: GlobPattern[], options: NormalizeOptions): GlobPatternNormalized[];
export declare function normalizeGlobPattern(g: GlobPattern, options: NormalizeOptions): GlobPatternNormalized[];
/**
* Try to adjust the root of a glob to match a new root. If it is not possible, the original glob is returned.
* Note: this does NOT generate absolutely correct glob patterns. The results are intended to be used as a
* first pass only filter. Followed by testing against the original glob/root pair.
* @param glob - glob to map
* @param root - new root to use if possible
* @param path - Node Path modules to use (testing only)
*/
export declare function normalizeGlobToRoot<Glob extends GlobPatternWithRoot>(glob: Glob, root: string, path: PathInterface): Glob;
export declare function isRelativeValueNested(rel: string): boolean;
/**
* Rebase a glob string to a new root.
* @param glob - glob string
* @param fromRootToGlob - relative path from root to globRoot
* @param fromGlobToRoot - relative path from globRoot to root
*/
export declare function rebaseGlob(glob: string, fromRootToGlob: string, fromGlobToRoot: string): string;
/**
* Trims any trailing spaces, tabs, line-feeds, new-lines, and comments
* @param glob - glob string
* @returns trimmed glob
*/
export declare function trimGlob(glob: string): string;
/**
* Test if a glob pattern has a leading `**`.
* @param glob - the glob
* @returns true if the glob pattern starts with `**`
*/
declare function isGlobalGlob(glob: string): boolean;
export declare function workaroundPicomatchBug(glob: string): string;
export declare const __testing__: {
rebaseGlob: typeof rebaseGlob;
trimGlob: typeof trimGlob;
isGlobalGlob: typeof isGlobalGlob;
};
export {};
//# sourceMappingURL=globHelper.d.ts.map
/* eslint-disable no-irregular-whitespace */
import * as Path from 'node:path';
import { FileUrlBuilder } from '@cspell/url';
const { posix } = Path;
// const relRegExp = /^\..?[\\/]/;
/** test for glob patterns starting with `**` */
const isGlobalPatternRegExp = /^!*[*]{2}/;
const hasGlobCharactersRegExp = /[*?{}[\]]/;
const fileUrlBuilder = new FileUrlBuilder();
export const GlobPlaceHolders = {
cwd: '${cwd}',
};
export const GlobPatterns = {
suffixAny: '/**',
/**
* Use as as suffix for a directory. Example `node_modules/` becomes `node_modules/**​/*`.
*/
suffixDir: '/**/*',
prefixAny: '**/',
};
let cacheCalls = 0;
let cacheMisses = 0;
let cachePath = Path;
let cacheRoot = '<>';
const cache = new Map();
const debugCache = false;
/**
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
* @param fileOrGlob - file (with absolute path) or glob.
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
* @param path - optional node path methods - used for testing
*/
export function fileOrGlobToGlob(fileOrGlob, root, path = Path) {
if (cacheRoot !== root || cachePath !== path) {
cache.clear();
cacheCalls = 0;
cacheMisses = 0;
cacheRoot = root;
cachePath = path;
}
++cacheCalls;
debugCache &&
!(cacheCalls & 0x7) &&
console.error('cache miss rate: %d%% cache size: %d', (cacheMisses / cacheCalls) * 100, cache.size);
const found = cache.get(fileOrGlob);
if (found)
return found;
++cacheMisses;
const pattern = _fileOrGlobToGlob(fileOrGlob, root, path);
cache.set(fileOrGlob, pattern);
return pattern;
}
/**
* This function tries its best to determine if `fileOrGlob` is a path to a file or a glob pattern.
* @param fileOrGlob - file (with absolute path) or glob.
* @param root - absolute path to the directory that will be considered the root when testing the glob pattern.
* @param path - optional node path methods - used for testing
*/
function _fileOrGlobToGlob(fileOrGlob, root, path = Path) {
const toForwardSlash = path.sep === '\\' ? (p) => p.replaceAll('\\', '/') : (p) => p;
const builder = urlBuilder(path);
fileOrGlob = typeof fileOrGlob === 'string' ? toForwardSlash(fileOrGlob) : fileOrGlob;
const rootUrl = builder.toFileDirURL(root);
// Normalize root
root = builder.urlToFilePathOrHref(rootUrl);
const pattern = toGlobPatternWithRoot(fileOrGlob, root, builder);
// if (root.includes(GlobPlaceHolders.cwd) || pattern.root.includes(GlobPlaceHolders.cwd)) {
// console.warn('fileOrGlobToGlob: root or pattern contains ${cwd}', { root, pattern, fileOrGlob });
// }
return pattern;
}
function toGlobPatternWithRoot(glob, root, builder) {
function toPattern() {
if (isGlobPatternWithRoot(glob))
return fixPatternRoot({ ...glob }, builder);
const rootUrl = builder.toFileDirURL(root);
if (typeof glob === 'string')
return filePathOrGlobToGlob(glob, rootUrl, builder);
const pattern = { isGlobalPattern: isGlobalGlob(glob.glob), ...glob, root: glob.root ?? root };
fixPatternRoot(pattern, builder);
// pattern.glob might still be a file or a relative glob pattern.
fixPatternGlob(pattern, builder);
return pattern;
}
const pattern = toPattern();
if (pattern.glob.startsWith(GlobPlaceHolders.cwd)) {
pattern.root = GlobPlaceHolders.cwd;
pattern.glob = pattern.glob.replace(GlobPlaceHolders.cwd, '');
}
return pattern;
}
export function isGlobPatternWithOptionalRoot(g) {
return typeof g !== 'string' && typeof g.glob === 'string';
}
export function isGlobPatternWithRoot(g) {
if (typeof g === 'string')
return false;
return typeof g.root === 'string' && 'isGlobalPattern' in g;
}
export function isGlobPatternNormalized(g) {
if (!isGlobPatternWithRoot(g))
return false;
const gr = g;
return 'rawGlob' in gr && 'rawRoot' in gr && typeof gr.rawGlob === 'string';
}
export function isGlobPatternNormalizedToRoot(g, options) {
if (!isGlobPatternNormalized(g))
return false;
return g.root === options.root;
}
function urlBuilder(path = Path) {
return path === Path ? fileUrlBuilder : new FileUrlBuilder({ path });
}
/**
* @param pattern glob pattern
* @param nested when true add `**​/<glob>/​**`
* @returns the set of matching globs.
*/
function normalizePattern(pattern, nested) {
pattern = pattern.replace(/^(!!)+/, '');
const isNeg = pattern.startsWith('!');
const prefix = isNeg ? '!' : '';
pattern = isNeg ? pattern.slice(1) : pattern;
const patterns = nested ? normalizePatternNested(pattern) : normalizePatternGeneral(pattern);
return patterns.map((p) => prefix + p);
}
function normalizePatternNested(pattern) {
// no slashes will match files names or folders
if (!pattern.includes('/')) {
if (pattern === '**')
return ['**'];
return ['**/' + pattern, '**/' + pattern + '/**'];
}
const hasLeadingSlash = pattern.startsWith('/');
pattern = hasLeadingSlash ? pattern.slice(1) : pattern;
if (pattern.endsWith('/')) {
// See: https://git-scm.com/docs/gitignore#_pattern_format
// if it only has a trailing slash, allow matching against a nested directory.
return hasLeadingSlash || pattern.slice(0, -1).includes('/') ? [pattern + '**/*'] : ['**/' + pattern + '**/*'];
}
if (pattern.endsWith('**')) {
return [pattern];
}
return [pattern, pattern + '/**'];
}
function normalizePatternGeneral(pattern) {
pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern;
pattern = pattern.endsWith('/') ? pattern + '**/*' : pattern;
return [pattern];
}
/**
*
* @param patterns - glob patterns to normalize.
* @param options - Normalization options.
*/
export function normalizeGlobPatterns(patterns, options) {
function* normalize() {
for (const glob of patterns) {
if (isGlobPatternNormalized(glob)) {
yield isGlobPatternNormalizedToRoot(glob, options)
? glob
: normalizeGlobToRoot(glob, options.root, options.nodePath || Path);
continue;
}
yield* normalizeGlobPattern(glob, options);
}
}
return [...normalize()];
}
export function normalizeGlobPattern(g, options) {
const { root, nodePath: path = Path, nested } = options;
const builder = urlBuilder(path);
const cwd = options.cwd ?? path.resolve();
const cwdUrl = builder.toFileDirURL(cwd);
const rootUrl = builder.toFileDirURL(root, cwdUrl);
const gIsGlobalPattern = isGlobPatternWithRoot(g) ? g.isGlobalPattern : undefined;
g = !isGlobPatternWithOptionalRoot(g) ? { glob: g } : g;
const gr = { ...g, root: g.root ?? root };
const rawRoot = gr.root;
const rawGlob = g.glob;
gr.glob = trimGlob(g.glob);
if (gr.glob.startsWith(GlobPlaceHolders.cwd)) {
gr.glob = gr.glob.replace(GlobPlaceHolders.cwd, '');
gr.root = GlobPlaceHolders.cwd;
}
if (gr.root.startsWith(GlobPlaceHolders.cwd)) {
const relRoot = gr.root.replace(GlobPlaceHolders.cwd, './');
const r = builder.toFileDirURL(relRoot, cwdUrl);
r.pathname = posix.normalize(r.pathname);
gr.root = builder.urlToFilePathOrHref(r);
}
const isGlobalPattern = gIsGlobalPattern ?? isGlobalGlob(gr.glob);
gr.root = builder.urlToFilePathOrHref(builder.toFileDirURL(gr.root, rootUrl));
const globs = normalizePattern(gr.glob, nested);
return globs.map((glob) => ({ ...gr, glob, rawGlob, rawRoot, isGlobalPattern }));
}
/**
* Try to adjust the root of a glob to match a new root. If it is not possible, the original glob is returned.
* Note: this does NOT generate absolutely correct glob patterns. The results are intended to be used as a
* first pass only filter. Followed by testing against the original glob/root pair.
* @param glob - glob to map
* @param root - new root to use if possible
* @param path - Node Path modules to use (testing only)
*/
export function normalizeGlobToRoot(glob, root, path) {
const builder = urlBuilder(path);
glob = { ...glob };
fixPatternRoot(glob, builder);
const rootURL = builder.toFileDirURL(root);
root = builder.urlToFilePathOrHref(rootURL);
if (glob.root === root) {
return glob;
}
const globRootUrl = builder.toFileDirURL(glob.root);
const relFromRootToGlob = builder.relative(rootURL, globRootUrl);
if (!relFromRootToGlob) {
return glob;
}
if (glob.isGlobalPattern) {
return { ...glob, root };
}
const relFromGlobToRoot = builder.relative(globRootUrl, rootURL);
const globIsUnderRoot = isRelativeValueNested(relFromRootToGlob);
const rootIsUnderGlob = isRelativeValueNested(relFromGlobToRoot);
// Root and Glob are not in the same part of the directory tree.
if (!globIsUnderRoot && !rootIsUnderGlob) {
return glob;
}
const isNeg = glob.glob.startsWith('!');
const g = isNeg ? glob.glob.slice(1) : glob.glob;
const prefix = isNeg ? '!' : '';
// prefix with root
if (globIsUnderRoot) {
const relGlob = relFromRootToGlob;
return {
...glob,
glob: prefix + posix.join(relGlob, g),
root,
};
}
// The root is under the glob root
// The more difficult case, the glob is higher than the root
// A best effort is made, but does not do advanced matching.
const rebasedGlob = rebaseGlob(g, nRel(relFromRootToGlob), nRel(relFromGlobToRoot));
return rebasedGlob ? { ...glob, glob: prefix + rebasedGlob, root } : glob;
}
function nRel(rel) {
return rel.endsWith('/') ? rel : rel + '/';
}
export function isRelativeValueNested(rel) {
return !rel || !(rel === '..' || rel.startsWith('../') || rel.startsWith('/'));
}
/**
* Rebase a glob string to a new root.
* @param glob - glob string
* @param fromRootToGlob - relative path from root to globRoot
* @param fromGlobToRoot - relative path from globRoot to root
*/
export function rebaseGlob(glob, fromRootToGlob, fromGlobToRoot) {
if (!fromGlobToRoot || fromGlobToRoot === '/')
return glob;
if (fromRootToGlob.startsWith('../') && !fromGlobToRoot.startsWith('../') && glob.startsWith('**'))
return glob;
fromRootToGlob = nRel(fromRootToGlob);
fromGlobToRoot = nRel(fromGlobToRoot);
const relToParts = fromRootToGlob.split('/');
const relFromParts = fromGlobToRoot.split('/');
// console.warn('rebaseGlob 1: %o', { glob, fromRootToGlob, fromGlobToRoot, relToParts, relFromParts });
if (glob.startsWith(fromGlobToRoot) && fromRootToGlob === '../'.repeat(relToParts.length - 1)) {
return glob.slice(fromGlobToRoot.length);
}
const lastRelIdx = relToParts.findIndex((s) => s !== '..');
const lastRel = lastRelIdx < 0 ? relToParts.length : lastRelIdx;
const globParts = [...relToParts.slice(lastRel).filter((a) => a), ...glob.split('/')];
relToParts.length = lastRel;
// console.warn('rebaseGlob 2: %o', { glob, fromRootToGlob, fromGlobToRoot, globParts, relToParts, relFromParts });
if (fromRootToGlob.startsWith('../') && relFromParts.length !== relToParts.length + 1) {
return fromRootToGlob + (glob.startsWith('/') ? glob.slice(1) : glob);
}
// console.warn('rebaseGlob 3: %o', { glob, fromRootToGlob, fromGlobToRoot, globParts, relToParts, relFromParts });
for (let i = 0; i < relFromParts.length && i < globParts.length; ++i) {
const relSeg = relFromParts[i];
const globSeg = globParts[i];
// the empty segment due to the end relGlob / allows for us to test against an empty segment.
if (!relSeg || globSeg === '**') {
return globParts.slice(i).join('/');
}
if (relSeg !== globSeg && globSeg !== '*') {
break;
}
}
return fromRootToGlob + (glob.startsWith('/') ? glob.slice(1) : glob);
}
/**
* Trims any trailing spaces, tabs, line-feeds, new-lines, and comments
* @param glob - glob string
* @returns trimmed glob
*/
export function trimGlob(glob) {
glob = globRemoveComment(glob);
glob = trimGlobLeft(glob);
glob = trimGlobRight(glob);
return glob;
}
function globRemoveComment(glob) {
return glob.replace(/(?<=^|\s)#.*/, '');
}
const spaces = {
' ': true,
'\t': true,
'\n': true,
'\r': true,
};
/**
* Trim any trailing spaces, tabs, line-feeds, or new-lines
* Handles a trailing \<space>
* @param glob - glob string
* @returns glob string with space to the right removed.
*/
function trimGlobRight(glob) {
const lenMin1 = glob.length - 1;
let i = lenMin1;
while (i >= 0 && glob[i] in spaces) {
--i;
}
if (glob[i] === '\\') {
++i;
}
++i;
return i ? glob.slice(0, i) : '';
}
/**
* Trim any leading spaces, tabs, line-feeds, or new-lines
* @param glob - any string
* @returns string with leading spaces removed.
*/
function trimGlobLeft(glob) {
return glob.trimStart();
}
/**
* Test if a glob pattern has a leading `**`.
* @param glob - the glob
* @returns true if the glob pattern starts with `**`
*/
function isGlobalGlob(glob) {
return isGlobalPatternRegExp.test(glob);
}
function hasGlobCharacters(glob) {
return hasGlobCharactersRegExp.test(glob);
}
function isGlobPart(part) {
if (part === GlobPlaceHolders.cwd)
return false;
return hasGlobCharacters(part);
}
/**
* Split a glob into a path and a glob portion.
* The path portion does not contain any glob characters.
* Path might be empty. The glob portion should always be non-empty.
* @param glob - glob string pattern
* @returns
*/
function splitGlob(glob) {
const parts = glob.split('/');
const p = parts.findIndex(isGlobPart);
const s = p < 0 ? parts.length - 1 : p;
return createSplitGlob(s ? parts.slice(0, s).join('/') + '/' : undefined, parts.slice(s).join('/'));
}
/**
* Split a glob into a path and a glob portion.
* The path portion does not contain any glob characters.
* Path might be empty. The glob portion should always be non-empty.
* @param glob - glob string pattern
* @param relOnly - Indicates that only `..` and `.` path segments are considered for the path.
* @returns
*/
function splitGlobRel(glob) {
const parts = glob.split('/');
if (!parts.includes('..') && !parts.includes('.'))
return { path: undefined, glob };
const firstGlobPartIdx = parts.findIndex(isGlobPart);
const lastRelIdx = Math.max(parts.lastIndexOf('..'), parts.lastIndexOf('.'));
const p = firstGlobPartIdx >= 0 ? Math.min(firstGlobPartIdx, lastRelIdx + 1) : lastRelIdx + 1;
const s = p < 0 ? parts.length - 1 : p;
return createSplitGlob(s ? parts.slice(0, s).join('/') + '/' : undefined, parts.slice(s).join('/'));
}
function createSplitGlob(path, glob) {
glob = path ? '/' + glob : glob;
glob = glob.startsWith('/**') ? glob.slice(1) : glob;
return { path, glob };
}
function rootToUrl(root, builder) {
if (root.startsWith(GlobPlaceHolders.cwd)) {
return new URL(builder.normalizeFilePathForUrl(root.replace(GlobPlaceHolders.cwd, '.')), builder.cwd);
}
return builder.toFileDirURL(root);
}
function fixPatternRoot(glob, builder) {
// Gets resolved later.
if (glob.root.startsWith(GlobPlaceHolders.cwd)) {
return glob;
}
glob.root = builder.urlToFilePathOrHref(rootToUrl(glob.root, builder));
return glob;
}
/**
* Adjust the glob pattern in case it is a file or a relative glob.
* @param glob
* @param builder
* @returns
*/
function fixPatternGlob(glob, builder) {
const rootURL = builder.toFileURL(glob.root);
const split = splitGlobRel(glob.glob);
glob.glob = split.glob;
if (split.path !== undefined) {
const relRootPath = split.path.startsWith('/') ? '.' + split.path : split.path;
glob.root = builder.urlToFilePathOrHref(builder.toFileDirURL(relRootPath, glob.root));
}
fixPatternRelativeToRoot(glob, rootURL, builder);
}
function fixPatternRelativeToRoot(glob, root, builder) {
if (glob.root.startsWith(GlobPlaceHolders.cwd))
return;
const rel = builder.relative(root, builder.toFileDirURL(glob.root));
if (rel.startsWith('/') || rel.startsWith('../'))
return;
glob.root = builder.urlToFilePathOrHref(root);
glob.glob = rel + glob.glob;
}
function filePathOrGlobToGlob(filePathOrGlob, root, builder) {
const isGlobalPattern = isGlobalGlob(filePathOrGlob);
const isAbsolute = builder.isAbsolute(filePathOrGlob);
const { path, glob } = isAbsolute ? splitGlob(filePathOrGlob) : splitGlobRel(filePathOrGlob);
const url = builder.toFileDirURL(path || './', root);
return { root: builder.urlToFilePathOrHref(url), glob, isGlobalPattern };
}
export function workaroundPicomatchBug(glob) {
const obj = {};
return glob
.split('/')
.map((s) => (obj[s] ? `{${s},${s}}` : s))
.join('/');
}
export const __testing__ = {
rebaseGlob,
trimGlob,
isGlobalGlob,
};
//# sourceMappingURL=globHelper.js.map
import type { GlobMatch, GlobPattern, GlobPatternNormalized, GlobPatternWithRoot, PathInterface } from './GlobMatcherTypes.js';
type Optional<T> = {
[P in keyof T]?: T[P] | undefined;
};
export type GlobMatchOptions = Optional<NormalizedGlobMatchOptions>;
export type MatcherMode = 'exclude' | 'include';
interface NormalizedGlobMatchOptions {
/**
* The matcher has two modes (`include` or `exclude`) that impact how globs behave.
*
* `include` - designed for searching for file. By default it matches a sub-set of file.
* In include mode, the globs need to be more explicit to match.
* - `dot` is by default false.
* - `nested` is by default false.
*
* `exclude` - designed to emulate `.gitignore`. By default it matches a larger range of files.
* - `dot` is by default true.
* - `nested` is by default true.
*
* @default: 'exclude'
*/
mode: MatcherMode;
/**
* The default directory from which a glob is relative.
* Any globs that are not relative to the root will ignored.
* @default: process.cwd()
*/
root: string;
/**
* The directory to use as the current working directory.
* @default: process.cwd();
*/
cwd: string;
/**
* Allows matching against directories with a leading `.`.
*
* @default: mode == 'exclude'
*/
dot: boolean;
/**
* Allows matching against nested directories or files without needing to add `**`
*
* @default: mode == 'exclude'
*/
nested: boolean;
/**
* Mostly used for testing purposes. It allows explicitly specifying `path.win32` or `path.posix`.
*
* @default: require('path')
*/
nodePath: PathInterface;
/**
* Disable brace matching, so that `{a,b}` and `{1..3}` would be treated as literal characters.
*
* @default false
*/
nobrace?: boolean | undefined;
}
export declare class GlobMatcher {
/**
* @param filename full path of file to match against.
* @returns a GlobMatch - information about the match.
*/
readonly matchEx: (filename: string) => GlobMatch;
readonly path: PathInterface;
readonly patterns: GlobPatternWithRoot[];
readonly patternsNormalizedToRoot: GlobPatternNormalized[];
/**
* path or href of the root directory.
*/
readonly root: string;
readonly dot: boolean;
readonly options: NormalizedGlobMatchOptions;
/**
* Instance ID
*/
readonly id: number;
/**
* Construct a `.gitignore` emulator
* @param patterns - the contents of a `.gitignore` style file or an array of individual glob rules.
* @param root - the working directory
*/
constructor(patterns: GlobPattern | GlobPattern[], root?: string | URL, nodePath?: PathInterface);
/**
* Construct a `.gitignore` emulator
* @param patterns - the contents of a `.gitignore` style file or an array of individual glob rules.
* @param options - to set the root and other options
*/
constructor(patterns: GlobPattern | GlobPattern[], options?: GlobMatchOptions);
constructor(patterns: GlobPattern | GlobPattern[], rootOrOptions?: string | URL | GlobMatchOptions);
/**
* Check to see if a filename matches any of the globs.
* If filename is relative, it is considered relative to the root.
* If filename is absolute and contained within the root, it will be made relative before being tested for a glob match.
* If filename is absolute and not contained within the root, it will be tested as is.
* @param filename full path of the file to check.
*/
match(filename: string): boolean;
}
export {};
//# sourceMappingURL=GlobMatcher.d.ts.map
import * as Path from 'node:path';
import { FileUrlBuilder } from '@cspell/url';
import pm from 'picomatch';
import { GlobPatterns, isRelativeValueNested, normalizeGlobPatterns, normalizeGlobToRoot, workaroundPicomatchBug, } from './globHelper.js';
const traceMode = false;
let idGlobMatcher = 0;
export class GlobMatcher {
/**
* @param filename full path of file to match against.
* @returns a GlobMatch - information about the match.
*/
matchEx;
path;
patterns;
patternsNormalizedToRoot;
/**
* path or href of the root directory.
*/
root;
dot;
options;
/**
* Instance ID
*/
id;
constructor(patterns, rootOrOptions, _nodePath) {
this.id = idGlobMatcher++;
// traceMode && console.warn('GlobMatcher(%d)', this.id, new Error('trace'));
const options = typeof rootOrOptions === 'string' || rootOrOptions instanceof URL
? { root: rootOrOptions.toString() }
: (rootOrOptions ?? {});
const mode = options.mode ?? 'exclude';
const isExcludeMode = mode !== 'include';
const nodePath = options.nodePath ?? _nodePath ?? Path;
this.path = nodePath;
const cwd = options.cwd ?? nodePath.resolve();
const dot = options.dot ?? isExcludeMode;
const nested = options.nested ?? isExcludeMode;
const nobrace = options.nobrace;
const root = options.root ?? nodePath.resolve();
const builder = new FileUrlBuilder({ path: nodePath });
const rootURL = builder.toFileDirURL(root);
const normalizedRoot = builder.urlToFilePathOrHref(rootURL);
this.options = { root: normalizedRoot, dot, nodePath, nested, mode, nobrace, cwd };
patterns = Array.isArray(patterns)
? patterns
: typeof patterns === 'string'
? patterns.split(/\r?\n/g)
: [patterns];
const globPatterns = normalizeGlobPatterns(patterns, this.options);
this.patternsNormalizedToRoot = globPatterns
.map((g) => normalizeGlobToRoot(g, normalizedRoot, nodePath))
// Only keep globs that do not match the root when using exclude mode.
.filter((g) => builder.relative(builder.toFileDirURL(g.root), rootURL) === '');
this.patterns = globPatterns;
this.root = normalizedRoot;
this.dot = dot;
this.matchEx = buildMatcherFn(this.id, this.patterns, this.options);
}
/**
* Check to see if a filename matches any of the globs.
* If filename is relative, it is considered relative to the root.
* If filename is absolute and contained within the root, it will be made relative before being tested for a glob match.
* If filename is absolute and not contained within the root, it will be tested as is.
* @param filename full path of the file to check.
*/
match(filename) {
return this.matchEx(filename).matched;
}
}
/**
* This function attempts to emulate .gitignore functionality as much as possible.
*
* The resulting matcher function: (filename: string) => GlobMatch
*
* If filename is relative, it is considered relative to the root.
* If filename is absolute and contained within the root, it will be made relative before being tested for a glob match.
* If filename is absolute and not contained within the root, it will return a GlobMatchNoRule.
*
* @param patterns - the contents of a .gitignore style file or an array of individual glob rules.
* @param options - defines root and other options
* @returns a function given a filename returns true if it matches.
*/
function buildMatcherFn(_id, patterns, options) {
// outputBuildMatcherFnPerfData(_id, patterns, options);
const { nodePath, dot, nobrace } = options;
const builder = new FileUrlBuilder({ path: nodePath });
const makeReOptions = { dot, nobrace };
const suffixDir = GlobPatterns.suffixDir;
const rules = patterns
.map((pattern, index) => ({ pattern, index }))
.filter((r) => !!r.pattern.glob)
.filter((r) => !r.pattern.glob.startsWith('#'))
.map(({ pattern, index }) => {
const matchNeg = pattern.glob.match(/^!/);
const glob = pattern.glob.replace(/^!/, '');
const isNeg = (matchNeg && matchNeg[0].length & 1 && true) || false;
const reg = pm.makeRe(workaroundPicomatchBug(glob), makeReOptions);
const fn = pattern.glob.endsWith(suffixDir)
? (filename) => {
// Note: this is a hack to get around the limitations of globs.
// We want to match a filename with a trailing slash, but micromatch does not support it.
// So it is necessary to pretend that the filename has a space at the end.
return reg.test(filename) || (filename.endsWith('/') && reg.test(filename + ' '));
}
: (filename) => {
return reg.test(filename);
};
return { pattern, index, isNeg, fn, reg };
});
const negRules = rules.filter((r) => r.isNeg);
const posRules = rules.filter((r) => !r.isNeg);
const mapRoots = new Map();
// const negRegEx = negRules.map((r) => r.reg).map((r) => r.toString());
// const posRegEx = posRules.map((r) => r.reg).map((r) => r.toString());
// console.error('buildMatcherFn %o', { negRegEx, posRegEx, stack: new Error().stack });
// const negReg = joinRegExp(negRegEx);
// const posReg = joinRegExp(posRegEx);
const fn = (filename) => {
const fileUrl = builder.toFileURL(filename);
const relFilePathname = builder.relative(new URL('file:///'), fileUrl);
let lastRoot = new URL('placeHolder://');
let lastRel = '';
function rootToUrl(root) {
const found = mapRoots.get(root);
if (found)
return found;
const url = builder.toFileDirURL(root);
mapRoots.set(root, url);
return url;
}
function relativeToRoot(root) {
if (root.href !== lastRoot.href) {
lastRoot = root;
lastRel = builder.relative(root, fileUrl);
}
return lastRel;
}
function testRules(rules, matched) {
for (const rule of rules) {
const pattern = rule.pattern;
const root = pattern.root;
const rootURL = rootToUrl(root);
const isRelPat = !pattern.isGlobalPattern;
let fname = relFilePathname;
if (isRelPat) {
const relPathToFile = relativeToRoot(rootURL);
if (!isRelativeValueNested(relPathToFile)) {
continue;
}
fname = relPathToFile;
}
if (rule.fn(fname)) {
return {
matched,
glob: pattern.glob,
root,
pattern,
index: rule.index,
isNeg: rule.isNeg,
};
}
}
}
const result = testRules(negRules, false) || testRules(posRules, true) || { matched: false };
traceMode && logMatchTest(_id, filename, result);
return result;
};
return fn;
}
function logMatchTest(id, filename, match) {
console.warn('%s;%d;%s', filename, id, JSON.stringify(match.matched));
}
// function outputBuildMatcherFnPerfData(patterns: GlobPatternWithRoot[], options: NormalizedGlobMatchOptions) {
// console.warn(
// JSON.stringify({
// options: {
// ...options,
// nodePath: undefined,
// root: Path.relative(process.cwd(), options.root),
// cwd: Path.relative(process.cwd(), options.cwd),
// },
// patterns: patterns.map(({ glob, root, isGlobalPattern }) => ({
// glob,
// root: isGlobalPattern ? undefined : Path.relative(process.cwd(), root),
// })),
// }) + ',',
// );
// }
//# sourceMappingURL=GlobMatcher.js.map
export interface PathInterface {
normalize(p: string): string;
join(...paths: string[]): string;
resolve(...paths: string[]): string;
relative(from: string, to: string): string;
isAbsolute(p: string): boolean;
parse(p: string): {
root: string;
dir: string;
base: string;
ext: string;
name: string;
};
sep: string;
}
export type GlobMatch = GlobMatchRule | GlobMatchNoRule;
export interface GlobMatchRule {
matched: boolean;
glob: string;
root: string;
pattern: GlobPatternWithRoot;
index: number;
isNeg: boolean;
}
export interface GlobMatchNoRule {
matched: false;
}
export type GlobPattern = SimpleGlobPattern | GlobPatternWithRoot | GlobPatternWithOptionalRoot;
export type SimpleGlobPattern = string;
export interface GlobPatternWithOptionalRoot {
/**
* a glob pattern
*/
glob: string;
/**
* The root from which the glob pattern is relative.
* @default: options.root
*/
root?: string | undefined;
/**
* Optional value useful for tracing which file a glob pattern was defined in.
*/
source?: string | undefined;
/**
* Optional line number in the source
*/
line?: number | undefined;
}
export interface GlobPatternWithRoot extends GlobPatternWithOptionalRoot {
root: string;
/**
* Global patterns do not need to be relative to the root.
* Note: Some patterns start with `**` but they are tied to the root. In this case, `isGlobalPattern` is `false`.
*/
isGlobalPattern: boolean;
}
export interface GlobPatternNormalized extends GlobPatternWithRoot {
/** the original glob pattern before it was normalized */
rawGlob: string;
/** the original root */
rawRoot: string | undefined;
}
//# sourceMappingURL=GlobMatcherTypes.d.ts.map
// cspell:ignore fname
export {};
//# sourceMappingURL=GlobMatcherTypes.js.map