Skip to content

Commit

Permalink
Pass XRegExp.build flags through asXRegExp to XRegExp constructor (#163)
Browse files Browse the repository at this point in the history
Work around Firefox issue where '\n' is not ignored with flag x when used with XRegExp.build. See #163.
  • Loading branch information
josephfrazier authored and slevithan committed Mar 23, 2017
1 parent 56ccef1 commit c1629b8
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 12 deletions.
21 changes: 15 additions & 6 deletions src/addons/build.js
Expand Up @@ -38,22 +38,24 @@ module.exports = function(XRegExp) {
}

/**
* Converts the provided value to an XRegExp. Native RegExp flags are not preserved.
* Converts the provided value to an XRegExp. Native RegExp flags are not preserved. Flags can
* be provided as a separate argument.
*
* @private
* @param {String|RegExp} value Value to convert.
* @param {String} [flags] Any combination of XRegExp flags.
* @returns {RegExp} XRegExp object with XRegExp syntax applied.
*/
function asXRegExp(value) {
function asXRegExp(value, flags) {
return XRegExp.isRegExp(value) ?
(value[REGEX_DATA] && value[REGEX_DATA].captureNames ?
// Don't recompile, to preserve capture names
value :
// Recompile as XRegExp
XRegExp(value.source)
XRegExp(value.source, flags)
) :
// Compile string as XRegExp
XRegExp(value);
XRegExp(value, flags);
}

/**
Expand Down Expand Up @@ -83,6 +85,13 @@ module.exports = function(XRegExp) {
*/
XRegExp.build = function(pattern, subs, flags) {
var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern),
// These flags will be passed to the asXRegExp calls for `pattern` and for every
// subpattern in `subs`. This is to work around the following browser bugs:
//
// * Firefox converts '\n' to a regex that contains the literal characters \ and n
// You can verify this by running `console.log(RegExp('\n').source)`
// See here for more details: https://github.com/slevithan/xregexp/pull/163
asXRegExpFlags = (flags || '').indexOf('x') > -1 ? 'x' : '',
data = {},
numCaps = 0, // 'Caps' is short for captures
numPriorCaps,
Expand All @@ -107,7 +116,7 @@ module.exports = function(XRegExp) {
// lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For
// subpatterns provided as native regexes, it dies on octals and adds the property
// used to hold extended regex instance data, for simplicity
sub = asXRegExp(subs[p]);
sub = asXRegExp(subs[p], asXRegExpFlags);
data[p] = {
// Deanchoring allows embedding independently useful anchored regexes. If you
// really need to keep your anchors, double them (i.e., `^^...$$`)
Expand All @@ -119,7 +128,7 @@ module.exports = function(XRegExp) {

// Passing to XRegExp dies on octals and ensures the outer pattern is independently valid;
// helps keep this simple. Named captures will be put back
pattern = asXRegExp(pattern);
pattern = asXRegExp(pattern, asXRegExpFlags);
outerCapNames = pattern[REGEX_DATA].captureNames || [];
pattern = pattern.source.replace(parts, function($0, $1, $2, $3, $4) {
var subName = $1 || $2,
Expand Down
5 changes: 5 additions & 0 deletions tests/spec/s-addons-build.js
Expand Up @@ -8,6 +8,11 @@ describe('XRegExp.build addon:', function() {
expect(function() {XRegExp.build('(?x)({{a}})', {a: /#/});}).toThrow();
});

it('should ignore newlines when "x" flag is passed', function() {
expect(XRegExp.build("\n", {}, 'x').test('')).toBe(true);
expect(XRegExp.build("{{sub}}", {sub: "\n"}, "x").test('')).toBe(true);
});

it('should apply a mode modifier with a native flag in the outer pattern to the final result', function() {
expect(XRegExp.build('(?m){{a}}', {a: /a/}).multiline).toBe(true);
expect(XRegExp.build('(?i){{a}}', {a: /a/}).ignoreCase).toBe(true);
Expand Down
21 changes: 15 additions & 6 deletions xregexp-all.js
Expand Up @@ -39,22 +39,24 @@ module.exports = function(XRegExp) {
}

/**
* Converts the provided value to an XRegExp. Native RegExp flags are not preserved.
* Converts the provided value to an XRegExp. Native RegExp flags are not preserved. Flags can
* be provided as a separate argument.
*
* @private
* @param {String|RegExp} value Value to convert.
* @param {String} [flags] Any combination of XRegExp flags.
* @returns {RegExp} XRegExp object with XRegExp syntax applied.
*/
function asXRegExp(value) {
function asXRegExp(value, flags) {
return XRegExp.isRegExp(value) ?
(value[REGEX_DATA] && value[REGEX_DATA].captureNames ?
// Don't recompile, to preserve capture names
value :
// Recompile as XRegExp
XRegExp(value.source)
XRegExp(value.source, flags)
) :
// Compile string as XRegExp
XRegExp(value);
XRegExp(value, flags);
}

/**
Expand Down Expand Up @@ -84,6 +86,13 @@ module.exports = function(XRegExp) {
*/
XRegExp.build = function(pattern, subs, flags) {
var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern),
// These flags will be passed to the asXRegExp calls for `pattern` and for every
// subpattern in `subs`. This is to work around the following browser bugs:
//
// * Firefox converts '\n' to a regex that contains the literal characters \ and n
// You can verify this by running `console.log(RegExp('\n').source)`
// See here for more details: https://github.com/slevithan/xregexp/pull/163
asXRegExpFlags = (flags || '').indexOf('x') > -1 ? 'x' : '',
data = {},
numCaps = 0, // 'Caps' is short for captures
numPriorCaps,
Expand All @@ -108,7 +117,7 @@ module.exports = function(XRegExp) {
// lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For
// subpatterns provided as native regexes, it dies on octals and adds the property
// used to hold extended regex instance data, for simplicity
sub = asXRegExp(subs[p]);
sub = asXRegExp(subs[p], asXRegExpFlags);
data[p] = {
// Deanchoring allows embedding independently useful anchored regexes. If you
// really need to keep your anchors, double them (i.e., `^^...$$`)
Expand All @@ -120,7 +129,7 @@ module.exports = function(XRegExp) {

// Passing to XRegExp dies on octals and ensures the outer pattern is independently valid;
// helps keep this simple. Named captures will be put back
pattern = asXRegExp(pattern);
pattern = asXRegExp(pattern, asXRegExpFlags);
outerCapNames = pattern[REGEX_DATA].captureNames || [];
pattern = pattern.source.replace(parts, function($0, $1, $2, $3, $4) {
var subName = $1 || $2,
Expand Down

0 comments on commit c1629b8

Please sign in to comment.