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