transpec 1.2.2 → 1.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 59870e21327ecc3a1a3a436db95ed2f6ba446a66
4
- data.tar.gz: dbb7b187b7a2d0224727c8bad33ff25e26cb9fe5
3
+ metadata.gz: c9cd93b43c3629f7c2f09782ee9616100b5147b8
4
+ data.tar.gz: 663a9de354fa930e147853a2024da3f904afae5a
5
5
  SHA512:
6
- metadata.gz: 407da45de0345dd8d3e3e857b4e02038aa9c8d379d9dc49c49ae30cb100ba0ade7d82a3cf348001b59c9c0d15debea59203ec0a034fd59039e34895aca80a5d9
7
- data.tar.gz: 8584f6ffac8f1157e9e78ca6f1d158cc67f603f669480f400c16c8e9f8ea8425f508d65a78a5d3ef8b88072c4df67012cdad477faebd69b31091b2975e6665e2
6
+ metadata.gz: 94f4f9b4a08a5d8ca703b9588e3bdda7a63eef38dc3b67b21de62a87c9f60ea09f560151ed08e68994281e0361ce7d2b344635843380c410c053c9f3c14f9f37
7
+ data.tar.gz: 65afbbd2ddd3f72b9794d95ed261c6d7f6c4f7f54ea02e415eee927c36c1fa95e6a2a71f8f60cce3e83bb26a65e52dea067a81db6d6352cf09117f4bc512e515
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Development
4
4
 
