transpec 1.3.1 → 1.4.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +1 -3
  4. data/CHANGELOG.md +9 -0
  5. data/README.md +141 -24
  6. data/README.md.erb +136 -24
  7. data/lib/transpec/ast/node.rb +7 -3
  8. data/lib/transpec/cli.rb +1 -1
  9. data/lib/transpec/configuration.rb +1 -0
  10. data/lib/transpec/converter.rb +31 -2
  11. data/lib/transpec/dynamic_analyzer.rb +1 -1
  12. data/lib/transpec/option_parser.rb +12 -9
  13. data/lib/transpec/project.rb +23 -12
  14. data/lib/transpec/rspec_version.rb +18 -4
  15. data/lib/transpec/static_context_inspector.rb +0 -15
  16. data/lib/transpec/syntax/example.rb +83 -0
  17. data/lib/transpec/syntax/expect.rb +5 -0
  18. data/lib/transpec/syntax/have.rb +111 -54
  19. data/lib/transpec/syntax/method_stub.rb +58 -37
  20. data/lib/transpec/syntax/mixin/allow_no_message.rb +2 -0
  21. data/lib/transpec/syntax/mixin/any_instance.rb +2 -0
  22. data/lib/transpec/syntax/mixin/should_base.rb +39 -0
  23. data/lib/transpec/syntax/oneliner_should.rb +218 -0
  24. data/lib/transpec/syntax/operator_matcher.rb +1 -0
  25. data/lib/transpec/syntax/should.rb +3 -30
  26. data/lib/transpec/util.rb +54 -0
  27. data/lib/transpec/version.rb +2 -2
  28. data/spec/support/shared_context.rb +21 -29
  29. data/spec/transpec/ast/node_spec.rb +1 -1
  30. data/spec/transpec/commit_message_spec.rb +29 -23
  31. data/spec/transpec/configuration_spec.rb +1 -0
  32. data/spec/transpec/converter_spec.rb +208 -5
  33. data/spec/transpec/dynamic_analyzer_spec.rb +2 -2
  34. data/spec/transpec/option_parser_spec.rb +1 -0
  35. data/spec/transpec/project_spec.rb +10 -0
  36. data/spec/transpec/rspec_version_spec.rb +52 -28
  37. data/spec/transpec/static_context_inspector_spec.rb +2 -2
  38. data/spec/transpec/syntax/be_boolean_spec.rb +6 -13
  39. data/spec/transpec/syntax/be_close_spec.rb +2 -9
  40. data/spec/transpec/syntax/double_spec.rb +2 -9
  41. data/spec/transpec/syntax/example_spec.rb +249 -0
  42. data/spec/transpec/syntax/expect_spec.rb +1 -1
  43. data/spec/transpec/syntax/have_spec.rb +127 -22
  44. data/spec/transpec/syntax/its_spec.rb +9 -18
  45. data/spec/transpec/syntax/method_stub_spec.rb +193 -158
  46. data/spec/transpec/syntax/oneliner_should_spec.rb +653 -0
  47. data/spec/transpec/syntax/operator_matcher_spec.rb +7 -8
  48. data/spec/transpec/syntax/raise_error_spec.rb +6 -13
  49. data/spec/transpec/syntax/rspec_configure_spec.rb +1 -8
  50. data/spec/transpec/syntax/should_receive_spec.rb +19 -28
  51. data/spec/transpec/syntax/should_spec.rb +18 -16
  52. data/spec/transpec/util_spec.rb +30 -0
  53. data/transpec.gemspec +8 -7
  54. metadata +49 -28
@@ -5,9 +5,13 @@ require 'parser'
5
5
  module Transpec
6
6
  module AST
7
7
  class Node < Parser::AST::Node
8
+ attr_reader :metadata
9
+
8
10
  def initialize(type, children = [], properties = {})
9
- @properties = {}
11
+ @metadata = {}
12
+ @mutable_attributes = {}
10
13
 
14
+ # ::AST::Node#initialize freezes itself.
11
15
  super
12
16
 
13
17
  each_child_node do |child_node|
@@ -16,11 +20,11 @@ module Transpec
16
20
  end
17
21
 
18
22
  def parent_node
19
- @properties[:parent_node]
23
+ @mutable_attributes[:parent_node]
20
24
  end
21
25
 
22
26
  def parent_node=(node)
23
- @properties[:parent_node] = node
27
+ @mutable_attributes[:parent_node] = node
24
28
  end
