summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/util/glob-utils.js
blob: fd4cfa00851d059c87b08c99bd9933ff7aa05aac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/**
 * @fileoverview Utilities for working with globs and the filesystem.
 * @author Ian VanSchooten
 */
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const lodash = require("lodash"),
    fs = require("fs"),
    path = require("path"),
    GlobSync = require("./glob"),

    pathUtils = require("./path-utils"),
    IgnoredPaths = require("./ignored-paths");

const debug = require("debug")("eslint:glob-utils");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Checks whether a directory exists at the given location
 * @param {string} resolvedPath A path from the CWD
 * @returns {boolean} `true` if a directory exists
 */
function directoryExists(resolvedPath) {
    return fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory();
}

/**
 * Checks if a provided path is a directory and returns a glob string matching
 * all files under that directory if so, the path itself otherwise.
 *
 * Reason for this is that `glob` needs `/**` to collect all the files under a
 * directory where as our previous implementation without `glob` simply walked
 * a directory that is passed. So this is to maintain backwards compatibility.
 *
 * Also makes sure all path separators are POSIX style for `glob` compatibility.
 *
 * @param {Object}   [options]                    An options object
 * @param {string[]} [options.extensions=[".js"]] An array of accepted extensions
 * @param {string}   [options.cwd=process.cwd()]  The cwd to use to resolve relative pathnames
 * @returns {Function} A function that takes a pathname and returns a glob that
 *                     matches all files with the provided extensions if
 *                     pathname is a directory.
 */
function processPath(options) {
    const cwd = (options && options.cwd) || process.cwd();
    let extensions = (options && options.extensions) || [".js"];

    extensions = extensions.map(ext => ext.replace(/^\./, ""));

    let suffix = "/**";

    if (extensions.length === 1) {
        suffix += `/*.${extensions[0]}`;
    } else {
        suffix += `/*.{${extensions.join(",")}}`;
    }

    /**
     * A function that converts a directory name to a glob pattern
     *
     * @param {string} pathname The directory path to be modified
     * @returns {string} The glob path or the file path itself
     * @private
     */
    return function(pathname) {
        let newPath = pathname;
        const resolvedPath = path.resolve(cwd, pathname);

        if (directoryExists(resolvedPath)) {
            newPath = pathname.replace(/[/\\]$/, "") + suffix;
        }

        return pathUtils.convertPathToPosix(newPath);
    };
}

/**
 * The error type when no files match a glob.
 */
class NoFilesFoundError extends Error {

    /**
     * @param {string} pattern - The glob pattern which was not found.
     */
    constructor(pattern) {
        super(`No files matching '${pattern}' were found.`);

        this.messageTemplate = "file-not-found";
        this.messageData = { pattern };
    }

}

/**
 * The error type when there are files matched by a glob, but all of them have been ignored.
 */
class AllFilesIgnoredError extends Error {

    /**
     * @param {string} pattern - The glob pattern which was not found.
     */
    constructor(pattern) {
        super(`All files matched by '${pattern}' are ignored.`);
        this.messageTemplate = "all-files-ignored";
        this.messageData = { pattern };
    }
}

const NORMAL_LINT = {};
const SILENTLY_IGNORE = {};
const IGNORE_AND_WARN = {};

/**
 * Tests whether a file should be linted or ignored
 * @param {string} filename The file to be processed
 * @param {{ignore: (boolean|null)}} options If `ignore` is false, updates the behavior to
 * not process custom ignore paths, and lint files specified by direct path even if they
 * match the default ignore path
 * @param {boolean} isDirectPath True if the file was provided as a direct path
 * (as opposed to being resolved from a glob)
 * @param {IgnoredPaths} ignoredPaths An instance of IgnoredPaths to check whether a given
 * file is ignored.
 * @returns {(NORMAL_LINT|SILENTLY_IGNORE|IGNORE_AND_WARN)} A directive for how the
 * file should be processed (either linted normally, or silently ignored, or ignored
 * with a warning that it is being ignored)
 */
