blob: 31ae6adbe8a8ce3316bdbc79fe2a2252de8b6471 [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001'use strict';
2
3const utils = require('./utils');
4const {
5 CHAR_ASTERISK, /* * */
6 CHAR_AT, /* @ */
7 CHAR_BACKWARD_SLASH, /* \ */
8 CHAR_COMMA, /* , */
9 CHAR_DOT, /* . */
10 CHAR_EXCLAMATION_MARK, /* ! */
11 CHAR_FORWARD_SLASH, /* / */
12 CHAR_LEFT_CURLY_BRACE, /* { */
13 CHAR_LEFT_PARENTHESES, /* ( */
14 CHAR_LEFT_SQUARE_BRACKET, /* [ */
15 CHAR_PLUS, /* + */
16 CHAR_QUESTION_MARK, /* ? */
17 CHAR_RIGHT_CURLY_BRACE, /* } */
18 CHAR_RIGHT_PARENTHESES, /* ) */
19 CHAR_RIGHT_SQUARE_BRACKET /* ] */
20} = require('./constants');
21
22const isPathSeparator = code => {
23 return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
24};
25
26const depth = token => {
27 if (token.isPrefix !== true) {
28 token.depth = token.isGlobstar ? Infinity : 1;
29 }
30};
31
32/**
33 * Quickly scans a glob pattern and returns an object with a handful of
34 * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists),
35 * `glob` (the actual pattern), and `negated` (true if the path starts with `!`).
36 *
37 * ```js
38 * const pm = require('picomatch');
39 * console.log(pm.scan('foo/bar/*.js'));
40 * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' }
41 * ```
42 * @param {String} `str`
43 * @param {Object} `options`
44 * @return {Object} Returns an object with tokens and regex source string.
45 * @api public
46 */
47
48const scan = (input, options) => {
49 const opts = options || {};
50
51 const length = input.length - 1;
52 const scanToEnd = opts.parts === true || opts.scanToEnd === true;
53 const slashes = [];
54 const tokens = [];
55 const parts = [];
56
57 let str = input;
58 let index = -1;
59 let start = 0;
60 let lastIndex = 0;
61 let isBrace = false;
62 let isBracket = false;
63 let isGlob = false;
64 let isExtglob = false;
65 let isGlobstar = false;
66 let braceEscaped = false;
67 let backslashes = false;
68 let negated = false;
69 let finished = false;
70 let braces = 0;
71 let prev;
72 let code;
73 let token = { value: '', depth: 0, isGlob: false };
74
75 const eos = () => index >= length;
76 const peek = () => str.charCodeAt(index + 1);
77 const advance = () => {
78 prev = code;
79 return str.charCodeAt(++index);
80 };
81
82 while (index < length) {
83 code = advance();
84 let next;
85
86 if (code === CHAR_BACKWARD_SLASH) {
87 backslashes = token.backslashes = true;
88 code = advance();
89
90 if (code === CHAR_LEFT_CURLY_BRACE) {
91 braceEscaped = true;
92 }
93 continue;
94 }
95
96 if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
97 braces++;
98
99 while (eos() !== true && (code = advance())) {
100 if (code === CHAR_BACKWARD_SLASH) {
101 backslashes = token.backslashes = true;
102 advance();
103 continue;
104 }
105
106 if (code === CHAR_LEFT_CURLY_BRACE) {
107 braces++;
108 continue;
109 }
110
111 if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) {
112 isBrace = token.isBrace = true;
113 isGlob = token.isGlob = true;
114 finished = true;
115
116 if (scanToEnd === true) {
117 continue;
118 }
119
120 break;
121 }
122
123 if (braceEscaped !== true && code === CHAR_COMMA) {
124 isBrace = token.isBrace = true;
125 isGlob = token.isGlob = true;
126 finished = true;
127
128 if (scanToEnd === true) {
129 continue;
130 }
131
132 break;
133 }
134
135 if (code === CHAR_RIGHT_CURLY_BRACE) {
136 braces--;
137
138 if (braces === 0) {
139 braceEscaped = false;
140 isBrace = token.isBrace = true;
141 finished = true;
142 break;
143 }
144 }
145 }
146
147 if (scanToEnd === true) {
148 continue;
149 }
150
151 break;
152 }
153
154 if (code === CHAR_FORWARD_SLASH) {
155 slashes.push(index);
156 tokens.push(token);
157 token = { value: '', depth: 0, isGlob: false };
158
159 if (finished === true) continue;
160 if (prev === CHAR_DOT && index === (start + 1)) {
161 start += 2;
162 continue;
163 }
164
165 lastIndex = index + 1;
166 continue;
167 }
168
169 if (opts.noext !== true) {
170 const isExtglobChar = code === CHAR_PLUS
171 || code === CHAR_AT
172 || code === CHAR_ASTERISK
173 || code === CHAR_QUESTION_MARK
174 || code === CHAR_EXCLAMATION_MARK;
175
176 if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) {
177 isGlob = token.isGlob = true;
178 isExtglob = token.isExtglob = true;
179 finished = true;
180
181 if (scanToEnd === true) {
182 while (eos() !== true && (code = advance())) {
183 if (code === CHAR_BACKWARD_SLASH) {
184 backslashes = token.backslashes = true;
185 code = advance();
186 continue;
187 }
188
189 if (code === CHAR_RIGHT_PARENTHESES) {
190 isGlob = token.isGlob = true;
191 finished = true;
192 break;
193 }
194 }
195 continue;
196 }
197 break;
198 }
199 }
200
201 if (code === CHAR_ASTERISK) {
202 if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true;
203 isGlob = token.isGlob = true;
204 finished = true;
205
206 if (scanToEnd === true) {
207 continue;
208 }
209 break;
210 }
211
212 if (code === CHAR_QUESTION_MARK) {
213 isGlob = token.isGlob = true;
214 finished = true;
215
216 if (scanToEnd === true) {
217 continue;
218 }
219 break;
220 }
221
222 if (code === CHAR_LEFT_SQUARE_BRACKET) {
223 while (eos() !== true && (next = advance())) {
224 if (next === CHAR_BACKWARD_SLASH) {
225 backslashes = token.backslashes = true;
226 advance();
227 continue;
228 }
229
230 if (next === CHAR_RIGHT_SQUARE_BRACKET) {
231 isBracket = token.isBracket = true;
232 isGlob = token.isGlob = true;
233 finished = true;
234
235 if (scanToEnd === true) {
236 continue;
237 }
238 break;
239 }
240 }
241 }
242
243 if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) {
244 negated = token.negated = true;
245 start++;
246 continue;
247 }
248
249 if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) {
250 isGlob = token.isGlob = true;
251
252 if (scanToEnd === true) {
253 while (eos() !== true && (code = advance())) {
254 if (code === CHAR_LEFT_PARENTHESES) {
255 backslashes = token.backslashes = true;
256 code = advance();
257 continue;
258 }
259
260 if (code === CHAR_RIGHT_PARENTHESES) {
261 finished = true;
262 break;
263 }
264 }
265 continue;
266 }
267 break;
268 }
269
270 if (isGlob === true) {
271 finished = true;
272
273 if (scanToEnd === true) {
274 continue;
275 }
276
277 break;
278 }
279 }
280
281 if (opts.noext === true) {
282 isExtglob = false;
283 isGlob = false;
284 }
285
286 let base = str;
287 let prefix = '';
288 let glob = '';
289
290 if (start > 0) {
291 prefix = str.slice(0, start);
292 str = str.slice(start);
293 lastIndex -= start;
294 }
295
296 if (base && isGlob === true && lastIndex > 0) {
297 base = str.slice(0, lastIndex);
298 glob = str.slice(lastIndex);
299 } else if (isGlob === true) {
300 base = '';
301 glob = str;
302 } else {
303 base = str;
304 }
305
306 if (base && base !== '' && base !== '/' && base !== str) {
307 if (isPathSeparator(base.charCodeAt(base.length - 1))) {
308 base = base.slice(0, -1);
309 }
310 }
311
312 if (opts.unescape === true) {
313 if (glob) glob = utils.removeBackslashes(glob);
314
315 if (base && backslashes === true) {
316 base = utils.removeBackslashes(base);
317 }
318 }
319
320 const state = {
321 prefix,
322 input,
323 start,
324 base,
325 glob,
326 isBrace,
327 isBracket,
328 isGlob,
329 isExtglob,
330 isGlobstar,
331 negated
332 };
333
334 if (opts.tokens === true) {
335 state.maxDepth = 0;
336 if (!isPathSeparator(code)) {
337 tokens.push(token);
338 }
339 state.tokens = tokens;
340 }
341
342 if (opts.parts === true || opts.tokens === true) {
343 let prevIndex;
344
345 for (let idx = 0; idx < slashes.length; idx++) {
346 const n = prevIndex ? prevIndex + 1 : start;
347 const i = slashes[idx];
348 const value = input.slice(n, i);
349 if (opts.tokens) {
350 if (idx === 0 && start !== 0) {
351 tokens[idx].isPrefix = true;
352 tokens[idx].value = prefix;
353 } else {
354 tokens[idx].value = value;
355 }
356 depth(tokens[idx]);
357 state.maxDepth += tokens[idx].depth;
358 }
359 if (idx !== 0 || value !== '') {
360 parts.push(value);
361 }
362 prevIndex = i;
363 }
364
365 if (prevIndex && prevIndex + 1 < input.length) {
366 const value = input.slice(prevIndex + 1);
367 parts.push(value);
368
369 if (opts.tokens) {
370 tokens[tokens.length - 1].value = value;
371 depth(tokens[tokens.length - 1]);
372 state.maxDepth += tokens[tokens.length - 1].depth;
373 }
374 }
375
376 state.slashes = slashes;
377 state.parts = parts;
378 }
379
380 return state;
381};
382
383module.exports = scan;