UNPKG

cspell-glob

Version:
102 lines (101 loc) 4.74 kB
import mm from 'micromatch'; import * as Path from 'path'; import { doesRootContainPath, normalizeGlobPatterns, normalizeGlobToRoot } from './globHelper.mjs'; export class GlobMatcher { constructor(patterns, rootOrOptions, _nodePath) { _nodePath = _nodePath ?? Path; const options = typeof rootOrOptions === 'string' ? { root: rootOrOptions } : rootOrOptions ?? {}; const { mode = 'exclude' } = options; const isExcludeMode = mode !== 'include'; _nodePath = options.nodePath ?? _nodePath; const { root = _nodePath.resolve(), dot = isExcludeMode, nodePath = _nodePath, nested = isExcludeMode, cwd = process.cwd(), nobrace, } = options; const normalizedRoot = nodePath.resolve(nodePath.normalize(root)); 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) => nodePath.relative(g.root, normalizedRoot) === ''); this.patterns = globPatterns; this.root = normalizedRoot; this.path = nodePath; this.dot = dot; this.matchEx = buildMatcherFn(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(patterns, options) { const { nodePath: path, dot, nobrace } = options; const makeReOptions = { dot, nobrace }; 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 = mm.makeRe(glob, makeReOptions); const fn = (filename) => { const match = filename.match(reg); return !!match; }; return { pattern, index, isNeg, fn, reg }; }); const negRules = rules.filter((r) => r.isNeg); const posRules = rules.filter((r) => !r.isNeg); const fn = (filename) => { filename = path.resolve(path.normalize(filename)); function testRules(rules, matched) { for (const rule of rules) { const pattern = rule.pattern; const root = pattern.root; const isRelPat = !pattern.isGlobalPattern; if (isRelPat && !doesRootContainPath(root, filename, path)) { continue; } const relName = isRelPat ? path.relative(root, filename) : filename; const fname = path.sep === '\\' ? relName.replace(/\\/g, '/') : relName; 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; }