1 | /**
|
2 | * @fileoverview Responsible for loading ignore config files and managing ignore patterns
|
3 | * @author Jonathan Rajavuori
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Requirements
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | const fs = require("fs"),
|
13 | path = require("path"),
|
14 | ignore = require("ignore"),
|
15 | pathUtil = require("./util/path-util");
|
16 |
|
17 | const debug = require("debug")("eslint:ignored-paths");
|
18 |
|
19 |
|
20 | //------------------------------------------------------------------------------
|
21 | // Constants
|
22 | //------------------------------------------------------------------------------
|
23 |
|
24 | const ESLINT_IGNORE_FILENAME = ".eslintignore";
|
25 |
|
26 | /**
|
27 | * Adds `"*"` at the end of `"node_modules/"`,
|
28 | * so that subtle directories could be re-included by .gitignore patterns
|
29 | * such as `"!node_modules/should_not_ignored"`
|
30 | */
|
31 | const DEFAULT_IGNORE_DIRS = [
|
32 | "/node_modules/*",
|
33 | "/bower_components/*"
|
34 | ];
|
35 | const DEFAULT_OPTIONS = {
|
36 | dotfiles: false,
|
37 | cwd: process.cwd()
|
38 | };
|
39 |
|
40 |
|
41 | //------------------------------------------------------------------------------
|
42 | // Helpers
|
43 | //------------------------------------------------------------------------------
|
44 |
|
45 |
|
46 | /**
|
47 | * Find an ignore file in the current directory.
|
48 | * @param {string} cwd Current working directory
|
49 | * @returns {string} Path of ignore file or an empty string.
|
50 | */
|
51 | function findIgnoreFile(cwd) {
|
52 | cwd = cwd || DEFAULT_OPTIONS.cwd;
|
53 |
|
54 | const ignoreFilePath = path.resolve(cwd, ESLINT_IGNORE_FILENAME);
|
55 |
|
56 | return fs.existsSync(ignoreFilePath) && fs.statSync(ignoreFilePath).isFile() ? ignoreFilePath : "";
|
57 | }
|
58 |
|
59 | /**
|
60 | * Merge options with defaults
|
61 | * @param {Object} options Options to merge with DEFAULT_OPTIONS constant
|
62 | * @returns {Object} Merged options
|
63 | */
|
64 | function mergeDefaultOptions(options) {
|
65 | options = (options || {});
|
66 | return Object.assign({}, DEFAULT_OPTIONS, options);
|
67 | }
|
68 |
|
69 | //------------------------------------------------------------------------------
|
70 | // Public Interface
|
71 | //------------------------------------------------------------------------------
|
72 |
|
73 | /**
|
74 | * IgnoredPaths class
|
75 | */
|
76 | class IgnoredPaths {
|
77 |
|
78 | /**
|
79 | * @param {Object} options object containing 'ignore', 'ignorePath' and 'patterns' properties
|
80 | */
|
81 | constructor(options) {
|
82 | options = mergeDefaultOptions(options);
|
83 |
|
84 | /**
|
85 | * add pattern to node-ignore instance
|
86 | * @param {Object} ig, instance of node-ignore
|
87 | * @param {string} pattern, pattern do add to ig
|
88 | * @returns {array} raw ignore rules
|
89 | */
|
90 | function addPattern(ig, pattern) {
|
91 | return ig.addPattern(pattern);
|
92 | }
|
93 |
|
94 | /**
|
95 | * add ignore file to node-ignore instance
|
96 | * @param {Object} ig, instance of node-ignore
|
97 | * @param {string} filepath, file to add to ig
|
98 | * @returns {array} raw ignore rules
|
99 | */
|
100 | function addIgnoreFile(ig, filepath) {
|
101 | ig.ignoreFiles.push(filepath);
|
102 | return ig.add(fs.readFileSync(filepath, "utf8"));
|
103 | }
|
104 |
|
105 | this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
|
106 | this.baseDir = options.cwd;
|
107 |
|
108 | this.ig = {
|
109 | custom: ignore(),
|
110 | default: ignore()
|
111 | };
|
112 |
|
113 | // Add a way to keep track of ignored files. This was present in node-ignore
|
114 | // 2.x, but dropped for now as of 3.0.10.
|
115 | this.ig.custom.ignoreFiles = [];
|
116 | this.ig.default.ignoreFiles = [];
|
117 |
|
118 | if (options.dotfiles !== true) {
|
119 |
|
120 | /*
|
121 | * ignore files beginning with a dot, but not files in a parent or
|
122 | * ancestor directory (which in relative format will begin with `../`).
|
123 | */
|
124 | addPattern(this.ig.default, [".*", "!../"]);
|
125 | }
|
126 |
|
127 | addPattern(this.ig.default, this.defaultPatterns);
|
128 |
|
129 | if (options.ignore !== false) {
|
130 | let ignorePath;
|
131 |
|
132 | if (options.ignorePath) {
|
133 | debug("Using specific ignore file");
|
134 |
|
135 | try {
|
136 | fs.statSync(options.ignorePath);
|
137 | ignorePath = options.ignorePath;
|
138 | } catch (e) {
|
139 | e.message = `Cannot read ignore file: ${options.ignorePath}\nError: ${e.message}`;
|
140 | throw e;
|
141 | }
|
142 | } else {
|
143 | debug(`Looking for ignore file in ${options.cwd}`);
|
144 | ignorePath = findIgnoreFile(options.cwd);
|
145 |
|
146 | try {
|
147 | fs.statSync(ignorePath);
|
148 | debug(`Loaded ignore file ${ignorePath}`);
|
149 | } catch (e) {
|
150 | debug("Could not find ignore file in cwd");
|
151 | this.options = options;
|
152 | }
|
153 | }
|
154 |
|
155 | if (ignorePath) {
|
156 | debug(`Adding ${ignorePath}`);
|
157 | this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
|
158 | addIgnoreFile(this.ig.custom, ignorePath);
|
159 | addIgnoreFile(this.ig.default, ignorePath);
|
160 | }
|
161 |
|
162 | if (options.ignorePattern) {
|
163 | addPattern(this.ig.custom, options.ignorePattern);
|
164 | addPattern(this.ig.default, options.ignorePattern);
|
165 | }
|
166 | }
|
167 |
|
168 | this.options = options;
|
169 | }
|
170 |
|
171 | /**
|
172 | * Determine whether a file path is included in the default or custom ignore patterns
|
173 | * @param {string} filepath Path to check
|
174 | * @param {string} [category=null] check 'default', 'custom' or both (null)
|
175 | * @returns {boolean} true if the file path matches one or more patterns, false otherwise
|
176 | */
|
177 | contains(filepath, category) {
|
178 |
|
179 | let result = false;
|
180 | const absolutePath = path.resolve(this.options.cwd, filepath);
|
181 | const relativePath = pathUtil.getRelativePath(absolutePath, this.baseDir);
|
182 |
|
183 | if ((typeof category === "undefined") || (category === "default")) {
|
184 | result = result || (this.ig.default.filter([relativePath]).length === 0);
|
185 | }
|
186 |
|
187 | if ((typeof category === "undefined") || (category === "custom")) {
|
188 | result = result || (this.ig.custom.filter([relativePath]).length === 0);
|
189 | }
|
190 |
|
191 | return result;
|
192 |
|
193 | }
|
194 |
|
195 | /**
|
196 | * Returns a list of dir patterns for glob to ignore
|
197 | * @returns {function()} method to check whether a folder should be ignored by glob.
|
198 | */
|
199 | getIgnoredFoldersGlobChecker() {
|
200 |
|
201 | const ig = ignore().add(DEFAULT_IGNORE_DIRS);
|
202 |
|
203 | if (this.options.dotfiles !== true) {
|
204 |
|
205 | // Ignore hidden folders. (This cannot be ".*", or else it's not possible to unignore hidden files)
|
206 | ig.add([".*/*", "!../"]);
|
207 | }
|
208 |
|
209 | if (this.options.ignore) {
|
210 | ig.add(this.ig.custom);
|
211 | }
|
212 |
|
213 | const filter = ig.createFilter();
|
214 |
|
215 | const base = this.baseDir;
|
216 |
|
217 | return function(absolutePath) {
|
218 | const relative = pathUtil.getRelativePath(absolutePath, base);
|
219 |
|
220 | if (!relative) {
|
221 | return false;
|
222 | }
|
223 |
|
224 | return !filter(relative);
|
225 | };
|
226 | }
|
227 | }
|
228 |
|
229 | module.exports = IgnoredPaths;
|