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
@@ -14,7 +14,7 @@ module Transpec
14
14
  include Mixin::Send, Mixin::MonkeyPatch, Mixin::AllowNoMessage, Mixin::AnyInstance, Util
15
15
 
16
16
  def self.target_method?(receiver_node, method_name)
17
- !receiver_node.nil? && [:stub, :unstub, :stub!, :unstub!].include?(method_name)
17
+ !receiver_node.nil? && [:stub, :stub!, :stub_chain, :unstub, :unstub!].include?(method_name)
18
18
  end
19
19
 
20
20
  def register_request_for_dynamic_analysis(rewriter)
@@ -30,23 +30,17 @@ module Transpec
30
30
  check_syntax_availability(__method__)
31
31
  end
32
32
 
33
- def allowize!(receive_messages_available = false)
33
+ def allowize!(rspec_version)
34
34
  # There's no way of unstubbing in #allow syntax.
35
- return unless [:stub, :stub!].include?(method_name)
35
+ return unless [:stub, :stub!, :stub_chain].include?(method_name)
36
+ return if method_name == :stub_chain && !rspec_version.receive_message_chain_available?
36
37
 
37
38
  unless allow_to_receive_available?
38
39
  fail InvalidContextError.new(selector_range, "##{method_name}", '#allow')
39
40
  end
40
41
 
41
- source, type = if arg_node.type == :hash
42
- if receive_messages_available
43
- [build_allow_to_receive_messages_expression, :allow_to_receive_messages]
44
- else
45
- [build_allow_expressions_from_hash_node(arg_node), :allow_to_receive]
46
- end
47
- else
48
- [build_allow_expression(arg_node), :allow_to_receive]
49
- end
42
+ source, type = replacement_source_and_conversion_type(rspec_version)
43
+ return unless source
50
44
 
51
45
  replace(expression_range, source)
52
46
 
@@ -63,12 +57,28 @@ module Transpec
63
57
 
64
58
  private
65
59
 
66
- def build_allow_expressions_from_hash_node(hash_node)
60
+ def replacement_source_and_conversion_type(rspec_version)
61
+ if method_name == :stub_chain
62
+ [build_allow_to(:receive_message_chain), :allow_to_receive_message_chain]
63
+ else
64
+ if arg_node.type == :hash
65
+ if rspec_version.receive_messages_available?
66
+ [build_allow_to(:receive_messages), :allow_to_receive_messages]
67
+ else
68
+ [build_multiple_allow_to_receive_with_hash(arg_node), :allow_to_receive]
69
+ end
70
+ else
71
+ [build_allow_to_receive(arg_node), :allow_to_receive]
72
+ end
73
+ end
74
+ end
75
+
76
+ def build_multiple_allow_to_receive_with_hash(hash_node)
67
77
  expressions = []
68
78
 
69
79
  hash_node.children.each_with_index do |pair_node, index|
70
80
  key_node, value_node = *pair_node
71
- expression = build_allow_expression(key_node, value_node, false)
81
+ expression = build_allow_to_receive(key_node, value_node, false)
72
82
  expression.prepend(indentation_of_line(@node)) if index > 0
73
83
  expressions << expression
74
84
  end
@@ -76,26 +86,21 @@ module Transpec
76
86
  expressions.join($RS)
77
87
  end
78
88
 
79
- def build_allow_expression(message_node, return_value_node = nil, keep_form_around_arg = true)
89
+ def build_allow_to_receive(message_node, value_node = nil, keep_form_around_arg = true)
80
90
  expression = allow_source
81
91
  expression << range_in_between_receiver_and_selector.source
82
92
  expression << 'to receive'
83
93
  expression << (keep_form_around_arg ? range_in_between_selector_and_arg.source : '(')
84
94
  expression << message_source(message_node)
85
95
  expression << (keep_form_around_arg ? range_after_arg.source : ')')
86
-
87
- if return_value_node
88
- return_value_source = return_value_node.loc.expression.source
89
- expression << ".and_return(#{return_value_source})"
90
- end
91
-
96
+ expression << ".and_return(#{value_node.loc.expression.source})" if value_node
92
97
  expression
93
98
  end
94
99
 
95
- def build_allow_to_receive_messages_expression
100
+ def build_allow_to(method)
96
101
  expression = allow_source
97
102
  expression << range_in_between_receiver_and_selector.source
98
- expression << 'to receive_messages'
103
+ expression << "to #{method}"
99
104
  expression << parentheses_range.source
100
105
  expression
101
106
  end
@@ -129,28 +134,44 @@ module Transpec
129
134
  def original_syntax
130
135
  syntax = any_instance? ? 'Klass.any_instance' : 'obj'
131
136
  syntax << ".#{method_name}"
