ast.js 27 KB

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