cspell-glob
Advanced tools
+202
-3
@@ -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 |
+555
-3
@@ -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 |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
31229
-22.48%5
-54.55%755
-15.55%1
Infinity%+ Added
- Removed
Updated