re2 2.9.0 → 2.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'objspace'
3
4
 
4
5
  RSpec.describe RE2::MatchData do
@@ -9,6 +10,55 @@ RSpec.describe RE2::MatchData do
9
10
  expect(ObjectSpace.memsize_of(matches1)).to be < ObjectSpace.memsize_of(matches2)
10
11
  end
11
12
 
13
+ describe "#dup" do
14
+ it "returns a copy of the match data" do
15
+ md = RE2::Regexp.new('(\d+) (\d+)').match("123 456")
16
+ copy = md.dup
17
+
18
+ expect(copy.to_a).to eq(["123 456", "123", "456"])
19
+ end
20
+
21
+ it "returns a different object" do
22
+ md = RE2::Regexp.new('(\d+) (\d+)').match("123 456")
23
+ copy = md.dup
24
+
25
+ expect(copy).to_not equal(md)
26
+ end
27
+
28
+ it "preserves the original string" do
29
+ str = "bob 123"
30
+ md = RE2::Regexp.new('(\d+)').match(str)
31
+ copy = md.dup
32
+
33
+ expect(copy.string).to equal(str)
34
+ end
35
+
36
+ it "preserves the original regexp" do
37
+ re = RE2::Regexp.new('(\d+)')
38
+ md = re.match("123")
39
+ copy = md.dup
40
+
41
+ expect(copy.regexp).to equal(re)
42
+ end
43
+
44
+ it "raises an error when called on an uninitialized object" do
45
+ expect { described_class.allocate.dup }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
46
+ end
47
+ end
48
+
49
+ describe "#clone" do
50
+ it "returns a copy of the match data" do
51
+ md = RE2::Regexp.new('(\d+) (\d+)').match("123 456")
52
+ copy = md.clone
53
+
54
+ expect(copy.to_a).to eq(["123 456", "123", "456"])
55
+ end
56
+
57
+ it "raises an error when called on an uninitialized object" do
58
+ expect { described_class.allocate.clone }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
59
+ end
60
+ end
61
+
12
62
  describe "#to_a" do
13
63
  it "is populated with the match and capturing groups" do
14
64
  a = RE2::Regexp.new('w(o)(o)').match('woo').to_a
@@ -16,10 +66,16 @@ RSpec.describe RE2::MatchData do
16
66
  expect(a).to eq(["woo", "o", "o"])
17
67
  end
18
68
 
19
- it "populates optional capturing groups with nil if they are missing" do
69
+ it "populates optional capturing groups with empty strings if they match zero characters" do
20
70
  a = RE2::Regexp.new('(\d?)(a)(b)').match('ab').to_a
21
71
 
22
- expect(a).to eq(["ab", nil, "a", "b"])
72
+ expect(a).to eq(["ab", "", "a", "b"])
73
+ end
74
+
75
+ it "distinguishes between zero-length matches and unmatched groups" do
76
+ a = RE2::Regexp.new('()(a)?').match('b').to_a
77
+
78
+ expect(a).to eq(["", "", nil])
23
79
  end
24
80
 
25
81
  it "returns UTF-8 strings if the pattern is UTF-8" do
@@ -33,6 +89,10 @@ RSpec.describe RE2::MatchData do
33
89
 
34
90
  expect(a.map(&:encoding)).to all eq(Encoding::ISO_8859_1)
35
91
  end
92
+
93
+ it "raises an error when called on an uninitialized object" do
94
+ expect { described_class.allocate.to_a }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
95
+ end
36
96
  end
37
97
 
38
98
  describe "#[]" do
@@ -104,6 +164,12 @@ RSpec.describe RE2::MatchData do
104
164
  expect(md[:numbers]).to eq("123")
105
165
  end
106
166
 
167
+ it "returns an empty string for a zero-length capturing group" do
168
+ md = RE2::Regexp.new('()').match("bob")
169
+
170
+ expect(md[1]).to eq("")
171
+ end
172
+
107
173
  it "returns nil if no such named group exists", :aggregate_failures do
108
174
  md = RE2::Regexp.new('(\d+)').match("bob 123")
109
175
 
@@ -132,6 +198,17 @@ RSpec.describe RE2::MatchData do
132
198
  expect(md["name"].encoding.name).to eq("ISO-8859-1")
133
199
  expect(md[:name].encoding.name).to eq("ISO-8859-1")
134
200
  end
