-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement the rest of the eslint-like behavior requirements, use gene…
…rators to avoid problems with big arrays
- Loading branch information
Showing
16 changed files
with
401 additions
and
193 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
"use strict"; | ||
|
||
const path = require("path"); | ||
const fs = require("fs"); | ||
const globby = require("globby"); | ||
const { flattenArray } = require("./util"); | ||
|
||
/** @typedef {import('./util').Context} Context */ | ||
|
||
/** | ||
* @param {Context} context | ||
*/ | ||
function* expandPatterns(context) { | ||
const cwd = process.cwd(); | ||
const seen = Object.create(null); | ||
let noResults = true; | ||
for (const pathOrError of expandPatternsInternal(context)) { | ||
noResults = false; | ||
if (typeof pathOrError !== "string") { | ||
yield pathOrError; | ||
continue; | ||
} | ||
// filter out duplicates | ||
if (pathOrError in seen) { | ||
continue; | ||
} | ||
seen[pathOrError] = true; | ||
yield path.relative(cwd, pathOrError); | ||
} | ||
if (noResults) { | ||
// If there was no files and no other errors, let's yield a general error. | ||
yield { | ||
error: `No matching files. Patterns: ${context.filePatterns.join(" ")}` | ||
}; | ||
} | ||
} | ||
|
||
const isWindows = path.sep === "\\"; | ||
// TODO: use `fast-glob` directly, without `globby` | ||
const baseGlobbyOptions = { | ||
dot: true, | ||
expandDirectories: false, | ||
absolute: true | ||
}; | ||
|
||
/** | ||
* @param {Context} context | ||
*/ | ||
function* expandPatternsInternal(context) { | ||
// Ignores files in version control systems directories and `node_modules` | ||
const ignoredDirectories = { | ||
".git": true, | ||
".svn": true, | ||
".hg": true, | ||
node_modules: context.argv["with-node-modules"] !== true | ||
}; | ||
|
||
const globbyOptions = { | ||
...baseGlobbyOptions, | ||
ignore: Object.keys(ignoredDirectories) | ||
.filter(dir => ignoredDirectories[dir]) | ||
.map(dir => "**/" + dir) | ||
}; | ||
|
||
const cwd = process.cwd(); | ||
|
||
/** | ||
* @type {Array<{ | ||
* type: 'file' | 'dir' | 'glob'; | ||
* path?: string; | ||
* glob?: string; | ||
* input: string; | ||
* }>} | ||
*/ | ||
const entries = []; | ||
|
||
for (const pattern of context.filePatterns) { | ||
const absolutePath = path.resolve(cwd, pattern); | ||
|
||
if (containsIgnoredPathSegment(absolutePath, cwd, ignoredDirectories)) { | ||
continue; | ||
} | ||
|
||
const stat = statSafeSync(absolutePath); | ||
if (stat) { | ||
if (stat.isFile()) { | ||
entries.push({ type: "file", path: absolutePath, input: pattern }); | ||
} else if (stat.isDirectory()) { | ||
entries.push({ | ||
type: "dir", | ||
path: absolutePath, | ||
glob: getSupportedFilesGlob(), | ||
input: pattern | ||
}); | ||
} | ||
} else if (pattern[0] === "!") { | ||
// convert negative patterns to `ignore` entries | ||
globbyOptions.ignore.push(pattern.slice(1)); | ||
} else { | ||
entries.push({ | ||
type: "glob", | ||
// Using backslashes in globs is probably not okay, but not accepting | ||
// backslashes as path separators on Windows is even more not okay. | ||
// https://github.com/prettier/prettier/pull/6776#discussion_r380723717 | ||
// https://github.com/mrmlnc/fast-glob#how-to-write-patterns-on-windows | ||
glob: isWindows ? pattern.replace(/\\/g, "/") : pattern, | ||
input: pattern | ||
}); | ||
} | ||
} | ||
|
||
for (const { type, path, glob, input } of entries) { | ||
switch (type) { | ||
case "file": | ||
yield path; | ||
continue; | ||
|
||
case "dir": { | ||
let result; | ||
try { | ||
result = globby.sync(glob, { ...globbyOptions, cwd: path }); | ||
} catch ({ message }) { | ||
yield { error: `Unable to expand directory: ${input}\n${message}` }; | ||
continue; | ||
} | ||
if (result.length === 0) { | ||
yield { | ||
error: `No supported files were found in the directory "${input}".` | ||
}; | ||
} else { | ||
yield* sortIfNotBig(result); | ||
} | ||
continue; | ||
} | ||
|
||
case "glob": { | ||
let result; | ||
try { | ||
result = globby.sync(glob, globbyOptions); | ||
} catch ({ message }) { | ||
yield { | ||
error: `Unable to expand glob pattern: ${input}\n${message}` | ||
}; | ||
continue; | ||
} | ||
if (result.length === 0) { | ||
yield { | ||
error: `No files matching the pattern "${input}" were found.` | ||
}; | ||
} else { | ||
yield* sortIfNotBig(result); | ||
} | ||
continue; | ||
} | ||
} | ||
} | ||
|
||
let supportedFilesGlob; | ||
|
||
function getSupportedFilesGlob() { | ||
if (!supportedFilesGlob) { | ||
const extensions = flattenArray( | ||
context.languages.map(lang => lang.extensions || []) | ||
); | ||
const filenames = flattenArray( | ||
context.languages.map(lang => lang.filenames || []) | ||
); | ||
supportedFilesGlob = `**/{${extensions | ||
.map(ext => "*" + (ext[0] === "." ? ext : "." + ext)) | ||
.concat(filenames)}}`; | ||
} | ||
return supportedFilesGlob; | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} absolutePath | ||
* @param {string} cwd | ||
* @param {Record<string, boolean>} ignoredDirectories | ||
*/ | ||
function containsIgnoredPathSegment(absolutePath, cwd, ignoredDirectories) { | ||
return path | ||
.relative(cwd, absolutePath) | ||
.split(path.sep) | ||
.some(dir => ignoredDirectories[dir]); | ||
} | ||
|
||
/** | ||
* @param {string[]} paths | ||
*/ | ||
function sortIfNotBig(paths) { | ||
return paths.length > 10000 | ||
? paths | ||
: paths.sort((a, b) => a.localeCompare(b)); | ||
} | ||
|
||
/** | ||
* Get stats of a given path. | ||
* @param {string} filePath The path to target file. | ||
* @returns {fs.Stats | undefined} The stats. | ||
*/ | ||
function statSafeSync(filePath) { | ||
try { | ||
return fs.statSync(filePath); | ||
} catch (error) { | ||
/* istanbul ignore next */ | ||
if (error.code !== "ENOENT") { | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
module.exports = expandPatterns; |
Oops, something went wrong.