132
- syntax << (arg_node.type == :hash ? '(:message => value)' : '(:message)')
137
+
138
+ if method_name == :stub_chain
139
+ syntax << '(:message1, :message2)'
140
+ else
141
+ syntax << (arg_node.type == :hash ? '(:message => value)' : '(:message)')
142
+ end
133
143
  end
134
144
 
135
145
  def converted_syntax(conversion_type)
146
+ if conversion_type == :deprecated
147
+ converted_syntax_from_deprecated
148
+ else
149
+ allowized_syntax(conversion_type)
150
+ end
151
+ end
152
+
153
+ def allowized_syntax(conversion_type)
154
+ syntax = any_instance? ? 'allow_any_instance_of(Klass)' : 'allow(obj)'
155
+ syntax << '.to '
156
+
136
157
  case conversion_type
137
- when :allow_to_receive, :allow_to_receive_messages
138
- syntax = any_instance? ? 'allow_any_instance_of(Klass)' : 'allow(obj)'
139
- syntax << '.to '
140
- if conversion_type == :allow_to_receive
141
- syntax << 'receive(:message)'
142
- syntax << '.and_return(value)' if arg_node.type == :hash
143
- else
144
- syntax << 'receive_messages(:message => value)'
145
- end
146
- when :deprecated
147
- syntax = 'obj.'
148
- syntax << replacement_method_for_deprecated_method
149
- syntax << '(:message)'
158
+ when :allow_to_receive
159
+ syntax << 'receive(:message)'
160
+ syntax << '.and_return(value)' if arg_node.type == :hash
161
+ when :allow_to_receive_messages
162
+ syntax << 'receive_messages(:message => value)'
163
+ when :allow_to_receive_message_chain
164
+ syntax << 'receive_message_chain(:message1, :message2)'
150
165
  end
151
166
 
152
167
  syntax
153
168
  end
169
+
170
+ def converted_syntax_from_deprecated
171
+ syntax = 'obj.'
172
+ syntax << replacement_method_for_deprecated_method
173
+ syntax << '(:message)'
174
+ end
154
175
  end
155
176
  end
156
177
  end
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'ast'
4
+
3
5
  module Transpec
4
6
  class Syntax
5
7
  module Mixin
@@ -1,5 +1,7 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'ast'
4
+
3
5
  module Transpec
4
6
  class Syntax
