diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dff9224..f54f87dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Released packages +- complex-matcher v0.4.0 - xo-server-backup-reports v0.12.3 - xo-server v5.23.0 - xo-web v5.23.0 diff --git a/packages/complex-matcher/src/index.fixtures.js b/packages/complex-matcher/src/index.fixtures.js index 44d0dbc18..a93e29358 100644 --- a/packages/complex-matcher/src/index.fixtures.js +++ b/packages/complex-matcher/src/index.fixtures.js @@ -1,7 +1,7 @@ import * as CM from './' export const pattern = - 'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape? age:32 chi*go' + 'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape? age:32 chi*go /^foo\\/bar\\./i' export const ast = new CM.And([ new CM.String('foo'), @@ -13,4 +13,5 @@ export const ast = new CM.And([ new CM.TruthyProperty('hasCape'), new CM.Property('age', new CM.Number(32)), new CM.GlobPattern('chi*go'), + new CM.RegExp('^foo/bar\\.', 'i'), ]) diff --git a/packages/complex-matcher/src/index.js b/packages/complex-matcher/src/index.js index f55b9831c..604fd42e6 100644 --- a/packages/complex-matcher/src/index.js +++ b/packages/complex-matcher/src/index.js @@ -220,6 +220,37 @@ export class GlobPattern extends Node { return this.value } } + +export class RegExpNode extends Node { + constructor (pattern, flags) { + super() + + this.re = new RegExp(pattern, flags) + + // should not be enumerable for the tests + Object.defineProperty(this, 'match', { + value: this.match.bind(this), + }) + } + + match (value) { + if (typeof value === 'string') { + return this.re.test(value) + } + + if (Array.isArray(value) || isPlainObject(value)) { + return some(value, this.match) + } + + return false + } + + toString () { + return this.re.toString() + } +} +export { RegExpNode as RegExp } + export class StringNode extends Node { constructor (value) { super() @@ -471,6 +502,33 @@ const parser = P.grammar({ ? new Failure(pos, 'a raw string') : new Success(pos, value) }), + regex: new P((input, pos, end) => { + if (input[pos] !== '/') { + return new Failure(pos, '/') + } + ++pos + + let c + + let pattern = '' + let escaped = false + while (pos < end && ((c = input[pos++]) !== '/' || escaped)) { + escaped = c === '\\' + pattern += c + } + + if (c !== '/') { + return new Failure(pos, '/') + } + + let flags = '' + if (pos < end && (c = input[pos]) === 'i') { + ++pos + flags += c + } + + return new Success(pos, new RegExpNode(pattern, flags)) + }), term: r => P.alt( P.seq(P.text('('), r.ws, r.term.repeat(1), P.text(')')).map( @@ -501,6 +559,7 @@ const parser = P.grammar({ value: r => P.alt( r.quotedString.map(_ => new StringNode(_)), + r.regex, r.globPattern.map(str => { const asNum = +str return Number.isNaN(asNum)