transpec 1.3.1 → 1.4.0

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