2 * @fileoverview `IgnorePattern` class.
4 * `IgnorePattern` class has the set of glob patterns and the base path.
6 * It provides two static methods.
8 * - `IgnorePattern.createDefaultIgnore(cwd)`
9 * Create the default predicate function.
10 * - `IgnorePattern.createIgnore(ignorePatterns)`
11 * Create the predicate function from multiple `IgnorePattern` objects.
13 * It provides two properties and a method.
16 * The glob patterns that ignore to lint.
18 * The base path of the glob patterns. If absolute paths existed in the
19 * glob patterns, those are handled as relative paths to the base path.
20 * - `getPatternsRelativeTo(basePath)`
21 * Get `patterns` as modified for a given base path. It modifies the
22 * absolute paths in the patterns as prepending the difference of two base
25 * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes
26 * `ignorePatterns` properties.
28 * @author Toru Nagashima <https://github.com/mysticatea>
32 //------------------------------------------------------------------------------
34 //------------------------------------------------------------------------------
36 const assert = require("assert");
37 const path = require("path");
38 const ignore = require("ignore");
39 const debug = require("debug")("eslint:ignore-pattern");
41 /** @typedef {ReturnType<import("ignore").default>} Ignore */
43 //------------------------------------------------------------------------------
45 //------------------------------------------------------------------------------
48 * Get the path to the common ancestor directory of given paths.
49 * @param {string[]} sourcePaths The paths to calculate the common ancestor.
50 * @returns {string} The path to the common ancestor directory.
52 function getCommonAncestorPath(sourcePaths) {
53 let result = sourcePaths[0];
55 for (let i = 1; i < sourcePaths.length; ++i) {
57 const b = sourcePaths[i];
59 // Set the shorter one (it's the common ancestor if one includes the other).
60 result = a.length < b.length ? a : b;
62 // Set the common ancestor.
63 for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) {
65 result = a.slice(0, lastSepPos);
68 if (a[j] === path.sep) {
74 return result || path.sep;
79 * @param {string} from The source path to get relative path.
80 * @param {string} to The destination path to get relative path.
81 * @returns {string} The relative path.
83 function relative(from, to) {
84 const relPath = path.relative(from, to);
86 if (path.sep === "/") {
89 return relPath.split(path.sep).join("/");
93 * Get the trailing slash if existed.
94 * @param {string} filePath The path to check.
95 * @returns {string} The trailing slash if existed.
97 function dirSuffix(filePath) {
99 filePath.endsWith(path.sep) ||
100 (process.platform === "win32" && filePath.endsWith("/"))
103 return isDir ? "/" : "";
106 const DefaultPatterns = Object.freeze(["/node_modules/*", "/bower_components/*"]);
107 const DotPatterns = Object.freeze([".*", "!../"]);
109 //------------------------------------------------------------------------------
111 //------------------------------------------------------------------------------
113 class IgnorePattern {
116 * The default patterns.
119 static get DefaultPatterns() {
120 return DefaultPatterns;
124 * Create the default predicate function.
125 * @param {string} cwd The current working directory.
126 * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}}
127 * The preficate function.
128 * The first argument is an absolute path that is checked.
129 * The second argument is the flag to not ignore dotfiles.
130 * If the predicate function returned `true`, it means the path should be ignored.
132 static createDefaultIgnore(cwd) {
133 return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]);
137 * Create the predicate function from multiple `IgnorePattern` objects.
138 * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns.
139 * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}}
140 * The preficate function.
141 * The first argument is an absolute path that is checked.
142 * The second argument is the flag to not ignore dotfiles.
143 * If the predicate function returned `true`, it means the path should be ignored.
145 static createIgnore(ignorePatterns) {
146 debug("Create with: %o", ignorePatterns);
148 const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath));
149 const patterns = [].concat(
150 ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath))
152 const ig = ignore().add([...DotPatterns, ...patterns]);
153 const dotIg = ignore().add(patterns);
155 debug(" processed: %o", { basePath, patterns });
157 return Object.assign(
158 (filePath, dot = false) => {
159 assert(path.isAbsolute(filePath), "'filePath' should be an absolute path.");
160 const relPathRaw = relative(basePath, filePath);
161 const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath));
162 const adoptedIg = dot ? dotIg : ig;
163 const result = relPath !== "" && adoptedIg.ignores(relPath);
165 debug("Check", { filePath, dot, relativePath: relPath, result });
168 { basePath, patterns }
173 * Initialize a new `IgnorePattern` instance.
174 * @param {string[]} patterns The glob patterns that ignore to lint.
175 * @param {string} basePath The base path of `patterns`.
177 constructor(patterns, basePath) {
178 assert(path.isAbsolute(basePath), "'basePath' should be an absolute path.");
181 * The glob patterns that ignore to lint.
184 this.patterns = patterns;
187 * The base path of `patterns`.
190 this.basePath = basePath;
193 * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`.
195 * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility.
196 * It's `false` as-is for `ignorePatterns` property in config files.
203 * Get `patterns` as modified for a given base path. It modifies the
204 * absolute paths in the patterns as prepending the difference of two base
206 * @param {string} newBasePath The base path.
207 * @returns {string[]} Modifired patterns.
209 getPatternsRelativeTo(newBasePath) {
210 assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path.");
211 const { basePath, loose, patterns } = this;
213 if (newBasePath === basePath) {
216 const prefix = `/${relative(newBasePath, basePath)}`;
218 return patterns.map(pattern => {
219 const negative = pattern.startsWith("!");
220 const head = negative ? "!" : "";
221 const body = negative ? pattern.slice(1) : pattern;
223 if (body.startsWith("/") || body.startsWith("../")) {
224 return `${head}${prefix}${body}`;
226 return loose ? pattern : `${head}${prefix}/**/${body}`;
231 module.exports = { IgnorePattern };