25
29
 
26
30
  protected :parent_node=
data/lib/transpec/cli.rb CHANGED
@@ -46,7 +46,7 @@ module Transpec
46
46
  runtime_data = nil
47
47
 
48
48
  unless @configuration.skip_dynamic_analysis?
49
- puts 'Copying project for dynamic analysis...'
49
+ puts 'Copying the project for dynamic analysis...'
50
50
  DynamicAnalyzer.new(rspec_command: @configuration.rspec_command) do |analyzer|
51
51
  puts "Running dynamic analysis with command \"#{analyzer.rspec_command}\"..."
52
52
  runtime_data = analyzer.analyze(paths)
@@ -8,6 +8,7 @@ module Transpec
8
8
 
9
9
  PREDICATES = [
10
10
  [:convert_should, true],
11
+ [:convert_oneliner, true],
11
12
  [:convert_should_receive, true],
12
13
  [:convert_stub, true],
13
14
  [:convert_have_items, true],
@@ -8,9 +8,11 @@ require 'transpec/syntax'
8
8
  require 'transpec/syntax/be_boolean'
9
9
  require 'transpec/syntax/be_close'
10
10
  require 'transpec/syntax/double'
11
+ require 'transpec/syntax/example'
11
12
  require 'transpec/syntax/expect'
12
13
  require 'transpec/syntax/its'
13
14
  require 'transpec/syntax/method_stub'
15
+ require 'transpec/syntax/oneliner_should'
14
16
  require 'transpec/syntax/raise_error'
15
17
  require 'transpec/syntax/rspec_configure'
16
18
  require 'transpec/syntax/should'
@@ -68,9 +70,31 @@ module Transpec
68
70
  end
69
71
  end
70
72
 
73
+ def process_oneliner_should(oneliner_should)
74
+ negative_form = @configuration.negative_form_of_to
75
+ parenthesize = @configuration.parenthesize_matcher_arg?
76
+
77
+ # TODO: Referencing oneliner_should.have_matcher.project_requires_collection_matcher?
78
+ # from this converter is considered bad design.
79
+ should_convert_have_items = @configuration.convert_have_items? &&
80
+ oneliner_should.have_matcher &&
81
+ !oneliner_should.have_matcher.project_requires_collection_matcher?
82
+
83
+ if should_convert_have_items
84
+ if @configuration.convert_should?
85
+ oneliner_should.convert_have_items_to_standard_expect!(negative_form, parenthesize)
86
+ else
87
+ oneliner_should.convert_have_items_to_standard_should!
88
+ end
89
+ elsif @configuration.convert_oneliner? && @rspec_version.one_liner_is_expected_available?
90
+ oneliner_should.expectize!(negative_form, parenthesize)
91
+ end
92
+ end
93
+
71
94
  def process_expect(expect)
72
95
  if expect.have_matcher && @configuration.convert_have_items?
73
- expect.have_matcher.convert_to_standard_expectation!
96
+ parenthesize_matcher_arg = @configuration.parenthesize_matcher_arg
97
+ expect.have_matcher.convert_to_standard_expectation!(parenthesize_matcher_arg)
74
98
  end
75
99
  end
76
100
 
@@ -96,7 +120,7 @@ module Transpec
96
120
 
97
121
  def process_method_stub(method_stub)
98
122
  if @configuration.convert_stub?
99
- method_stub.allowize!(@rspec_version.receive_messages_available?)
123
+ method_stub.allowize!(@rspec_version)
100
124
  elsif @configuration.convert_deprecated_method?
101
125
  method_stub.convert_deprecated_method!
102
126
  end
@@ -130,6 +154,11 @@ module Transpec
130
154
  its.convert_to_describe_subject_it! if @configuration.convert_its?
131
155
  end
132
156
 
157
+ def process_example(example)
158
+ return unless @rspec_version.yielded_example_available?
159
+ example.convert! if @configuration.convert_deprecated_method?
160
+ end
161
+
133
162
  def process_rspec_configure(rspec_configure)
134
163
  if need_to_modify_expectation_syntax_configuration?(rspec_configure)
135
164
  rspec_configure.modify_expectation_syntaxes!(:expect)
@@ -19,7 +19,6 @@ module Transpec
19
19
  RESULT_FILE = 'transpec_analysis_result.json'
20
20
  HELPER_SOURCE = <<-END
21
21
  require 'pathname'
22
- require 'json'
23
22
 
24
23
  module TranspecAnalysis
25
24
  @base_path = Dir.pwd
@@ -44,6 +43,7 @@ module Transpec
44
43
  # (Such objects are stored as a string.)
45
44
  # * Singleton method information won't be serialized.
46
45
  # (With Marshal.load, `singleton can't be dumped (TypeError)` will be raised.)
46
+ require 'json'
47
47
  path = File.join(@base_path, '#{RESULT_FILE}')
48
48
  File.open(path, 'w') do |file|
49
49
  JSON.dump(data, file)
@@ -10,6 +10,7 @@ module Transpec
10
10
  class OptionParser
11
11
  CONFIG_ATTRS_FOR_KEEP_TYPES = {
12
12
  should: :convert_should=,
13
+ oneliner: :convert_oneliner=,
13
14
  should_receive: :convert_should_receive=,
14
15
  stub: :convert_stub=,
15
16
  have_items: :convert_have_items=,
@@ -140,6 +141,7 @@ module Transpec
140
141
  'conversions.',
141
142
  'Available syntax types:',
142
143
  " #{'should'.bright} (to #{'expect(obj).to'.underline})",
144
+ " #{'oneliner'.bright} (from #{'should'.underline} to #{'is_expected.to'.underline})",
143
145
  " #{'should_receive'.bright} (to #{'expect(obj).to receive'.underline})",
144
146
  " #{'stub'.bright} (to #{'allow(obj).to receive'.underline})",
145
147
  " #{'have_items'.bright} (to #{'expect(obj.size).to eq(x)'.underline})",
@@ -154,7 +156,7 @@ module Transpec
154
156
  "Default: #{'not_to'.bright}"
155
157
  ],
156
158
  '-b' => [
157
- "Specify matcher type that #{'be_true'.underline} and",
159
+ "Specify a matcher type that #{'be_true'.underline} and",
158
160
  "#{'be_false'.underline} will be converted to.",
159
161
  " #{'truthy,falsey'.bright} (conditional semantics)",
160
162
  " #{'truthy,falsy'.bright} (alias of #{'falsey'.underline})",
@@ -162,14 +164,15 @@ module Transpec
162
164
  "Default: #{'truthy,falsey'.bright}"
163
165
  ],
164
166
  '-p' => [
165
- 'Suppress parenthesizing argument of matcher',
166
- 'when converting operator to non-operator in',
167
- "the #{'expect'.underline} syntax. Note that it will be",
168
- 'parenthesized even if this option is',
169
- 'specified when parentheses are necessary to',
170
- 'keep the meaning of the expression.',
171
- 'By default, arguments of the following',
172
- 'operator matchers will be parenthesized.',
167
+ 'Suppress parenthesizing arguments of matchers',
168
+ "when converting #{'should'.underline} with operator matcher",
169
+ "to #{'expect'.underline} with non-operator matcher. Note",
170
+ 'that it will be parenthesized even if this',
171
+ 'option is specified when parentheses are',
172
+ 'necessary to keep the meaning of the',
173
+ 'expression. By default, arguments of the',
174
+ 'following operator matchers will be',
175
+ 'parenthesized.',
173
176
  " #{'== 10'.underline} to #{'eq(10)'.underline}",
174
177
  " #{'=~ /pattern/'.underline} to #{'match(/pattern/)'.underline}",
175
178
  " #{'=~ [1, 2]'.underline} to #{'match_array([1, 2])'.underline}"
@@ -1,6 +1,7 @@
1
1
  # coding: utf-8
2
2
 
3
3
  require 'transpec/rspec_version'
4
+ require 'English'
4
5
 
5
6
  module Transpec
6
7
  class Project
@@ -20,18 +21,7 @@ module Transpec
20
21
  end
21
22
 
22
23
  def rspec_version
23
- @rspec_version ||= begin
24
- command = 'rspec --version'
25
- command = 'bundle exec ' + command if require_bundler?
26
-
27
- version_string = nil
28
-
29
- Dir.chdir(@path) do
30
- with_bundler_clean_env { version_string = `#{command}`.chomp }
31
- end
32
-
33
- RSpecVersion.new(version_string)
34
- end
24
+ @rspec_version ||= RSpecVersion.new(fetch_rspec_version)
35
25
  end
36
26
 
37
27
  def with_bundler_clean_env
@@ -45,5 +35,26 @@ module Transpec
45
35
  yield
46
36
  end
47
37
  end
38
+
39
+ private
40
+
41
+ def fetch_rspec_version
42
+ command = 'rspec --version'
43
+ command = 'bundle exec ' + command if require_bundler?
44
+
45
+ output = nil
46
+
47
+ Dir.chdir(@path) do
48
+ with_bundler_clean_env do
49
+ IO.popen(command) do |io|
50
+ output = io.read
51
+ end
52
+ end
53
+ end
54
+
55
+ fail 'Failed checking RSpec version.' if output.nil? || $CHILD_STATUS.exitstatus != 0
56
+
57
+ output.chomp
58
+ end
48
59
  end
49
60
  end
@@ -15,8 +15,10 @@ module Transpec
15
15
  # Prerelease parts are sorted alphabetically using the normal Ruby string sorting rules.
16
16
  # If a prerelease part contains both letters and numbers, it will be broken into multiple parts
17
17
  # to provide expected sort behavior (1.0.a10 becomes 1.0.a.10, and is greater than 1.0.a9).
18
- GEM_VERSION_2_99 = Gem::Version.new('2.99.aaaaaaaaaa')
19
- GEM_VERSION_3_0 = Gem::Version.new('3.0.aaaaaaaaaa')
18
+ GEM_VERSION_2_99_0_BETA1 = Gem::Version.new('2.99.0.beta1')
19
+ GEM_VERSION_2_99_0_BETA2 = Gem::Version.new('2.99.0.beta2')
20
+ GEM_VERSION_3_0_0_BETA1 = Gem::Version.new('3.0.0.beta1')
21
+ GEM_VERSION_3_0_0_BETA2 = Gem::Version.new('3.0.0.beta2')
20
22
 
21
23
  attr_reader :gem_version
22
24
 
@@ -29,11 +31,23 @@ module Transpec
29
31
  end
30
32
 
31
33
  def be_truthy_available?
32
- @gem_version >= GEM_VERSION_2_99
34
+ @gem_version >= GEM_VERSION_2_99_0_BETA1
35
+ end
36
+
37
+ def yielded_example_available?
38
+ @gem_version >= GEM_VERSION_2_99_0_BETA1
39
+ end
40
+
41
+ def one_liner_is_expected_available?
42
+ @gem_version >= GEM_VERSION_2_99_0_BETA2
33
43
  end
34
44
 
35
45
  def receive_messages_available?
36
- @gem_version >= GEM_VERSION_3_0
46
+ @gem_version >= GEM_VERSION_3_0_0_BETA1
47
+ end
48
+
49
+ def receive_message_chain_available?
50
+ @gem_version >= GEM_VERSION_3_0_0_BETA2
37
51
  end
38
52
 
39
53
  def <=>(other)
@@ -8,21 +8,6 @@ module Transpec
8
8
 
9
9
  SCOPE_TYPES = [:module, :class, :sclass, :def, :defs, :block].freeze
10
10
 
11
- EXAMPLE_GROUP_METHODS = [
12
- :describe, :context,
13
- :shared_examples, :shared_context, :share_examples_for, :shared_examples_for
14
- ].freeze
15
-
16
- EXAMPLE_METHODS = [
17
- :example, :it, :specify,
18
- :focus, :focused, :fit,
19
- :pending, :xexample, :xit, :xspecify
20
- ].freeze
21
-
22
- HOOK_METHODS = [:before, :after, :around].freeze
23
-
24
- HELPER_METHODS = [:subject, :subject!, :let, :let!]
25
-
26
11
  NON_MONKEY_PATCH_EXPECTATION_AVAILABLE_CONTEXT = [
27
12
  [:example_group, :example],
28
13
  [:example_group, :each_before_after],
@@ -0,0 +1,83 @@
1
+ # coding: utf-8
2
+
3
+ require 'transpec/syntax'
4
+ require 'transpec/syntax/mixin/send'
5
+ require 'transpec/util'
6
+
7
+ module Transpec
8
+ class Syntax
9
+ class Example < Syntax
10
+ include Mixin::Send, Util
11
+
12
+ METHODS_YIELD_EXAMPLE = (EXAMPLE_METHODS + HOOK_METHODS + HELPER_METHODS).freeze
13
+
14
+ def self.target_method?(receiver_node, method_name)
15
+ receiver_node.nil? && [:example, :running_example].include?(method_name)
16
+ end
17
+
18
+ def convert!
19
+ if block_node
20
+ insert_after(block_node.loc.begin, " |#{block_arg_name}|") unless block_has_argument?
21
+ replace(selector_range, block_arg_name.to_s) unless method_name == block_arg_name
22
+ block_node.metadata[:added_example_block_arg] = true
23
+ else
24
+ replace(selector_range, 'RSpec.current_example')
25
+ end
26
+
27
+ register_record
28
+ end
29
+
30
+ private
31
+
32
+ def block_has_argument?
33
+ block_arg_node || block_node.metadata[:added_example_block_arg]
34
+ end
35
+
36
+ def block_node
37
+ return @block_node if instance_variable_defined?(:@block_node)
38
+
39
+ @block_node ||= @node.each_ancestor_node.find do |ancestor_node|
40
+ next false unless ancestor_node.type == :block
41
+ method_name = method_name_of_block_node(ancestor_node)
42
+ METHODS_YIELD_EXAMPLE.include?(method_name)
43
+ end
44
+ end
45
+
46
+ def block_method_name
47
+ method_name_of_block_node(block_node)
48
+ end
49
+
50
+ def block_arg_node
51
+ args_node = block_node.children[1]
52
+ args_node.children.first
53
+ end
54
+
55
+ def block_arg_name
56
+ if block_arg_node
57
+ block_arg_node.children.first
58
+ else
59
+ :example
60
+ end
61
+ end
62
+
63
+ def method_name_of_block_node(block_node)
64
+ send_node = block_node.children.first
65
+ send_node.children[1]
66
+ end
67
+
68
+ def register_record
69
+ if block_node
70
+ prefix = "#{block_method_name}"
71
+ prefix << '(:name)' if HELPER_METHODS.include?(block_method_name)
72
+ original_syntax = "#{prefix} { example }"
73
+ converted_syntax = "#{prefix} { |example| example }"
74
+ else
75
+ original_syntax = 'def helper_method example; end'
76
+ converted_syntax = 'def helper_method RSpec.current_example; end'
77
+ end
78
+
79
+ @report.records << Record.new(original_syntax, converted_syntax)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -21,6 +21,11 @@ module Transpec
21
21
  :expect
22
22
  end
23
23
 
24
+ def positive?
25
+ to_method_name = parent_node.children[1]
26
+ to_method_name == :to
27
+ end
28
+
24
29
  def matcher_node
25
30
  parent_node.children[2]
26
31
  end
@@ -44,9 +44,13 @@ module Transpec
44
44
 
45
45
  def convert_to_standard_expectation!(parenthesize_matcher_arg = true)
46
46
  return if project_requires_collection_matcher?
47
- replace(@expectation.subject_range, replacement_subject_source)
48
- replace(expression_range, replacement_matcher_source(size_source, parenthesize_matcher_arg))
49
- register_record
47
+ replace(@expectation.subject_range, replacement_subject_source) if explicit_subject?
48
+ replace(expression_range, replacement_matcher_source(parenthesize_matcher_arg))
49
+ register_record if explicit_subject?
50
+ end
51
+
52
+ def explicit_subject?
53
+ @expectation.respond_to?(:subject_node)
50
54
  end
51
55
 
52
56
  def have_node
@@ -105,24 +109,12 @@ module Transpec
105
109
  QUERY_METHOD_PRIORITIES.first
106
110
  end
107
111
 
108
- def replacement_matcher_source(size_source, parenthesize_arg = true)
109
- case @expectation.current_syntax_type
110
- when :should
111
- replacement_matcher_source_for_should(size_source)
112
- when :expect
113
- replacement_matcher_source_for_expect(size_source, parenthesize_arg)
114
- end
115
- end
116
-
117
- private
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)
112
+ def replacement_subject_source
113
+ build_replacement_subject_source(@expectation.subject_range.source)
122
114
  end
123
115
 
124
- def replacement_subject_source
125
- source = @expectation.subject_range.source
116
+ def build_replacement_subject_source(original_subject_source)
117
+ source = original_subject_source.dup
126
118
  if subject_is_owner_of_collection?
127
119
  if collection_accessor_is_private?
128
120
  source << ".send(#{collection_accessor.inspect}"
@@ -135,7 +127,33 @@ module Transpec
135
127
  source << ".#{query_method}"
136
128
  end
137
129
 
138
- def replacement_matcher_source_for_should(size_source)
130
+ def replacement_matcher_source(parenthesize_arg)
131
+ size_source = size_node.loc.expression.source
132
+ build_replacement_matcher_source(size_source, parenthesize_arg)
133
+ end
134
+
135
+ def build_replacement_matcher_source(size_source, parenthesize_arg = true)
136
+ case @expectation.current_syntax_type
137
+ when :should
138
+ build_replacement_matcher_source_for_should(size_source)
139
+ when :expect
140
+ build_replacement_matcher_source_for_expect(size_source, parenthesize_arg)
141
+ end
142
+ end
143
+
144
+ def size_source
145
+ size_node.loc.expression.source
146
+ end
147
+
148
+ private
149
+
150
+ def runtime_subject_data
151
+ return @runtime_subject_data if instance_variable_defined?(:@runtime_subject_data)
152
+ node = explicit_subject? ? @expectation.subject_node : @expectation.node
153
+ @runtime_subject_data = runtime_node_data(node)
154
+ end
155
+
156
+ def build_replacement_matcher_source_for_should(size_source)
139
157
  case have_method_name
140
158
  when :have, :have_exactly then "== #{size_source}"
141
159
  when :have_at_least then ">= #{size_source}"
@@ -143,7 +161,7 @@ module Transpec
143
161
  end
144
162
  end
145
163
 
146
- def replacement_matcher_source_for_expect(size_source, parenthesize_arg)
164
+ def build_replacement_matcher_source_for_expect(size_source, parenthesize_arg)
147
165
  case have_method_name
148
166
  when :have, :have_exactly
149
167
  if parenthesize_arg
@@ -158,10 +176,6 @@ module Transpec
158
176
  end
159
177
  end
160
178
 
161
- def size_source
162
- size_node.loc.expression.source
163
- end
164
-
165
179
  def register_record
166
180
  @report.records << HaveRecord.new(self)
167
181
  end
@@ -177,33 +191,47 @@ module Transpec
177
191
  end
178
192
 
179
193
  def target_node
180
- @have.expectation.subject_node
194
+ if @have.explicit_subject?
195
+ @have.expectation.subject_node
196
+ else
197
+ @have.expectation.node
198
+ end
199
+ end
200
+
201
+ def target_type
202
+ if @have.explicit_subject?
203
+ :object
204
+ else
205
+ :context
206
+ end
181
207
  end
182
208
 
183
209
  def register_request
184
210
  key = :collection_accessor
185
211
  code = collection_accessor_inspection_code
186
- @rewriter.register_request(target_node, key, code)
212
+ @rewriter.register_request(target_node, key, code, target_type)
187
213
 
188
214
  # Give up inspecting query methods of collection accessor with arguments
189
215
  # (e.g. have(2).errors_on(variable)) since this is a context of #instance_eval.
190
216
  unless @have.items_method_has_arguments?
191
217
  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)
218
+ code = available_query_methods_inspection_code
219
+ @rewriter.register_request(target_node, key, code, target_type)
196
220
  end
197
221
 
198
222
  key = :collection_accessor_is_private?
199
- code = "private_methods.include?(#{@have.items_name.inspect})"
200
- @rewriter.register_request(target_node, key, code)
223
+ code = "#{subject_code}.private_methods.include?(#{@have.items_name.inspect})"
224
+ @rewriter.register_request(target_node, key, code, target_type)
201
225
 
202
226
  key = :project_requires_collection_matcher?
203
227
  code = 'defined?(RSpec::Rails) || defined?(RSpec::CollectionMatchers)'
204
228
  @rewriter.register_request(target_node, key, code, :context)
205
229
  end
206
230
 
231
+ def subject_code
232
+ @have.explicit_subject? ? 'self' : 'subject'
233
+ end
234
+
207
235
  # rubocop:disable MethodLength
208
236
  def collection_accessor_inspection_code
209
237
  # `expect(owner).to have(n).things` invokes private owner#things with Object#__send__
@@ -212,7 +240,7 @@ module Transpec
212
240
  # rubocop:disable LineLength
213
241
  # https://github.com/rspec/rspec-expectations/blob/v2.14.3/lib/rspec/matchers/built_in/have.rb#L48-L58
214
242
  # rubocop:enable LineLength
215
- <<-END.gsub(/^\s+\|/, '').chomp
243
+ @collection_accessor_inspection_code ||= <<-END.gsub(/^\s+\|/, '').chomp
216
244
  |begin
217
245
  | exact_name = #{@have.items_name.inspect}
218
246
  |
@@ -227,12 +255,13 @@ module Transpec
227
255
  |
228
256
  | if inflector
229
257
  | pluralized_name = inflector.pluralize(exact_name).to_sym
230
- | respond_to_pluralized_name = respond_to?(pluralized_name)
258
+ | respond_to_pluralized_name = #{subject_code}.respond_to?(pluralized_name)
231
259
  | end
232
260
  |
233
- | respond_to_query_methods = !(methods & #{QUERY_METHOD_PRIORITIES.inspect}).empty?
261
+ | respond_to_query_methods =
262
+ | !(#{subject_code}.methods & #{QUERY_METHOD_PRIORITIES.inspect}).empty?
234
263
  |
235
- | if respond_to?(exact_name)
264
+ | if #{subject_code}.respond_to?(exact_name)
236
265
  | exact_name
237
266
  | elsif respond_to_pluralized_name
238
267
  | pluralized_name
@@ -245,6 +274,18 @@ module Transpec
245
274
  END
246
275
  end
247
276
  # rubocop:enable MethodLength
277
+
278
+ def available_query_methods_inspection_code
279
+ <<-END.gsub(/^\s+\|/, '').chomp
280
+ |collection_accessor = #{collection_accessor_inspection_code}
281
+ |target = if collection_accessor
282
+ | #{subject_code}.__send__(collection_accessor)
283
+ | else
284
+ | #{subject_code}
285
+ | end
286
+ |target.methods & #{QUERY_METHOD_PRIORITIES.inspect}
287
+ END
288
+ end
248
289
  end
249
290
 
250
291
  class HaveRecord < Record
@@ -254,28 +295,35 @@ module Transpec
254
295
 
255
296
  def original_syntax
256
297
  @original_syntax ||= begin
257
- syntax = case @have.expectation
258
- when Should
259
- "#{original_subject}.should"
260
- when Expect
261
- "expect(#{original_subject}).to"
262
- end
263
-
298
+ type = @have.expectation.class.snake_case_name.to_sym
299
+ syntax = build_expectation(original_subject, type)
264
300
  syntax << " #{@have.have_method_name}(n).#{original_items}"
265
301
  end
266
302
  end
267
303
 
268
304
  def converted_syntax
269
305
  @converted_syntax ||= begin
270
- syntax = case @have.expectation.current_syntax_type
271
- when :should
272
- "#{converted_subject}.should"
273
- when :expect
274
- "expect(#{converted_subject}).to"
275
- end
276
-
277
- syntax << " #{@have.replacement_matcher_source('n')}"
306
+ type = @have.expectation.current_syntax_type
307
+ syntax = build_expectation(converted_subject, type)
308
+ syntax << " #{@have.build_replacement_matcher_source('n')}"
309
+ end
310
+ end
311
+
312
+ def build_expectation(subject, type)
313
+ case type
314
+ when :should
315
+ syntax = "#{subject}.should"
316
+ syntax << '_not' unless positive?
317
+ when :expect
318
+ syntax = "expect(#{subject})."
319
+ syntax << (positive? ? 'to' : 'not_to')
278
320
  end
321
+
322
+ syntax
323
+ end
324
+
325
+ def positive?
326
+ @have.expectation.positive?
279
327
  end
280
328
 
281
329
  def original_subject
@@ -300,7 +348,16 @@ module Transpec
300
348
 
301
349
  def converted_subject
302
350
  if @have.subject_is_owner_of_collection?
303
- subject = 'obj.'
351
+ build_converted_subject('obj')
352
+ else
353
+ build_converted_subject('collection')
354
+ end
355
+ end
356
+
357
+ def build_converted_subject(subject)
358
+ subject << '.'
359
+
360
+ if @have.subject_is_owner_of_collection?
304
361
  if @have.collection_accessor_is_private?
305
362
  subject << "send(#{@have.collection_accessor.inspect}"
306
363
  subject << ', ...' if @have.items_method_has_arguments?
@@ -311,7 +368,7 @@ module Transpec
311
368
  end
312
369
  subject << ".#{@have.query_method}"
313
370
  else
314
- "collection.#{@have.default_query_method}"
371
+ subject << "#{@have.default_query_method}"
315
372
  end
316
373
  end
317
374
  end