forked from prettier/prettier
-
Notifications
You must be signed in to change notification settings - Fork 0
/
expand-patterns.js
208 lines (182 loc) · 5.61 KB
/
expand-patterns.js
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
"use strict";
const path = require("path");
const fastGlob = require("fast-glob");
const { statSafe } = require("./utils.js");
/** @typedef {import('./context').Context} Context */
/**
* @param {Context} context
*/
async function* expandPatterns(context) {
const cwd = process.cwd();
const seen = new Set();
let noResults = true;
for await (const pathOrError of expandPatternsInternal(context)) {
noResults = false;
if (typeof pathOrError !== "string") {
yield pathOrError;
continue;
}
const relativePath = path.relative(cwd, pathOrError);
// filter out duplicates
if (seen.has(relativePath)) {
continue;
}
seen.add(relativePath);
yield relativePath;
}
if (noResults && context.argv.errorOnUnmatchedPattern !== false) {
// If there was no files and no other errors, let's yield a general error.
yield {
error: `No matching files. Patterns: ${context.filePatterns.join(" ")}`,
};
}
}
/**
* @param {Context} context
*/
async function* expandPatternsInternal(context) {
// Ignores files in version control systems directories and `node_modules`
const silentlyIgnoredDirs = [".git", ".sl", ".svn", ".hg"];
if (context.argv.withNodeModules !== true) {
silentlyIgnoredDirs.push("node_modules");
}
const globOptions = {
dot: true,
ignore: silentlyIgnoredDirs.map((dir) => "**/" + dir),
};
let supportedFilesGlob;
const cwd = process.cwd();
/** @type {Array<{ type: 'file' | 'dir' | 'glob'; glob: string; input: string; }>} */
const entries = [];
for (const pattern of context.filePatterns) {
const absolutePath = path.resolve(cwd, pattern);
if (containsIgnoredPathSegment(absolutePath, cwd, silentlyIgnoredDirs)) {
continue;
}
const stat = await statSafe(absolutePath);
if (stat) {
if (stat.isFile()) {
entries.push({
type: "file",
glob: escapePathForGlob(fixWindowsSlashes(pattern)),
input: pattern,
});
} else if (stat.isDirectory()) {
/*
1. Remove trailing `/`, `fast-glob` can't find files for `src//*.js` pattern
2. Cleanup dirname, when glob `src/../*.js` pattern with `fast-glob`,
it returns files like 'src/../index.js'
*/
const relativePath = path.relative(cwd, absolutePath) || ".";
entries.push({
type: "dir",
glob:
escapePathForGlob(fixWindowsSlashes(relativePath)) +
"/" +
getSupportedFilesGlob(),
input: pattern,
});
}
} else if (pattern[0] === "!") {
// convert negative patterns to `ignore` entries
globOptions.ignore.push(fixWindowsSlashes(pattern.slice(1)));
} else {
entries.push({
type: "glob",
glob: fixWindowsSlashes(pattern),
input: pattern,
});
}
}
for (const { type, glob, input } of entries) {
let result;
try {
result = await fastGlob(glob, globOptions);
} catch ({ message }) {
/* istanbul ignore next */
yield { error: `${errorMessages.globError[type]}: ${input}\n${message}` };
/* istanbul ignore next */
continue;
}
if (result.length === 0) {
if (context.argv.errorOnUnmatchedPattern !== false) {
yield { error: `${errorMessages.emptyResults[type]}: "${input}".` };
}
} else {
yield* sortPaths(result);
}
}
function getSupportedFilesGlob() {
if (!supportedFilesGlob) {
const extensions = context.languages.flatMap(
(lang) => lang.extensions || []
);
const filenames = context.languages.flatMap(
(lang) => lang.filenames || []
);
supportedFilesGlob = `**/{${[
...extensions.map((ext) => "*" + (ext[0] === "." ? ext : "." + ext)),
...filenames,
]}}`;
}
return supportedFilesGlob;
}
}
const errorMessages = {
globError: {
file: "Unable to resolve file",
dir: "Unable to expand directory",
glob: "Unable to expand glob pattern",
},
emptyResults: {
file: "Explicitly specified file was ignored due to negative glob patterns",
dir: "No supported files were found in the directory",
glob: "No files matching the pattern were found",
},
};
/**
* @param {string} absolutePath
* @param {string} cwd
* @param {string[]} ignoredDirectories
*/
function containsIgnoredPathSegment(absolutePath, cwd, ignoredDirectories) {
return path
.relative(cwd, absolutePath)
.split(path.sep)
.some((dir) => ignoredDirectories.includes(dir));
}
/**
* @param {string[]} paths
*/
function sortPaths(paths) {
return paths.sort((a, b) => a.localeCompare(b));
}
/**
* This function should be replaced with `fastGlob.escapePath` when these issues are fixed:
* - https://github.com/mrmlnc/fast-glob/issues/261
* - https://github.com/mrmlnc/fast-glob/issues/262
* @param {string} path
*/
function escapePathForGlob(path) {
return fastGlob
.escapePath(
path.replace(/\\/g, "\0") // Workaround for fast-glob#262 (part 1)
)
.replace(/\\!/g, "@(!)") // Workaround for fast-glob#261
.replace(/\0/g, "@(\\\\)"); // Workaround for fast-glob#262 (part 2)
}
const isWindows = path.sep === "\\";
/**
* 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
* @param {string} pattern
*/
function fixWindowsSlashes(pattern) {
return isWindows ? pattern.replace(/\\/g, "/") : pattern;
}
module.exports = {
expandPatterns,
fixWindowsSlashes,
};