Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const date = XRegExp(
(?<month> [0-9]{2} ) -? # month
(?<day> [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;
Expand All @@ -36,7 +36,7 @@ while (match = XRegExp.exec('<1><2><3>4<5>', /<(\d+)>/, pos, 'sticky')) {
XRegExp.replace('2017-02-22', date, '$<month>/$<day>/$<year>');
// -> '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'

Expand Down Expand Up @@ -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>{{name}})`. Named subpatterns cannot be embedded within character classes.
Expand All @@ -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>${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.
Expand Down
6 changes: 3 additions & 3 deletions src/xregexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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\\+(?<hex>[0-9A-F]{4})'));
* match.hex; // -> '2620'
* match.groups.hex; // -> '2620'
*
* // With pos and sticky, in a loop
* let pos = 2, result = [], match;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module.exports = {
},
"globals": {
"XRegExp": true,
"disableOptInFeatures": true,
"resetFeatures": true,
"REGEX_DATA": true,
"hasNativeU": true,
"hasNativeY": true,
Expand Down
7 changes: 4 additions & 3 deletions tests/helpers/h.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions tests/spec/s-addons-build.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
beforeEach(function() {
global.disableOptInFeatures();
global.resetFeatures();
global.addToEqualMatchMatcher();
});

Expand Down Expand Up @@ -87,7 +87,7 @@ describe('XRegExp.build addon:', function() {
var time = XRegExp.tag('x')`^ ${hours} (?<minutes>${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');
});

});
Expand Down Expand Up @@ -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() {
Expand All @@ -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');
});

});
Expand Down
2 changes: 1 addition & 1 deletion tests/spec/s-addons-matchrecursive.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
beforeEach(function() {
global.disableOptInFeatures();
global.resetFeatures();
global.addToEqualMatchMatcher();
});

Expand Down
2 changes: 1 addition & 1 deletion tests/spec/s-addons-unicode.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
beforeEach(function() {
global.disableOptInFeatures();
global.resetFeatures();
global.addToEqualMatchMatcher();
});

Expand Down
25 changes: 14 additions & 11 deletions tests/spec/s-xregexp-methods.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
beforeEach(function() {
global.disableOptInFeatures();
global.resetFeatures();
global.addToEqualMatchMatcher();
});

Expand Down Expand Up @@ -445,34 +445,35 @@ 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('(?<name>a)'));

expect(match.name).toBe('a');
expect(match[1]).toBe('a');
});

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('(?<name>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('(?<name>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('(?<name>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('(?<concat>a)')).concat).toBe('a');
expect(XRegExp.exec('a', XRegExp('(?<index>a)')).index).toBe('a');
});
Expand All @@ -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('(?<first>\\w)\\w*'), function(m) {
result.push(m.first);
result.push(m.groups.first);
});

expect(result).toEqual(['a', '1', 'd']);
Expand Down Expand Up @@ -711,7 +712,7 @@ describe('XRegExp.globalize()', function() {
it('should retain named capture capabilities', function() {
var regex = XRegExp('(?<name>x)\\k<name>');

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() {
Expand All @@ -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'];

Expand Down Expand Up @@ -1100,14 +1103,14 @@ 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('.(?<n>.)'), backref: 'n'}
])).toEqual(['s']);
});

it('should handle named and numbered backrefs when namespacing is installed', function() {
XRegExp.install('namespacing');
expect(XRegExp.matchChain('test', [
{regex: /.(..)/, backref: 1},
{regex: XRegExp('.(?<n>.)'), backref: 'n'}
Expand Down Expand Up @@ -1186,14 +1189,14 @@ 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)(?<groupName>.)');
XRegExp.replace('test', regex, function(match, capture1, pos, str, groups) {
expect(groups).toBeUndefined();
});
});

it('should pass the groups argument to callbacks when namespacing is installed', function() {
XRegExp.install('namespacing');
var regex = XRegExp('(?s)(?<groupName>.)');
var groupsObject = Object.create(null);
groupsObject.groupName = 't';
Expand All @@ -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('(?<name>.).'), 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('(?<name>.).'), function(match) {
return ':' + match.name + ':';
})).toBe(':undefined:c');
Expand Down
4 changes: 2 additions & 2 deletions tests/spec/s-xregexp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
beforeEach(function() {
global.disableOptInFeatures();
global.resetFeatures();
global.addToEqualMatchMatcher();
});

Expand Down Expand Up @@ -421,14 +421,14 @@ 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);
});
});

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();
});
Expand Down
6 changes: 3 additions & 3 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,13 +577,13 @@ declare namespace XRegExp {
*
* // Basic use, with named capture groups
* let match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[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']
*/
Expand Down Expand Up @@ -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
Expand Down