Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

382 строки
13 KiB

  1. /**
  2. * @fileoverview Responsible for loading ignore config files and managing ignore patterns
  3. * @author Jonathan Rajavuori
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const fs = require("fs"),
  10. path = require("path"),
  11. ignore = require("ignore"),
  12. pathUtils = require("./util/path-utils");
  13. const debug = require("debug")("eslint:ignored-paths");
  14. //------------------------------------------------------------------------------
  15. // Constants
  16. //------------------------------------------------------------------------------
  17. const ESLINT_IGNORE_FILENAME = ".eslintignore";
  18. /**
  19. * Adds `"*"` at the end of `"node_modules/"`,
  20. * so that subtle directories could be re-included by .gitignore patterns
  21. * such as `"!node_modules/should_not_ignored"`
  22. */
  23. const DEFAULT_IGNORE_DIRS = [
  24. "/node_modules/*",
  25. "/bower_components/*"
  26. ];
  27. const DEFAULT_OPTIONS = {
  28. dotfiles: false,
  29. cwd: process.cwd()
  30. };
  31. //------------------------------------------------------------------------------
  32. // Helpers
  33. //------------------------------------------------------------------------------
  34. /**
  35. * Find a file in the current directory.
  36. * @param {string} cwd Current working directory
  37. * @param {string} name File name
  38. * @returns {string} Path of ignore file or an empty string.
  39. */
  40. function findFile(cwd, name) {
  41. const ignoreFilePath = path.resolve(cwd, name);
  42. return fs.existsSync(ignoreFilePath) && fs.statSync(ignoreFilePath).isFile() ? ignoreFilePath : "";
  43. }
  44. /**
  45. * Find an ignore file in the current directory.
  46. * @param {string} cwd Current working directory
  47. * @returns {string} Path of ignore file or an empty string.
  48. */
  49. function findIgnoreFile(cwd) {
  50. return findFile(cwd, ESLINT_IGNORE_FILENAME);
  51. }
  52. /**
  53. * Find an package.json file in the current directory.
  54. * @param {string} cwd Current working directory
  55. * @returns {string} Path of package.json file or an empty string.
  56. */
  57. function findPackageJSONFile(cwd) {
  58. return findFile(cwd, "package.json");
  59. }
  60. /**
  61. * Merge options with defaults
  62. * @param {Object} options Options to merge with DEFAULT_OPTIONS constant
  63. * @returns {Object} Merged options
  64. */
  65. function mergeDefaultOptions(options) {
  66. return Object.assign({}, DEFAULT_OPTIONS, options);
  67. }
  68. /* eslint-disable valid-jsdoc */
  69. /**
  70. * Normalize the path separators in a given string.
  71. * On Windows environment, this replaces `\` by `/`.
  72. * Otherwrise, this does nothing.
  73. * @param {string} str The path string to normalize.
  74. * @returns {string} The normalized path.
  75. */
  76. const normalizePathSeps = path.sep === "/"
  77. ? (str => str)
  78. : ((seps, str) => str.replace(seps, "/")).bind(null, new RegExp(`\\${path.sep}`, "g"));
  79. /* eslint-enable valid-jsdoc */
  80. /**
  81. * Converts a glob pattern to a new glob pattern relative to a different directory
  82. * @param {string} globPattern The glob pattern, relative the the old base directory
  83. * @param {string} relativePathToOldBaseDir A relative path from the new base directory to the old one
  84. * @returns {string} A glob pattern relative to the new base directory
  85. */
  86. function relativize(globPattern, relativePathToOldBaseDir) {
  87. if (relativePathToOldBaseDir === "") {
  88. return globPattern;
  89. }
  90. const prefix = globPattern.startsWith("!") ? "!" : "";
  91. const globWithoutPrefix = globPattern.replace(/^!/, "");
  92. if (globWithoutPrefix.startsWith("/")) {
  93. return `${prefix}/${normalizePathSeps(relativePathToOldBaseDir)}${globWithoutPrefix}`;
  94. }
  95. return globPattern;
  96. }
  97. //------------------------------------------------------------------------------
  98. // Public Interface
  99. //------------------------------------------------------------------------------
  100. /**
  101. * IgnoredPaths class
  102. */
  103. class IgnoredPaths {
  104. /**
  105. * @param {Object} providedOptions object containing 'ignore', 'ignorePath' and 'patterns' properties
  106. */
  107. constructor(providedOptions) {
  108. const options = mergeDefaultOptions(providedOptions);
  109. this.cache = {};
  110. this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
  111. this.ignoreFileDir = options.ignore !== false && options.ignorePath
  112. ? path.dirname(path.resolve(options.cwd, options.ignorePath))
  113. : options.cwd;
  114. this.options = options;
  115. this._baseDir = null;
  116. this.ig = {
  117. custom: ignore(),
  118. default: ignore()
  119. };
  120. this.defaultPatterns.forEach(pattern => this.addPatternRelativeToCwd(this.ig.default, pattern));
  121. if (options.dotfiles !== true) {
  122. /*
  123. * ignore files beginning with a dot, but not files in a parent or
  124. * ancestor directory (which in relative format will begin with `../`).
  125. */
  126. this.addPatternRelativeToCwd(this.ig.default, ".*");
  127. this.addPatternRelativeToCwd(this.ig.default, "!../");
  128. }
  129. /*
  130. * Add a way to keep track of ignored files. This was present in node-ignore
  131. * 2.x, but dropped for now as of 3.0.10.
  132. */
  133. this.ig.custom.ignoreFiles = [];
  134. this.ig.default.ignoreFiles = [];
  135. if (options.ignore !== false) {
  136. let ignorePath;
  137. if (options.ignorePath) {
  138. debug("Using specific ignore file");
  139. try {
  140. fs.statSync(options.ignorePath);
  141. ignorePath = options.ignorePath;
  142. } catch (e) {
  143. e.message = `Cannot read ignore file: ${options.ignorePath}\nError: ${e.message}`;
  144. throw e;
  145. }
  146. } else {
  147. debug(`Looking for ignore file in ${options.cwd}`);
  148. ignorePath = findIgnoreFile(options.cwd);
  149. try {
  150. fs.statSync(ignorePath);
  151. debug(`Loaded ignore file ${ignorePath}`);
  152. } catch (e) {
  153. debug("Could not find ignore file in cwd");
  154. }
  155. }
  156. if (ignorePath) {
  157. debug(`Adding ${ignorePath}`);
  158. this.addIgnoreFile(this.ig.custom, ignorePath);
  159. this.addIgnoreFile(this.ig.default, ignorePath);
  160. } else {
  161. try {
  162. // if the ignoreFile does not exist, check package.json for eslintIgnore
  163. const packageJSONPath = findPackageJSONFile(options.cwd);
  164. if (packageJSONPath) {
  165. let packageJSONOptions;
  166. try {
  167. packageJSONOptions = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"));
  168. } catch (e) {
  169. debug("Could not read package.json file to check eslintIgnore property");
  170. e.messageTemplate = "failed-to-read-json";
  171. e.messageData = {
  172. path: packageJSONPath,
  173. message: e.message
  174. };
  175. throw e;
  176. }
  177. if (packageJSONOptions.eslintIgnore) {
  178. if (Array.isArray(packageJSONOptions.eslintIgnore)) {
  179. packageJSONOptions.eslintIgnore.forEach(pattern => {
  180. this.addPatternRelativeToIgnoreFile(this.ig.custom, pattern);
  181. this.addPatternRelativeToIgnoreFile(this.ig.default, pattern);
  182. });
  183. } else {
  184. throw new TypeError("Package.json eslintIgnore property requires an array of paths");
  185. }
  186. }
  187. }
  188. } catch (e) {
  189. debug("Could not find package.json to check eslintIgnore property");
  190. throw e;
  191. }
  192. }
  193. if (options.ignorePattern) {
  194. this.addPatternRelativeToCwd(this.ig.custom, options.ignorePattern);
  195. this.addPatternRelativeToCwd(this.ig.default, options.ignorePattern);
  196. }
  197. }
  198. }
  199. /*
  200. * If `ignoreFileDir` is a subdirectory of `cwd`, all paths will be normalized to be relative to `cwd`.
  201. * Otherwise, all paths will be normalized to be relative to `ignoreFileDir`.
  202. * This ensures that the final normalized ignore rule will not contain `..`, which is forbidden in
  203. * ignore rules.
  204. */
  205. addPatternRelativeToCwd(ig, pattern) {
  206. const baseDir = this.getBaseDir();
  207. const cookedPattern = baseDir === this.options.cwd
  208. ? pattern
  209. : relativize(pattern, path.relative(baseDir, this.options.cwd));
  210. ig.addPattern(cookedPattern);
  211. debug("addPatternRelativeToCwd:\n original = %j\n cooked = %j", pattern, cookedPattern);
  212. }
  213. addPatternRelativeToIgnoreFile(ig, pattern) {
  214. const baseDir = this.getBaseDir();
  215. const cookedPattern = baseDir === this.ignoreFileDir
  216. ? pattern
  217. : relativize(pattern, path.relative(baseDir, this.ignoreFileDir));
  218. ig.addPattern(cookedPattern);
  219. debug("addPatternRelativeToIgnoreFile:\n original = %j\n cooked = %j", pattern, cookedPattern);
  220. }
  221. // Detect the common ancestor
  222. getBaseDir() {
  223. if (!this._baseDir) {
  224. const a = path.resolve(this.options.cwd);
  225. const b = path.resolve(this.ignoreFileDir);
  226. let lastSepPos = 0;
  227. // Set the shorter one (it's the common ancestor if one includes the other).
  228. this._baseDir = a.length < b.length ? a : b;
  229. // Set the common ancestor.
  230. for (let i = 0; i < a.length && i < b.length; ++i) {
  231. if (a[i] !== b[i]) {
  232. this._baseDir = a.slice(0, lastSepPos);
  233. break;
  234. }
  235. if (a[i] === path.sep) {
  236. lastSepPos = i;
  237. }
  238. }
  239. // If it's only Windows drive letter, it needs \
  240. if (/^[A-Z]:$/.test(this._baseDir)) {
  241. this._baseDir += "\\";
  242. }
  243. debug("baseDir = %j", this._baseDir);
  244. }
  245. return this._baseDir;
  246. }
  247. /**
  248. * read ignore filepath
  249. * @param {string} filePath, file to add to ig
  250. * @returns {array} raw ignore rules
  251. */
  252. readIgnoreFile(filePath) {
  253. if (typeof this.cache[filePath] === "undefined") {
  254. this.cache[filePath] = fs.readFileSync(filePath, "utf8").split(/\r?\n/g).filter(Boolean);
  255. }
  256. return this.cache[filePath];
  257. }
  258. /**
  259. * add ignore file to node-ignore instance
  260. * @param {Object} ig, instance of node-ignore
  261. * @param {string} filePath, file to add to ig
  262. * @returns {void}
  263. */
  264. addIgnoreFile(ig, filePath) {
  265. ig.ignoreFiles.push(filePath);
  266. this
  267. .readIgnoreFile(filePath)
  268. .forEach(ignoreRule => this.addPatternRelativeToIgnoreFile(ig, ignoreRule));
  269. }
  270. /**
  271. * Determine whether a file path is included in the default or custom ignore patterns
  272. * @param {string} filepath Path to check
  273. * @param {string} [category=undefined] check 'default', 'custom' or both (undefined)
  274. * @returns {boolean} true if the file path matches one or more patterns, false otherwise
  275. */
  276. contains(filepath, category) {
  277. let result = false;
  278. const absolutePath = path.resolve(this.options.cwd, filepath);
  279. const relativePath = pathUtils.getRelativePath(absolutePath, this.getBaseDir());
  280. if (typeof category === "undefined") {
  281. result = (this.ig.default.filter([relativePath]).length === 0) ||
  282. (this.ig.custom.filter([relativePath]).length === 0);
  283. } else {
  284. result = (this.ig[category].filter([relativePath]).length === 0);
  285. }
  286. debug("contains:");
  287. debug(" target = %j", filepath);
  288. debug(" result = %j", result);
  289. return result;
  290. }
  291. /**
  292. * Returns a list of dir patterns for glob to ignore
  293. * @returns {function()} method to check whether a folder should be ignored by glob.
  294. */
  295. getIgnoredFoldersGlobChecker() {
  296. const baseDir = this.getBaseDir();
  297. const ig = ignore();
  298. DEFAULT_IGNORE_DIRS.forEach(ignoreDir => this.addPatternRelativeToCwd(ig, ignoreDir));
  299. if (this.options.dotfiles !== true) {
  300. // Ignore hidden folders. (This cannot be ".*", or else it's not possible to unignore hidden files)
  301. ig.add([".*/*", "!../*"]);
  302. }
  303. if (this.options.ignore) {
  304. ig.add(this.ig.custom);
  305. }
  306. const filter = ig.createFilter();
  307. return function(absolutePath) {
  308. const relative = pathUtils.getRelativePath(absolutePath, baseDir);
  309. if (!relative) {
  310. return false;
  311. }
  312. return !filter(relative);
  313. };
  314. }
  315. }
  316. module.exports = IgnoredPaths;