ignore-pattern.js 8.4 KB

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