avancement planning
This commit is contained in:
+312
-59
@@ -1,11 +1,113 @@
|
||||
"use strict";
|
||||
// parse a single path portion
|
||||
var _a;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AST = void 0;
|
||||
const brace_expressions_js_1 = require("./brace-expressions.js");
|
||||
const unescape_js_1 = require("./unescape.js");
|
||||
const types = new Set(['!', '?', '+', '*', '@']);
|
||||
const isExtglobType = (c) => types.has(c);
|
||||
const isExtglobAST = (c) => isExtglobType(c.type);
|
||||
// Map of which extglob types can adopt the children of a nested extglob
|
||||
//
|
||||
// anything but ! can adopt a matching type:
|
||||
// +(a|+(b|c)|d) => +(a|b|c|d)
|
||||
// *(a|*(b|c)|d) => *(a|b|c|d)
|
||||
// @(a|@(b|c)|d) => @(a|b|c|d)
|
||||
// ?(a|?(b|c)|d) => ?(a|b|c|d)
|
||||
//
|
||||
// * can adopt anything, because 0 or repetition is allowed
|
||||
// *(a|?(b|c)|d) => *(a|b|c|d)
|
||||
// *(a|+(b|c)|d) => *(a|b|c|d)
|
||||
// *(a|@(b|c)|d) => *(a|b|c|d)
|
||||
//
|
||||
// + can adopt @, because 1 or repetition is allowed
|
||||
// +(a|@(b|c)|d) => +(a|b|c|d)
|
||||
//
|
||||
// + and @ CANNOT adopt *, because 0 would be allowed
|
||||
// +(a|*(b|c)|d) => would match "", on *(b|c)
|
||||
// @(a|*(b|c)|d) => would match "", on *(b|c)
|
||||
//
|
||||
// + and @ CANNOT adopt ?, because 0 would be allowed
|
||||
// +(a|?(b|c)|d) => would match "", on ?(b|c)
|
||||
// @(a|?(b|c)|d) => would match "", on ?(b|c)
|
||||
//
|
||||
// ? can adopt @, because 0 or 1 is allowed
|
||||
// ?(a|@(b|c)|d) => ?(a|b|c|d)
|
||||
//
|
||||
// ? and @ CANNOT adopt * or +, because >1 would be allowed
|
||||
// ?(a|*(b|c)|d) => would match bbb on *(b|c)
|
||||
// @(a|*(b|c)|d) => would match bbb on *(b|c)
|
||||
// ?(a|+(b|c)|d) => would match bbb on +(b|c)
|
||||
// @(a|+(b|c)|d) => would match bbb on +(b|c)
|
||||
//
|
||||
// ! CANNOT adopt ! (nothing else can either)
|
||||
// !(a|!(b|c)|d) => !(a|b|c|d) would fail to match on b (not not b|c)
|
||||
//
|
||||
// ! can adopt @
|
||||
// !(a|@(b|c)|d) => !(a|b|c|d)
|
||||
//
|
||||
// ! CANNOT adopt *
|
||||
// !(a|*(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
|
||||
//
|
||||
// ! CANNOT adopt +
|
||||
// !(a|+(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
|
||||
//
|
||||
// ! CANNOT adopt ?
|
||||
// x!(a|?(b|c)|d) => x!(a|b|c|d) would fail to match "x"
|
||||
const adoptionMap = new Map([
|
||||
['!', ['@']],
|
||||
['?', ['?', '@']],
|
||||
['@', ['@']],
|
||||
['*', ['*', '+', '?', '@']],
|
||||
['+', ['+', '@']],
|
||||
]);
|
||||
// nested extglobs that can be adopted in, but with the addition of
|
||||
// a blank '' element.
|
||||
const adoptionWithSpaceMap = new Map([
|
||||
['!', ['?']],
|
||||
['@', ['?']],
|
||||
['+', ['?', '*']],
|
||||
]);
|
||||
// union of the previous two maps
|
||||
const adoptionAnyMap = new Map([
|
||||
['!', ['?', '@']],
|
||||
['?', ['?', '@']],
|
||||
['@', ['?', '@']],
|
||||
['*', ['*', '+', '?', '@']],
|
||||
['+', ['+', '@', '?', '*']],
|
||||
]);
|
||||
// Extglobs that can take over their parent if they are the only child
|
||||
// the key is parent, value maps child to resulting extglob parent type
|
||||
// '@' is omitted because it's a special case. An `@` extglob with a single
|
||||
// member can always be usurped by that subpattern.
|
||||
const usurpMap = new Map([
|
||||
['!', new Map([['!', '@']])],
|
||||
[
|
||||
'?',
|
||||
new Map([
|
||||
['*', '*'],
|
||||
['+', '*'],
|
||||
]),
|
||||
],
|
||||
[
|
||||
'@',
|
||||
new Map([
|
||||
['!', '!'],
|
||||
['?', '?'],
|
||||
['@', '@'],
|
||||
['*', '*'],
|
||||
['+', '+'],
|
||||
]),
|
||||
],
|
||||
[
|
||||
'+',
|
||||
new Map([
|
||||
['?', '*'],
|
||||
['*', '*'],
|
||||
]),
|
||||
],
|
||||
]);
|
||||
// Patterns that get prepended to bind to the start of either the
|
||||
// entire string, or just a single path portion, to prevent dots
|
||||
// and/or traversal patterns, when needed.
|
||||
@@ -29,6 +131,7 @@ const star = qmark + '*?';
|
||||
const starNoEmpty = qmark + '+?';
|
||||
// remove the \ chars that we added if we end up doing a nonmagic compare
|
||||
// const deslash = (s: string) => s.replace(/\\(.)/g, '$1')
|
||||
let ID = 0;
|
||||
class AST {
|
||||
type;
|
||||
#root;
|
||||
@@ -44,6 +147,22 @@ class AST {
|
||||
// set to true if it's an extglob with no children
|
||||
// (which really means one child of '')
|
||||
#emptyExt = false;
|
||||
id = ++ID;
|
||||
get depth() {
|
||||
return (this.#parent?.depth ?? -1) + 1;
|
||||
}
|
||||
[Symbol.for('nodejs.util.inspect.custom')]() {
|
||||
return {
|
||||
'@@type': 'AST',
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
root: this.#root.id,
|
||||
parent: this.#parent?.id,
|
||||
depth: this.depth,
|
||||
partsLength: this.#parts.length,
|
||||
parts: this.#parts,
|
||||
};
|
||||
}
|
||||
constructor(type, parent, options = {}) {
|
||||
this.type = type;
|
||||
// extglobs are inherently magical
|
||||
@@ -73,15 +192,14 @@ class AST {
|
||||
}
|
||||
// reconstructs the pattern
|
||||
toString() {
|
||||
if (this.#toString !== undefined)
|
||||
return this.#toString;
|
||||
if (!this.type) {
|
||||
return (this.#toString = this.#parts.map(p => String(p)).join(''));
|
||||
}
|
||||
else {
|
||||
return (this.#toString =
|
||||
this.type + '(' + this.#parts.map(p => String(p)).join('|') + ')');
|
||||
}
|
||||
return (this.#toString !== undefined ? this.#toString
|
||||
: !this.type ?
|
||||
(this.#toString = this.#parts.map(p => String(p)).join(''))
|
||||
: (this.#toString =
|
||||
this.type +
|
||||
'(' +
|
||||
this.#parts.map(p => String(p)).join('|') +
|
||||
')'));
|
||||
}
|
||||
#fillNegs() {
|
||||
/* c8 ignore start */
|
||||
@@ -122,7 +240,8 @@ class AST {
|
||||
if (p === '')
|
||||
continue;
|
||||
/* c8 ignore start */
|
||||
if (typeof p !== 'string' && !(p instanceof AST && p.#parent === this)) {
|
||||
if (typeof p !== 'string' &&
|
||||
!(p instanceof _a && p.#parent === this)) {
|
||||
throw new Error('invalid part: ' + p);
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
@@ -130,8 +249,10 @@ class AST {
|
||||
}
|
||||
}
|
||||
toJSON() {
|
||||
const ret = this.type === null
|
||||
? this.#parts.slice().map(p => (typeof p === 'string' ? p : p.toJSON()))
|
||||
const ret = this.type === null ?
|
||||
this.#parts
|
||||
.slice()
|
||||
.map(p => (typeof p === 'string' ? p : p.toJSON()))
|
||||
: [this.type, ...this.#parts.map(p => p.toJSON())];
|
||||
if (this.isStart() && !this.type)
|
||||
ret.unshift([]);
|
||||
@@ -154,7 +275,7 @@ class AST {
|
||||
const p = this.#parent;
|
||||
for (let i = 0; i < this.#parentIndex; i++) {
|
||||
const pp = p.#parts[i];
|
||||
if (!(pp instanceof AST && pp.type === '!')) {
|
||||
if (!(pp instanceof _a && pp.type === '!')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -182,13 +303,14 @@ class AST {
|
||||
this.push(part.clone(this));
|
||||
}
|
||||
clone(parent) {
|
||||
const c = new AST(this.type, parent);
|
||||
const c = new _a(this.type, parent);
|
||||
for (const p of this.#parts) {
|
||||
c.copyIn(p);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
static #parseAST(str, ast, pos, opt) {
|
||||
static #parseAST(str, ast, pos, opt, extDepth) {
|
||||
const maxDepth = opt.maxExtglobRecursion ?? 2;
|
||||
let escaping = false;
|
||||
let inBrace = false;
|
||||
let braceStart = -1;
|
||||
@@ -225,11 +347,17 @@ class AST {
|
||||
acc += c;
|
||||
continue;
|
||||
}
|
||||
if (!opt.noext && isExtglobType(c) && str.charAt(i) === '(') {
|
||||
// we don't have to check for adoption here, because that's
|
||||
// done at the other recursion point.
|
||||
const doRecurse = !opt.noext &&
|
||||
isExtglobType(c) &&
|
||||
str.charAt(i) === '(' &&
|
||||
extDepth <= maxDepth;
|
||||
if (doRecurse) {
|
||||
ast.push(acc);
|
||||
acc = '';
|
||||
const ext = new AST(c, ast);
|
||||
i = AST.#parseAST(str, ext, i, opt);
|
||||
const ext = new _a(c, ast);
|
||||
i = _a.#parseAST(str, ext, i, opt, extDepth + 1);
|
||||
ast.push(ext);
|
||||
continue;
|
||||
}
|
||||
@@ -241,7 +369,7 @@ class AST {
|
||||
// some kind of extglob, pos is at the (
|
||||
// find the next | or )
|
||||
let i = pos + 1;
|
||||
let part = new AST(null, ast);
|
||||
let part = new _a(null, ast);
|
||||
const parts = [];
|
||||
let acc = '';
|
||||
while (i < str.length) {
|
||||
@@ -272,19 +400,26 @@ class AST {
|
||||
acc += c;
|
||||
continue;
|
||||
}
|
||||
if (isExtglobType(c) && str.charAt(i) === '(') {
|
||||
const doRecurse = !opt.noext &&
|
||||
isExtglobType(c) &&
|
||||
str.charAt(i) === '(' &&
|
||||
/* c8 ignore start - the maxDepth is sufficient here */
|
||||
(extDepth <= maxDepth || (ast && ast.#canAdoptType(c)));
|
||||
/* c8 ignore stop */
|
||||
if (doRecurse) {
|
||||
const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1;
|
||||
part.push(acc);
|
||||
acc = '';
|
||||
const ext = new AST(c, part);
|
||||
const ext = new _a(c, part);
|
||||
part.push(ext);
|
||||
i = AST.#parseAST(str, ext, i, opt);
|
||||
i = _a.#parseAST(str, ext, i, opt, extDepth + depthAdd);
|
||||
continue;
|
||||
}
|
||||
if (c === '|') {
|
||||
part.push(acc);
|
||||
acc = '';
|
||||
parts.push(part);
|
||||
part = new AST(null, ast);
|
||||
part = new _a(null, ast);
|
||||
continue;
|
||||
}
|
||||
if (c === ')') {
|
||||
@@ -306,9 +441,82 @@ class AST {
|
||||
ast.#parts = [str.substring(pos - 1)];
|
||||
return i;
|
||||
}
|
||||
#canAdoptWithSpace(child) {
|
||||
return this.#canAdopt(child, adoptionWithSpaceMap);
|
||||
}
|
||||
#canAdopt(child, map = adoptionMap) {
|
||||
if (!child ||
|
||||
typeof child !== 'object' ||
|
||||
child.type !== null ||
|
||||
child.#parts.length !== 1 ||
|
||||
this.type === null) {
|
||||
return false;
|
||||
}
|
||||
const gc = child.#parts[0];
|
||||
if (!gc || typeof gc !== 'object' || gc.type === null) {
|
||||
return false;
|
||||
}
|
||||
return this.#canAdoptType(gc.type, map);
|
||||
}
|
||||
#canAdoptType(c, map = adoptionAnyMap) {
|
||||
return !!map.get(this.type)?.includes(c);
|
||||
}
|
||||
#adoptWithSpace(child, index) {
|
||||
const gc = child.#parts[0];
|
||||
const blank = new _a(null, gc, this.options);
|
||||
blank.#parts.push('');
|
||||
gc.push(blank);
|
||||
this.#adopt(child, index);
|
||||
}
|
||||
#adopt(child, index) {
|
||||
const gc = child.#parts[0];
|
||||
this.#parts.splice(index, 1, ...gc.#parts);
|
||||
for (const p of gc.#parts) {
|
||||
if (typeof p === 'object')
|
||||
p.#parent = this;
|
||||
}
|
||||
this.#toString = undefined;
|
||||
}
|
||||
#canUsurpType(c) {
|
||||
const m = usurpMap.get(this.type);
|
||||
return !!m?.has(c);
|
||||
}
|
||||
#canUsurp(child) {
|
||||
if (!child ||
|
||||
typeof child !== 'object' ||
|
||||
child.type !== null ||
|
||||
child.#parts.length !== 1 ||
|
||||
this.type === null ||
|
||||
this.#parts.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const gc = child.#parts[0];
|
||||
if (!gc || typeof gc !== 'object' || gc.type === null) {
|
||||
return false;
|
||||
}
|
||||
return this.#canUsurpType(gc.type);
|
||||
}
|
||||
#usurp(child) {
|
||||
const m = usurpMap.get(this.type);
|
||||
const gc = child.#parts[0];
|
||||
const nt = m?.get(gc.type);
|
||||
/* c8 ignore start - impossible */
|
||||
if (!nt)
|
||||
return false;
|
||||
/* c8 ignore stop */
|
||||
this.#parts = gc.#parts;
|
||||
for (const p of this.#parts) {
|
||||
if (typeof p === 'object') {
|
||||
p.#parent = this;
|
||||
}
|
||||
}
|
||||
this.type = nt;
|
||||
this.#toString = undefined;
|
||||
this.#emptyExt = false;
|
||||
}
|
||||
static fromGlob(pattern, options = {}) {
|
||||
const ast = new AST(null, undefined, options);
|
||||
AST.#parseAST(pattern, ast, 0, options);
|
||||
const ast = new _a(null, undefined, options);
|
||||
_a.#parseAST(pattern, ast, 0, options, 0);
|
||||
return ast;
|
||||
}
|
||||
// returns the regular expression if there's magic, or the unescaped
|
||||
@@ -412,14 +620,18 @@ class AST {
|
||||
// or start or whatever) and prepend ^ or / at the Regexp construction.
|
||||
toRegExpSource(allowDot) {
|
||||
const dot = allowDot ?? !!this.#options.dot;
|
||||
if (this.#root === this)
|
||||
if (this.#root === this) {
|
||||
this.#flatten();
|
||||
this.#fillNegs();
|
||||
if (!this.type) {
|
||||
const noEmpty = this.isStart() && this.isEnd();
|
||||
}
|
||||
if (!isExtglobAST(this)) {
|
||||
const noEmpty = this.isStart() &&
|
||||
this.isEnd() &&
|
||||
!this.#parts.some(s => typeof s !== 'string');
|
||||
const src = this.#parts
|
||||
.map(p => {
|
||||
const [re, _, hasMagic, uflag] = typeof p === 'string'
|
||||
? AST.#parseGlob(p, this.#hasMagic, noEmpty)
|
||||
const [re, _, hasMagic, uflag] = typeof p === 'string' ?
|
||||
_a.#parseGlob(p, this.#hasMagic, noEmpty)
|
||||
: p.toRegExpSource(allowDot);
|
||||
this.#hasMagic = this.#hasMagic || hasMagic;
|
||||
this.#uflag = this.#uflag || uflag;
|
||||
@@ -448,7 +660,10 @@ class AST {
|
||||
// no need to prevent dots if it can't match a dot, or if a
|
||||
// sub-pattern will be preventing it anyway.
|
||||
const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
|
||||
start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : '';
|
||||
start =
|
||||
needNoTrav ? startNoTraversal
|
||||
: needNoDot ? startNoDot
|
||||
: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,14 +693,14 @@ class AST {
|
||||
// invalid extglob, has to at least be *something* present, if it's
|
||||
// the entire path portion.
|
||||
const s = this.toString();
|
||||
this.#parts = [s];
|
||||
this.type = null;
|
||||
this.#hasMagic = undefined;
|
||||
const me = this;
|
||||
me.#parts = [s];
|
||||
me.type = null;
|
||||
me.#hasMagic = undefined;
|
||||
return [s, (0, unescape_js_1.unescape)(this.toString()), false, false];
|
||||
}
|
||||
// XXX abstract out this map method
|
||||
let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot
|
||||
? ''
|
||||
let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ?
|
||||
''
|
||||
: this.#partsToRegExp(true);
|
||||
if (bodyDotAllowed === body) {
|
||||
bodyDotAllowed = '';
|
||||
@@ -499,20 +714,16 @@ class AST {
|
||||
final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty;
|
||||
}
|
||||
else {
|
||||
const close = this.type === '!'
|
||||
? // !() must match something,but !(x) can match ''
|
||||
'))' +
|
||||
(this.isStart() && !dot && !allowDot ? startNoDot : '') +
|
||||
star +
|
||||
')'
|
||||
: this.type === '@'
|
||||
? ')'
|
||||
: this.type === '?'
|
||||
? ')?'
|
||||
: this.type === '+' && bodyDotAllowed
|
||||
? ')'
|
||||
: this.type === '*' && bodyDotAllowed
|
||||
? `)?`
|
||||
const close = this.type === '!' ?
|
||||
// !() must match something,but !(x) can match ''
|
||||
'))' +
|
||||
(this.isStart() && !dot && !allowDot ? startNoDot : '') +
|
||||
star +
|
||||
')'
|
||||
: this.type === '@' ? ')'
|
||||
: this.type === '?' ? ')?'
|
||||
: this.type === '+' && bodyDotAllowed ? ')'
|
||||
: this.type === '*' && bodyDotAllowed ? `)?`
|
||||
: `)${this.type}`;
|
||||
final = start + body + close;
|
||||
}
|
||||
@@ -523,6 +734,42 @@ class AST {
|
||||
this.#uflag,
|
||||
];
|
||||
}
|
||||
#flatten() {
|
||||
if (!isExtglobAST(this)) {
|
||||
for (const p of this.#parts) {
|
||||
if (typeof p === 'object') {
|
||||
p.#flatten();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// do up to 10 passes to flatten as much as possible
|
||||
let iterations = 0;
|
||||
let done = false;
|
||||
do {
|
||||
done = true;
|
||||
for (let i = 0; i < this.#parts.length; i++) {
|
||||
const c = this.#parts[i];
|
||||
if (typeof c === 'object') {
|
||||
c.#flatten();
|
||||
if (this.#canAdopt(c)) {
|
||||
done = false;
|
||||
this.#adopt(c, i);
|
||||
}
|
||||
else if (this.#canAdoptWithSpace(c)) {
|
||||
done = false;
|
||||
this.#adoptWithSpace(c, i);
|
||||
}
|
||||
else if (this.#canUsurp(c)) {
|
||||
done = false;
|
||||
this.#usurp(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!done && ++iterations < 10);
|
||||
}
|
||||
this.#toString = undefined;
|
||||
}
|
||||
#partsToRegExp(dot) {
|
||||
return this.#parts
|
||||
.map(p => {
|
||||
@@ -544,6 +791,8 @@ class AST {
|
||||
let escaping = false;
|
||||
let re = '';
|
||||
let uflag = false;
|
||||
// multiple stars that aren't globstars coalesce into one *
|
||||
let inStar = false;
|
||||
for (let i = 0; i < glob.length; i++) {
|
||||
const c = glob.charAt(i);
|
||||
if (escaping) {
|
||||
@@ -551,6 +800,17 @@ class AST {
|
||||
re += (reSpecials.has(c) ? '\\' : '') + c;
|
||||
continue;
|
||||
}
|
||||
if (c === '*') {
|
||||
if (inStar)
|
||||
continue;
|
||||
inStar = true;
|
||||
re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
|
||||
hasMagic = true;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
inStar = false;
|
||||
}
|
||||
if (c === '\\') {
|
||||
if (i === glob.length - 1) {
|
||||
re += '\\\\';
|
||||
@@ -570,14 +830,6 @@ class AST {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (c === '*') {
|
||||
if (noEmpty && glob === '*')
|
||||
re += starNoEmpty;
|
||||
else
|
||||
re += star;
|
||||
hasMagic = true;
|
||||
continue;
|
||||
}
|
||||
if (c === '?') {
|
||||
re += qmark;
|
||||
hasMagic = true;
|
||||
@@ -589,4 +841,5 @@ class AST {
|
||||
}
|
||||
}
|
||||
exports.AST = AST;
|
||||
_a = AST;
|
||||
//# sourceMappingURL=ast.js.map
|
||||
Reference in New Issue
Block a user