201
+
202
+ it "supports GC compaction" do
203
+ md = RE2::Regexp.new('(wo{2})').match('woohoo' * 5)
204
+ GC.compact
205
+
206
+ expect(md[1]).to eq("woo")
207
+ end
208
+
209
+ it "raises an error when called on an uninitialized object" do
210
+ expect { described_class.allocate[0] }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
211
+ end
135
212
  end
136
213
 
137
214
  describe "#string" do
@@ -142,7 +219,7 @@ RSpec.describe RE2::MatchData do
142
219
  end
143
220
 
144
221
  it "returns a copy, not the actual original" do
145
- string = "bob"
222
+ string = +"bob"
146
223
  re = RE2::Regexp.new('(\D+)').match(string)
147
224
 
148
225
  expect(re.string).to_not equal(string)
@@ -155,11 +232,15 @@ RSpec.describe RE2::MatchData do
155
232
  end
156
233
 
157
234
  it "does not copy the string if it was already frozen" do
158
- string = "bob".freeze
235
+ string = "bob"
159
236
  re = RE2::Regexp.new('(\D+)').match(string)
160
237
 
161
238
  expect(re.string).to equal(string)
162
239
  end
240
+
241
+ it "raises an error when called on an uninitialized object" do
242
+ expect { described_class.allocate.string }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
243
+ end
163
244
  end
164
245
 
165
246
  describe "#size" do
@@ -168,6 +249,10 @@ RSpec.describe RE2::MatchData do
168
249
 
169
250
  expect(md.size).to eq(3)
170
251
  end
252
+
253
+ it "raises an error when called on an uninitialized object" do
254
+ expect { described_class.allocate.size }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
255
+ end
171
256
  end
172
257
 
173
258
  describe "#length" do
@@ -176,6 +261,10 @@ RSpec.describe RE2::MatchData do
176
261
 
177
262
  expect(md.length).to eq(3)
178
263
  end
264
+
265
+ it "raises an error when called on an uninitialized object" do
266
+ expect { described_class.allocate.length }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
267
+ end
179
268
  end
180
269
 
181
270
  describe "#regexp" do
@@ -185,6 +274,10 @@ RSpec.describe RE2::MatchData do
185
274
 
186
275
  expect(md.regexp).to equal(re)
187
276
  end
277
+
278
+ it "raises an error when called on an uninitialized object" do
279
+ expect { described_class.allocate.regexp }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
280
+ end
188
281
  end
189
282
 
190
283
  describe "#inspect" do
@@ -200,11 +293,21 @@ RSpec.describe RE2::MatchData do
200
293
  expect(md.inspect).to eq('#<RE2::MatchData "1234 " 1:"1234" 2:nil>')
201
294
  end
202
295
 
296
+ it "represents zero-length capturing groups as empty strings" do
297
+ md = RE2::Regexp.new('()').match("bob")
298
+
299
+ expect(md.inspect).to eq('#<RE2::MatchData "" 1:"">')
300
+ end
301
+
203
302
  it "supports matches with null bytes" do
204
303
  md = RE2::Regexp.new("(\\w\0\\w) (\\w\0\\w)").match("a\0b c\0d")
205
304
 
206
305
  expect(md.inspect).to eq("#<RE2::MatchData \"a\0b c\0d\" 1:\"a\0b\" 2:\"c\0d\">")
207
306
  end
307
+
308
+ it "raises an error when called on an uninitialized object" do
309
+ expect { described_class.allocate.inspect }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
310
+ end
208
311
  end
209
312
 
210
313
  describe "#to_s" do
@@ -213,6 +316,16 @@ RSpec.describe RE2::MatchData do
213
316
 
214
317
  expect(md.to_s).to eq("23456")
215
318
  end
319
+
320
+ it "returns an empty string for a zero-length match" do
321
+ md = RE2::Regexp.new('()').match("bob")
322
+
323
+ expect(md.to_s).to eq("")
324
+ end
325
+
326
+ it "raises an error when called on an uninitialized object" do
327
+ expect { described_class.allocate.to_s }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
328
+ end
216
329
  end
217
330
 
218
331
  describe "#to_ary" do
@@ -257,6 +370,12 @@ RSpec.describe RE2::MatchData do
257
370
  expect(md.string[md.begin(0)..-1]).to eq('Ruby')
258
371
  end
259
372
 
