UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

226 lines (196 loc) 5.37 kB
// The same patterns are reused for every source file, so compile each one once. const compiledGlobCache = new Map<string, RegExp>(); /** * Returns true when `path` should be included given user-supplied include and * exclude glob lists. `include` is the gate: an empty `include` matches * nothing. `exclude` then narrows the included set. */ export function isPathSelected( path: string, include: string[], exclude: string[], ): boolean { if (include.length === 0) { return false; } if (!matchesAny(path, include)) { return false; } if (exclude.length > 0 && matchesAny(path, exclude)) { return false; } return true; } /** * Returns true if `value` matches at least one of the given glob patterns. */ function matchesAny(value: string, patterns: string[]): boolean { for (const pattern of patterns) { if (getCompiledGlob(pattern).test(value)) { return true; } } return false; } /** * Compiles a glob pattern into a regular expression. Supports `*`, `**`, * `?`, `[abc]` (including `[a-z]` ranges and `[!abc]` / `[^abc]` negation), * and `{a,b,c}` alternation. */ function globToRegExp(pattern: string): RegExp { return new RegExp(`^${translateGlob(pattern)}$`); } function translateGlob(pattern: string): string { let regex = ""; let i = 0; while (i < pattern.length) { const c = pattern[i]; if (c === "*") { if (pattern[i + 1] === "*") { // `**` as a full path segment matches zero or more directories, // e.g. `**/x.sol` matches both `x.sol` and `a/b/x.sol`. const afterSlash = i === 0 || pattern[i - 1] === "/"; const beforeSlash = pattern[i + 2] === "/"; if (afterSlash && beforeSlash) { regex += "(?:.*/)?"; i += 3; } else { regex += ".*"; i += 2; } } else { regex += "[^/]*"; i += 1; } } else if (c === "?") { regex += "[^/]"; i += 1; } else if (c === "[") { const end = findCharClassEnd(pattern, i); if (end !== -1) { regex += translateCharClass(pattern.slice(i + 1, end)); i = end + 1; continue; } regex += "\\["; i += 1; } else if (c === "{") { const end = findBraceEnd(pattern, i); if (end !== -1) { const alternatives = splitBraceAlternatives( pattern.slice(i + 1, end), ).map(translateGlob); regex += `(?:${alternatives.join("|")})`; i = end + 1; continue; } regex += "\\{"; i += 1; } else if (/[.+^$()|\\\]}]/.test(c)) { regex += `\\${c}`; i += 1; } else { regex += c; i += 1; } } return regex; } /** * Returns the index of the `]` that closes the character class opened at * `start`, or -1 if none is found. E.g. for `a[bc]d` starting at 1, returns 4. */ function findCharClassEnd(pattern: string, start: number): number { for (let i = start + 1; i < pattern.length; i++) { if (pattern[i] === "]") { return i; } } return -1; } /** * Returns the index of the `}` that closes the brace group opened at `start`, * handling nested groups and skipping character classes, or -1 if unterminated. * E.g. `{a,{b,c}}` returns the outer `}`; `{a[}]b}` skips the bracketed `}`. */ function findBraceEnd(pattern: string, start: number): number { let depth = 1; let i = start + 1; while (i < pattern.length) { const c = pattern[i]; if (c === "{") { depth += 1; } else if (c === "}") { depth -= 1; if (depth === 0) { return i; } } else if (c === "[") { const end = findCharClassEnd(pattern, i); if (end !== -1) { i = end; } } i += 1; } return -1; } /** * Splits a brace group's body on top-level commas, leaving commas inside * nested braces or character classes untouched. E.g. `a,{b,c},[d,e]` → * `["a", "{b,c}", "[d,e]"]`. */ function splitBraceAlternatives(inside: string): string[] { const result: string[] = []; let depth = 0; let start = 0; let i = 0; while (i < inside.length) { const c = inside[i]; if (c === "{") { depth += 1; } else if (c === "}") { depth -= 1; } else if (c === "[") { const end = findCharClassEnd(inside, i); if (end !== -1) { i = end; } } else if (c === "," && depth === 0) { result.push(inside.slice(start, i)); start = i + 1; } i += 1; } result.push(inside.slice(start)); return result; } /** * Translates a glob character class body into a regex character class. * E.g. `Mm` → `[Mm]`, `!ab` → `[^ab]`, `a-z` → `[a-z]`. */ function translateCharClass(content: string): string { let negated = false; let body = content; if (body.startsWith("!") || body.startsWith("^")) { negated = true; body = body.slice(1); } let escaped = ""; for (const ch of body) { if (ch === "\\" || ch === "]") { escaped += `\\${ch}`; } else { escaped += ch; } } return `(?!/)[${negated ? "^" : ""}${escaped}]`; } function getCompiledGlob(pattern: string): RegExp { let compiled = compiledGlobCache.get(pattern); if (compiled === undefined) { compiled = globToRegExp(pattern); compiledGlobCache.set(pattern, compiled); } return compiled; }