diff --git a/README.md b/README.md index 0d2dd7f0..afe0f852 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,9 @@ const date = XRegExp( (? [0-9]{2} ) -? # month (? [0-9]{2} ) # day`, 'x'); -// XRegExp.exec gives you named backreferences on the match result +// XRegExp.exec gives you named backreferences on the match result's groups property let match = XRegExp.exec('2017-02-22', date); -match.year; // -> '2017' +match.groups.year; // -> '2017' // It also includes optional pos and sticky arguments let pos = 3; @@ -36,7 +36,7 @@ while (match = XRegExp.exec('<1><2><3>4<5>', /<(\d+)>/, pos, 'sticky')) { XRegExp.replace('2017-02-22', date, '$/$/$'); // -> '02/22/2017' XRegExp.replace('2017-02-22', date, (match) => { - return `${match.month}/${match.day}/${match.year}`; + return `${match.groups.month}/${match.groups.day}/${match.groups.year}`; }); // -> '02/22/2017' @@ -135,7 +135,7 @@ const time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', { }); time.test('10:59'); // -> true -XRegExp.exec('10:59', time).minutes; // -> '59' +XRegExp.exec('10:59', time).groups.minutes; // -> '59' ``` Named subpatterns can be provided as strings or regex objects. A leading `^` and trailing unescaped `$` are stripped from subpatterns if both are present, which allows embedding independently-useful anchored patterns. `{{…}}` tokens can be quantified as a single unit. Any backreferences in the outer pattern or provided subpatterns are automatically renumbered to work correctly within the larger combined pattern. The syntax `({{name}})` works as shorthand for named capture via `(?{{name}})`. Named subpatterns cannot be embedded within character classes. @@ -152,7 +152,7 @@ const minutes = /^[0-5][0-9]$/; // Note that explicitly naming the 'minutes' group is required for named backreferences const time = XRegExp.tag('x')`^ ${hours} (?${minutes}) $`; time.test('10:59'); // -> true -XRegExp.exec('10:59', time).minutes; // -> '59' +XRegExp.exec('10:59', time).groups.minutes; // -> '59' ``` XRegExp.tag does more than just basic interpolation. For starters, you get all the XRegExp syntax and flags. Even better, since `XRegExp.tag` uses your pattern as a raw string, you no longer need to escape all your backslashes. And since it relies on `XRegExp.build` under the hood, you get all of its extras for free. Leading `^` and trailing unescaped `$` are stripped from interpolated patterns if both are present (to allow embedding independently useful anchored regexes), interpolating into a character class is an error (to avoid unintended meaning in edge cases), interpolated patterns are treated as atomic units when quantified, interpolated strings have their special characters escaped, and any backreferences within an interpolated regex are rewritten to work within the overall pattern. diff --git a/src/xregexp.js b/src/xregexp.js index ea4388be..0501ddbd 100644 --- a/src/xregexp.js +++ b/src/xregexp.js @@ -20,7 +20,7 @@ const REGEX_DATA = 'xregexp'; // Optional features that can be installed and uninstalled const features = { astral: false, - namespacing: false + namespacing: true }; // Native methods to use and restore ('native' is an ES3 reserved keyword) const nativ = { @@ -822,7 +822,7 @@ XRegExp.escape = (str) => nativ.replace.call(toObject(str), /[-\[\]{}()*+?.,\\^$ * * // Basic use, with named backreference * let match = XRegExp.exec('U+2620', XRegExp('U\\+(?[0-9A-F]{4})')); - * match.hex; // -> '2620' + * match.groups.hex; // -> '2620' * * // With pos and sticky, in a loop * let pos = 2, result = [], match; @@ -1158,7 +1158,7 @@ XRegExp.matchChain = (str, chain) => (function recurseChain(values, level) { * // -> 'Smith, John' * * // Regex search, using named backreferences in replacement function - * XRegExp.replace('John Smith', name, (match) => `${match.last}, ${match.first}`); + * XRegExp.replace('John Smith', name, (match) => `${match.groups.last}, ${match.groups.first}`); * // -> 'Smith, John' * * // String search, with replace-all diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index 9d4ebe17..50916a36 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { }, "globals": { "XRegExp": true, - "disableOptInFeatures": true, + "resetFeatures": true, "REGEX_DATA": true, "hasNativeU": true, "hasNativeY": true, diff --git a/tests/helpers/h.js b/tests/helpers/h.js index 3bd155df..404ff365 100644 --- a/tests/helpers/h.js +++ b/tests/helpers/h.js @@ -4,9 +4,10 @@ if (typeof global === 'undefined') { global.XRegExp = require('../../xregexp-all'); } -// Ensure that all opt-in features are disabled when each spec starts -global.disableOptInFeatures = function() { - XRegExp.uninstall('namespacing astral'); +// Ensure that all features are reset to default when each spec starts +global.resetFeatures = function() { + XRegExp.uninstall('astral'); + XRegExp.install('namespacing'); }; // Property name used for extended regex instance data diff --git a/tests/spec/s-addons-build.js b/tests/spec/s-addons-build.js index 2eefa78e..f3aa4886 100644 --- a/tests/spec/s-addons-build.js +++ b/tests/spec/s-addons-build.js @@ -1,5 +1,5 @@ beforeEach(function() { - global.disableOptInFeatures(); + global.resetFeatures(); global.addToEqualMatchMatcher(); }); @@ -87,7 +87,7 @@ describe('XRegExp.build addon:', function() { var time = XRegExp.tag('x')`^ ${hours} (?${minutes}) $`; expect(time.test('10:59')).toBe(true); - expect(XRegExp.exec('10:59', time).minutes).toEqual('59'); + expect(XRegExp.exec('10:59', time).groups.minutes).toEqual('59'); }); }); @@ -169,7 +169,7 @@ describe('XRegExp.build addon:', function() { }); expect(time.test('10:59')).toBe(true); - expect(XRegExp.exec('10:59', time).minutes).toBe('59'); + expect(XRegExp.exec('10:59', time).groups.minutes).toBe('59'); }); it('should pass a series of complex backreference rewrites', function() { @@ -181,11 +181,11 @@ describe('XRegExp.build addon:', function() { var match = XRegExp.exec('aaaabbbbaabbbb', built); expect(match).toBeTruthy(); - expect(match.n1).toBe('aa'); - expect(match.n2).toBeUndefined(); - expect(match.nX).toBe('bb'); - expect(match.yo).toBe('a'); - expect(match.yo2).toBe('b'); + expect(match.groups.n1).toBe('aa'); + expect(match.groups.n2).toBeUndefined(); + expect(match.groups.nX).toBe('bb'); + expect(match.groups.yo).toBe('a'); + expect(match.groups.yo2).toBe('b'); }); }); diff --git a/tests/spec/s-addons-matchrecursive.js b/tests/spec/s-addons-matchrecursive.js index 1c872021..4dfbc4f1 100644 --- a/tests/spec/s-addons-matchrecursive.js +++ b/tests/spec/s-addons-matchrecursive.js @@ -1,5 +1,5 @@ beforeEach(function() { - global.disableOptInFeatures(); + global.resetFeatures(); global.addToEqualMatchMatcher(); }); diff --git a/tests/spec/s-addons-unicode.js b/tests/spec/s-addons-unicode.js index ebb3b60e..603c1c05 100644 --- a/tests/spec/s-addons-unicode.js +++ b/tests/spec/s-addons-unicode.js @@ -1,5 +1,5 @@ beforeEach(function() { - global.disableOptInFeatures(); + global.resetFeatures(); global.addToEqualMatchMatcher(); }); diff --git a/tests/spec/s-xregexp-methods.js b/tests/spec/s-xregexp-methods.js index da72a2c1..03ad94b5 100644 --- a/tests/spec/s-xregexp-methods.js +++ b/tests/spec/s-xregexp-methods.js @@ -1,5 +1,5 @@ beforeEach(function() { - global.disableOptInFeatures(); + global.resetFeatures(); global.addToEqualMatchMatcher(); }); @@ -445,6 +445,7 @@ describe('XRegExp.exec()', function() { // for the RegExp.prototype.exec and nonglobal String.prototype.match specs... it('should include named capture properties on the match array if namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); var match = XRegExp.exec('a', XRegExp('(?a)')); expect(match.name).toBe('a'); @@ -452,27 +453,27 @@ describe('XRegExp.exec()', function() { }); it('should not include named capture properties on the match array if namespacing is installed', function() { - XRegExp.install('namespacing'); var match = XRegExp.exec('a', XRegExp('(?a)')); expect(match.name).toBeUndefined(); }); it('should not include named capture properties on a groups object if namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); var match = XRegExp.exec('a', XRegExp('(?a)')); expect(match.groups).toBeUndefined(); }); it('should include named capture properties on a groups object if namespacing is installed', function() { - XRegExp.install('namespacing'); var match = XRegExp.exec('a', XRegExp('(?a)')); expect(match.groups.name).toBe('a'); expect(match[1]).toBe('a'); }); - it('should shaddow array prototype properties with named capture properties', function() { + it('should shadow array prototype properties with named capture properties if namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); expect(XRegExp.exec('a', XRegExp('(?a)')).concat).toBe('a'); expect(XRegExp.exec('a', XRegExp('(?a)')).index).toBe('a'); }); @@ -492,10 +493,10 @@ describe('XRegExp.forEach()', function() { }); }); - it('should provide named backreferences on the match array', function() { + it('should provide named backreferences on the match array\'s groups property', function() { var result = []; XRegExp.forEach('abc 123 def', XRegExp('(?\\w)\\w*'), function(m) { - result.push(m.first); + result.push(m.groups.first); }); expect(result).toEqual(['a', '1', 'd']); @@ -711,7 +712,7 @@ describe('XRegExp.globalize()', function() { it('should retain named capture capabilities', function() { var regex = XRegExp('(?x)\\k'); - expect(XRegExp.exec('xx', XRegExp.globalize(regex)).name).toBe('x'); + expect(XRegExp.exec('xx', XRegExp.globalize(regex)).groups.name).toBe('x'); }); it('should throw an exception if not given a RegExp object', function() { @@ -736,7 +737,9 @@ describe('XRegExp.globalize()', function() { describe('XRegExp.install()', function() { - // NOTE: All optional features are uninstalled before each spec runs + beforeEach(function() { + XRegExp.uninstall('namespacing astral'); + }); var features = ['namespacing', 'astral']; @@ -1100,6 +1103,7 @@ describe('XRegExp.matchChain()', function() { }); it('should handle named and numbered backrefs when namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); expect(XRegExp.matchChain('test', [ {regex: /.(..)/, backref: 1}, {regex: XRegExp('.(?.)'), backref: 'n'} @@ -1107,7 +1111,6 @@ describe('XRegExp.matchChain()', function() { }); it('should handle named and numbered backrefs when namespacing is installed', function() { - XRegExp.install('namespacing'); expect(XRegExp.matchChain('test', [ {regex: /.(..)/, backref: 1}, {regex: XRegExp('.(?.)'), backref: 'n'} @@ -1186,6 +1189,7 @@ describe('XRegExp.replace()', function() { }); it('should not pass the groups argument to callbacks when namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); var regex = XRegExp('(?s)(?.)'); XRegExp.replace('test', regex, function(match, capture1, pos, str, groups) { expect(groups).toBeUndefined(); @@ -1193,7 +1197,6 @@ describe('XRegExp.replace()', function() { }); it('should pass the groups argument to callbacks when namespacing is installed', function() { - XRegExp.install('namespacing'); var regex = XRegExp('(?s)(?.)'); var groupsObject = Object.create(null); groupsObject.groupName = 't'; @@ -1203,13 +1206,13 @@ describe('XRegExp.replace()', function() { }); it('should allow accessing named backreferences in callbacks as properties of the first argument when namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); expect(XRegExp.replace('abc', XRegExp('(?.).'), function(match) { return ':' + match.name + ':'; })).toBe(':a:c'); }); it('should not allow accessing named backreferences in callbacks as properties of the first argument when namespacing is installed', function() { - XRegExp.install('namespacing'); expect(XRegExp.replace('abc', XRegExp('(?.).'), function(match) { return ':' + match.name + ':'; })).toBe(':undefined:c'); diff --git a/tests/spec/s-xregexp.js b/tests/spec/s-xregexp.js index 13f76cd2..a0a228e5 100644 --- a/tests/spec/s-xregexp.js +++ b/tests/spec/s-xregexp.js @@ -1,5 +1,5 @@ beforeEach(function() { - global.disableOptInFeatures(); + global.resetFeatures(); global.addToEqualMatchMatcher(); }); @@ -421,6 +421,7 @@ describe('XRegExp()', function() { }); it('should throw an exception if reserved words are used as capture names if namespacing is not installed', function() { + XRegExp.uninstall('namespacing'); // Only these names are reserved ['length', '__proto__'].forEach(function(name) { expect(function() {XRegExp('(?<' + name + '>)');}).toThrowError(SyntaxError); @@ -428,7 +429,6 @@ describe('XRegExp()', function() { }); it('should not throw an exception if reserved words are used as capture names if namespacing is installed', function() { - XRegExp.install('namespacing'); ['length', '__proto__'].forEach(function(name) { expect(function() {XRegExp('(?<' + name + '>)');}).not.toThrow(); }); diff --git a/types/index.d.ts b/types/index.d.ts index dc9033dc..b0c76df4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -577,13 +577,13 @@ declare namespace XRegExp { * * // Basic use, with named capture groups * let match = XRegExp.exec('U+2620', XRegExp('U\\+(?[0-9A-F]{4})')); - * match.hex; // -> '2620' + * match.groups.hex; // -> '2620' * * // With pos and sticky, in a loop * let pos = 2, result = [], match; * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) { * result.push(match[1]); - * pos = match.index + match[0].length; + * pos = match.groups.index + match[0].length; * } * // result -> ['2', '3', '4'] */ @@ -809,7 +809,7 @@ declare namespace XRegExp { * // -> 'Smith, John' * * // Regex search, using named backreferences in replacement function - * XRegExp.replace('John Smith', name, (match) => `${match.last as string}, ${match.first as string}`); + * XRegExp.replace('John Smith', name, (match) => `${match.groups.last as string}, ${match.groups.first as string}`); * // -> 'Smith, John' * * // String search, with replace-all