ast.js 27 KB

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