/** * @fileoverview `IgnorePattern` class. * * `IgnorePattern` class has the set of glob patterns and the base path. * * It provides two static methods. * * - `IgnorePattern.createDefaultIgnore(cwd)` * Create the default predicate function. * - `IgnorePattern.createIgnore(ignorePatterns)` * Create the predicate function from multiple `IgnorePattern` objects. * * It provides two properties and a method. * * - `patterns` * The glob patterns that ignore to lint. * - `basePath` * The base path of the glob patterns. If absolute paths existed in the * glob patterns, those are handled as relative paths to the base path. * - `getPatternsRelativeTo(basePath)` * Get `patterns` as modified for a given base path. It modifies the * absolute paths in the patterns as prepending the difference of two base * paths. * * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes * `ignorePatterns` properties. * * @author Toru Nagashima */ //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ import assert from "assert"; import path from "path"; import ignore from "ignore"; import debugOrig from "debug"; const debug = debugOrig("eslintrc:ignore-pattern"); /** @typedef {ReturnType} Ignore */ //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Get the path to the common ancestor directory of given paths. * @param {string[]} sourcePaths The paths to calculate the common ancestor. * @returns {string} The path to the common ancestor directory. */ function getCommonAncestorPath(sourcePaths) { let result = sourcePaths[0]; for (let i = 1; i < sourcePaths.length; ++i) { const a = result; const b = sourcePaths[i]; // Set the shorter one (it's the common ancestor if one includes the other). result = a.length < b.length ? a : b; // Set the common ancestor. for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) { if (a[j] !== b[j]) { result = a.slice(0, lastSepPos); break; } if (a[j] === path.sep) { lastSepPos = j; } } } let resolvedResult = result || path.sep; // if Windows common ancestor is root of drive must have trailing slash to be absolute. if (resolvedResult && resolvedResult.endsWith(":") && process.platform === "win32") { resolvedResult += path.sep; } return resolvedResult; } /** * Make relative path. * @param {string} from The source path to get relative path. * @param {string} to The destination path to get relative path. * @returns {string} The relative path. */ function relative(from, to) { const relPath = path.relative(from, to); if (path.sep === "/") { return relPath; } return relPath.split(path.sep).join("/"); } /** * Get the trailing slash if existed. * @param {string} filePath The path to check. * @returns {string} The trailing slash if existed. */ function dirSuffix(filePath) { const isDir = ( filePath.endsWith(path.sep) || (process.platform === "win32" && filePath.endsWith("/")) ); return isDir ? "/" : ""; } const DefaultPatterns = Object.freeze(["/**/node_modules/*"]); const DotPatterns = Object.freeze([".*", "!.eslintrc.*", "!../"]); //------------------------------------------------------------------------------ // Public //------------------------------------------------------------------------------ class IgnorePattern { /** * The default patterns. * @type {string[]} */ static get DefaultPatterns() { return DefaultPatterns; } /** * Create the default predicate function. * @param {string} cwd The current working directory. * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}} * The preficate function. * The first argument is an absolute path that is checked. * The second argument is the flag to not ignore dotfiles. * If the predicate function returned `true`, it means the path should be ignored. */ static createDefaultIgnore(cwd) { return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]); } /** * Create the predicate function from multiple `IgnorePattern` objects. * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns. * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}} * The preficate function. * The first argument is an absolute path that is checked. * The second argument is the flag to not ignore dotfiles. * If the predicate function returned `true`, it means the path should be ignored. */ static createIgnore(ignorePatterns) { debug("Create with: %o", ignorePatterns); const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath)); const patterns = [].concat( ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath)) ); const ig = ignore({ allowRelativePaths: true }).add([...DotPatterns, ...patterns]); const dotIg = ignore({ allowRelativePaths: true }).add(patterns); debug(" processed: %o", { basePath, patterns }); return Object.assign( (filePath, dot = false) => { assert(path.isAbsolute(filePath), "'filePath' should be an absolute path."); const relPathRaw = relative(basePath, filePath); const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath)); const adoptedIg = dot ? dotIg : ig; const result = relPath !== "" && adoptedIg.ignores(relPath); debug("Check", { filePath, dot, relativePath: relPath, result }); return result; }, { basePath, patterns } ); } /** * Initialize a new `IgnorePattern` instance. * @param {string[]} patterns The glob patterns that ignore to lint. * @param {string} basePath The base path of `patterns`. */ constructor(patterns, basePath) { assert(path.isAbsolute(basePath), "'basePath' should be an absolute path."); /** * The glob patterns that ignore to lint. * @type {string[]} */ this.patterns = patterns; /** * The base path of `patterns`. * @type {string} */ this.basePath = basePath; /** * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`. * * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility. * It's `false` as-is for `ignorePatterns` property in config files. * @type {boolean} */ this.loose = false; } /** * Get `patterns` as modified for a given base path. It modifies the * absolute paths in the patterns as prepending the difference of two base * paths. * @param {string} newBasePath The base path. * @returns {string[]} Modifired patterns. */ getPatternsRelativeTo(newBasePath) { assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path."); const { basePath, loose, patterns } = this; if (newBasePath === basePath) { return patterns; } const prefix = `/${relative(newBasePath, basePath)}`; return patterns.map(pattern => { const negative = pattern.startsWith("!"); const head = negative ? "!" : ""; const body = negative ? pattern.slice(1) : pattern; if (body.startsWith("/") || body.startsWith("../")) { return `${head}${prefix}${body}`; } return loose ? pattern : `${head}${prefix}/**/${body}`; }); } } export { IgnorePattern };