| 1 | "use strict";
|
|---|
| 2 | // parse a single path portion
|
|---|
| 3 | var _a;
|
|---|
| 4 | Object.defineProperty(exports, "__esModule", { value: true });
|
|---|
| 5 | exports.AST = void 0;
|
|---|
| 6 | const brace_expressions_js_1 = require("./brace-expressions.js");
|
|---|
| 7 | const unescape_js_1 = require("./unescape.js");
|
|---|
| 8 | const types = new Set(['!', '?', '+', '*', '@']);
|
|---|
| 9 | const isExtglobType = (c) => types.has(c);
|
|---|
| 10 | const isExtglobAST = (c) => isExtglobType(c.type);
|
|---|
| 11 | // Map of which extglob types can adopt the children of a nested extglob
|
|---|
| 12 | //
|
|---|
| 13 | // anything but ! can adopt a matching type:
|
|---|
| 14 | // +(a|+(b|c)|d) => +(a|b|c|d)
|
|---|
| 15 | // *(a|*(b|c)|d) => *(a|b|c|d)
|
|---|
| 16 | // @(a|@(b|c)|d) => @(a|b|c|d)
|
|---|
| 17 | // ?(a|?(b|c)|d) => ?(a|b|c|d)
|
|---|
| 18 | //
|
|---|
| 19 | // * can adopt anything, because 0 or repetition is allowed
|
|---|
| 20 | // *(a|?(b|c)|d) => *(a|b|c|d)
|
|---|
| 21 | // *(a|+(b|c)|d) => *(a|b|c|d)
|
|---|
| 22 | // *(a|@(b|c)|d) => *(a|b|c|d)
|
|---|
| 23 | //
|
|---|
| 24 | // + can adopt @, because 1 or repetition is allowed
|
|---|
| 25 | // +(a|@(b|c)|d) => +(a|b|c|d)
|
|---|
| 26 | //
|
|---|
| 27 | // + and @ CANNOT adopt *, because 0 would be allowed
|
|---|
| 28 | // +(a|*(b|c)|d) => would match "", on *(b|c)
|
|---|
| 29 | // @(a|*(b|c)|d) => would match "", on *(b|c)
|
|---|
| 30 | //
|
|---|
| 31 | // + and @ CANNOT adopt ?, because 0 would be allowed
|
|---|
| 32 | // +(a|?(b|c)|d) => would match "", on ?(b|c)
|
|---|
| 33 | // @(a|?(b|c)|d) => would match "", on ?(b|c)
|
|---|
| 34 | //
|
|---|
| 35 | // ? can adopt @, because 0 or 1 is allowed
|
|---|
| 36 | // ?(a|@(b|c)|d) => ?(a|b|c|d)
|
|---|
| 37 | //
|
|---|
| 38 | // ? and @ CANNOT adopt * or +, because >1 would be allowed
|
|---|
| 39 | // ?(a|*(b|c)|d) => would match bbb on *(b|c)
|
|---|
| 40 | // @(a|*(b|c)|d) => would match bbb on *(b|c)
|
|---|
| 41 | // ?(a|+(b|c)|d) => would match bbb on +(b|c)
|
|---|
| 42 | // @(a|+(b|c)|d) => would match bbb on +(b|c)
|
|---|
| 43 | //
|
|---|
| 44 | // ! CANNOT adopt ! (nothing else can either)
|
|---|
| 45 | // !(a|!(b|c)|d) => !(a|b|c|d) would fail to match on b (not not b|c)
|
|---|
| 46 | //
|
|---|
| 47 | // ! can adopt @
|
|---|
| 48 | // !(a|@(b|c)|d) => !(a|b|c|d)
|
|---|
| 49 | //
|
|---|
| 50 | // ! CANNOT adopt *
|
|---|
| 51 | // !(a|*(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
|
|---|
| 52 | //
|
|---|
| 53 | // ! CANNOT adopt +
|
|---|
| 54 | // !(a|+(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
|
|---|
| 55 | //
|
|---|
| 56 | // ! CANNOT adopt ?
|
|---|
| 57 | // x!(a|?(b|c)|d) => x!(a|b|c|d) would fail to match "x"
|
|---|
| 58 | const adoptionMap = new Map([
|
|---|
| 59 | ['!', ['@']],
|
|---|
| 60 | ['?', ['?', '@']],
|
|---|
| 61 | ['@', ['@']],
|
|---|
| 62 | ['*', ['*', '+', '?', '@']],
|
|---|
| 63 | ['+', ['+', '@']],
|
|---|
| 64 | ]);
|
|---|
| 65 | // nested extglobs that can be adopted in, but with the addition of
|
|---|
| 66 | // a blank '' element.
|
|---|
| 67 | const adoptionWithSpaceMap = new Map([
|
|---|
| 68 | ['!', ['?']],
|
|---|
| 69 | ['@', ['?']],
|
|---|
| 70 | ['+', ['?', '*']],
|
|---|
| 71 | ]);
|
|---|
| 72 | // union of the previous two maps
|
|---|
| 73 | const adoptionAnyMap = new Map([
|
|---|
| 74 | ['!', ['?', '@']],
|
|---|
| 75 | ['?', ['?', '@']],
|
|---|
| 76 | ['@', ['?', '@']],
|
|---|
| 77 | ['*', ['*', '+', '?', '@']],
|
|---|
| 78 | ['+', ['+', '@', '?', '*']],
|
|---|
| 79 | ]);
|
|---|
| 80 | // Extglobs that can take over their parent if they are the only child
|
|---|
| 81 | // the key is parent, value maps child to resulting extglob parent type
|
|---|
| 82 | // '@' is omitted because it's a special case. An `@` extglob with a single
|
|---|
| 83 | // member can always be usurped by that subpattern.
|
|---|
| 84 | const usurpMap = new Map([
|
|---|
| 85 | ['!', new Map([['!', '@']])],
|
|---|
| 86 | [
|
|---|
| 87 | '?',
|
|---|
| 88 | new Map([
|
|---|
| 89 | ['*', '*'],
|
|---|
| 90 | ['+', '*'],
|
|---|
| 91 | ]),
|
|---|
| 92 | ],
|
|---|
| 93 | [
|
|---|
| 94 | '@',
|
|---|
| 95 | new Map([
|
|---|
| 96 | ['!', '!'],
|
|---|
| 97 | ['?', '?'],
|
|---|
| 98 | ['@', '@'],
|
|---|
| 99 | ['*', '*'],
|
|---|
| 100 | ['+', '+'],
|
|---|
| 101 | ]),
|
|---|
| 102 | ],
|
|---|
| 103 | [
|
|---|
| 104 | '+',
|
|---|
| 105 | new Map([
|
|---|
| 106 | ['?', '*'],
|
|---|
| 107 | ['*', '*'],
|
|---|
| 108 | ]),
|
|---|
| 109 | ],
|
|---|
| 110 | ]);
|
|---|
| 111 | // Patterns that get prepended to bind to the start of either the
|
|---|
| 112 | // entire string, or just a single path portion, to prevent dots
|
|---|
| 113 | // and/or traversal patterns, when needed.
|
|---|
| 114 | // Exts don't need the ^ or / bit, because the root binds that already.
|
|---|
| 115 | const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))';
|
|---|
| 116 | const startNoDot = '(?!\\.)';
|
|---|
| 117 | // characters that indicate a start of pattern needs the "no dots" bit,
|
|---|
| 118 | // because a dot *might* be matched. ( is not in the list, because in
|
|---|
| 119 | // the case of a child extglob, it will handle the prevention itself.
|
|---|
| 120 | const addPatternStart = new Set(['[', '.']);
|
|---|
| 121 | // cases where traversal is A-OK, no dot prevention needed
|
|---|
| 122 | const justDots = new Set(['..', '.']);
|
|---|
| 123 | const reSpecials = new Set('().*{}+?[]^$\\!');
|
|---|
| 124 | const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|---|
| 125 | // any single thing other than /
|
|---|
| 126 | const qmark = '[^/]';
|
|---|
| 127 | // * => any number of characters
|
|---|
| 128 | const star = qmark + '*?';
|
|---|
| 129 | // use + when we need to ensure that *something* matches, because the * is
|
|---|
| 130 | // the only thing in the path portion.
|
|---|
| 131 | const starNoEmpty = qmark + '+?';
|
|---|
| 132 | // remove the \ chars that we added if we end up doing a nonmagic compare
|
|---|
| 133 | // const deslash = (s: string) => s.replace(/\\(.)/g, '$1')
|
|---|
| 134 | let ID = 0;
|
|---|
| 135 | class AST {
|
|---|
| 136 | type;
|
|---|
| 137 | #root;
|
|---|
| 138 | #hasMagic;
|
|---|
| 139 | #uflag = false;
|
|---|
| 140 | #parts = [];
|
|---|
| 141 | #parent;
|
|---|
| 142 | #parentIndex;
|
|---|
| 143 | #negs;
|
|---|
| 144 | #filledNegs = false;
|
|---|
| 145 | #options;
|
|---|
| 146 | #toString;
|
|---|
| 147 | // set to true if it's an extglob with no children
|
|---|
| 148 | // (which really means one child of '')
|
|---|
| 149 | #emptyExt = false;
|
|---|
| 150 | id = ++ID;
|
|---|
| 151 | get depth() {
|
|---|
| 152 | return (this.#parent?.depth ?? -1) + 1;
|
|---|
| 153 | }
|
|---|
| 154 | [Symbol.for('nodejs.util.inspect.custom')]() {
|
|---|
| 155 | return {
|
|---|
| 156 | '@@type': 'AST',
|
|---|
| 157 | id: this.id,
|
|---|
| 158 | type: this.type,
|
|---|
| 159 | root: this.#root.id,
|
|---|
| 160 | parent: this.#parent?.id,
|
|---|
| 161 | depth: this.depth,
|
|---|
| 162 | partsLength: this.#parts.length,
|
|---|
| 163 | parts: this.#parts,
|
|---|
| 164 | };
|
|---|
| 165 | }
|
|---|
| 166 | constructor(type, parent, options = {}) {
|
|---|
| 167 | this.type = type;
|
|---|
| 168 | // extglobs are inherently magical
|
|---|
| 169 | if (type)
|
|---|
| 170 | this.#hasMagic = true;
|
|---|
| 171 | this.#parent = parent;
|
|---|
| 172 | this.#root = this.#parent ? this.#parent.#root : this;
|
|---|
| 173 | this.#options = this.#root === this ? options : this.#root.#options;
|
|---|
| 174 | this.#negs = this.#root === this ? [] : this.#root.#negs;
|
|---|
| 175 | if (type === '!' && !this.#root.#filledNegs)
|
|---|
| 176 | this.#negs.push(this);
|
|---|
| 177 | this.#parentIndex = this.#parent ? this.#parent.#parts.length : 0;
|
|---|
| 178 | }
|
|---|
| 179 | get hasMagic() {
|
|---|
| 180 | /* c8 ignore start */
|
|---|
| 181 | if (this.#hasMagic !== undefined)
|
|---|
| 182 | return this.#hasMagic;
|
|---|
| 183 | /* c8 ignore stop */
|
|---|
| 184 | for (const p of this.#parts) {
|
|---|
| 185 | if (typeof p === 'string')
|
|---|
| 186 | continue;
|
|---|
| 187 | if (p.type || p.hasMagic)
|
|---|
| 188 | return (this.#hasMagic = true);
|
|---|
| 189 | }
|
|---|
| 190 | // note: will be undefined until we generate the regexp src and find out
|
|---|
| 191 | return this.#hasMagic;
|
|---|
| 192 | }
|
|---|
| 193 | // reconstructs the pattern
|
|---|
| 194 | toString() {
|
|---|
| 195 | return (this.#toString !== undefined ? this.#toString
|
|---|
| 196 | : !this.type ?
|
|---|
| 197 | (this.#toString = this.#parts.map(p => String(p)).join(''))
|
|---|
| 198 | : (this.#toString =
|
|---|
| 199 | this.type +
|
|---|
| 200 | '(' +
|
|---|
| 201 | this.#parts.map(p => String(p)).join('|') +
|
|---|
| 202 | ')'));
|
|---|
| 203 | }
|
|---|
| 204 | #fillNegs() {
|
|---|
| 205 | /* c8 ignore start */
|
|---|
| 206 | if (this !== this.#root)
|
|---|
| 207 | throw new Error('should only call on root');
|
|---|
| 208 | if (this.#filledNegs)
|
|---|
| 209 | return this;
|
|---|
| 210 | /* c8 ignore stop */
|
|---|
| 211 | // call toString() once to fill this out
|
|---|
| 212 | this.toString();
|
|---|
| 213 | this.#filledNegs = true;
|
|---|
| 214 | let n;
|
|---|
| 215 | while ((n = this.#negs.pop())) {
|
|---|
| 216 | if (n.type !== '!')
|
|---|
| 217 | continue;
|
|---|
| 218 | // walk up the tree, appending everthing that comes AFTER parentIndex
|
|---|
| 219 | let p = n;
|
|---|
| 220 | let pp = p.#parent;
|
|---|
| 221 | while (pp) {
|
|---|
| 222 | for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) {
|
|---|
| 223 | for (const part of n.#parts) {
|
|---|
| 224 | /* c8 ignore start */
|
|---|
| 225 | if (typeof part === 'string') {
|
|---|
| 226 | throw new Error('string part in extglob AST??');
|
|---|
| 227 | }
|
|---|
| 228 | /* c8 ignore stop */
|
|---|
| 229 | part.copyIn(pp.#parts[i]);
|
|---|
| 230 | }
|
|---|
| 231 | }
|
|---|
| 232 | p = pp;
|
|---|
| 233 | pp = p.#parent;
|
|---|
| 234 | }
|
|---|
| 235 | }
|
|---|
| 236 | return this;
|
|---|
| 237 | }
|
|---|
| 238 | push(...parts) {
|
|---|
| 239 | for (const p of parts) {
|
|---|
| 240 | if (p === '')
|
|---|
| 241 | continue;
|
|---|
| 242 | /* c8 ignore start */
|
|---|
| 243 | if (typeof p !== 'string' &&
|
|---|
| 244 | !(p instanceof _a && p.#parent === this)) {
|
|---|
| 245 | throw new Error('invalid part: ' + p);
|
|---|
| 246 | }
|
|---|
| 247 | /* c8 ignore stop */
|
|---|
| 248 | this.#parts.push(p);
|
|---|
| 249 | }
|
|---|
| 250 | }
|
|---|
| 251 | toJSON() {
|
|---|
| 252 | const ret = this.type === null ?
|
|---|
| 253 | this.#parts
|
|---|
| 254 | .slice()
|
|---|
| 255 | .map(p => (typeof p === 'string' ? p : p.toJSON()))
|
|---|
| 256 | : [this.type, ...this.#parts.map(p => p.toJSON())];
|
|---|
| 257 | if (this.isStart() && !this.type)
|
|---|
| 258 | ret.unshift([]);
|
|---|
| 259 | if (this.isEnd() &&
|
|---|
| 260 | (this === this.#root ||
|
|---|
| 261 | (this.#root.#filledNegs && this.#parent?.type === '!'))) {
|
|---|
| 262 | ret.push({});
|
|---|
| 263 | }
|
|---|
| 264 | return ret;
|
|---|
| 265 | }
|
|---|
| 266 | isStart() {
|
|---|
| 267 | if (this.#root === this)
|
|---|
| 268 | return true;
|
|---|
| 269 | // if (this.type) return !!this.#parent?.isStart()
|
|---|
| 270 | if (!this.#parent?.isStart())
|
|---|
| 271 | return false;
|
|---|
| 272 | if (this.#parentIndex === 0)
|
|---|
| 273 | return true;
|
|---|
| 274 | // if everything AHEAD of this is a negation, then it's still the "start"
|
|---|
| 275 | const p = this.#parent;
|
|---|
| 276 | for (let i = 0; i < this.#parentIndex; i++) {
|
|---|
| 277 | const pp = p.#parts[i];
|
|---|
| 278 | if (!(pp instanceof _a && pp.type === '!')) {
|
|---|
| 279 | return false;
|
|---|
| 280 | }
|
|---|
| 281 | }
|
|---|
| 282 | return true;
|
|---|
| 283 | }
|
|---|
| 284 | isEnd() {
|
|---|
| 285 | if (this.#root === this)
|
|---|
| 286 | return true;
|
|---|
| 287 | if (this.#parent?.type === '!')
|
|---|
| 288 | return true;
|
|---|
| 289 | if (!this.#parent?.isEnd())
|
|---|
| 290 | return false;
|
|---|
| 291 | if (!this.type)
|
|---|
| 292 | return this.#parent?.isEnd();
|
|---|
| 293 | // if not root, it'll always have a parent
|
|---|
| 294 | /* c8 ignore start */
|
|---|
| 295 | const pl = this.#parent ? this.#parent.#parts.length : 0;
|
|---|
| 296 | /* c8 ignore stop */
|
|---|
| 297 | return this.#parentIndex === pl - 1;
|
|---|
| 298 | }
|
|---|
| 299 | copyIn(part) {
|
|---|
| 300 | if (typeof part === 'string')
|
|---|
| 301 | this.push(part);
|
|---|
| 302 | else
|
|---|
| 303 | this.push(part.clone(this));
|
|---|
| 304 | }
|
|---|
| 305 | clone(parent) {
|
|---|
| 306 | const c = new _a(this.type, parent);
|
|---|
| 307 | for (const p of this.#parts) {
|
|---|
| 308 | c.copyIn(p);
|
|---|
| 309 | }
|
|---|
| 310 | return c;
|
|---|
| 311 | }
|
|---|
| 312 | static #parseAST(str, ast, pos, opt, extDepth) {
|
|---|
| 313 | const maxDepth = opt.maxExtglobRecursion ?? 2;
|
|---|
| 314 | let escaping = false;
|
|---|
| 315 | let inBrace = false;
|
|---|
| 316 | let braceStart = -1;
|
|---|
| 317 | let braceNeg = false;
|
|---|
| 318 | if (ast.type === null) {
|
|---|
| 319 | // outside of a extglob, append until we find a start
|
|---|
| 320 | let i = pos;
|
|---|
| 321 | let acc = '';
|
|---|
| 322 | while (i < str.length) {
|
|---|
| 323 | const c = str.charAt(i++);
|
|---|
| 324 | // still accumulate escapes at this point, but we do ignore
|
|---|
| 325 | // starts that are escaped
|
|---|
| 326 | if (escaping || c === '\\') {
|
|---|
| 327 | escaping = !escaping;
|
|---|
| 328 | acc += c;
|
|---|
| 329 | continue;
|
|---|
| 330 | }
|
|---|
| 331 | if (inBrace) {
|
|---|
| 332 | if (i === braceStart + 1) {
|
|---|
| 333 | if (c === '^' || c === '!') {
|
|---|
| 334 | braceNeg = true;
|
|---|
| 335 | }
|
|---|
| 336 | }
|
|---|
| 337 | else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
|
|---|
| 338 | inBrace = false;
|
|---|
| 339 | }
|
|---|
| 340 | acc += c;
|
|---|
| 341 | continue;
|
|---|
| 342 | }
|
|---|
| 343 | else if (c === '[') {
|
|---|
| 344 | inBrace = true;
|
|---|
| 345 | braceStart = i;
|
|---|
| 346 | braceNeg = false;
|
|---|
| 347 | acc += c;
|
|---|
| 348 | continue;
|
|---|
| 349 | }
|
|---|
| 350 | // we don't have to check for adoption here, because that's
|
|---|
| 351 | // done at the other recursion point.
|
|---|
| 352 | const doRecurse = !opt.noext &&
|
|---|
| 353 | isExtglobType(c) &&
|
|---|
| 354 | str.charAt(i) === '(' &&
|
|---|
| 355 | extDepth <= maxDepth;
|
|---|
| 356 | if (doRecurse) {
|
|---|
| 357 | ast.push(acc);
|
|---|
| 358 | acc = '';
|
|---|
| 359 | const ext = new _a(c, ast);
|
|---|
| 360 | i = _a.#parseAST(str, ext, i, opt, extDepth + 1);
|
|---|
| 361 | ast.push(ext);
|
|---|
| 362 | continue;
|
|---|
| 363 | }
|
|---|
| 364 | acc += c;
|
|---|
| 365 | }
|
|---|
| 366 | ast.push(acc);
|
|---|
| 367 | return i;
|
|---|
| 368 | }
|
|---|
| 369 | // some kind of extglob, pos is at the (
|
|---|
| 370 | // find the next | or )
|
|---|
| 371 | let i = pos + 1;
|
|---|
| 372 | let part = new _a(null, ast);
|
|---|
| 373 | const parts = [];
|
|---|
| 374 | let acc = '';
|
|---|
| 375 | while (i < str.length) {
|
|---|
| 376 | const c = str.charAt(i++);
|
|---|
| 377 | // still accumulate escapes at this point, but we do ignore
|
|---|
| 378 | // starts that are escaped
|
|---|
| 379 | if (escaping || c === '\\') {
|
|---|
| 380 | escaping = !escaping;
|
|---|
| 381 | acc += c;
|
|---|
| 382 | continue;
|
|---|
| 383 | }
|
|---|
| 384 | if (inBrace) {
|
|---|
| 385 | if (i === braceStart + 1) {
|
|---|
| 386 | if (c === '^' || c === '!') {
|
|---|
| 387 | braceNeg = true;
|
|---|
| 388 | }
|
|---|
| 389 | }
|
|---|
| 390 | else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
|
|---|
| 391 | inBrace = false;
|
|---|
| 392 | }
|
|---|
| 393 | acc += c;
|
|---|
| 394 | continue;
|
|---|
| 395 | }
|
|---|
| 396 | else if (c === '[') {
|
|---|
| 397 | inBrace = true;
|
|---|
| 398 | braceStart = i;
|
|---|
| 399 | braceNeg = false;
|
|---|
| 400 | acc += c;
|
|---|
| 401 | continue;
|
|---|
| 402 | }
|
|---|
| 403 | const doRecurse = !opt.noext &&
|
|---|
| 404 | isExtglobType(c) &&
|
|---|
| 405 | str.charAt(i) === '(' &&
|
|---|
| 406 | /* c8 ignore start - the maxDepth is sufficient here */
|
|---|
| 407 | (extDepth <= maxDepth || (ast && ast.#canAdoptType(c)));
|
|---|
| 408 | /* c8 ignore stop */
|
|---|
| 409 | if (doRecurse) {
|
|---|
| 410 | const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1;
|
|---|
| 411 | part.push(acc);
|
|---|
| 412 | acc = '';
|
|---|
| 413 | const ext = new _a(c, part);
|
|---|
| 414 | part.push(ext);
|
|---|
| 415 | i = _a.#parseAST(str, ext, i, opt, extDepth + depthAdd);
|
|---|
| 416 | continue;
|
|---|
| 417 | }
|
|---|
| 418 | if (c === '|') {
|
|---|
| 419 | part.push(acc);
|
|---|
| 420 | acc = '';
|
|---|
| 421 | parts.push(part);
|
|---|
| 422 | part = new _a(null, ast);
|
|---|
| 423 | continue;
|
|---|
| 424 | }
|
|---|
| 425 | if (c === ')') {
|
|---|
| 426 | if (acc === '' && ast.#parts.length === 0) {
|
|---|
| 427 | ast.#emptyExt = true;
|
|---|
| 428 | }
|
|---|
| 429 | part.push(acc);
|
|---|
| 430 | acc = '';
|
|---|
| 431 | ast.push(...parts, part);
|
|---|
| 432 | return i;
|
|---|
| 433 | }
|
|---|
| 434 | acc += c;
|
|---|
| 435 | }
|
|---|
| 436 | // unfinished extglob
|
|---|
| 437 | // if we got here, it was a malformed extglob! not an extglob, but
|
|---|
| 438 | // maybe something else in there.
|
|---|
| 439 | ast.type = null;
|
|---|
| 440 | ast.#hasMagic = undefined;
|
|---|
| 441 | ast.#parts = [str.substring(pos - 1)];
|
|---|
| 442 | return i;
|
|---|
| 443 | }
|
|---|
| 444 | #canAdoptWithSpace(child) {
|
|---|
| 445 | return this.#canAdopt(child, adoptionWithSpaceMap);
|
|---|
| 446 | }
|
|---|
| 447 | #canAdopt(child, map = adoptionMap) {
|
|---|
| 448 | if (!child ||
|
|---|
| 449 | typeof child !== 'object' ||
|
|---|
| 450 | child.type !== null ||
|
|---|
| 451 | child.#parts.length !== 1 ||
|
|---|
| 452 | this.type === null) {
|
|---|
| 453 | return false;
|
|---|
| 454 | }
|
|---|
| 455 | const gc = child.#parts[0];
|
|---|
| 456 | if (!gc || typeof gc !== 'object' || gc.type === null) {
|
|---|
| 457 | return false;
|
|---|
| 458 | }
|
|---|
| 459 | return this.#canAdoptType(gc.type, map);
|
|---|
| 460 | }
|
|---|
| 461 | #canAdoptType(c, map = adoptionAnyMap) {
|
|---|
| 462 | return !!map.get(this.type)?.includes(c);
|
|---|
| 463 | }
|
|---|
| 464 | #adoptWithSpace(child, index) {
|
|---|
| 465 | const gc = child.#parts[0];
|
|---|
| 466 | const blank = new _a(null, gc, this.options);
|
|---|
| 467 | blank.#parts.push('');
|
|---|
| 468 | gc.push(blank);
|
|---|
| 469 | this.#adopt(child, index);
|
|---|
| 470 | }
|
|---|
| 471 | #adopt(child, index) {
|
|---|
| 472 | const gc = child.#parts[0];
|
|---|
| 473 | this.#parts.splice(index, 1, ...gc.#parts);
|
|---|
| 474 | for (const p of gc.#parts) {
|
|---|
| 475 | if (typeof p === 'object')
|
|---|
| 476 | p.#parent = this;
|
|---|
| 477 | }
|
|---|
| 478 | this.#toString = undefined;
|
|---|
| 479 | }
|
|---|
| 480 | #canUsurpType(c) {
|
|---|
| 481 | const m = usurpMap.get(this.type);
|
|---|
| 482 | return !!m?.has(c);
|
|---|
| 483 | }
|
|---|
| 484 | #canUsurp(child) {
|
|---|
| 485 | if (!child ||
|
|---|
| 486 | typeof child !== 'object' ||
|
|---|
| 487 | child.type !== null ||
|
|---|
| 488 | child.#parts.length !== 1 ||
|
|---|
| 489 | this.type === null ||
|
|---|
| 490 | this.#parts.length !== 1) {
|
|---|
| 491 | return false;
|
|---|
| 492 | }
|
|---|
| 493 | const gc = child.#parts[0];
|
|---|
| 494 | if (!gc || typeof gc !== 'object' || gc.type === null) {
|
|---|
| 495 | return false;
|
|---|
| 496 | }
|
|---|
| 497 | return this.#canUsurpType(gc.type);
|
|---|
| 498 | }
|
|---|
| 499 | #usurp(child) {
|
|---|
| 500 | const m = usurpMap.get(this.type);
|
|---|
| 501 | const gc = child.#parts[0];
|
|---|
| 502 | const nt = m?.get(gc.type);
|
|---|
| 503 | /* c8 ignore start - impossible */
|
|---|
| 504 | if (!nt)
|
|---|
| 505 | return false;
|
|---|
| 506 | /* c8 ignore stop */
|
|---|
| 507 | this.#parts = gc.#parts;
|
|---|
| 508 | for (const p of this.#parts) {
|
|---|
| 509 | if (typeof p === 'object') {
|
|---|
| 510 | p.#parent = this;
|
|---|
| 511 | }
|
|---|
| 512 | }
|
|---|
| 513 | this.type = nt;
|
|---|
| 514 | this.#toString = undefined;
|
|---|
| 515 | this.#emptyExt = false;
|
|---|
| 516 | }
|
|---|
| 517 | static fromGlob(pattern, options = {}) {
|
|---|
| 518 | const ast = new _a(null, undefined, options);
|
|---|
| 519 | _a.#parseAST(pattern, ast, 0, options, 0);
|
|---|
| 520 | return ast;
|
|---|
| 521 | }
|
|---|
| 522 | // returns the regular expression if there's magic, or the unescaped
|
|---|
| 523 | // string if not.
|
|---|
| 524 | toMMPattern() {
|
|---|
| 525 | // should only be called on root
|
|---|
| 526 | /* c8 ignore start */
|
|---|
| 527 | if (this !== this.#root)
|
|---|
| 528 | return this.#root.toMMPattern();
|
|---|
| 529 | /* c8 ignore stop */
|
|---|
| 530 | const glob = this.toString();
|
|---|
| 531 | const [re, body, hasMagic, uflag] = this.toRegExpSource();
|
|---|
| 532 | // if we're in nocase mode, and not nocaseMagicOnly, then we do
|
|---|
| 533 | // still need a regular expression if we have to case-insensitively
|
|---|
| 534 | // match capital/lowercase characters.
|
|---|
| 535 | const anyMagic = hasMagic ||
|
|---|
| 536 | this.#hasMagic ||
|
|---|
| 537 | (this.#options.nocase &&
|
|---|
| 538 | !this.#options.nocaseMagicOnly &&
|
|---|
| 539 | glob.toUpperCase() !== glob.toLowerCase());
|
|---|
| 540 | if (!anyMagic) {
|
|---|
| 541 | return body;
|
|---|
| 542 | }
|
|---|
| 543 | const flags = (this.#options.nocase ? 'i' : '') + (uflag ? 'u' : '');
|
|---|
| 544 | return Object.assign(new RegExp(`^${re}$`, flags), {
|
|---|
| 545 | _src: re,
|
|---|
| 546 | _glob: glob,
|
|---|
| 547 | });
|
|---|
| 548 | }
|
|---|
| 549 | get options() {
|
|---|
| 550 | return this.#options;
|
|---|
| 551 | }
|
|---|
| 552 | // returns the string match, the regexp source, whether there's magic
|
|---|
| 553 | // in the regexp (so a regular expression is required) and whether or
|
|---|
| 554 | // not the uflag is needed for the regular expression (for posix classes)
|
|---|
| 555 | // TODO: instead of injecting the start/end at this point, just return
|
|---|
| 556 | // the BODY of the regexp, along with the start/end portions suitable
|
|---|
| 557 | // for binding the start/end in either a joined full-path makeRe context
|
|---|
| 558 | // (where we bind to (^|/), or a standalone matchPart context (where
|
|---|
| 559 | // we bind to ^, and not /). Otherwise slashes get duped!
|
|---|
| 560 | //
|
|---|
| 561 | // In part-matching mode, the start is:
|
|---|
| 562 | // - if not isStart: nothing
|
|---|
| 563 | // - if traversal possible, but not allowed: ^(?!\.\.?$)
|
|---|
| 564 | // - if dots allowed or not possible: ^
|
|---|
| 565 | // - if dots possible and not allowed: ^(?!\.)
|
|---|
| 566 | // end is:
|
|---|
| 567 | // - if not isEnd(): nothing
|
|---|
| 568 | // - else: $
|
|---|
| 569 | //
|
|---|
| 570 | // In full-path matching mode, we put the slash at the START of the
|
|---|
| 571 | // pattern, so start is:
|
|---|
| 572 | // - if first pattern: same as part-matching mode
|
|---|
| 573 | // - if not isStart(): nothing
|
|---|
| 574 | // - if traversal possible, but not allowed: /(?!\.\.?(?:$|/))
|
|---|
| 575 | // - if dots allowed or not possible: /
|
|---|
| 576 | // - if dots possible and not allowed: /(?!\.)
|
|---|
| 577 | // end is:
|
|---|
| 578 | // - if last pattern, same as part-matching mode
|
|---|
| 579 | // - else nothing
|
|---|
| 580 | //
|
|---|
| 581 | // Always put the (?:$|/) on negated tails, though, because that has to be
|
|---|
| 582 | // there to bind the end of the negated pattern portion, and it's easier to
|
|---|
| 583 | // just stick it in now rather than try to inject it later in the middle of
|
|---|
| 584 | // the pattern.
|
|---|
| 585 | //
|
|---|
| 586 | // We can just always return the same end, and leave it up to the caller
|
|---|
| 587 | // to know whether it's going to be used joined or in parts.
|
|---|
| 588 | // And, if the start is adjusted slightly, can do the same there:
|
|---|
| 589 | // - if not isStart: nothing
|
|---|
| 590 | // - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$)
|
|---|
| 591 | // - if dots allowed or not possible: (?:/|^)
|
|---|
| 592 | // - if dots possible and not allowed: (?:/|^)(?!\.)
|
|---|
| 593 | //
|
|---|
| 594 | // But it's better to have a simpler binding without a conditional, for
|
|---|
| 595 | // performance, so probably better to return both start options.
|
|---|
| 596 | //
|
|---|
| 597 | // Then the caller just ignores the end if it's not the first pattern,
|
|---|
| 598 | // and the start always gets applied.
|
|---|
| 599 | //
|
|---|
| 600 | // But that's always going to be $ if it's the ending pattern, or nothing,
|
|---|
| 601 | // so the caller can just attach $ at the end of the pattern when building.
|
|---|
| 602 | //
|
|---|
| 603 | // So the todo is:
|
|---|
| 604 | // - better detect what kind of start is needed
|
|---|
| 605 | // - return both flavors of starting pattern
|
|---|
| 606 | // - attach $ at the end of the pattern when creating the actual RegExp
|
|---|
| 607 | //
|
|---|
| 608 | // Ah, but wait, no, that all only applies to the root when the first pattern
|
|---|
| 609 | // is not an extglob. If the first pattern IS an extglob, then we need all
|
|---|
| 610 | // that dot prevention biz to live in the extglob portions, because eg
|
|---|
| 611 | // +(*|.x*) can match .xy but not .yx.
|
|---|
| 612 | //
|
|---|
| 613 | // So, return the two flavors if it's #root and the first child is not an
|
|---|
| 614 | // AST, otherwise leave it to the child AST to handle it, and there,
|
|---|
| 615 | // use the (?:^|/) style of start binding.
|
|---|
| 616 | //
|
|---|
| 617 | // Even simplified further:
|
|---|
| 618 | // - Since the start for a join is eg /(?!\.) and the start for a part
|
|---|
| 619 | // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
|
|---|
| 620 | // or start or whatever) and prepend ^ or / at the Regexp construction.
|
|---|
| 621 | toRegExpSource(allowDot) {
|
|---|
| 622 | const dot = allowDot ?? !!this.#options.dot;
|
|---|
| 623 | if (this.#root === this) {
|
|---|
| 624 | this.#flatten();
|
|---|
| 625 | this.#fillNegs();
|
|---|
| 626 | }
|
|---|
| 627 | if (!isExtglobAST(this)) {
|
|---|
| 628 | const noEmpty = this.isStart() &&
|
|---|
| 629 | this.isEnd() &&
|
|---|
| 630 | !this.#parts.some(s => typeof s !== 'string');
|
|---|
| 631 | const src = this.#parts
|
|---|
| 632 | .map(p => {
|
|---|
| 633 | const [re, _, hasMagic, uflag] = typeof p === 'string' ?
|
|---|
| 634 | _a.#parseGlob(p, this.#hasMagic, noEmpty)
|
|---|
| 635 | : p.toRegExpSource(allowDot);
|
|---|
| 636 | this.#hasMagic = this.#hasMagic || hasMagic;
|
|---|
| 637 | this.#uflag = this.#uflag || uflag;
|
|---|
| 638 | return re;
|
|---|
| 639 | })
|
|---|
| 640 | .join('');
|
|---|
| 641 | let start = '';
|
|---|
| 642 | if (this.isStart()) {
|
|---|
| 643 | if (typeof this.#parts[0] === 'string') {
|
|---|
| 644 | // this is the string that will match the start of the pattern,
|
|---|
| 645 | // so we need to protect against dots and such.
|
|---|
| 646 | // '.' and '..' cannot match unless the pattern is that exactly,
|
|---|
| 647 | // even if it starts with . or dot:true is set.
|
|---|
| 648 | const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]);
|
|---|
| 649 | if (!dotTravAllowed) {
|
|---|
| 650 | const aps = addPatternStart;
|
|---|
| 651 | // check if we have a possibility of matching . or ..,
|
|---|
| 652 | // and prevent that.
|
|---|
| 653 | const needNoTrav =
|
|---|
| 654 | // dots are allowed, and the pattern starts with [ or .
|
|---|
| 655 | (dot && aps.has(src.charAt(0))) ||
|
|---|
| 656 | // the pattern starts with \., and then [ or .
|
|---|
| 657 | (src.startsWith('\\.') && aps.has(src.charAt(2))) ||
|
|---|
| 658 | // the pattern starts with \.\., and then [ or .
|
|---|
| 659 | (src.startsWith('\\.\\.') && aps.has(src.charAt(4)));
|
|---|
| 660 | // no need to prevent dots if it can't match a dot, or if a
|
|---|
| 661 | // sub-pattern will be preventing it anyway.
|
|---|
| 662 | const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
|
|---|
| 663 | start =
|
|---|
| 664 | needNoTrav ? startNoTraversal
|
|---|
| 665 | : needNoDot ? startNoDot
|
|---|
| 666 | : '';
|
|---|
| 667 | }
|
|---|
| 668 | }
|
|---|
| 669 | }
|
|---|
| 670 | // append the "end of path portion" pattern to negation tails
|
|---|
| 671 | let end = '';
|
|---|
| 672 | if (this.isEnd() &&
|
|---|
| 673 | this.#root.#filledNegs &&
|
|---|
| 674 | this.#parent?.type === '!') {
|
|---|
| 675 | end = '(?:$|\\/)';
|
|---|
| 676 | }
|
|---|
| 677 | const final = start + src + end;
|
|---|
| 678 | return [
|
|---|
| 679 | final,
|
|---|
| 680 | (0, unescape_js_1.unescape)(src),
|
|---|
| 681 | (this.#hasMagic = !!this.#hasMagic),
|
|---|
| 682 | this.#uflag,
|
|---|
| 683 | ];
|
|---|
| 684 | }
|
|---|
| 685 | // We need to calculate the body *twice* if it's a repeat pattern
|
|---|
| 686 | // at the start, once in nodot mode, then again in dot mode, so a
|
|---|
| 687 | // pattern like *(?) can match 'x.y'
|
|---|
| 688 | const repeated = this.type === '*' || this.type === '+';
|
|---|
| 689 | // some kind of extglob
|
|---|
| 690 | const start = this.type === '!' ? '(?:(?!(?:' : '(?:';
|
|---|
| 691 | let body = this.#partsToRegExp(dot);
|
|---|
| 692 | if (this.isStart() && this.isEnd() && !body && this.type !== '!') {
|
|---|
| 693 | // invalid extglob, has to at least be *something* present, if it's
|
|---|
| 694 | // the entire path portion.
|
|---|
| 695 | const s = this.toString();
|
|---|
| 696 | const me = this;
|
|---|
| 697 | me.#parts = [s];
|
|---|
| 698 | me.type = null;
|
|---|
| 699 | me.#hasMagic = undefined;
|
|---|
| 700 | return [s, (0, unescape_js_1.unescape)(this.toString()), false, false];
|
|---|
| 701 | }
|
|---|
| 702 | let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ?
|
|---|
| 703 | ''
|
|---|
| 704 | : this.#partsToRegExp(true);
|
|---|
| 705 | if (bodyDotAllowed === body) {
|
|---|
| 706 | bodyDotAllowed = '';
|
|---|
| 707 | }
|
|---|
| 708 | if (bodyDotAllowed) {
|
|---|
| 709 | body = `(?:${body})(?:${bodyDotAllowed})*?`;
|
|---|
| 710 | }
|
|---|
| 711 | // an empty !() is exactly equivalent to a starNoEmpty
|
|---|
| 712 | let final = '';
|
|---|
| 713 | if (this.type === '!' && this.#emptyExt) {
|
|---|
| 714 | final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty;
|
|---|
| 715 | }
|
|---|
| 716 | else {
|
|---|
| 717 | const close = this.type === '!' ?
|
|---|
| 718 | // !() must match something,but !(x) can match ''
|
|---|
| 719 | '))' +
|
|---|
| 720 | (this.isStart() && !dot && !allowDot ? startNoDot : '') +
|
|---|
| 721 | star +
|
|---|
| 722 | ')'
|
|---|
| 723 | : this.type === '@' ? ')'
|
|---|
| 724 | : this.type === '?' ? ')?'
|
|---|
| 725 | : this.type === '+' && bodyDotAllowed ? ')'
|
|---|
| 726 | : this.type === '*' && bodyDotAllowed ? `)?`
|
|---|
| 727 | : `)${this.type}`;
|
|---|
| 728 | final = start + body + close;
|
|---|
| 729 | }
|
|---|
| 730 | return [
|
|---|
| 731 | final,
|
|---|
| 732 | (0, unescape_js_1.unescape)(body),
|
|---|
| 733 | (this.#hasMagic = !!this.#hasMagic),
|
|---|
| 734 | this.#uflag,
|
|---|
| 735 | ];
|
|---|
| 736 | }
|
|---|
| 737 | #flatten() {
|
|---|
| 738 | if (!isExtglobAST(this)) {
|
|---|
| 739 | for (const p of this.#parts) {
|
|---|
| 740 | if (typeof p === 'object') {
|
|---|
| 741 | p.#flatten();
|
|---|
| 742 | }
|
|---|
| 743 | }
|
|---|
| 744 | }
|
|---|
| 745 | else {
|
|---|
| 746 | // do up to 10 passes to flatten as much as possible
|
|---|
| 747 | let iterations = 0;
|
|---|
| 748 | let done = false;
|
|---|
| 749 | do {
|
|---|
| 750 | done = true;
|
|---|
| 751 | for (let i = 0; i < this.#parts.length; i++) {
|
|---|
| 752 | const c = this.#parts[i];
|
|---|
| 753 | if (typeof c === 'object') {
|
|---|
| 754 | c.#flatten();
|
|---|
| 755 | if (this.#canAdopt(c)) {
|
|---|
| 756 | done = false;
|
|---|
| 757 | this.#adopt(c, i);
|
|---|
| 758 | }
|
|---|
| 759 | else if (this.#canAdoptWithSpace(c)) {
|
|---|
| 760 | done = false;
|
|---|
| 761 | this.#adoptWithSpace(c, i);
|
|---|
| 762 | }
|
|---|
| 763 | else if (this.#canUsurp(c)) {
|
|---|
| 764 | done = false;
|
|---|
| 765 | this.#usurp(c);
|
|---|
| 766 | }
|
|---|
| 767 | }
|
|---|
| 768 | }
|
|---|
| 769 | } while (!done && ++iterations < 10);
|
|---|
| 770 | }
|
|---|
| 771 | this.#toString = undefined;
|
|---|
| 772 | }
|
|---|
| 773 | #partsToRegExp(dot) {
|
|---|
| 774 | return this.#parts
|
|---|
| 775 | .map(p => {
|
|---|
| 776 | // extglob ASTs should only contain parent ASTs
|
|---|
| 777 | /* c8 ignore start */
|
|---|
| 778 | if (typeof p === 'string') {
|
|---|
| 779 | throw new Error('string type in extglob ast??');
|
|---|
| 780 | }
|
|---|
| 781 | /* c8 ignore stop */
|
|---|
| 782 | // can ignore hasMagic, because extglobs are already always magic
|
|---|
| 783 | const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot);
|
|---|
| 784 | this.#uflag = this.#uflag || uflag;
|
|---|
| 785 | return re;
|
|---|
| 786 | })
|
|---|
| 787 | .filter(p => !(this.isStart() && this.isEnd()) || !!p)
|
|---|
| 788 | .join('|');
|
|---|
| 789 | }
|
|---|
| 790 | static #parseGlob(glob, hasMagic, noEmpty = false) {
|
|---|
| 791 | let escaping = false;
|
|---|
| 792 | let re = '';
|
|---|
| 793 | let uflag = false;
|
|---|
| 794 | // multiple stars that aren't globstars coalesce into one *
|
|---|
| 795 | let inStar = false;
|
|---|
| 796 | for (let i = 0; i < glob.length; i++) {
|
|---|
| 797 | const c = glob.charAt(i);
|
|---|
| 798 | if (escaping) {
|
|---|
| 799 | escaping = false;
|
|---|
| 800 | re += (reSpecials.has(c) ? '\\' : '') + c;
|
|---|
| 801 | continue;
|
|---|
| 802 | }
|
|---|
| 803 | if (c === '*') {
|
|---|
| 804 | if (inStar)
|
|---|
| 805 | continue;
|
|---|
| 806 | inStar = true;
|
|---|
| 807 | re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
|
|---|
| 808 | hasMagic = true;
|
|---|
| 809 | continue;
|
|---|
| 810 | }
|
|---|
| 811 | else {
|
|---|
| 812 | inStar = false;
|
|---|
| 813 | }
|
|---|
| 814 | if (c === '\\') {
|
|---|
| 815 | if (i === glob.length - 1) {
|
|---|
| 816 | re += '\\\\';
|
|---|
| 817 | }
|
|---|
| 818 | else {
|
|---|
| 819 | escaping = true;
|
|---|
| 820 | }
|
|---|
| 821 | continue;
|
|---|
| 822 | }
|
|---|
| 823 | if (c === '[') {
|
|---|
| 824 | const [src, needUflag, consumed, magic] = (0, brace_expressions_js_1.parseClass)(glob, i);
|
|---|
| 825 | if (consumed) {
|
|---|
| 826 | re += src;
|
|---|
| 827 | uflag = uflag || needUflag;
|
|---|
| 828 | i += consumed - 1;
|
|---|
| 829 | hasMagic = hasMagic || magic;
|
|---|
| 830 | continue;
|
|---|
| 831 | }
|
|---|
| 832 | }
|
|---|
| 833 | if (c === '?') {
|
|---|
| 834 | re += qmark;
|
|---|
| 835 | hasMagic = true;
|
|---|
| 836 | continue;
|
|---|
| 837 | }
|
|---|
| 838 | re += regExpEscape(c);
|
|---|
| 839 | }
|
|---|
| 840 | return [re, (0, unescape_js_1.unescape)(glob), !!hasMagic, uflag];
|
|---|
| 841 | }
|
|---|
| 842 | }
|
|---|
| 843 | exports.AST = AST;
|
|---|
| 844 | _a = AST;
|
|---|
| 845 | //# sourceMappingURL=ast.js.map |
|---|