5
7
  module Mixin
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+
3
+ require 'transpec/syntax/operator_matcher'
4
+
5
+ module Transpec
6
+ class Syntax
7
+ module Mixin
8
+ module ShouldBase
9
+ def positive?
10
+ method_name == :should
11
+ end
12
+
13
+ def matcher_node
14
+ arg_node || parent_node
15
+ end
16
+
17
+ def operator_matcher
18
+ return @operator_matcher if instance_variable_defined?(:@operator_matcher)
19
+
20
+ @operator_matcher ||= begin
21
+ if OperatorMatcher.target_node?(matcher_node, @runtime_data)
22
+ OperatorMatcher.new(matcher_node, @source_rewriter, @runtime_data, @report)
23
+ else
24
+ nil
25
+ end
26
+ end
27
+ end
28
+
29
+ def should_range
30
+ if arg_node
31
+ selector_range
32
+ else
33
+ selector_range.join(expression_range.end)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,218 @@
1
+ # coding: utf-8
2
+
3
+ require 'transpec/syntax'
4
+ require 'transpec/syntax/mixin/should_base'
5
+ require 'transpec/syntax/mixin/send'
6
+ require 'transpec/syntax/mixin/have_matcher'
7
+ require 'transpec/util'
8
+ require 'active_support/inflector/methods'
9
+
10
+ module Transpec
11
+ class Syntax
12
+ class OnelinerShould < Syntax
13
+ include Mixin::ShouldBase, Mixin::Send, Mixin::HaveMatcher, Util
14
+
15
+ attr_reader :current_syntax_type
16
+
17
+ def self.target_method?(receiver_node, method_name)
18
+ receiver_node.nil? && [:should, :should_not].include?(method_name)
19
+ end
20
+
21
+ def initialize(node, source_rewriter = nil, runtime_data = nil, report = nil)
22
+ super
23
+ @current_syntax_type = :should
24
+ end
25
+
26
+ def register_request_for_dynamic_analysis(rewriter)
27
+ operator_matcher.register_request_for_dynamic_analysis(rewriter) if operator_matcher
28
+ have_matcher.register_request_for_dynamic_analysis(rewriter) if have_matcher
29
+ end
30
+
31
+ def expectize!(negative_form = 'not_to', parenthesize_matcher_arg = true)
32
+ replacement = 'is_expected.'
33
+ replacement << (positive? ? 'to' : negative_form)
34
+ replace(should_range, replacement)
35
+
36
+ @current_syntax_type = :expect
37
+ operator_matcher.convert_operator!(parenthesize_matcher_arg) if operator_matcher
38
+
39
+ register_record(negative_form)
40
+ end
41
+
42
+ def convert_have_items_to_standard_should!
43
+ return if have_matcher.project_requires_collection_matcher?
44
+
45
+ insert_example_description!
46
+
47
+ subject_source = have_matcher.build_replacement_subject_source('subject')
48
+ insert_before(expression_range, "#{subject_source}.")
49
+
50
+ have_matcher.convert_to_standard_expectation!
51
+
52
+ @report.records << OnelinerShouldHaveRecord.new(self, have_matcher)
53
+ end
54
+
55
+ # rubocop:disable LineLength
56
+ def convert_have_items_to_standard_expect!(negative_form = 'not_to', parenthesize_matcher_arg = true)
57
+ # rubocop:enable LineLength
58
+ return if have_matcher.project_requires_collection_matcher?
59
+
60
+ insert_example_description!
61
+
62
+ subject_source = have_matcher.build_replacement_subject_source('subject')
63
+ expect_to_source = "expect(#{subject_source})."
64
+ expect_to_source << (positive? ? 'to' : negative_form)
65
+ replace(should_range, expect_to_source)
66
+
67
+ @current_syntax_type = :expect
68
+ have_matcher.convert_to_standard_expectation!
69
+
70
+ @report.records << OnelinerShouldHaveRecord.new(self, have_matcher, negative_form)
71
+ end
72
+
73
+ def example_has_description?
74
+ send_node = example_block_node.children.first
75
+ !send_node.children[2].nil?
76
+ end
77
+
78
+ def build_description(size)
79
+ description = positive? ? 'has ' : 'does not have '
80
+
81
+ case have_matcher.have_method_name
82
+ when :have_at_least then description << 'at least '
83
+ when :have_at_most then description << 'at most '
84
+ end
85
+
86
+ items = have_matcher.items_name
87
+
88
+ if positive? && size == '0'
89
+ size = 'no'
90
+ elsif size == '1'
91
+ items = ActiveSupport::Inflector.singularize(have_matcher.items_name)
92
+ end
93
+
94
+ description << "#{size} #{items}"
95
+ end
96
+
97
+ private
98
+
99
+ def insert_example_description!
100
+ fail 'This one-liner #should does not have #have matcher!' unless have_matcher
101
+
102
+ unless example_has_description?
103
+ insert_before(example_block_node.loc.begin, "'#{generated_description}' ")
104
+ end
105
+
106
+ indentation = indentation_of_line(example_block_node)
107
+
108
+ unless linefeed_at_beginning_of_block?
109
+ replace(left_curly_and_whitespaces_range, "do\n#{indentation} ")
110
+ end
111
+
112
+ unless linefeed_at_end_of_block?
113
+ replace(whitespaces_and_right_curly_range, "\n#{indentation}end")
114
+ end
115
+ end
116
+
117
+ def example_block_node
118
+ return @example_block_node if instance_variable_defined?(:@example_block_node)
119
+
120
+ @example_block_node = @node.each_ancestor_node.find do |node|
121
+ next false unless node.type == :block
122
+ send_node = node.children.first
123
+ receiver_node, method_name, = *send_node
124
+ next false if receiver_node
125
+ EXAMPLE_METHODS.include?(method_name)
126
+ end
127
+ end
128
+
129
+ def generated_description
130
+ build_description(have_matcher.size_source)
131
+ end
132
+
133
+ def linefeed_at_beginning_of_block?
134
+ beginning_to_body_range = example_block_node.loc.begin.join(expression_range.begin)
135
+ beginning_to_body_range.source.include?("\n")
136
+ end
137
+
138
+ def linefeed_at_end_of_block?
139
+ body_to_end_range = expression_range.end.join(example_block_node.loc.end)
140
+ body_to_end_range.source.include?("\n")
141
+ end
142
+
143
+ def left_curly_and_whitespaces_range
144
+ expand_range_to_adjacent_whitespaces(example_block_node.loc.begin, :end)
145
+ end
146
+
147
+ def whitespaces_and_right_curly_range
148
+ expand_range_to_adjacent_whitespaces(example_block_node.loc.end, :begin)
149
+ end
150
+
151
+ def register_record(negative_form_of_to)
152
+ original_syntax = 'it { should'
153
+ converted_syntax = 'it { is_expected.'
154
+
155
+ if positive?
156
+ converted_syntax << 'to'
157
+ else
158
+ original_syntax << '_not'
159
+ converted_syntax << negative_form_of_to
160
+ end
161
+
162
+ [original_syntax, converted_syntax].each do |syntax|
163
+ syntax << ' ... }'
164
+ end
165
+
166
+ @report.records << Record.new(original_syntax, converted_syntax)
167
+ end
168
+
169
+ class OnelinerShouldHaveRecord < Have::HaveRecord
170
+ def initialize(should, have, negative_form_of_to = nil)
171
+ @should = should
172
+ @have = have
173
+ @negative_form_of_to = negative_form_of_to
174
+ end
175
+
176
+ def original_syntax
177
+ @original_syntax ||= begin
178
+ syntax = @should.example_has_description? ? "it '...' do" : 'it {'
179
+ syntax << " #{@should.method_name} #{@have.have_method_name}(n).#{original_items} "
180
+ syntax << (@should.example_has_description? ? 'end' : '}')
181
+ end
182
+ end
183
+
184
+ def converted_syntax
185
+ @converted_syntax ||= begin
186
+ syntax = converted_description
187
+ syntax << ' '
188
+ syntax << converted_expectation
189
+ syntax << ' '
190
+ syntax << @have.build_replacement_matcher_source('n')
191
+ syntax << ' end'
192
+ end
193
+ end
194
+
195
+ def converted_description
196
+ if @should.example_has_description?
197
+ "it '...' do"
198
+ else
199
+ "it '#{@should.build_description('n')}' do"
200
+ end
201
+ end
202
+
203
+ def converted_expectation
204
+ case @should.current_syntax_type
205
+ when :should
206
+ "#{converted_subject}.#{@should.method_name}"
207
+ when :expect
208
+ "expect(#{converted_subject})." + (@should.positive? ? 'to' : @negative_form_of_to)
209
+ end
210
+ end
211
+
212
+ def converted_subject
213
+ build_converted_subject('subject')
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -3,6 +3,7 @@
3
3
  require 'transpec/syntax'
