]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview `IgnorePattern` class. | |
3 | * | |
4 | * `IgnorePattern` class has the set of glob patterns and the base path. | |
5 | * | |
6 | * It provides two static methods. | |
7 | * | |
8 | * - `IgnorePattern.createDefaultIgnore(cwd)` | |
9 | * Create the default predicate function. | |
10 | * - `IgnorePattern.createIgnore(ignorePatterns)` | |
11 | * Create the predicate function from multiple `IgnorePattern` objects. | |
12 | * | |
13 | * It provides two properties and a method. | |
14 | * | |
15 | * - `patterns` | |
16 | * The glob patterns that ignore to lint. | |
17 | * - `basePath` | |
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 | |
23 | * paths. | |
24 | * | |
25 | * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes | |
26 | * `ignorePatterns` properties. | |
27 | * | |
28 | * @author Toru Nagashima <https://github.com/mysticatea> | |
29 | */ | |
30 | "use strict"; | |
31 | ||
32 | //------------------------------------------------------------------------------ | |
33 | // Requirements | |
34 | //------------------------------------------------------------------------------ | |
35 | ||
36 | const assert = require("assert"); | |
37 | const path = require("path"); | |
38 | const ignore = require("ignore"); | |
39 | const debug = require("debug")("eslint:ignore-pattern"); | |
40 | ||
41 | /** @typedef {ReturnType<import("ignore").default>} Ignore */ | |
42 | ||
43 | //------------------------------------------------------------------------------ | |
44 | // Helpers | |
45 | //------------------------------------------------------------------------------ | |
46 | ||
47 | /** | |
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. | |
51 | */ | |
52 | function getCommonAncestorPath(sourcePaths) { | |
53 | let result = sourcePaths[0]; | |
54 | ||
55 | for (let i = 1; i < sourcePaths.length; ++i) { | |
56 | const a = result; | |
57 | const b = sourcePaths[i]; | |
58 | ||
59 | // Set the shorter one (it's the common ancestor if one includes the other). | |
60 | result = a.length < b.length ? a : b; | |
61 | ||
62 | // Set the common ancestor. | |
63 | for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) { | |
64 | if (a[j] !== b[j]) { | |
65 | result = a.slice(0, lastSepPos); | |
66 | break; | |
67 | } | |
68 | if (a[j] === path.sep) { | |
69 | lastSepPos = j; | |
70 | } | |
71 | } | |
72 | } | |
73 | ||
56c4a2cb DC |
74 | let resolvedResult = result || path.sep; |
75 | ||
76 | // if Windows common ancestor is root of drive must have trailing slash to be absolute. | |
77 | if (resolvedResult && resolvedResult.endsWith(":") && process.platform === "win32") { | |
78 | resolvedResult += path.sep; | |
79 | } | |
80 | return resolvedResult; | |
eb39fafa DC |
81 | } |
82 | ||
83 | /** | |
84 | * Make relative path. | |
85 | * @param {string} from The source path to get relative path. | |
86 | * @param {string} to The destination path to get relative path. | |
87 | * @returns {string} The relative path. | |
88 | */ | |
89 | function relative(from, to) { | |
90 | const relPath = path.relative(from, to); | |
91 | ||
92 | if (path.sep === "/") { | |
93 | return relPath; | |
94 | } | |
95 | return relPath.split(path.sep).join("/"); | |
96 | } | |
97 | ||
98 | /** | |
99 | * Get the trailing slash if existed. | |
100 | * @param {string} filePath The path to check. | |
101 | * @returns {string} The trailing slash if existed. | |
102 | */ | |
103 | function dirSuffix(filePath) { | |
104 | const isDir = ( | |
105 | filePath.endsWith(path.sep) || | |
106 | (process.platform === "win32" && filePath.endsWith("/")) | |
107 | ); | |
108 | ||
109 | return isDir ? "/" : ""; | |
110 | } | |
111 | ||
112 | const DefaultPatterns = Object.freeze(["/**/node_modules/*"]); | |
113 | const DotPatterns = Object.freeze([".*", "!.eslintrc.*", "!../"]); | |
114 | ||
115 | //------------------------------------------------------------------------------ | |
116 | // Public | |
117 | //------------------------------------------------------------------------------ | |
118 | ||
119 | class IgnorePattern { | |
120 | ||
121 | /** | |
122 | * The default patterns. | |
123 | * @type {string[]} | |
124 | */ | |
125 | static get DefaultPatterns() { | |
126 | return DefaultPatterns; | |
127 | } | |
128 | ||
129 | /** | |
130 | * Create the default predicate function. | |
131 | * @param {string} cwd The current working directory. | |
132 | * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}} | |
133 | * The preficate function. | |
134 | * The first argument is an absolute path that is checked. | |
135 | * The second argument is the flag to not ignore dotfiles. | |
136 | * If the predicate function returned `true`, it means the path should be ignored. | |
137 | */ | |
138 | static createDefaultIgnore(cwd) { | |
139 | return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]); | |
140 | } | |
141 | ||
142 | /** | |
143 | * Create the predicate function from multiple `IgnorePattern` objects. | |
144 | * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns. | |
145 | * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}} | |
146 | * The preficate function. | |
147 | * The first argument is an absolute path that is checked. | |
148 | * The second argument is the flag to not ignore dotfiles. | |
149 | * If the predicate function returned `true`, it means the path should be ignored. | |
150 | */ | |
151 | static createIgnore(ignorePatterns) { | |
152 | debug("Create with: %o", ignorePatterns); | |
153 | ||
154 | const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath)); | |
155 | const patterns = [].concat( | |
156 | ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath)) | |
157 | ); | |
158 | const ig = ignore().add([...DotPatterns, ...patterns]); | |
159 | const dotIg = ignore().add(patterns); | |
160 | ||
161 | debug(" processed: %o", { basePath, patterns }); | |
162 | ||
163 | return Object.assign( | |
164 | (filePath, dot = false) => { | |
165 | assert(path.isAbsolute(filePath), "'filePath' should be an absolute path."); | |
166 | const relPathRaw = relative(basePath, filePath); | |
167 | const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath)); | |
168 | const adoptedIg = dot ? dotIg : ig; | |
169 | const result = relPath !== "" && adoptedIg.ignores(relPath); | |
170 | ||
171 | debug("Check", { filePath, dot, relativePath: relPath, result }); | |
172 | return result; | |
173 | }, | |
174 | { basePath, patterns } | |
175 | ); | |
176 | } | |
177 | ||
178 | /** | |
179 | * Initialize a new `IgnorePattern` instance. | |
180 | * @param {string[]} patterns The glob patterns that ignore to lint. | |
181 | * @param {string} basePath The base path of `patterns`. | |
182 | */ | |
183 | constructor(patterns, basePath) { | |
184 | assert(path.isAbsolute(basePath), "'basePath' should be an absolute path."); | |
185 | ||
186 | /** | |
187 | * The glob patterns that ignore to lint. | |
188 | * @type {string[]} | |
189 | */ | |
190 | this.patterns = patterns; | |
191 | ||
192 | /** | |
193 | * The base path of `patterns`. | |
194 | * @type {string} | |
195 | */ | |
196 | this.basePath = basePath; | |
197 | ||
198 | /** | |
199 | * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`. | |
200 | * | |
201 | * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility. | |
202 | * It's `false` as-is for `ignorePatterns` property in config files. | |
203 | * @type {boolean} | |
204 | */ | |
205 | this.loose = false; | |
206 | } | |
207 | ||
208 | /** | |
209 | * Get `patterns` as modified for a given base path. It modifies the | |
210 | * absolute paths in the patterns as prepending the difference of two base | |
211 | * paths. | |
212 | * @param {string} newBasePath The base path. | |
213 | * @returns {string[]} Modifired patterns. | |
214 | */ | |
215 | getPatternsRelativeTo(newBasePath) { | |
216 | assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path."); | |
217 | const { basePath, loose, patterns } = this; | |
218 | ||
219 | if (newBasePath === basePath) { | |
220 | return patterns; | |
221 | } | |
222 | const prefix = `/${relative(newBasePath, basePath)}`; | |
223 | ||
224 | return patterns.map(pattern => { | |
225 | const negative = pattern.startsWith("!"); | |
226 | const head = negative ? "!" : ""; | |
227 | const body = negative ? pattern.slice(1) : pattern; | |
228 | ||
229 | if (body.startsWith("/") || body.startsWith("../")) { | |
230 | return `${head}${prefix}${body}`; | |
231 | } | |
232 | return loose ? pattern : `${head}${prefix}/**/${body}`; | |
233 | }); | |
234 | } | |
235 | } | |
236 | ||
237 | module.exports = { IgnorePattern }; |