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