5
+ ## v1.3.0
6
+
7
+ * Handle singular collection names like `have(n).item` ([#18](https://github.com/yujinakayama/transpec/issues/18))
8
+ * Handle collection accessors with arguments like `have(n).errors_on(...)` ([#18](https://github.com/yujinakayama/transpec/issues/18))
9
+ * Handle `described_class.any_instance` ([#18](https://github.com/yujinakayama/transpec/issues/18))
10
+ * Handle indirect `any_instance` subject with runtime information (e.g. `variable = SomeClass.any_instance; variable.stub(:message)`)
11
+ * Disable conversion of `have(n).items` automatically if `rspec-rails` or `rspec-collection_matchers` is loaded in the target project
12
+ * Disable conversion of `its` automatically if `rspec-its` is loaded in the target project
13
+
5
14
  ## v1.2.2
6
15
 
7
16
  * Fix error `singleton can't be dumped (TypeError)` at the end of dynamic analysis ([#17](https://github.com/yujinakayama/transpec/issues/17))
data/README.md CHANGED
@@ -117,11 +117,11 @@ Before converting your specs:
117
117
  * Run `rspec` and check if all the specs pass.
118
118
  * Ensure the Git repository is clean. (You don't want to mix up your changes and Transpec's changes, right?)
119
119
 
120
- Then, run `transpec` (using `--generate-commit-message` is recommended) in the project root directory:
120
+ Then, run `transpec` (using `-m/--generate-commit-message` is recommended) in the project root directory:
121
121
 
122
122
  ```bash
123
123
  $ cd some-project
124
- $ transpec --generate-commit-message
124
+ $ transpec -m
125
125
  Copying project for dynamic analysis...
126
126
  Running dynamic analysis with command "bundle exec rspec"...
127
127
  ...............................................................................
@@ -214,12 +214,12 @@ $ transpec --keep should_receive,stub
214
214
  #### Available syntax types
215
215
 
216
216
  Type | Target Syntax | Converted Syntax
217
- -----------------|----------------------------------|----------------------------
217
+ -----------------|----------------------------------|-----------------------------------
218
218
  `should` | `obj.should matcher` | `expect(obj).to matcher`
219
219
  `should_receive` | `obj.should_receive` | `expect(obj).to receive`
220
220
  `stub` | `obj.stub` | `allow(obj).to receive`
221
221
  `have_items` | `expect(obj).to have(x).items` | `expect(obj.size).to eq(x)`
222
- `its` | `its(:attr) { }` | `describe { subject { } it { } }`
222
+ `its` | `its(:attr) { }` | `describe { subject { }; it { } }`
223
223
  `deprecated` | `obj.stub!`, `mock('foo')`, etc. | `obj.stub`, `double('foo')`
224
224
 
225
225
  See [Supported Conversions](#supported-conversions) for more details.
@@ -409,6 +409,8 @@ expect(obj).to be false
409
409
 
410
410
  So, converting `be_true`/`be_false` to `be_truthy`/`be_falsey` never breaks your specs and this is the Transpec's default. If you are willing to test boolean values strictly, you can convert them to `be true`/`be false` with `--boolean-matcher true,false` option. Note that this may break your specs if your library codes don't return exact boolean values.
411
411
 
412
+ ---
413
+
412
414
  * Conversion can be disabled by: `--keep deprecated`
413
415
  * Deprecation: Deprecated since RSpec 2.99, removed at RSpec 3.0
414
416
  * See also: [Consider renaming `be_true` and `be_false` to `be_truthy` and `be_falsey` · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/issues/283)
@@ -429,6 +431,8 @@ expect(1.0 / 3.0).to be_within(0.001).of(0.333)
429
431
 
430
432
  ### `have(n).items` matcher
431
433
 
434
+ **This conversion will be disabled automatically if `rspec-collection_matchers` or `rspec-rails` is loaded in your spec.**
435
+
432
436
  ```ruby
433
437
  # Targets
434
438
  expect(collection).to have(3).items
@@ -455,8 +459,15 @@ expect(team.players.size).to eq(3)
455
459
  expect(team.send(:players).size).to eq(3)
456
460
  ```
457
461
 
458
- There's the option to continue using `have(n).items` matcher with [rspec-collection_matchers](https://github.com/rspec/rspec-collection_matchers) that is an external gem extracted from `rspec-expectations`.
459
- If you choose so, disable this conversion with `--keep have_items`.
462
+ There's an option to continue using `have(n).items` matcher with [rspec-collection_matchers](https://github.com/rspec/rspec-collection_matchers) that is an external gem extracted from `rspec-expectations`.
463
+ If you choose so, disable this conversion by either:
464
+
465
+ * Specify `--keep have_items` option manually.
466
+ * Require `rspec-collection_matchers` or `rspec-rails` in your spec so that Transpec automatically disables this conversion.
467
+
468
+ Note: `rspec-rails` 3.0 [still uses `have(n).items` matcher with `rspec-collection_matchers`](https://github.com/rspec/rspec-rails/blob/v3.0.0.beta1/rspec-rails.gemspec#L41).
469
+
470
+ ---
460
471
 
461
472
  * Conversion can be disabled by: `--keep have_items`
462
473
  * Deprecation: Deprecated since RSpec 2.99, removed at RSpec 3.0
@@ -501,11 +512,11 @@ lambda { do_something }.should_not raise_error # with `--keep should`
501
512
  ```ruby
502
513
  # Targets
503
514
  obj.should_receive(:foo)
504
- SomeClass.any_instance.should_receive(:foo)
515
+ Klass.any_instance.should_receive(:foo)
505
516
 
506
517
  # Converted
507
518
  expect(obj).to receive(:foo)
508
- expect_any_instance_of(SomeClass).to receive(:foo)
519
+ expect_any_instance_of(Klass).to receive(:foo)
509
520
  ```
510
521
 
511
522
  * Conversion can be disabled by: `--keep should_receive`
@@ -519,15 +530,15 @@ expect_any_instance_of(SomeClass).to receive(:foo)
519
530
  obj.should_receive(:foo).any_number_of_times
520
531
  obj.should_receive(:foo).at_least(0)
521
532
 
522
- SomeClass.any_instance.should_receive(:foo).any_number_of_times
523
- SomeClass.any_instance.should_receive(:foo).at_least(0)
533
+ Klass.any_instance.should_receive(:foo).any_number_of_times
534
+ Klass.any_instance.should_receive(:foo).at_least(0)
524
535
 
525
536
  # Converted
526
537
  allow(obj).to receive(:foo)
527
538
  obj.stub(:foo) # with `--keep stub`
528
539
 
529
- allow_any_instance_of(SomeClass).to receive(:foo)
530
- SomeClass.any_instance.stub(:foo) # with `--keep stub`
540
+ allow_any_instance_of(Klass).to receive(:foo)
541
+ Klass.any_instance.stub(:foo) # with `--keep stub`
531
542
  ```
532
543
 
533
544
  * Conversion can be disabled by: `--keep deprecated`
@@ -544,7 +555,7 @@ obj.stub!(:foo)
544
555
 
545
556
  obj.stub(:foo => 1, :bar => 2)
546
557
 
547
- SomeClass.any_instance.stub(:foo)
558
+ Klass.any_instance.stub(:foo)
548
559
 
549
560
  # Converted
550
561
  allow(obj).to receive(:foo)
@@ -558,7 +569,7 @@ allow(obj).to receive(:bar).and_return(2)
558
569
  # If the target project's rspec gem dependency is 3.0 or later
559
570
  allow(obj).to receive_messages(:foo => 1, :bar => 2)
560
571
 
561
- allow_any_instance_of(SomeClass).to receive(:foo)
572
+ allow_any_instance_of(Klass).to receive(:foo)
562
573
  ```
563
574
 
564
575
  Note: `allow(obj).to receive_messages(:foo => 1, :bar => 2)` that is designed to be the replacement for `obj.stub(:foo => 1, :bar => 2)` is available from RSpec 3.0 (though [it's now being considered to be backported to RSpec 2.99](https://github.com/rspec/rspec-mocks/issues/454)). So, in [the upgrade path to RSpec 3](http://myronmars.to/n/dev-blog/2013/07/the-plan-for-rspec-3#the_upgrade_path), if you want to convert them with keeping the syntax correspondence, you need to follow these steps:
@@ -570,6 +581,8 @@ Note: `allow(obj).to receive_messages(:foo => 1, :bar => 2)` that is designed to
570
581
 
571
582
  Otherwise `obj.stub(:foo => 1, :bar => 2)` will be converted to two `allow(obj).to receive(...).and_return(...)` expressions on RSpec 2.99.
572
583
 
584
+ ---
585
+
573
586
  * Conversion can be disabled by: `--keep stub`
574
587
  * Deprecation: Deprecated since RSpec 3.0
575
588
  * See also:
@@ -625,6 +638,8 @@ double('something')
625
638
 
626
639
  ### Expectations on attribute of subject with `its`
627
640
 
641
+ **This conversion will be disabled automatically if `rspec-its` is loaded in your spec.**
642
+
628
643
  ```ruby
629
644
  # Targets
630
645
  describe 'example' do
@@ -658,8 +673,13 @@ describe 'example' do
658
673
  end
659
674
  ```
660
675
 
661
- There's the option to continue using `its` with [rspec-its](https://github.com/rspec/rspec-its) that is an external gem extracted from `rspec-core`.
662
- If you choose so, disable this conversion with `--keep its`.
676
+ There's an option to continue using `its` with [rspec-its](https://github.com/rspec/rspec-its) that is an external gem extracted from `rspec-core`.
677
+ If you choose so, disable this conversion by either:
678
+
679
+ * Specify `--keep its` option manually.
680
+ * Require `rspec-its` in your spec so that Transpec automatically disables this conversion.
681
+
682
+ ---
663
683
 
664
684
  * Conversion can be disabled by: `--keep its`
665
685
  * Deprecation: Deprecated since RSpec 2.99, removed at RSpec 3.0
data/README.md.erb CHANGED
@@ -90,11 +90,11 @@ Before converting your specs:
90
90
  * Run `rspec` and check if all the specs pass.
91
91
  * Ensure the Git repository is clean. (You don't want to mix up your changes and Transpec's changes, right?)
92
92
 
93
- Then, run `transpec` (using `--generate-commit-message` is recommended) in the project root directory:
93
+ Then, run `transpec` (using `-m/--generate-commit-message` is recommended) in the project root directory:
94
94
 
95
95
  ```bash
96
96
  $ cd some-project
97
- $ transpec --generate-commit-message
97
+ $ transpec -m
98
98
  Copying project for dynamic analysis...
99
99
  Running dynamic analysis with command "bundle exec rspec"...
100
100
  ...............................................................................
@@ -187,14 +187,14 @@ $ transpec --keep should_receive,stub
187
187
  #### Available syntax types
188
188
 
189
189
  Type | Target Syntax | Converted Syntax
190
- -----------------|----------------------------------|----------------------------
190
+ -----------------|----------------------------------|-----------------------------------
191
191
  <%=
192
192
  conversion_type_table = <<END
193
193
  `should` | `obj.should matcher` | `expect(obj).to matcher`
194
194
  `should_receive` | `obj.should_receive` | `expect(obj).to receive`
195
195
  `stub` | `obj.stub` | `allow(obj).to receive`
196
196
  `have_items` | `expect(obj).to have(x).items` | `expect(obj.size).to eq(x)`
197
- `its` | `its(:attr) { }` | `describe { subject { } it { } }`
197
+ `its` | `its(:attr) { }` | `describe { subject { }; it { } }`
198
198
  `deprecated` | `obj.stub!`, `mock('foo')`, etc. | `obj.stub`, `double('foo')`
199
199
  END
200
200
 
@@ -405,6 +405,8 @@ expect(obj).to be false
405
405
 
406
406
  So, converting `be_true`/`be_false` to `be_truthy`/`be_falsey` never breaks your specs and this is the Transpec's default. If you are willing to test boolean values strictly, you can convert them to `be true`/`be false` with `--boolean-matcher true,false` option. Note that this may break your specs if your library codes don't return exact boolean values.
407
407
 
408
+ ---
409
+
408
410
  * Conversion can be disabled by: `--keep deprecated`
409
411
  * Deprecation: Deprecated since RSpec 2.99, removed at RSpec 3.0
410
412
  * See also: [Consider renaming `be_true` and `be_false` to `be_truthy` and `be_falsey` · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/issues/283)
@@ -425,6 +427,8 @@ expect(1.0 / 3.0).to be_within(0.001).of(0.333)
425
427
 
426
428
  ### `have(n).items` matcher
427
429
 
430
+ **This conversion will be disabled automatically if `rspec-collection_matchers` or `rspec-rails` is loaded in your spec.**
431
+
428
432
  ```ruby
429
433
  # Targets
430
434
  expect(collection).to have(3).items
@@ -451,8 +455,15 @@ expect(team.players.size).to eq(3)
451
455
  expect(team.send(:players).size).to eq(3)
452
456
  ```
453
457
 
454
- There's the option to continue using `have(n).items` matcher with [rspec-collection_matchers](https://github.com/rspec/rspec-collection_matchers) that is an external gem extracted from `rspec-expectations`.
455
- If you choose so, disable this conversion with `--keep have_items`.
458
+ There's an option to continue using `have(n).items` matcher with [rspec-collection_matchers](https://github.com/rspec/rspec-collection_matchers) that is an external gem extracted from `rspec-expectations`.
459
+ If you choose so, disable this conversion by either:
460
+
461
+ * Specify `--keep have_items` option manually.
462
+ * Require `rspec-collection_matchers` or `rspec-rails` in your spec so that Transpec automatically disables this conversion.
463
+
464
+ Note: `rspec-rails` 3.0 [still uses `have(n).items` matcher with `rspec-collection_matchers`](https://github.com/rspec/rspec-rails/blob/v3.0.0.beta1/rspec-rails.gemspec#L41).
465
+
466
+ ---
456
467
 
457
468
  * Conversion can be disabled by: `--keep have_items`
458
469
  * Deprecation: Deprecated since RSpec 2.99, removed at RSpec 3.0
@@ -497,11 +508,11 @@ lambda { do_something }.should_not raise_error # with `--keep should`
497
508
  ```ruby
498
509
  # Targets
499
510
  obj.should_receive(:foo)
500
- SomeClass.any_instance.should_receive(:foo)
511
+ Klass.any_instance.should_receive(:foo)
501
512
 
502
513
  # Converted
503
514
  expect(obj).to receive(:foo)
504
- expect_any_instance_of(SomeClass).to receive(:foo)
515
+ expect_any_instance_of(Klass).to receive(:foo)
505
516
  ```
506
517
 
507
518
  * Conversion can be disabled by: `--keep should_receive`
@@ -515,15 +526,15 @@ expect_any_instance_of(SomeClass).to receive(:foo)
515
526
  obj.should_receive(:foo).any_number_of_times
516
527
  obj.should_receive(:foo).at_least(0)
517
528
 
518
- SomeClass.any_instance.should_receive(:foo).any_number_of_times
519
- SomeClass.any_instance.should_receive(:foo).at_least(0)
529
+ Klass.any_instance.should_receive(:foo).any_number_of_times
530
+ Klass.any_instance.should_receive(:foo).at_least(0)
520
531
 
521
532
  # Converted
522
533
  allow(obj).to receive(:foo)
523
534
  obj.stub(:foo) # with `--keep stub`
524
535
 
525
- allow_any_instance_of(SomeClass).to receive(:foo)
526
- SomeClass.any_instance.stub(:foo) # with `--keep stub`
536
+ allow_any_instance_of(Klass).to receive(:foo)
537
+ Klass.any_instance.stub(:foo) # with `--keep stub`
527
538
  ```
528
539
 
529
540
  * Conversion can be disabled by: `--keep deprecated`
@@ -540,7 +551,7 @@ obj.stub!(:foo)
540
551
 
541
552
  obj.stub(:foo => 1, :bar => 2)
542
553
 
543
- SomeClass.any_instance.stub(:foo)
554
+ Klass.any_instance.stub(:foo)
544
555
 
545
556
  # Converted
546
557
  allow(obj).to receive(:foo)
@@ -554,7 +565,7 @@ allow(obj).to receive(:bar).and_return(2)
554
565
  # If the target project's rspec gem dependency is 3.0 or later
555
566
  allow(obj).to receive_messages(:foo => 1, :bar => 2)
556
567
 
557
- allow_any_instance_of(SomeClass).to receive(:foo)
568
+ allow_any_instance_of(Klass).to receive(:foo)
558
569
  ```
559
570
 
560
571
  Note: `allow(obj).to receive_messages(:foo => 1, :bar => 2)` that is designed to be the replacement for `obj.stub(:foo => 1, :bar => 2)` is available from RSpec 3.0 (though [it's now being considered to be backported to RSpec 2.99](https://github.com/rspec/rspec-mocks/issues/454)). So, in [the upgrade path to RSpec 3](http://myronmars.to/n/dev-blog/2013/07/the-plan-for-rspec-3#the_upgrade_path), if you want to convert them with keeping the syntax correspondence, you need to follow these steps:
@@ -566,6 +577,8 @@ Note: `allow(obj).to receive_messages(:foo => 1, :bar => 2)` that is designed to
566
577
 
567
578
  Otherwise `obj.stub(:foo => 1, :bar => 2)` will be converted to two `allow(obj).to receive(...).and_return(...)` expressions on RSpec 2.99.
568
579
 
580
+ ---
581
+
569
582
  * Conversion can be disabled by: `--keep stub`
570
583
  * Deprecation: Deprecated since RSpec 3.0
571
584
  * See also:
@@ -621,6 +634,8 @@ double('something')
621
634
 
622
635
  ### Expectations on attribute of subject with `its`
623
636
 
637
+ **This conversion will be disabled automatically if `rspec-its` is loaded in your spec.**
638
+
624
639
  ```ruby
625
640
  # Targets
626
641
  <%=
@@ -638,8 +653,13 @@ END
638
653
  <%= Transpec::Converter.new.convert(its_target) -%>
639
654
  ```
640
655
 
641
- There's the option to continue using `its` with [rspec-its](https://github.com/rspec/rspec-its) that is an external gem extracted from `rspec-core`.
642
- If you choose so, disable this conversion with `--keep its`.
656
+ There's an option to continue using `its` with [rspec-its](https://github.com/rspec/rspec-its) that is an external gem extracted from `rspec-core`.
657
+ If you choose so, disable this conversion by either:
658
+
659
+ * Specify `--keep its` option manually.
660
+ * Require `rspec-its` in your spec so that Transpec automatically disables this conversion.
661
+
662
+ ---
643
663
 
644
664
  * Conversion can be disabled by: `--keep its`
645
665
  * Deprecation: Deprecated since RSpec 2.99, removed at RSpec 3.0
@@ -143,7 +143,7 @@ module Transpec
143
143
  " #{'should_receive'.bright} (to #{'expect(obj).to receive'.underline})",
144
144
  " #{'stub'.bright} (to #{'allow(obj).to receive'.underline})",
145
145
  " #{'have_items'.bright} (to #{'expect(obj.size).to eq(x)'.underline})",
146
- " #{'its'.bright} (to #{'describe { subject { } it { } }'.underline})",
146
+ " #{'its'.bright} (to #{'describe { subject { }; it { } }'.underline})",
147
147
  " #{'deprecated'.bright} (e.g. from #{'mock'.underline} to #{'double'.underline})",
148
148
  'These are all converted by default.'
149
149
  ],
@@ -39,30 +39,11 @@ module Transpec
39
39
  end
40
40
 
41
41
  def register_request_for_dynamic_analysis(rewriter)
42
- node = @expectation.subject_node
43
-
44
- # `expect(owner).to have(n).things` invokes private owner#things with Object#__send__
45
- # if the owner does not respond to any of #size, #count and #length.
46
- #
47
- # rubocop:disable LineLength
48
- # https://github.com/rspec/rspec-expectations/blob/v2.14.3/lib/rspec/matchers/built_in/have.rb#L48-L58
49
- # rubocop:enable LineLength
50
- key = :subject_is_owner_of_collection?
51
- code = "respond_to?(#{items_name.inspect}) || " +
52
- "(methods & #{QUERY_METHOD_PRIORITIES.inspect}).empty?"
53
- rewriter.register_request(node, key, code)
54
-
55
- key = :available_query_methods
56
- code = "target = #{code} ? #{items_name} : self; " +
57
- "target.methods & #{QUERY_METHOD_PRIORITIES.inspect}"
58
- rewriter.register_request(node, key, code)
59
-
60
- key = :collection_accessor_is_private?
61
- code = "private_methods.include?(#{items_name.inspect})"
62
- rewriter.register_request(node, key, code)
42
+ DynamicInspector.register_request(self, rewriter)
63
43
  end
64
44
 
65
45
  def convert_to_standard_expectation!(parenthesize_matcher_arg = true)
46
+ return if project_requires_collection_matcher?
66
47
  replace(@expectation.subject_range, replacement_subject_source)
67
48
  replace(expression_range, replacement_matcher_source(size_source, parenthesize_matcher_arg))
68
49
  register_record
@@ -78,6 +59,10 @@ module Transpec
78
59
 
79
60
  alias_method :items_node, :node
80
61
 
62
+ def items_method_has_arguments?
63
+ items_node.children.size > 2
64
+ end
65
+
81
66
  def have_method_name
82
67
  have_node.children[1]
83
68
  end
@@ -86,21 +71,31 @@ module Transpec
86
71
  items_node.children[1]
87
72
  end
88
73
 
74
+ def project_requires_collection_matcher?
75
+ runtime_subject_data && runtime_subject_data[:project_requires_collection_matcher?].result
76
+ end
77
+
78
+ def collection_accessor
79
+ if runtime_subject_data && runtime_subject_data[:collection_accessor].result
80
+ runtime_subject_data[:collection_accessor].result.to_sym
81
+ else
82
+ items_name
83
+ end
84
+ end
85
+
89
86
  def subject_is_owner_of_collection?
90
- node_data = runtime_node_data(@expectation.subject_node)
91
- node_data && node_data[:subject_is_owner_of_collection?].result
87
+ return true if items_method_has_arguments?
88
+ runtime_subject_data && !runtime_subject_data[:collection_accessor].result.nil?
92
89
  end
93
90
 
94
91
  def collection_accessor_is_private?
95
- node_data = runtime_node_data(@expectation.subject_node)
96
- node_data && node_data[:collection_accessor_is_private?].result
92
+ runtime_subject_data && runtime_subject_data[:collection_accessor_is_private?].result
97
93
  end
98
94
 
99
95
  def query_method
100
- node_data = runtime_node_data(@expectation.subject_node)
101
- if node_data && node_data[:available_query_methods].result.is_a?(Array)
102
- available_query_methods = node_data[:available_query_methods].result.map(&:to_sym)
103
- (QUERY_METHOD_PRIORITIES & available_query_methods).first
96
+ if runtime_subject_data && runtime_subject_data[:available_query_methods]
97
+ available_query_methods = runtime_subject_data[:available_query_methods].result
98
+ (QUERY_METHOD_PRIORITIES & available_query_methods.map(&:to_sym)).first
104
99
  else
105
100
  default_query_method
106
101
  end
@@ -121,13 +116,20 @@ module Transpec
121
116
 
122
117
  private
123
118
 
119
+ def runtime_subject_data
120
+ return @runtime_subject_data if instance_variable_defined?(:@runtime_subject_data)
121
+ @runtime_subject_data = runtime_node_data(@expectation.subject_node)
122
+ end
123
+
124
124
  def replacement_subject_source
125
125
  source = @expectation.subject_range.source
126
126
  if subject_is_owner_of_collection?
127
127
  if collection_accessor_is_private?
128
- source << ".send(#{items_name.inspect})"
128
+ source << ".send(#{collection_accessor.inspect}"
129
+ source << ", #{args_range.source}" if items_method_has_arguments?
130
+ source << ')'
129
131
  else
130
- source << ".#{items_name}"
132
+ source << ".#{collection_accessor}#{parentheses_range.source}"
131
133
  end
132
134
  end
133
135
  source << ".#{query_method}"
@@ -160,15 +162,91 @@ module Transpec
160
162
  size_node.loc.expression.source
161
163
  end
162
164
 
163
- def dot_items_range
164
- map = items_node.loc
165
- map.dot.join(map.selector)
166
- end
167
-
168
165
  def register_record
169
166
  @report.records << HaveRecord.new(self)
170
167
  end
171
168
 
169
+ class DynamicInspector
170
+ def self.register_request(have, rewriter)
171
+ new(have, rewriter).register_request
172
+ end
173
+
174
+ def initialize(have, rewriter)
175
+ @have = have
176
+ @rewriter = rewriter
177
+ end
178
+
179
+ def target_node
180
+ @have.expectation.subject_node
181
+ end
182
+
183
+ def register_request
184
+ key = :collection_accessor
185
+ code = collection_accessor_inspection_code
186
+ @rewriter.register_request(target_node, key, code)
187
+
188
+ # Give up inspecting query methods of collection accessor with arguments
189
+ # (e.g. have(2).errors_on(variable)) since this is a context of #instance_eval.
190
+ unless @have.items_method_has_arguments?
191
+ key = :available_query_methods
192
+ code = "collection_accessor = #{code}; " +
193
+ 'target = collection_accessor ? __send__(collection_accessor) : self; ' +
194
+ "target.methods & #{QUERY_METHOD_PRIORITIES.inspect}"
195
+ @rewriter.register_request(target_node, key, code)
196
+ end
197
+
198
+ key = :collection_accessor_is_private?
199
+ code = "private_methods.include?(#{@have.items_name.inspect})"
200
+ @rewriter.register_request(target_node, key, code)
201
+
202
+ key = :project_requires_collection_matcher?
203
+ code = 'defined?(RSpec::Rails) || defined?(RSpec::CollectionMatchers)'
204
+ @rewriter.register_request(target_node, key, code, :context)
205
+ end
206
+
207
+ # rubocop:disable MethodLength
208
+ def collection_accessor_inspection_code
209
+ # `expect(owner).to have(n).things` invokes private owner#things with Object#__send__
210
+ # if the owner does not respond to any of #size, #count and #length.
211
+ #
212
+ # rubocop:disable LineLength
213
+ # https://github.com/rspec/rspec-expectations/blob/v2.14.3/lib/rspec/matchers/built_in/have.rb#L48-L58
214
+ # rubocop:enable LineLength
215
+ <<-END.gsub(/^\s+\|/, '').chomp
216
+ |begin
217
+ | exact_name = #{@have.items_name.inspect}
218
+ |
219
+ | inflector = if defined?(ActiveSupport::Inflector) &&
220
+ | ActiveSupport::Inflector.respond_to?(:pluralize)
221
+ | ActiveSupport::Inflector
222
+ | elsif defined?(Inflector)
223
+ | Inflector
224
+ | else
225
+ | nil
226
+ | end
227
+ |
228
+ | if inflector
229
+ | pluralized_name = inflector.pluralize(exact_name).to_sym
230
+ | respond_to_pluralized_name = respond_to?(pluralized_name)
231
+ | end
232
+ |
233
+ | respond_to_query_methods = !(methods & #{QUERY_METHOD_PRIORITIES.inspect}).empty?
234
+ |
235
+ | if respond_to?(exact_name)
236
+ | exact_name
237
+ | elsif respond_to_pluralized_name
238
+ | pluralized_name
239
+ | elsif respond_to_query_methods
240
+ | nil
241
+ | else
242
+ | exact_name
243
+ | end
244
+ |end
245
+ END
246
+ end
247
+ # rubocop:enable MethodLength
248
+ end
249
+
172
250
  class HaveRecord < Record
173
251
  def initialize(have)
174
252
  @have = have
@@ -210,7 +288,11 @@ module Transpec
210
288
 
211
289
  def original_items
212
290
  if @have.subject_is_owner_of_collection?
213
- @have.items_name
291
+ if @have.items_method_has_arguments?
292
+ "#{@have.collection_accessor}(...)"
293
+ else
294
+ @have.collection_accessor
295
+ end
214
296
  else
215
297
  'items'
216
298
  end
@@ -218,11 +300,16 @@ module Transpec
218
300
 
219
301
  def converted_subject
220
302
  if @have.subject_is_owner_of_collection?
303
+ subject = 'obj.'
221
304
  if @have.collection_accessor_is_private?
222
- "obj.send(#{@have.items_name.inspect}).#{@have.query_method}"
305
+ subject << "send(#{@have.collection_accessor.inspect}"
306
+ subject << ', ...' if @have.items_method_has_arguments?
307
+ subject << ')'
223
308
  else
224
- "obj.#{@have.items_name}.#{@have.query_method}"
309
+ subject << "#{@have.collection_accessor}"
310
+ subject << '(...)' if @have.items_method_has_arguments?
225
311
  end
312
+ subject << ".#{@have.query_method}"
226
313
  else
227
314
  "collection.#{@have.default_query_method}"
228
315
  end
@@ -13,7 +13,15 @@ module Transpec
13
13
  receiver_node.nil? && method_name == :its
14
14
  end
15
15
 
16
+ def register_request_for_dynamic_analysis(rewriter)
17
+ key = :project_requires_its?
18
+ code = 'defined?(RSpec::Its)'
19
+ rewriter.register_request(@node, key, code, :context)
20
+ end
21
+
16
22
  def convert_to_describe_subject_it!
23
+ return if project_requires_its?
24
+
17
25
  front, rear = build_wrapper_codes
18
26
 
19
27
  insert_before(beginning_of_line_range, front)
@@ -37,6 +45,11 @@ module Transpec
37
45
  @node.parent_node
38
46
  end
39
47
 
48
+ def project_requires_its?
49
+ node_data = runtime_node_data(@node)
50
+ node_data && node_data[:project_requires_its?].result
51
+ end
52
+
40
53
  private
41
54
 
42
55
  def build_wrapper_codes
@@ -99,9 +112,9 @@ module Transpec
99
112
 
100
113
  def converted_syntax
101
114
  if attribute_expression.brackets?
102
- "describe '[:key]' do subject { super()[:key] } it { } end"
115
+ "describe '[:key]' do subject { super()[:key] }; it { } end"
103
116
  else
104
- "describe 'attr' do subject { super().attr } it { } end"
117
+ "describe 'attr' do subject { super().attr }; it { } end"
105
118
  end
106
119
  end
107
120
 
@@ -23,6 +23,7 @@ module Transpec
23
23
  :allow_to_receive_available?,
24
24
  [:allow, :receive]
25
25
  )
26
+ register_request_of_any_instance_inspection(rewriter)
26
27
  end
27
28
 
28
29
  def allow_to_receive_available?
@@ -101,8 +102,7 @@ module Transpec
101
102
 
102
103
  def allow_source
103
104
  if any_instance?
104
- class_source = class_node_of_any_instance.loc.expression.source
105
- "allow_any_instance_of(#{class_source})"
105
+ "allow_any_instance_of(#{any_instance_target_class_source})"
106
106
  else
107
107
  "allow(#{subject_range.source})"
108
108
  end
@@ -127,7 +127,7 @@ module Transpec
127
127
  end
128
128
 
129
129
  def original_syntax
130
- syntax = any_instance? ? 'SomeClass.any_instance' : 'obj'
130
+ syntax = any_instance? ? 'Klass.any_instance' : 'obj'
131
131
  syntax << ".#{method_name}"
132
132
  syntax << (arg_node.type == :hash ? '(:message => value)' : '(:message)')
133
133
  end
@@ -135,7 +135,7 @@ module Transpec
135
135
  def converted_syntax(conversion_type)
136
136
  case conversion_type
137
137
  when :allow_to_receive, :allow_to_receive_messages
138
- syntax = any_instance? ? 'allow_any_instance_of(SomeClass)' : 'allow(obj)'
138
+ syntax = any_instance? ? 'allow_any_instance_of(Klass)' : 'allow(obj)'
139
139
  syntax << '.to '
140
140
  if conversion_type == :allow_to_receive
141
141
  syntax << 'receive(:message)'