[d565449] | 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 };
|
---|