function testFileAgainstIgnorePatterns(filename, options, isDirectPath, ignoredPaths) {
    const shouldProcessCustomIgnores = options.ignore !== false;
    const shouldLintIgnoredDirectPaths = options.ignore === false;
    const fileMatchesIgnorePatterns = ignoredPaths.contains(filename, "default") ||
        (shouldProcessCustomIgnores && ignoredPaths.contains(filename, "custom"));

    if (fileMatchesIgnorePatterns && isDirectPath && !shouldLintIgnoredDirectPaths) {
        return IGNORE_AND_WARN;
    }

    if (!fileMatchesIgnorePatterns || (isDirectPath && shouldLintIgnoredDirectPaths)) {
        return NORMAL_LINT;
    }

    return SILENTLY_IGNORE;
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/**
 * Resolves any directory patterns into glob-based patterns for easier handling.
 * @param   {string[]} patterns               File patterns (such as passed on the command line).
 * @param   {Object} options                  An options object.
 * @param   {string} [options.globInputPaths] False disables glob resolution.
 * @returns {string[]} The equivalent glob patterns and filepath strings.
 */
function resolveFileGlobPatterns(patterns, options) {
    if (options.globInputPaths === false) {
        return patterns;
    }

    const processPathExtensions = processPath(options);

    return patterns.map(processPathExtensions);
}

const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/;

/**
 * Build a list of absolute filesnames on which ESLint will act.
 * Ignored files are excluded from the results, as are duplicates.
 *
 * @param   {string[]} globPatterns                     Glob patterns.
 * @param   {Object}   [providedOptions]                An options object.
 * @param   {string}   [providedOptions.cwd]            CWD (considered for relative filenames)
 * @param   {boolean}  [providedOptions.ignore]         False disables use of .eslintignore.
 * @param   {string}   [providedOptions.ignorePath]     The ignore file to use instead of .eslintignore.
 * @param   {string}   [providedOptions.ignorePattern]  A pattern of files to ignore.
 * @param   {string}   [providedOptions.globInputPaths] False disables glob resolution.
 * @returns {string[]} Resolved absolute filenames.
 */
function listFilesToProcess(globPatterns, providedOptions) {
    const options = providedOptions || { ignore: true };
    const cwd = options.cwd || process.cwd();

    const getIgnorePaths = lodash.memoize(
        optionsObj =>
            new IgnoredPaths(optionsObj)
    );

    /*
     * The test "should use default options if none are provided" (source-code-utils.js) checks that 'module.exports.resolveFileGlobPatterns' was called.
     * So it cannot use the local function "resolveFileGlobPatterns".
     */
    const resolvedGlobPatterns = module.exports.resolveFileGlobPatterns(globPatterns, options);

    debug("Creating list of files to process.");
    const resolvedPathsByGlobPattern = resolvedGlobPatterns.map(pattern => {
        const file = path.resolve(cwd, pattern);

        if (options.globInputPaths === false || (fs.existsSync(file) && fs.statSync(file).isFile())) {
            const ignoredPaths = getIgnorePaths(options);
            const fullPath = options.globInputPaths === false ? file : fs.realpathSync(file);

            return [{
                filename: fullPath,
                behavior: testFileAgainstIgnorePatterns(fullPath, options, true, ignoredPaths)
            }];
        }

        // regex to find .hidden or /.hidden patterns, but not ./relative or ../relative
        const globIncludesDotfiles = dotfilesPattern.test(pattern);
        let newOptions = options;

        if (!options.dotfiles) {
            newOptions = Object.assign({}, options, { dotfiles: globIncludesDotfiles });
        }

        const ignoredPaths = getIgnorePaths(newOptions);
        const shouldIgnore = ignoredPaths.getIgnoredFoldersGlobChecker();
        const globOptions = {
            nodir: true,
            dot: true,
            cwd
        };

        return new GlobSync(pattern, globOptions, shouldIgnore).found.map(globMatch => {
            const relativePath = path.resolve(cwd, globMatch);

            return {
                filename: relativePath,
                behavior: testFileAgainstIgnorePatterns(relativePath, options, false, ignoredPaths)
            };
        });
    });

    const allPathDescriptors = resolvedPathsByGlobPattern.reduce((pathsForAllGlobs, pathsForCurrentGlob, index) => {
        if (pathsForCurrentGlob.every(pathDescriptor => pathDescriptor.behavior === SILENTLY_IGNORE)) {
            throw new (pathsForCurrentGlob.length ? AllFilesIgnoredError : NoFilesFoundError)(globPatterns[index]);
        }

        pathsForCurrentGlob.forEach(pathDescriptor => {
            switch (pathDescriptor.behavior) {
                case NORMAL_LINT:
                    pathsForAllGlobs.push({ filename: pathDescriptor.filename, ignored: false });
                    break;
                case IGNORE_AND_WARN:
                    pathsForAllGlobs.push({ filename: pathDescriptor.filename, ignored: true });
                    break;
                case SILENTLY_IGNORE:

                    // do nothing
                    break;

                default:
                    throw new Error(`Unexpected file behavior for ${pathDescriptor.filename}`);
            }
        });

        return pathsForAllGlobs;
    }, []);

    return lodash.uniqBy(allPathDescriptors, pathDescriptor => pathDescriptor.filename);
}

module.exports = {
    resolveFileGlobPatterns,
    listFilesToProcess
};