373
+ it "returns the offset for a zero-length capturing group" do
374
+ md = RE2::Regexp.new('()').match("bob")
375
+
376
+ expect(md.begin(1)).to eq(0)
377
+ end
378
+
260
379
  it "returns nil for non-existent numerical matches" do
261
380
  md = RE2::Regexp.new('(\d)').match('123')
262
381
 
@@ -286,6 +405,17 @@ RSpec.describe RE2::MatchData do
286
405
 
287
406
  expect { md.begin(nil) }.to raise_error(TypeError)
288
407
  end
408
+
409
+ it "supports GC compaction" do
410
+ md = RE2::Regexp.new('(wo{2})').match('woohoo' * 5)
411
+ GC.compact
412
+
413
+ expect(md.string[md.begin(0)..-1]).to eq('woohoo' * 5)
414
+ end
415
+
416
+ it "raises an error when called on an uninitialized object" do
417
+ expect { described_class.allocate.begin(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
418
+ end
289
419
  end
290
420
 
291
421
  describe "#end" do
@@ -319,6 +449,12 @@ RSpec.describe RE2::MatchData do
319
449
  expect(md.string[0...md.end(0)]).to eq('I ♥ Ruby')
320
450
  end
321
451
 
452
+ it "returns the offset for a zero-length capturing group" do
453
+ md = RE2::Regexp.new('()').match("bob")
454
+
455
+ expect(md.end(1)).to eq(0)
456
+ end
457
+
322
458
  it "returns nil for non-existent numerical matches" do
323
459
  md = RE2::Regexp.new('(\d)').match('123')
324
460
 
@@ -348,6 +484,275 @@ RSpec.describe RE2::MatchData do
348
484
 
349
485
  expect { md.end(nil) }.to raise_error(TypeError)
350
486
  end
487
+
488
+ it "supports GC compaction" do
489
+ md = RE2::Regexp.new('(wo{2})').match('woohoo' * 5)
490
+ GC.compact
491
+
492
+ expect(md.string[0...md.end(0)]).to eq('woo')
493
+ end
494
+
495
+ it "raises an error when called on an uninitialized object" do
496
+ expect { described_class.allocate.end(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
497
+ end
498
+ end
499
+
500
+ describe "#pre_match" do
501
+ it "returns the portion of the string before the match" do
502
+ md = RE2::Regexp.new('(\d+)').match("bob 123 456")
503
+
504
+ expect(md.pre_match).to eq("bob ")
505
+ end
506
+
507
+ it "returns an empty string when the match starts at the beginning" do
508
+ md = RE2::Regexp.new('(\w+)').match("bob 123")
509
+
510
+ expect(md.pre_match).to eq("")
511
+ end
512
+
513
+ it "supports multibyte characters" do
514
+ md = RE2::Regexp.new('(\d+)').match("I ♥ 123")
515
+
516
+ expect(md.pre_match).to eq("I ♥ ")
517
+ end
518
+
519
+ it "returns UTF-8 strings by default" do
520
+ md = RE2::Regexp.new('(\d+)').match("abc 123")
521
+
522
+ expect(md.pre_match.encoding).to eq(Encoding::UTF_8)
523
+ end
524
+
525
+ it "returns ISO-8859-1 strings if the pattern is not UTF-8" do
526
+ md = RE2::Regexp.new('(\d+)', utf8: false).match("abc 123")
527
+
528
+ expect(md.pre_match.encoding).to eq(Encoding::ISO_8859_1)
529
+ end
530
+
531
+ it "returns the text before a zero-length match" do
532
+ md = RE2::Regexp.new('()').match("bob")
533
+
534
+ expect(md.pre_match).to eq("")
535
+ end
536
+
537
+ it "raises an error when called on an uninitialized object" do
538
+ expect { described_class.allocate.pre_match }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
539
+ end
540
+ end
541
+
542
+ describe "#post_match" do
543
+ it "returns the portion of the string after the match" do
544
+ md = RE2::Regexp.new('(\d+)').match("bob 123 456")
545
+
546
+ expect(md.post_match).to eq(" 456")
547
+ end
548
+
549
+ it "returns an empty string when the match ends at the end" do
550
+ md = RE2::Regexp.new('(\d+)$').match("bob 123")
551
+
552
+ expect(md.post_match).to eq("")
553
+ end
554
+
555
+ it "supports multibyte characters" do
556
+ md = RE2::Regexp.new('(\d+)').match("123 ♥ world")
557
+
558
+ expect(md.post_match).to eq(" ♥ world")
559
+ end
560
+
561
+ it "returns UTF-8 strings by default" do
562
+ md = RE2::Regexp.new('(\d+)').match("abc 123 def")
563
+
564
+ expect(md.post_match.encoding).to eq(Encoding::UTF_8)
565
+ end
566
+
567
+ it "returns ISO-8859-1 strings if the pattern is not UTF-8" do
568
+ md = RE2::Regexp.new('(\d+)', utf8: false).match("abc 123 def")
569
+
570
+ expect(md.post_match.encoding).to eq(Encoding::ISO_8859_1)
571
+ end
572
+
573
+ it "returns the text after a zero-length match" do
574
+ md = RE2::Regexp.new('()').match("bob")
575
+
576
+ expect(md.post_match).to eq("bob")
577
+ end
578
+
579
+ it "raises an error when called on an uninitialized object" do
580
+ expect { described_class.allocate.post_match }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
581
+ end
582
+ end
583
+
584
+ describe "#offset" do
585
+ it "returns the offset of a match by index" do
586
+ md = RE2::Regexp.new('ob (\d+)').match("bob 123")
587
+
588
+ expect(md.offset(0)).to eq([1, 7])
589
+ end
590
+
591
+ it "returns the offset of a submatch by index" do
592
+ md = RE2::Regexp.new('ob (\d+)').match("bob 123")
593
+
594
+ expect(md.offset(1)).to eq([4, 7])
595
+ end
596
+
597
+ it "returns the offset of a match by string name" do
598
+ md = RE2::Regexp.new('(?P<number>\d+)').match("bob 123")
599
+
600
+ expect(md.offset("number")).to eq([4, 7])
601
+ end
602
+
603
+ it "returns the offset of a match by symbol name" do
604
+ md = RE2::Regexp.new('(?P<number>\d+)').match("bob 123")
605
+
606
+ expect(md.offset(:number)).to eq([4, 7])
607
+ end
608
+
609
+ it "returns character offsets despite multibyte characters" do
610
+ md = RE2::Regexp.new('(Ruby)').match("I ♥ Ruby")
611
+
612
+ expect(md.offset(0)).to eq([4, 8])
613
+ end
614
+
615
+ it "returns identical offsets for a zero-length capturing group" do
616
+ md = RE2::Regexp.new('()').match("bob")
617
+
618
+ expect(md.offset(1)).to eq([0, 0])
619
+ end
620
+
621
+ it "returns nil for non-existent numerical matches" do
622
+ md = RE2::Regexp.new('(\d)').match("123")
623
+
624
+ expect(md.offset(10)).to be_nil
625
+ end
626
+
627
+ it "returns nil for non-existent named matches" do
628
+ md = RE2::Regexp.new('(\d)').match("123")
629
+
630
+ expect(md.offset("foo")).to be_nil
631
+ end
632
+
633
+ it "raises a type error if given an invalid name or number" do
634
+ md = RE2::Regexp.new('(\d)').match("123")
635
+
636
+ expect { md.offset(nil) }.to raise_error(TypeError)
637
+ end
638
+
639
+ it "raises an error when called on an uninitialized object" do
640
+ expect { described_class.allocate.offset(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
641
+ end
642
+ end
643
+
644
+ describe "#match_length" do
645
+ it "returns the length of the overall match" do
646
+ md = RE2::Regexp.new('ob (\d+)').match("bob 123")
647
+
648
+ expect(md.match_length(0)).to eq(6)
649
+ end
650
+
651
+ it "returns the length of a submatch by index" do
652
+ md = RE2::Regexp.new('ob (\d+)').match("bob 123")
653
+
654
+ expect(md.match_length(1)).to eq(3)
655
+ end
656
+
657
+ it "returns the length of a match by string name" do
658
+ md = RE2::Regexp.new('(?P<number>\d+)').match("bob 123")
659
+
660
+ expect(md.match_length("number")).to eq(3)
661
+ end
662
+
663
+ it "returns the length of a match by symbol name" do
664
+ md = RE2::Regexp.new('(?P<number>\d+)').match("bob 123")
665
+
666
+ expect(md.match_length(:number)).to eq(3)
667
+ end
668
+
669
+ it "returns character length despite multibyte characters" do
670
+ md = RE2::Regexp.new('(♥ Ruby)').match("I ♥ Ruby!")
671
+
672
+ expect(md.match_length(0)).to eq(6)
673
+ end
674
+
675
+ it "returns zero for a zero-length capturing group" do
676
+ md = RE2::Regexp.new('()').match("bob")
677
+
678
+ expect(md.match_length(1)).to eq(0)
679
+ end
680
+
681
+ it "returns nil for non-existent numerical matches" do
682
+ md = RE2::Regexp.new('(\d)').match("123")
683
+
684
+ expect(md.match_length(10)).to be_nil
685
+ end
686
+
687
+ it "returns nil for non-existent named matches" do
688
+ md = RE2::Regexp.new('(\d)').match("123")
689
+
690
+ expect(md.match_length("foo")).to be_nil
691
+ end
692
+
693
+ it "raises a type error if given an invalid name or number" do
694
+ md = RE2::Regexp.new('(\d)').match("123")
695
+
696
+ expect { md.match_length(nil) }.to raise_error(TypeError)
697
+ end
698
+
699
+ it "raises an error when called on an uninitialized object" do
700
+ expect { described_class.allocate.match_length(0) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
701
+ end
702
+ end
703
+
704
+ describe "#values_at" do
705
+ it "returns match values at the given indices" do
706
+ md = RE2::Regexp.new('(\d+) (\d+) (\d+)').match("123 456 789")
707
+
708
+ expect(md.values_at(1, 3)).to eq(["123", "789"])
709
+ end
710
+
711
+ it "returns match values by named groups" do
712
+ md = RE2::Regexp.new('(?P<a>\d+) (?P<b>\d+)').match("123 456")
713
+
714
+ expect(md.values_at(:a, :b)).to eq(["123", "456"])
715
+ end
716
+
717
+ it "supports a mix of indices and names" do
718
+ md = RE2::Regexp.new('(?P<a>\d+) (\d+)').match("123 456")
719
+
720
+ expect(md.values_at(2, :a)).to eq(["456", "123"])
721
+ end
722
+
723
+ it "returns nil for non-existent indices" do
724
+ md = RE2::Regexp.new('(\d+)').match("123")
725
+
726
+ expect(md.values_at(1, 5)).to eq(["123", nil])
727
+ end
728
+
729
+ it "returns nil for non-existent names" do
730
+ md = RE2::Regexp.new('(?P<a>\d+)').match("123")
731
+
732
+ expect(md.values_at(:a, :z)).to eq(["123", nil])
733
+ end
734
+
735
+ it "returns an empty string for a zero-length capturing group" do
736
+ md = RE2::Regexp.new('()(b)').match("bob")
737
+
738
+ expect(md.values_at(1, 2)).to eq(["", "b"])
739
+ end
740
+
741
+ it "returns the full match when given index 0" do
742
+ md = RE2::Regexp.new('(\d+) (\d+)').match("123 456")
743
+
744
+ expect(md.values_at(0, 1)).to eq(["123 456", "123"])
745
+ end
746
+
747
+ it "returns an empty array if given no arguments" do
748
+ md = RE2::Regexp.new('(\d+) (\d+)').match("123 456")
749
+
750
+ expect(md.values_at).to be_empty
751
+ end
752
+
753
+ it "raises an error when called on an uninitialized object" do
754
+ expect { described_class.allocate.values_at(1) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
755
+ end
351
756
  end
352
757
 
353
758
  describe "#deconstruct" do
@@ -362,6 +767,106 @@ RSpec.describe RE2::MatchData do
362
767
 
363
768
  expect(md.deconstruct).to eq(['o', 'o', nil])
364
769
  end
770
+
771
+ it "includes zero-length capturing groups as empty strings" do
772
+ md = RE2::Regexp.new('()').match("bob")
773
+
774
+ expect(md.deconstruct).to eq([""])
775
+ end
776
+
777
+ it "raises an error when called on an uninitialized object" do
778
+ expect { described_class.allocate.deconstruct }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
779
+ end
780
+ end
781
+
782
+ describe "#captures" do
783
+ it "returns all capturing groups" do
784
+ md = RE2::Regexp.new('w(o)(o)').match('woo')
785
+
786
+ expect(md.captures).to eq(['o', 'o'])
787
+ end
788
+
789
+ it "includes optional capturing groups as nil" do
790
+ md = RE2::Regexp.new('w(.)(.)(.)?').match('woo')
791
+
792
+ expect(md.captures).to eq(['o', 'o', nil])
793
+ end
794
+
795
+ it "raises an error when called on an uninitialized object" do
796
+ expect { described_class.allocate.captures }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
797
+ end
798
+ end
799
+
800
+ describe "#named_captures" do
801
+ it "returns a hash of capturing group names to matched strings" do
802
+ md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
803
+
804
+ expect(md.named_captures).to eq("numbers" => "123", "letters" => "abc")
805
+ end
806
+
807
+ it "returns an empty hash if there are no named capturing groups" do
808
+ md = RE2::Regexp.new('(\d+)').match('123')
809
+
810
+ expect(md.named_captures).to be_empty
811
+ end
812
+
813
+ it "returns unmatched optional groups as nil" do
814
+ md = RE2::Regexp.new('(?P<a>\d+) (?P<b>\w+)?').match('123 ')
815
+
816
+ expect(md.named_captures).to eq("a" => "123", "b" => nil)
817
+ end
818
+
819
+ it "returns an empty string for a zero-length named capturing group" do
820
+ md = RE2::Regexp.new('(?P<empty>)(?P<word>\w+)').match("bob")
821
+
822
+ expect(md.named_captures).to eq("empty" => "", "word" => "bob")
823
+ end
824
+
825
+ it "returns symbol keys when symbolize_names: true" do
826
+ md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
827
+
828
+ expect(md.named_captures(symbolize_names: true)).to eq(numbers: "123", letters: "abc")
829
+ end
830
+
831
+ it "returns string keys when symbolize_names: false" do
832
+ md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
833
+
834
+ expect(md.named_captures(symbolize_names: false)).to eq("numbers" => "123", "letters" => "abc")
835
+ end
836
+
837
+ it "raises an error when called on an uninitialized object" do
838
+ expect { described_class.allocate.named_captures }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
839
+ end
840
+ end
841
+
842
+ describe "#names" do
843
+ it "returns an array of names of named capturing groups" do
844
+ md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
845
+
846
+ expect(md.names).to eq(["letters", "numbers"])
847
+ end
848
+
849
+ it "returns an empty array if there are no named capturing groups" do
850
+ md = RE2::Regexp.new('(\d+)').match('123')
851
+
852
+ expect(md.names).to be_empty
853
+ end
854
+
855
+ it "returns UTF-8 strings if the pattern is UTF-8" do
856
+ md = RE2::Regexp.new('(?P<numbers>\d+)').match('123')
857
+
858
+ expect(md.names.first.encoding).to eq(Encoding::UTF_8)
859
+ end
860
+
861
+ it "returns ISO-8859-1 strings if the pattern is not UTF-8" do
862
+ md = RE2::Regexp.new('(?P<numbers>\d+)', utf8: false).match('123')
863
+
864
+ expect(md.names.first.encoding).to eq(Encoding::ISO_8859_1)
865
+ end
866
+
867
+ it "raises an error when called on an uninitialized object" do
868
+ expect { described_class.allocate.names }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
869
+ end
365
870
  end
366
871
 
367
872
  describe "#deconstruct_keys" do
@@ -395,6 +900,12 @@ RSpec.describe RE2::MatchData do
395
900
  expect(md.deconstruct_keys(nil)).to eq({})
396
901
  end
397
902
 
903
+ it "returns an empty string for a zero-length named capturing group" do
904
+ md = RE2::Regexp.new('(?P<empty>)(?P<word>\w+)').match("bob")
905
+
906
+ expect(md.deconstruct_keys(nil)).to eq(empty: "", word: "bob")
907
+ end
908
+
398
909
  it "raises an error if given a non-array of keys" do
399
910
  md = RE2::Regexp.new('(?P<numbers>\d+) (?P<letters>[a-zA-Z]+)').match('123 abc')
400
911
 
@@ -406,5 +917,9 @@ RSpec.describe RE2::MatchData do
406
917
 
407
918
  expect { md.deconstruct_keys([0]) }.to raise_error(TypeError)
408
919
  end
920
+
921
+ it "raises an error when called on an uninitialized object" do
922
+ expect { described_class.allocate.deconstruct_keys(nil) }.to raise_error(TypeError, /uninitialized RE2::MatchData/)
923
+ end
409
924
  end
410
925
  end