transpec 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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)'