regexp.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. var getRegexLiteral = function (stringRegex) {
  2. try {
  3. /* eslint-disable no-new-func */
  4. return Function('return ' + stringRegex + ';')();
  5. /* eslint-enable no-new-func */
  6. } catch (e) { /**/ }
  7. };
  8. var describeIfSupportsDescriptors = Object.getOwnPropertyDescriptor ? describe : describe.skip;
  9. var callAllowsPrimitives = (function () { return this === 3; }.call(3));
  10. var ifCallAllowsPrimitivesIt = callAllowsPrimitives ? it : it.skip;
  11. var ifShimIt = (typeof process !== 'undefined' && process.env.NO_ES6_SHIM) ? it.skip : it;
  12. var hasSymbols = typeof Symbol === 'function' && typeof Symbol['for'] === 'function' && typeof Symbol('') === 'symbol';
  13. var ifSymbolsDescribe = hasSymbols ? describe : describe.skip;
  14. var defaultRegex = (function () {
  15. // Chrome Canary 51 has an undefined RegExp#toSource, and
  16. // RegExp#toString produces `/undefined/`
  17. try {
  18. return RegExp.prototype.source ? String(RegExp.prototype) : '/(?:)/';
  19. } catch (e) {
  20. return '/(?:)/';
  21. }
  22. }());
  23. describe('RegExp', function () {
  24. ifShimIt('is on the exported object', function () {
  25. var exported = require('../');
  26. expect(exported.RegExp).to.equal(RegExp);
  27. });
  28. it('can be called with no arguments', function () {
  29. var regex = RegExp();
  30. expect(String(regex)).to.equal(defaultRegex);
  31. expect(regex).to.be.an.instanceOf(RegExp);
  32. });
  33. it('can be called with null/undefined', function () {
  34. expect(String(RegExp(null))).to.equal('/null/');
  35. expect(String(RegExp(undefined))).to.equal(defaultRegex);
  36. });
  37. describe('constructor', function () {
  38. it('allows a regex as the pattern', function () {
  39. var a = /a/g;
  40. var b = new RegExp(a);
  41. if (typeof a !== 'function') {
  42. // in browsers like Safari 5, new RegExp with a regex returns the same instance.
  43. expect(a).not.to.equal(b);
  44. }
  45. expect(a).to.eql(b);
  46. });
  47. it('allows a string with flags', function () {
  48. expect(new RegExp('a', 'mgi')).to.eql(/a/gim);
  49. expect(String(new RegExp('a', 'mgi'))).to.equal('/a/gim');
  50. });
  51. it('allows a regex with flags', function () {
  52. var a = /a/g;
  53. var makeRegex = function () { return new RegExp(a, 'mi'); };
  54. expect(makeRegex).not.to['throw'](TypeError);
  55. expect(makeRegex()).to.eql(/a/mi);
  56. expect(String(makeRegex())).to.equal('/a/im');
  57. });
  58. it('works with instanceof', function () {
  59. expect(/a/g).to.be.an.instanceOf(RegExp);
  60. expect(new RegExp('a', 'im')).to.be.an.instanceOf(RegExp);
  61. expect(new RegExp(/a/g, 'im')).to.be.an.instanceOf(RegExp);
  62. });
  63. it('has the right constructor', function () {
  64. expect(/a/g).to.have.property('constructor', RegExp);
  65. expect(new RegExp('a', 'im')).to.have.property('constructor', RegExp);
  66. expect(new RegExp(/a/g, 'im')).to.have.property('constructor', RegExp);
  67. });
  68. it('toStrings properly', function () {
  69. expect(Object.prototype.toString.call(/a/g)).to.equal('[object RegExp]');
  70. expect(Object.prototype.toString.call(new RegExp('a', 'g'))).to.equal('[object RegExp]');
  71. expect(Object.prototype.toString.call(new RegExp(/a/g, 'im'))).to.equal('[object RegExp]');
  72. });
  73. it('functions as a boxed primitive wrapper', function () {
  74. var regex = /a/g;
  75. expect(RegExp(regex)).to.equal(regex);
  76. });
  77. ifSymbolsDescribe('Symbol.replace', function () {
  78. if (!hasSymbols || typeof Symbol.replace === 'undefined') {
  79. return;
  80. }
  81. it('is a function', function () {
  82. expect(RegExp.prototype).to.have.property(Symbol.replace);
  83. expect(typeof RegExp.prototype[Symbol.replace]).to.equal('function');
  84. });
  85. it('is the same as String#replace', function () {
  86. var regex = /a/g;
  87. var str = 'abc';
  88. var symbolReplace = regex[Symbol.replace](str);
  89. var stringReplace = str.replace(regex);
  90. expect(Object.keys(symbolReplace)).to.eql(Object.keys(stringReplace));
  91. expect(symbolReplace).to.eql(stringReplace);
  92. });
  93. });
  94. ifSymbolsDescribe('Symbol.search', function () {
  95. if (!hasSymbols || typeof Symbol.search === 'undefined') {
  96. return;
  97. }
  98. it('is a function', function () {
  99. expect(RegExp.prototype).to.have.property(Symbol.search);
  100. expect(typeof RegExp.prototype[Symbol.search]).to.equal('function');
  101. });
  102. it('is the same as String#search', function () {
  103. var regex = /a/g;
  104. var str = 'abc';
  105. var symbolSearch = regex[Symbol.search](str);
  106. var stringSearch = str.search(regex);
  107. expect(Object.keys(symbolSearch)).to.eql(Object.keys(stringSearch));
  108. expect(symbolSearch).to.eql(stringSearch);
  109. });
  110. });
  111. ifSymbolsDescribe('Symbol.split', function () {
  112. if (!hasSymbols || typeof Symbol.split === 'undefined') {
  113. return;
  114. }
  115. it('is a function', function () {
  116. expect(RegExp.prototype).to.have.property(Symbol.split);
  117. expect(typeof RegExp.prototype[Symbol.split]).to.equal('function');
  118. });
  119. it('is the same as String#split', function () {
  120. var regex = /a/g;
  121. var str = 'abcabc';
  122. var symbolSplit = regex[Symbol.split](str, 1);
  123. var stringSplit = str.split(regex, 1);
  124. expect(Object.keys(symbolSplit)).to.eql(Object.keys(stringSplit));
  125. expect(symbolSplit).to.eql(stringSplit);
  126. });
  127. });
  128. ifSymbolsDescribe('Symbol.match', function () {
  129. if (!hasSymbols || typeof Symbol.match === 'undefined') {
  130. return;
  131. }
  132. var regexFalsyMatch;
  133. var nonregexTruthyMatch;
  134. beforeEach(function () {
  135. regexFalsyMatch = /./;
  136. regexFalsyMatch[Symbol.match] = false;
  137. nonregexTruthyMatch = { constructor: RegExp };
  138. nonregexTruthyMatch[Symbol.match] = true;
  139. });
  140. it('is a function', function () {
  141. expect(RegExp.prototype).to.have.property(Symbol.match);
  142. expect(typeof RegExp.prototype[Symbol.match]).to.equal('function');
  143. });
  144. it('is the same as String#match', function () {
  145. var regex = /a/g;
  146. var str = 'abc';
  147. var symbolMatch = regex[Symbol.match](str);
  148. var stringMatch = str.match(regex);
  149. expect(Object.keys(symbolMatch)).to.eql(Object.keys(stringMatch));
  150. expect(symbolMatch).to.eql(stringMatch);
  151. });
  152. it('function does not passthrough regexes with a falsy Symbol.match', function () {
  153. expect(RegExp(regexFalsyMatch)).not.to.equal(regexFalsyMatch);
  154. });
  155. it('constructor does not passthrough regexes with a falsy Symbol.match', function () {
  156. expect(new RegExp(regexFalsyMatch)).not.to.equal(regexFalsyMatch);
  157. });
  158. it('function passes through non-regexes with a truthy Symbol.match', function () {
  159. expect(RegExp(nonregexTruthyMatch)).to.equal(nonregexTruthyMatch);
  160. });
  161. it('constructor does not pass through non-regexes with a truthy Symbol.match', function () {
  162. expect(new RegExp(nonregexTruthyMatch)).not.to.equal(nonregexTruthyMatch);
  163. });
  164. });
  165. });
  166. describeIfSupportsDescriptors('#flags', function () {
  167. if (!Object.prototype.hasOwnProperty.call(RegExp.prototype, 'flags')) {
  168. return it('exists', function () {
  169. expect(RegExp.prototype).to.have.property('flags');
  170. });
  171. }
  172. var regexpFlagsDescriptor = Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags');
  173. var testGenericRegExpFlags = function (object) {
  174. return regexpFlagsDescriptor.get.call(object);
  175. };
  176. it('has the correct descriptor', function () {
  177. expect(regexpFlagsDescriptor.configurable).to.equal(true);
  178. expect(regexpFlagsDescriptor.enumerable).to.equal(false);
  179. expect(regexpFlagsDescriptor.get instanceof Function).to.equal(true);
  180. expect(regexpFlagsDescriptor.set).to.equal(undefined);
  181. });
  182. ifCallAllowsPrimitivesIt('throws when not called on an object', function () {
  183. var nonObjects = ['', false, true, 42, NaN, null, undefined];
  184. nonObjects.forEach(function (nonObject) {
  185. expect(function () { testGenericRegExpFlags(nonObject); }).to['throw'](TypeError);
  186. });
  187. });
  188. it('has the correct flags on a literal', function () {
  189. expect((/a/g).flags).to.equal('g');
  190. expect((/a/i).flags).to.equal('i');
  191. expect((/a/m).flags).to.equal('m');
  192. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'sticky')) {
  193. expect(getRegexLiteral('/a/y').flags).to.equal('y');
  194. }
  195. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'unicode')) {
  196. expect(getRegexLiteral('/a/u').flags).to.equal('u');
  197. }
  198. });
  199. it('has the correct flags on a constructed RegExp', function () {
  200. expect(new RegExp('a', 'g').flags).to.equal('g');
  201. expect(new RegExp('a', 'i').flags).to.equal('i');
  202. expect(new RegExp('a', 'm').flags).to.equal('m');
  203. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'sticky')) {
  204. expect(new RegExp('a', 'y').flags).to.equal('y');
  205. }
  206. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'unicode')) {
  207. expect(new RegExp('a', 'u').flags).to.equal('u');
  208. }
  209. });
  210. it('returns flags sorted on a literal', function () {
  211. expect((/a/gim).flags).to.equal('gim');
  212. expect((/a/mig).flags).to.equal('gim');
  213. expect((/a/mgi).flags).to.equal('gim');
  214. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'sticky')) {
  215. expect(getRegexLiteral('/a/gyim').flags).to.equal('gimy');
  216. }
  217. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'unicode')) {
  218. expect(getRegexLiteral('/a/ugmi').flags).to.equal('gimu');
  219. }
  220. });
  221. it('returns flags sorted on a constructed RegExp', function () {
  222. expect(new RegExp('a', 'gim').flags).to.equal('gim');
  223. expect(new RegExp('a', 'mig').flags).to.equal('gim');
  224. expect(new RegExp('a', 'mgi').flags).to.equal('gim');
  225. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'sticky')) {
  226. expect(new RegExp('a', 'mygi').flags).to.equal('gimy');
  227. }
  228. if (Object.prototype.hasOwnProperty.call(RegExp.prototype, 'unicode')) {
  229. expect(new RegExp('a', 'mugi').flags).to.equal('gimu');
  230. }
  231. });
  232. });
  233. describe('#toString()', function () {
  234. it('throws on null/undefined', function () {
  235. expect(function () { RegExp.prototype.toString.call(null); }).to['throw'](TypeError);
  236. expect(function () { RegExp.prototype.toString.call(undefined); }).to['throw'](TypeError);
  237. });
  238. it('has an undefined prototype', function () {
  239. expect(RegExp.prototype.toString.prototype).to.equal(undefined);
  240. });
  241. it('works on regexes', function () {
  242. expect(RegExp.prototype.toString.call(/a/g)).to.equal('/a/g');
  243. expect(RegExp.prototype.toString.call(new RegExp('a', 'g'))).to.equal('/a/g');
  244. });
  245. it('works on non-regexes', function () {
  246. expect(RegExp.prototype.toString.call({ source: 'abc', flags: '' })).to.equal('/abc/');
  247. expect(RegExp.prototype.toString.call({ source: 'abc', flags: 'xyz' })).to.equal('/abc/xyz');
  248. });
  249. ifSymbolsDescribe('Symbol.match', function () {
  250. if (!hasSymbols || typeof Symbol.match === 'undefined') {
  251. return;
  252. }
  253. it('accepts a non-regex with Symbol.match', function () {
  254. var obj = { source: 'abc', flags: 'def' };
  255. obj[Symbol.match] = RegExp.prototype[Symbol.match];
  256. expect(RegExp.prototype.toString.call(obj)).to.equal('/abc/def');
  257. });
  258. });
  259. });
  260. describe('Object properties', function () {
  261. it('does not have the nonstandard $input property', function () {
  262. expect(RegExp).not.to.have.property('$input'); // Chrome < 39, Opera < 26 have this
  263. });
  264. it('has "input" property', function () {
  265. expect(RegExp).to.have.ownProperty('input');
  266. expect(RegExp).to.have.ownProperty('$_');
  267. });
  268. it('has "last match" property', function () {
  269. expect(RegExp).to.have.ownProperty('lastMatch');
  270. expect(RegExp).to.have.ownProperty('$+');
  271. });
  272. it('has "last paren" property', function () {
  273. expect(RegExp).to.have.ownProperty('lastParen');
  274. expect(RegExp).to.have.ownProperty('$&');
  275. });
  276. it('has "leftContext" property', function () {
  277. expect(RegExp).to.have.ownProperty('leftContext');
  278. expect(RegExp).to.have.ownProperty('$`');
  279. });
  280. it('has "rightContext" property', function () {
  281. expect(RegExp).to.have.ownProperty('rightContext');
  282. expect(RegExp).to.have.ownProperty("$'");
  283. });
  284. it.skip('has "multiline" property', function () {
  285. // fails in IE 9, 10, 11
  286. expect(RegExp).to.have.ownProperty('multiline');
  287. expect(RegExp).to.have.ownProperty('$*');
  288. });
  289. it('has the right globals', function () {
  290. var matchVars = [
  291. '$1',
  292. '$2',
  293. '$3',
  294. '$4',
  295. '$5',
  296. '$6',
  297. '$7',
  298. '$8',
  299. '$9'
  300. ];
  301. matchVars.forEach(function (match) {
  302. expect(RegExp).to.have.property(match);
  303. });
  304. });
  305. describe('updates RegExp globals', function () {
  306. var str = 'abcdefghijklmnopq';
  307. var re;
  308. beforeEach(function () {
  309. re = /(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)(n)(o)(p)/;
  310. re.exec(str);
  311. });
  312. it('has "input"', function () {
  313. expect(RegExp.input).to.equal(str);
  314. expect(RegExp.$_).to.equal(str);
  315. });
  316. it('has "multiline"', function () {
  317. if (Object.prototype.hasOwnProperty.call(RegExp, 'multiline')) {
  318. expect(RegExp.multiline).to.equal(false);
  319. }
  320. if (Object.prototype.hasOwnProperty.call(RegExp, '$*')) {
  321. expect(RegExp['$*']).to.equal(false);
  322. }
  323. });
  324. it('has "lastMatch"', function () {
  325. expect(RegExp.lastMatch).to.equal('bcdefghijklmnop');
  326. expect(RegExp['$&']).to.equal('bcdefghijklmnop');
  327. });
  328. // in all but IE, this works. IE lastParen breaks after 11 tokens.
  329. it.skip('has "lastParen"', function () {
  330. expect(RegExp.lastParen).to.equal('p');
  331. expect(RegExp['$+']).to.equal('p');
  332. });
  333. it('has "lastParen" for less than 11 tokens', function () {
  334. (/(b)(c)(d)/).exec('abcdef');
  335. expect(RegExp.lastParen).to.equal('d');
  336. expect(RegExp['$+']).to.equal('d');
  337. });
  338. it('has "leftContext"', function () {
  339. expect(RegExp.leftContext).to.equal('a');
  340. expect(RegExp['$`']).to.equal('a');
  341. });
  342. it('has "rightContext"', function () {
  343. expect(RegExp.rightContext).to.equal('q');
  344. expect(RegExp["$'"]).to.equal('q');
  345. });
  346. it('has $1 - $9', function () {
  347. expect(RegExp.$1).to.equal('b');
  348. expect(RegExp.$2).to.equal('c');
  349. expect(RegExp.$3).to.equal('d');
  350. expect(RegExp.$4).to.equal('e');
  351. expect(RegExp.$5).to.equal('f');
  352. expect(RegExp.$6).to.equal('g');
  353. expect(RegExp.$7).to.equal('h');
  354. expect(RegExp.$8).to.equal('i');
  355. expect(RegExp.$9).to.equal('j');
  356. });
  357. });
  358. });
  359. });