diff --git a/src/addons/build.js b/src/addons/build.js index b6d2370..9a24612 100644 --- a/src/addons/build.js +++ b/src/addons/build.js @@ -61,6 +61,30 @@ module.exports = function(XRegExp) { XRegExp(value, flags); } + /** + * Provides a tag function for building regexes using template literals [1]. See GitHub issue + * 103 for discussion [2]. + * + * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals + * [2]: https://github.com/slevithan/xregexp/issues/103 + */ + XRegExp.tag = function (flags) { + return function tag (literals /*, ...substitutions */) { + var substitutions = [].slice.call(arguments, 1); + var subpatterns = substitutions.concat('').map(interpolate); + var pattern = literals.raw.map(embedSubpatternAfter).join(''); + return XRegExp.build(pattern, subpatterns, flags); + }; + + function interpolate (substitution) { + return substitution instanceof RegExp ? substitution : XRegExp.escape(substitution); + } + + function embedSubpatternAfter (raw, subpatternIndex) { + return raw + '{{' + subpatternIndex + '}}'; + } + }; + /** * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in * the outer pattern and provided subpatterns are automatically renumbered to work correctly. diff --git a/tests/spec/s-addons-build.js b/tests/spec/s-addons-build.js index 606293d..a26a773 100644 --- a/tests/spec/s-addons-build.js +++ b/tests/spec/s-addons-build.js @@ -1,5 +1,81 @@ describe('XRegExp.build addon:', function() { + describe('XRegExp.tag()', function() { + + it('should escape the metacharacters of interpolated strings', function() { + var inner = '.html' + var re = XRegExp.tag()`^index${inner}$`; + + expect(re.test('index.html')).toBe(true); + expect(re.test('index-html')).toBe(false); + }); + + it('should rewrite the backreferences of interpolated regexes', function() { + var inner = /(.)\1/; + var re = XRegExp.tag()`^${inner}${inner}$`; + + expect(re.test('aabb')).toBe(true); + expect(re.test('aaba')).toBe(false); + }); + + it('should treat interpolated strings as atomic tokens', function() { + var inner = 'ab'; + var re = XRegExp.tag()`^${inner}+$`; + + expect(re.test('abab')).toBe(true); + expect(re.test('abb')).toBe(false); + }); + + it('should treat interpolated regexes as atomic tokens', function() { + var inner = /ab/; + var re = XRegExp.tag()`^${inner}+$`; + + expect(re.test('abab')).toBe(true); + expect(re.test('abb')).toBe(false); + }); + + it('should support the "x" flag', function() { + var inner = /ab/; + var re = XRegExp.tag('x')` + ^ + ${inner} + + + $ + `; + + expect(re.test('abab')).toBe(true); + expect(re.test('abb')).toBe(false); + }); + + it('should support the "n" flag', function() { + var inner = XRegExp('(unnamed), (?named)'); + var re = XRegExp.tag('n')`${inner}`; + + expect(re.exec('unnamed, named')[1]).toBe('named'); + }); + + it('should support the "g" flag', function() { + var inner = 'a'; + var re = XRegExp.tag('g')`${inner}`; + + expect('aaa'.match(re)).toEqual(['a', 'a', 'a']); + }); + + it('should allow `false` to be interpolated', function() { + var inner = false; + var re = XRegExp.tag()`^${inner}$`; + + expect(re.test('false')).toBe(true); + }); + + it('should allow unescaped character classes', function() { + var re = XRegExp.tag()`\d`; + + expect(re.test('1')).toBe(true); + }); + + }); + describe('XRegExp.build()', function() { it('should apply a mode modifier in the outer pattern to the full regex with interpolated values', function() {