Skip to content

Commit 0adaa26

Browse files
BREAKING: Enable namespacing feature by default (#316)
Following up on the [change] to use ES2018 constraints on the names of capture groups, this makes it so that XRegExp `namespacing` feature is enabled by default, so that named capture group matches will appear on the `.groups` property of the match object, rather than directly on the match object, in accordance with the ES2018 spec. While this is a breaking change, users can restore the old behavior by running: XRegExp.uninstall('namespacing') [change]: #247 (comment)
1 parent 130bfba commit 0adaa26

File tree

10 files changed

+42
-38
lines changed

10 files changed

+42
-38
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const date = XRegExp(
1919
(?<month> [0-9]{2} ) -? # month
2020
(?<day> [0-9]{2} ) # day`, 'x');
2121

22-
// XRegExp.exec gives you named backreferences on the match result
22+
// XRegExp.exec gives you named backreferences on the match result's groups property
2323
let match = XRegExp.exec('2017-02-22', date);
24-
match.year; // -> '2017'
24+
match.groups.year; // -> '2017'
2525

2626
// It also includes optional pos and sticky arguments
2727
let pos = 3;
@@ -36,7 +36,7 @@ while (match = XRegExp.exec('<1><2><3>4<5>', /<(\d+)>/, pos, 'sticky')) {
3636
XRegExp.replace('2017-02-22', date, '$<month>/$<day>/$<year>');
3737
// -> '02/22/2017'
3838
XRegExp.replace('2017-02-22', date, (match) => {
39-
return `${match.month}/${match.day}/${match.year}`;
39+
return `${match.groups.month}/${match.groups.day}/${match.groups.year}`;
4040
});
4141
// -> '02/22/2017'
4242

@@ -135,7 +135,7 @@ const time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', {
135135
});
136136

137137
time.test('10:59'); // -> true
138-
XRegExp.exec('10:59', time).minutes; // -> '59'
138+
XRegExp.exec('10:59', time).groups.minutes; // -> '59'
139139
```
140140

141141
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.
@@ -152,7 +152,7 @@ const minutes = /^[0-5][0-9]$/;
152152
// Note that explicitly naming the 'minutes' group is required for named backreferences
153153
const time = XRegExp.tag('x')`^ ${hours} (?<minutes>${minutes}) $`;
154154
time.test('10:59'); // -> true
155-
XRegExp.exec('10:59', time).minutes; // -> '59'
155+
XRegExp.exec('10:59', time).groups.minutes; // -> '59'
156156
```
157157

158158
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.

src/xregexp.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const REGEX_DATA = 'xregexp';
2020
// Optional features that can be installed and uninstalled
2121
const features = {
2222
astral: false,
23-
namespacing: false
23+
namespacing: true
2424
};
2525
// Native methods to use and restore ('native' is an ES3 reserved keyword)
2626
const nativ = {
@@ -822,7 +822,7 @@ XRegExp.escape = (str) => nativ.replace.call(toObject(str), /[-\[\]{}()*+?.,\\^$
822822
*
823823
* // Basic use, with named backreference
824824
* let match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[0-9A-F]{4})'));
825-
* match.hex; // -> '2620'
825+
* match.groups.hex; // -> '2620'
826826
*
827827
* // With pos and sticky, in a loop
828828
* let pos = 2, result = [], match;
@@ -1158,7 +1158,7 @@ XRegExp.matchChain = (str, chain) => (function recurseChain(values, level) {
11581158
* // -> 'Smith, John'
11591159
*
11601160
* // Regex search, using named backreferences in replacement function
1161-
* XRegExp.replace('John Smith', name, (match) => `${match.last}, ${match.first}`);
1161+
* XRegExp.replace('John Smith', name, (match) => `${match.groups.last}, ${match.groups.first}`);
11621162
* // -> 'Smith, John'
11631163
*
11641164
* // String search, with replace-all

tests/.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module.exports = {
44
},
55
"globals": {
66
"XRegExp": true,
7-
"disableOptInFeatures": true,
7+
"resetFeatures": true,
88
"REGEX_DATA": true,
99
"hasNativeU": true,
1010
"hasNativeY": true,

tests/helpers/h.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ if (typeof global === 'undefined') {
44
global.XRegExp = require('../../xregexp-all');
55
}
66

7-
// Ensure that all opt-in features are disabled when each spec starts
8-
global.disableOptInFeatures = function() {
9-
XRegExp.uninstall('namespacing astral');
7+
// Ensure that all features are reset to default when each spec starts
8+
global.resetFeatures = function() {
9+
XRegExp.uninstall('astral');
10+
XRegExp.install('namespacing');
1011
};
1112

1213
// Property name used for extended regex instance data

tests/spec/s-addons-build.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
beforeEach(function() {
2-
global.disableOptInFeatures();
2+
global.resetFeatures();
33
global.addToEqualMatchMatcher();
44
});
55

@@ -87,7 +87,7 @@ describe('XRegExp.build addon:', function() {
8787
var time = XRegExp.tag('x')`^ ${hours} (?<minutes>${minutes}) $`;
8888

8989
expect(time.test('10:59')).toBe(true);
90-
expect(XRegExp.exec('10:59', time).minutes).toEqual('59');
90+
expect(XRegExp.exec('10:59', time).groups.minutes).toEqual('59');
9191
});
9292

9393
});
@@ -169,7 +169,7 @@ describe('XRegExp.build addon:', function() {
169169
});
170170

171171
expect(time.test('10:59')).toBe(true);
172-
expect(XRegExp.exec('10:59', time).minutes).toBe('59');
172+
expect(XRegExp.exec('10:59', time).groups.minutes).toBe('59');
173173
});
174174

175175
it('should pass a series of complex backreference rewrites', function() {
@@ -181,11 +181,11 @@ describe('XRegExp.build addon:', function() {
181181
var match = XRegExp.exec('aaaabbbbaabbbb', built);
182182

183183
expect(match).toBeTruthy();
184-
expect(match.n1).toBe('aa');
185-
expect(match.n2).toBeUndefined();
186-
expect(match.nX).toBe('bb');
187-
expect(match.yo).toBe('a');
188-
expect(match.yo2).toBe('b');
184+
expect(match.groups.n1).toBe('aa');
185+
expect(match.groups.n2).toBeUndefined();
186+
expect(match.groups.nX).toBe('bb');
187+
expect(match.groups.yo).toBe('a');
188+
expect(match.groups.yo2).toBe('b');
189189
});
190190

191191
});

tests/spec/s-addons-matchrecursive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
beforeEach(function() {
2-
global.disableOptInFeatures();
2+
global.resetFeatures();
33
global.addToEqualMatchMatcher();
44
});
55

tests/spec/s-addons-unicode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
beforeEach(function() {
2-
global.disableOptInFeatures();
2+
global.resetFeatures();
33
global.addToEqualMatchMatcher();
44
});
55

tests/spec/s-xregexp-methods.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
beforeEach(function() {
2-
global.disableOptInFeatures();
2+
global.resetFeatures();
33
global.addToEqualMatchMatcher();
44
});
55

@@ -445,34 +445,35 @@ describe('XRegExp.exec()', function() {
445445
// for the RegExp.prototype.exec and nonglobal String.prototype.match specs...
446446

447447
it('should include named capture properties on the match array if namespacing is not installed', function() {
448+
XRegExp.uninstall('namespacing');
448449
var match = XRegExp.exec('a', XRegExp('(?<name>a)'));
449450

450451
expect(match.name).toBe('a');
451452
expect(match[1]).toBe('a');
452453
});
453454

454455
it('should not include named capture properties on the match array if namespacing is installed', function() {
455-
XRegExp.install('namespacing');
456456
var match = XRegExp.exec('a', XRegExp('(?<name>a)'));
457457

458458
expect(match.name).toBeUndefined();
459459
});
460460

461461
it('should not include named capture properties on a groups object if namespacing is not installed', function() {
462+
XRegExp.uninstall('namespacing');
462463
var match = XRegExp.exec('a', XRegExp('(?<name>a)'));
463464

464465
expect(match.groups).toBeUndefined();
465466
});
466467

467468
it('should include named capture properties on a groups object if namespacing is installed', function() {
468-
XRegExp.install('namespacing');
469469
var match = XRegExp.exec('a', XRegExp('(?<name>a)'));
470470

471471
expect(match.groups.name).toBe('a');
472472
expect(match[1]).toBe('a');
473473
});
474474

475-
it('should shaddow array prototype properties with named capture properties', function() {
475+
it('should shadow array prototype properties with named capture properties if namespacing is not installed', function() {
476+
XRegExp.uninstall('namespacing');
476477
expect(XRegExp.exec('a', XRegExp('(?<concat>a)')).concat).toBe('a');
477478
expect(XRegExp.exec('a', XRegExp('(?<index>a)')).index).toBe('a');
478479
});
@@ -492,10 +493,10 @@ describe('XRegExp.forEach()', function() {
492493
});
493494
});
494495

495-
it('should provide named backreferences on the match array', function() {
496+
it('should provide named backreferences on the match array\'s groups property', function() {
496497
var result = [];
497498
XRegExp.forEach('abc 123 def', XRegExp('(?<first>\\w)\\w*'), function(m) {
498-
result.push(m.first);
499+
result.push(m.groups.first);
499500
});
500501

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

714-
expect(XRegExp.exec('xx', XRegExp.globalize(regex)).name).toBe('x');
715+
expect(XRegExp.exec('xx', XRegExp.globalize(regex)).groups.name).toBe('x');
715716
});
716717

717718
it('should throw an exception if not given a RegExp object', function() {
@@ -736,7 +737,9 @@ describe('XRegExp.globalize()', function() {
736737

737738
describe('XRegExp.install()', function() {
738739

739-
// NOTE: All optional features are uninstalled before each spec runs
740+
beforeEach(function() {
741+
XRegExp.uninstall('namespacing astral');
742+
});
740743

741744
var features = ['namespacing', 'astral'];
742745

@@ -1100,14 +1103,14 @@ describe('XRegExp.matchChain()', function() {
11001103
});
11011104

11021105
it('should handle named and numbered backrefs when namespacing is not installed', function() {
1106+
XRegExp.uninstall('namespacing');
11031107
expect(XRegExp.matchChain('test', [
11041108
{regex: /.(..)/, backref: 1},
11051109
{regex: XRegExp('.(?<n>.)'), backref: 'n'}
11061110
])).toEqual(['s']);
11071111
});
11081112

11091113
it('should handle named and numbered backrefs when namespacing is installed', function() {
1110-
XRegExp.install('namespacing');
11111114
expect(XRegExp.matchChain('test', [
11121115
{regex: /.(..)/, backref: 1},
11131116
{regex: XRegExp('.(?<n>.)'), backref: 'n'}
@@ -1186,14 +1189,14 @@ describe('XRegExp.replace()', function() {
11861189
});
11871190

11881191
it('should not pass the groups argument to callbacks when namespacing is not installed', function() {
1192+
XRegExp.uninstall('namespacing');
11891193
var regex = XRegExp('(?s)(?<groupName>.)');
11901194
XRegExp.replace('test', regex, function(match, capture1, pos, str, groups) {
11911195
expect(groups).toBeUndefined();
11921196
});
11931197
});
11941198

11951199
it('should pass the groups argument to callbacks when namespacing is installed', function() {
1196-
XRegExp.install('namespacing');
11971200
var regex = XRegExp('(?s)(?<groupName>.)');
11981201
var groupsObject = Object.create(null);
11991202
groupsObject.groupName = 't';
@@ -1203,13 +1206,13 @@ describe('XRegExp.replace()', function() {
12031206
});
12041207

12051208
it('should allow accessing named backreferences in callbacks as properties of the first argument when namespacing is not installed', function() {
1209+
XRegExp.uninstall('namespacing');
12061210
expect(XRegExp.replace('abc', XRegExp('(?<name>.).'), function(match) {
12071211
return ':' + match.name + ':';
12081212
})).toBe(':a:c');
12091213
});
12101214

12111215
it('should not allow accessing named backreferences in callbacks as properties of the first argument when namespacing is installed', function() {
1212-
XRegExp.install('namespacing');
12131216
expect(XRegExp.replace('abc', XRegExp('(?<name>.).'), function(match) {
12141217
return ':' + match.name + ':';
12151218
})).toBe(':undefined:c');

tests/spec/s-xregexp.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
beforeEach(function() {
2-
global.disableOptInFeatures();
2+
global.resetFeatures();
33
global.addToEqualMatchMatcher();
44
});
55

@@ -421,14 +421,14 @@ describe('XRegExp()', function() {
421421
});
422422

423423
it('should throw an exception if reserved words are used as capture names if namespacing is not installed', function() {
424+
XRegExp.uninstall('namespacing');
424425
// Only these names are reserved
425426
['length', '__proto__'].forEach(function(name) {
426427
expect(function() {XRegExp('(?<' + name + '>)');}).toThrowError(SyntaxError);
427428
});
428429
});
429430

430431
it('should not throw an exception if reserved words are used as capture names if namespacing is installed', function() {
431-
XRegExp.install('namespacing');
432432
['length', '__proto__'].forEach(function(name) {
433433
expect(function() {XRegExp('(?<' + name + '>)');}).not.toThrow();
434434
});

types/index.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,13 +577,13 @@ declare namespace XRegExp {
577577
*
578578
* // Basic use, with named capture groups
579579
* let match = XRegExp.exec('U+2620', XRegExp('U\\+(?<hex>[0-9A-F]{4})'));
580-
* match.hex; // -> '2620'
580+
* match.groups.hex; // -> '2620'
581581
*
582582
* // With pos and sticky, in a loop
583583
* let pos = 2, result = [], match;
584584
* while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) {
585585
* result.push(match[1]);
586-
* pos = match.index + match[0].length;
586+
* pos = match.groups.index + match[0].length;
587587
* }
588588
* // result -> ['2', '3', '4']
589589
*/
@@ -809,7 +809,7 @@ declare namespace XRegExp {
809809
* // -> 'Smith, John'
810810
*
811811
* // Regex search, using named backreferences in replacement function
812-
* XRegExp.replace('John Smith', name, (match) => `${match.last as string}, ${match.first as string}`);
812+
* XRegExp.replace('John Smith', name, (match) => `${match.groups.last as string}, ${match.groups.first as string}`);
813813
* // -> 'Smith, John'
814814
*
815815
* // String search, with replace-all

0 commit comments

Comments
 (0)