4
4
  require 'transpec/syntax/mixin/send'
5
5
  require 'transpec/util'
6
+ require 'ast'
6
7
 
7
8
  module Transpec
8
9
  class Syntax
@@ -1,17 +1,18 @@
1
1
  # coding: utf-8
2
2
 
3
3
  require 'transpec/syntax'
4
+ require 'transpec/syntax/mixin/should_base'
4
5
  require 'transpec/syntax/mixin/send'
5
6
  require 'transpec/syntax/mixin/monkey_patch'
6
7
  require 'transpec/syntax/mixin/expectizable'
7
8
  require 'transpec/syntax/mixin/have_matcher'
8
9
  require 'transpec/util'
9
- require 'transpec/syntax/operator_matcher'
10
10
 
11
11
  module Transpec
12
12
  class Syntax
13
13
  class Should < Syntax
14
- include Mixin::Send, Mixin::MonkeyPatch, Mixin::Expectizable, Mixin::HaveMatcher, Util
14
+ include Mixin::ShouldBase, Mixin::Send, Mixin::MonkeyPatch, Mixin::Expectizable,
15
+ Mixin::HaveMatcher, Util
15
16
 
16
17
  attr_reader :current_syntax_type
17
18
 
@@ -39,10 +40,6 @@ module Transpec
39
40
  check_syntax_availability(__method__)
40
41
  end
41
42
 
42
- def positive?
43
- method_name == :should
44
- end
45
-
46
43
  def expectize!(negative_form = 'not_to', parenthesize_matcher_arg = true)
47
44
  unless expect_available?
48
45
  fail InvalidContextError.new(selector_range, "##{method_name}", '#expect')
@@ -62,22 +59,6 @@ module Transpec
62
59
  operator_matcher.convert_operator!(parenthesize_matcher_arg) if operator_matcher
63
60
  end
64
61
 
65
- def operator_matcher
66
- return @operator_matcher if instance_variable_defined?(:@operator_matcher)
67
-
68
- @operator_matcher ||= begin
69
- if OperatorMatcher.target_node?(matcher_node, @runtime_data)
70
- OperatorMatcher.new(matcher_node, @source_rewriter, @runtime_data, @report)
71
- else
72
- nil
73
- end
74
- end
75
- end
76
-
77
- def matcher_node
78
- arg_node || parent_node
79
- end
80
-
81
62
  private
82
63
 
83
64
  def replace_proc_selector_with_expect!
@@ -86,14 +67,6 @@ module Transpec
86
67
  replace(range_of_subject_method_taking_block, 'expect')
87
68
  end
88
69
 
89
- def should_range
90
- if arg_node
91
- selector_range
92
- else
93
- selector_range.join(expression_range.end)
94
- end
95
- end
96
-
97
70
  def register_record(negative_form_of_to)
98
71
  if proc_literal?(subject_node)
99
72
  original_syntax = 'lambda { }.should'