transpec 1.9.3 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +5 -0
  5. data/CONTRIBUTING.md +19 -0
  6. data/README.md +78 -9
  7. data/README.md.erb +68 -10
  8. data/lib/transpec/annotatable.rb +16 -0
  9. data/lib/transpec/cli.rb +35 -27
  10. data/lib/transpec/configuration.rb +1 -0
  11. data/lib/transpec/conversion_error.rb +23 -0
  12. data/lib/transpec/converter.rb +59 -50
  13. data/lib/transpec/dynamic_analyzer.rb +13 -29
  14. data/lib/transpec/dynamic_analyzer/rewriter.rb +3 -10
  15. data/lib/transpec/dynamic_analyzer/runtime_data.rb +16 -8
  16. data/lib/transpec/file_finder.rb +1 -1
  17. data/lib/transpec/git.rb +1 -1
  18. data/lib/transpec/option_parser.rb +12 -10
  19. data/lib/transpec/project.rb +3 -3
  20. data/lib/transpec/record.rb +19 -2
  21. data/lib/transpec/report.rb +29 -13
  22. data/lib/transpec/rspec_version.rb +7 -7
  23. data/lib/transpec/static_context_inspector.rb +1 -5
  24. data/lib/transpec/syntax.rb +11 -16
  25. data/lib/transpec/syntax/be_boolean.rb +1 -1
  26. data/lib/transpec/syntax/be_close.rb +1 -1
  27. data/lib/transpec/syntax/current_example.rb +88 -0
  28. data/lib/transpec/syntax/double.rb +1 -1
  29. data/lib/transpec/syntax/example.rb +60 -54
  30. data/lib/transpec/syntax/have.rb +27 -15
  31. data/lib/transpec/syntax/have/have_record.rb +12 -0
  32. data/lib/transpec/syntax/have/source_builder.rb +18 -16
  33. data/lib/transpec/syntax/its.rb +12 -11
  34. data/lib/transpec/syntax/matcher_definition.rb +1 -1
  35. data/lib/transpec/syntax/method_stub.rb +3 -7
  36. data/lib/transpec/syntax/mixin/matcher_owner.rb +2 -2
  37. data/lib/transpec/syntax/mixin/monkey_patch.rb +3 -5
  38. data/lib/transpec/syntax/mixin/monkey_patch_any_instance.rb +2 -4
  39. data/lib/transpec/syntax/mixin/owned_matcher.rb +1 -4
  40. data/lib/transpec/syntax/mixin/send.rb +7 -9
  41. data/lib/transpec/syntax/oneliner_should.rb +4 -4
  42. data/lib/transpec/syntax/operator.rb +27 -11
  43. data/lib/transpec/syntax/pending.rb +110 -0
  44. data/lib/transpec/syntax/raise_error.rb +1 -1
  45. data/lib/transpec/syntax/receive.rb +4 -4
  46. data/lib/transpec/syntax/rspec_configure/framework.rb +3 -3
  47. data/lib/transpec/syntax/should.rb +2 -2
  48. data/lib/transpec/syntax/should_receive.rb +3 -3
  49. data/lib/transpec/util.rb +38 -6
  50. data/lib/transpec/version.rb +2 -2
  51. data/spec/support/file_helper.rb +1 -1
  52. data/spec/support/shared_context.rb +3 -8
  53. data/spec/transpec/cli_spec.rb +63 -1
  54. data/spec/transpec/configuration_spec.rb +1 -0
  55. data/spec/transpec/converter_spec.rb +106 -15
  56. data/spec/transpec/dynamic_analyzer/rewriter_spec.rb +12 -52
  57. data/spec/transpec/dynamic_analyzer_spec.rb +2 -2
  58. data/spec/transpec/option_parser_spec.rb +3 -2
  59. data/spec/transpec/report_spec.rb +33 -4
  60. data/spec/transpec/rspec_version_spec.rb +5 -2
  61. data/spec/transpec/syntax/current_example_spec.rb +267 -0
  62. data/spec/transpec/syntax/example_spec.rb +156 -122
  63. data/spec/transpec/syntax/have_spec.rb +43 -32
  64. data/spec/transpec/syntax/method_stub_spec.rb +8 -0
  65. data/spec/transpec/syntax/operator_spec.rb +67 -2
  66. data/spec/transpec/syntax/pending_spec.rb +375 -0
  67. metadata +12 -4
  68. data/lib/transpec/context_error.rb +0 -23
@@ -21,7 +21,7 @@ module Transpec
21
21
  private
22
22
 
23
23
  def register_record
24
- @report.records << Record.new("#{method_name}('something')", "double('something')")
24
+ report.records << Record.new("#{method_name}('something')", "double('something')")
25
25
  end
26
26
  end
27
27
  end
@@ -3,89 +3,95 @@
3
3
  require 'transpec/syntax'
4
4
  require 'transpec/syntax/mixin/send'
5
5
  require 'transpec/rspec_dsl'
6
- require 'transpec/util'
7
6
 
8
7
  module Transpec
9
8
  class Syntax
10
9
  class Example < Syntax
11
- include Mixin::Send, RSpecDSL, Util
10
+ include Mixin::Send, RSpecDSL
12
11
 
13
- METHODS_YIELD_EXAMPLE = (EXAMPLE_METHODS + HOOK_METHODS + HELPER_METHODS).freeze
12
+ def self.conversion_target_node?(node, runtime_data = nil)
13
+ return false unless target_node?(node, runtime_data)
14
14
 
15
- def self.target_node?(node, runtime_data = nil)
16
- receiver_node, method_name, *_ = *node
17
- return false if receiver_node
18
- return false unless [:example, :running_example].include?(method_name)
19
- return false if Util.block_node_taken_by_method(node)
20
- check_target_node_dynamically(node, runtime_data)
15
+ # Check whether the context is example group to differenciate
16
+ # RSpec::Core::ExampleGroup.pending (a relative of #it) and
17
+ # RSpec::Core::ExampleGroup#pending (marks the example as pending in #it block).
18
+ if runtime_data && runtime_data.run?(node)
19
+ # If we have runtime data, check with it.
20
+ runtime_data[node, :example_group_context?]
21
+ else
22
+ # Otherwise check statically.
23
+ inspector = StaticContextInspector.new(node)
24
+ inspector.scopes.last == :example_group
25
+ end
21
26
  end
22
27
 
23
28
  def self.target_method?(receiver_node, method_name)
24
- receiver_node.nil? && [:example, :running_example].include?(method_name)
29
+ receiver_node.nil? && EXAMPLE_METHODS.include?(method_name)
25
30
  end
26
31
 
27
- def convert!
28
- if block_node
29
- insert_after(block_node.loc.begin, " |#{block_arg_name}|") unless block_has_argument?
30
- replace(selector_range, block_arg_name.to_s) unless method_name == block_arg_name
31
- block_node.metadata[:added_example_block_arg] = true
32
- else
33
- replace(selector_range, 'RSpec.current_example')
34
- end
35
-
36
- register_record
32
+ define_dynamic_analysis_request do |rewriter|
33
+ code = "is_a?(Class) && ancestors.any? { |a| a.name == 'RSpec::Core::ExampleGroup' }"
34
+ rewriter.register_request(node, :example_group_context?, code, :context)
37
35
  end
38
36
 
39
- private
40
-
41
- def block_has_argument?
42
- block_arg_node || block_node.metadata[:added_example_block_arg]
37
+ def convert_pending_to_skip!
38
+ convert_pending_selector_to_skip!
39
+ convert_pending_metadata_to_skip!
43
40
  end
44
41
 
45
- def block_node
46
- return @block_node if instance_variable_defined?(:@block_node)
47
-
48
- @block_node ||= @node.each_ancestor_node.find do |ancestor_node|
49
- next false unless ancestor_node.block_type?
50
- method_name = method_name_of_block_node(ancestor_node)
51
- METHODS_YIELD_EXAMPLE.include?(method_name)
42
+ def metadata_key_nodes
43
+ metadata_nodes.each_with_object([]) do |node, key_nodes|
44
+ if node.hash_type?
45
+ key_nodes.concat(node.children.map { |pair_node| pair_node.children.first })
46
+ else
47
+ key_nodes << node
48
+ end
52
49
  end
53
50
  end
54
51
 
55
- def block_method_name
56
- method_name_of_block_node(block_node)
57
- end
52
+ private
58
53
 
59
- def block_arg_node
60
- args_node = block_node.children[1]
61
- args_node.children.first
54
+ def convert_pending_selector_to_skip!
55
+ return unless method_name == :pending
56
+ replace(selector_range, 'skip')
57
+ register_record("pending 'is an example' { }", "skip 'is an example' { }")
62
58
  end
63
59
 
64
- def block_arg_name
65
- if block_arg_node
66
- block_arg_node.children.first
67
- else
68
- :example
60
+ def convert_pending_metadata_to_skip!
61
+ metadata_key_nodes.each do |node|
62
+ next unless pending_symbol?(node)
63
+ replace(symbol_range_without_colon(node), 'skip')
64
+ if node.parent_node.pair_type?
65
+ register_record("it 'is an example', :pending => value { }",
66
+ "it 'is an example', :skip => value { }")
67
+ else
68
+ register_record("it 'is an example', :pending { }",
69
+ "it 'is an example', :skip { }")
70
+ end
69
71
  end
70
72
  end
71
73
 
72
- def method_name_of_block_node(block_node)
73
- send_node = block_node.children.first
74
- send_node.children[1]
74
+ def pending_symbol?(node)
75
+ return false unless node.sym_type?
76
+ key = node.children.first
77
+ key == :pending
75
78
  end
76
79
 
77
- def register_record
78
- if block_node
79
- prefix = "#{block_method_name}"
80
- prefix << '(:name)' if HELPER_METHODS.include?(block_method_name)
81
- original_syntax = "#{prefix} { example }"
82
- converted_syntax = "#{prefix} { |example| example }"
80
+ def symbol_range_without_colon(node)
81
+ range = node.loc.expression
82
+ if range.source.start_with?(':')
83
+ Parser::Source::Range.new(range.source_buffer, range.begin_pos + 1, range.end_pos)
83
84
  else
84
- original_syntax = 'def helper_method example; end'
85
- converted_syntax = 'def helper_method RSpec.current_example; end'
85
+ range
86
86
  end
87
+ end
88
+
89
+ def metadata_nodes
90
+ arg_nodes[1..-1] || []
91
+ end
87
92
 
88
- @report.records << Record.new(original_syntax, converted_syntax)
93
+ def register_record(original_syntax, converted_syntax)
94
+ report.records << Record.new(original_syntax, converted_syntax)
89
95
  end
90
96
  end
91
97
  end
@@ -60,12 +60,12 @@ module Transpec
60
60
  end
61
61
 
62
62
  def project_requires_collection_matcher?
63
- runtime_subject_data && runtime_subject_data[:project_requires_collection_matcher?].result
63
+ runtime_subject_data(:project_requires_collection_matcher?)
64
64
  end
65
65
 
66
66
  def collection_accessor
67
- if runtime_subject_data && runtime_subject_data[:collection_accessor].result
68
- runtime_subject_data[:collection_accessor].result.to_sym
67
+ if runtime_subject_data(:collection_accessor)
68
+ runtime_subject_data(:collection_accessor).to_sym
69
69
  else
70
70
  items_name
71
71
  end
@@ -73,16 +73,15 @@ module Transpec
73
73
 
74
74
  def subject_is_owner_of_collection?
75
75
  return true if items_method_has_arguments?
76
- runtime_subject_data && !runtime_subject_data[:collection_accessor].result.nil?
76
+ !runtime_subject_data(:collection_accessor).nil?
77
77
  end
78
78
 
79
79
  def collection_accessor_is_private?
80
- runtime_subject_data && runtime_subject_data[:collection_accessor_is_private?].result
80
+ runtime_subject_data(:collection_accessor_is_private?)
81
81
  end
82
82
 
83
83
  def query_method
84
- if runtime_subject_data && runtime_subject_data[:available_query_methods]
85
- available_query_methods = runtime_subject_data[:available_query_methods].result
84
+ if (available_query_methods = runtime_subject_data(:available_query_methods))
86
85
  (QUERY_METHOD_PRIORITIES & available_query_methods.map(&:to_sym)).first
87
86
  else
88
87
  default_query_method
@@ -102,24 +101,37 @@ module Transpec
102
101
  size_node.loc.expression.source
103
102
  end
104
103
 
104
+ def accurate_conversion?
105
+ !runtime_subject_data.nil?
106
+ end
107
+
108
+ def matcher_range
109
+ expression_range.join(items_node.loc.expression)
110
+ end
111
+
105
112
  private
106
113
 
107
114
  def source_builder
108
115
  @source_builder ||= SourceBuilder.new(self, size_source)
109
116
  end
110
117
 
111
- def runtime_subject_data
112
- return @runtime_subject_data if instance_variable_defined?(:@runtime_subject_data)
113
- node = explicit_subject? ? expectation.subject_node : expectation.node
114
- @runtime_subject_data = runtime_node_data(node)
115
- end
118
+ def runtime_subject_data(key = nil)
119
+ unless instance_variable_defined?(:@runtime_subject_data)
120
+ node = explicit_subject? ? expectation.subject_node : expectation.node
121
+ @runtime_subject_data = runtime_data[node]
122
+ end
116
123
 
117
- def matcher_range
118
- expression_range.join(items_node.loc.expression)
124
+ return @runtime_subject_data unless key
125
+
126
+ if @runtime_subject_data
127
+ @runtime_subject_data[key]
128
+ else
129
+ nil
130
+ end
119
131
  end
120
132
 
121
133
  def register_record
122
- @report.records << HaveRecord.new(self)
134
+ report.records << HaveRecord.new(self)
123
135
  end
124
136
  end
125
137
  end
@@ -28,6 +28,18 @@ module Transpec
28
28
  end
29
29
  end
30
30
 
31
+ def annotation
32
+ return @annotation if instance_variable_defined?(:@annotation)
33
+
34
+ @annotation = if have.accurate_conversion?
35
+ nil
36
+ else
37
+ AccuracyAnnotation.new(have.matcher_range)
38
+ end
39
+ end
40
+
41
+ private
42
+
31
43
  def build_expectation(subject, type)
32
44
  case type
33
45
  when :should
@@ -35,22 +35,6 @@ module Transpec
35
35
  source << ".#{have.query_method}"
36
36
  end
37
37
 
38
- def base_subject_source(node)
39
- if node.send_type? && (arg_node = node.children[2])
40
- left_of_arg_source = node.loc.selector.end.join(arg_node.loc.expression.begin).source
41
-
42
- if left_of_arg_source.match(/\A\s*\Z/)
43
- source = node.loc.expression.begin.join(node.loc.selector.end).source
44
- source << '('
45
- source << arg_node.loc.expression.begin.join(node.loc.expression.end).source
46
- source << ')'
47
- return source
48
- end
49
- end
50
-
51
- node.loc.expression.source
52
- end
53
-
54
38
  def replacement_matcher_source(parenthesize_arg = true)
55
39
  case have.expectation.current_syntax_type
56
40
  when :should
@@ -60,6 +44,24 @@ module Transpec
60
44
  end
61
45
  end
62
46
 
47
+ private
48
+
49
+ def base_subject_source(node)
50
+ map = node.loc
51
+ source = map.expression.source
52
+
53
+ if node.send_type? && (arg_node = node.children[2])
54
+ left_of_arg_range = map.selector.end.join(arg_node.loc.expression.begin)
55
+ unless left_of_arg_range.source.include?('(')
56
+ relative_index = left_of_arg_range.begin_pos - map.expression.begin_pos
57
+ source[relative_index, left_of_arg_range.length] = '('
58
+ source << ')'
59
+ end
60
+ end
61
+
62
+ source
63
+ end
64
+
63
65
  def replacement_matcher_source_for_should
64
66
  case have.method_name
65
67
  when :have, :have_exactly then "== #{size_source}"
@@ -16,7 +16,7 @@ module Transpec
16
16
  define_dynamic_analysis_request do |rewriter|
17
17
  key = :project_requires_its?
18
18
  code = 'defined?(RSpec::Its)'
19
- rewriter.register_request(@node, key, code, :context)
19
+ rewriter.register_request(node, key, code, :context)
20
20
  end
21
21
 
22
22
  def convert_to_describe_subject_it!
@@ -42,12 +42,11 @@ module Transpec
42
42
  alias_method :attribute_node, :arg_node
43
43
 
44
44
  def block_node
45
- @node.parent_node
45
+ node.parent_node
46
46
  end
47
47
 
48
48
  def project_requires_its?
49
- node_data = runtime_node_data(@node)
50
- node_data && node_data[:project_requires_its?].result
49
+ runtime_data[node, :project_requires_its?]
51
50
  end
52
51
 
53
52
  private
@@ -89,11 +88,11 @@ module Transpec
89
88
  end
90
89
 
91
90
  def base_indentation
92
- @base_indentation ||= indentation_of_line(@node)
91
+ @base_indentation ||= indentation_of_line(node)
93
92
  end
94
93
 
95
94
  def register_record
96
- @report.records << Record.new(original_syntax, converted_syntax)
95
+ report.records << Record.new(original_syntax, converted_syntax)
97
96
  end
98
97
 
99
98
  def original_syntax
@@ -113,16 +112,18 @@ module Transpec
113
112
  end
114
113
 
115
114
  class AttributeExpression
115
+ attr_reader :node
116
+
116
117
  def initialize(node)
117
118
  @node = node
118
119
  end
119
120
 
120
121
  def brackets?
121
- @node.array_type?
122
+ node.array_type?
122
123
  end
123
124
 
124
125
  def literal?
125
- Util.literal?(@node)
126
+ Util.literal?(node)
126
127
  end
127
128
 
128
129
  def attributes
@@ -136,20 +137,20 @@ module Transpec
136
137
  private
137
138
 
138
139
  def brackets_attributes
139
- selector = @node.loc.expression.source
140
+ selector = node.loc.expression.source
140
141
  description = literal? ? quote(selector) : selector
141
142
  [Attribute.new(selector, description)]
142
143
  end
143
144
 
144
145
  def non_brackets_attributes
145
146
  if literal?
146
- expression = @node.children.first.to_s
147
+ expression = node.children.first.to_s
147
148
  chained_names = expression.split('.')
148
149
  chained_names.map do |name|
149
150
  Attribute.new(".#{name}", quote("##{name}"))
150
151
  end
151
152
  else
152
- source = @node.loc.expression.source
153
+ source = node.loc.expression.source
153
154
  selector = ".send(#{source})"
154
155
  [Attribute.new(selector, source)]
155
156
  end
@@ -23,7 +23,7 @@ module Transpec
23
23
  replacement_method_name = CONVERSION_CORRESPONDENCE[method_name].to_s
24
24
  replace(selector_range, replacement_method_name)
25
25
 
26
- @report.records << Record.new("#{method_name} { }", "#{replacement_method_name} { }")
26
+ report.records << Record.new("#{method_name} { }", "#{replacement_method_name} { }")
27
27
  end
28
28
  end
29
29
  end
@@ -19,10 +19,6 @@ module Transpec
19
19
  ]
20
20
  # rubocop:enable LineLength
21
21
 
22
- def self.dynamic_analysis_target_node?(node)
23
- target_node?(node)
24
- end
25
-
26
22
  def self.conversion_target_node?(node, runtime_data = nil)
27
23
  return false unless check_target_node_statically(node)
28
24
 
@@ -64,7 +60,7 @@ module Transpec
64
60
  return if method_name == :stub_chain && !rspec_version.receive_message_chain_available?
65
61
 
66
62
  unless allow_to_receive_available?
67
- fail ContextError.new(selector_range, "##{method_name}", '#allow')
63
+ fail ContextError.new("##{method_name}", '#allow', selector_range)
68
64
  end
69
65
 
70
66
  source, type = replacement_source_and_conversion_type(rspec_version)
@@ -121,7 +117,7 @@ module Transpec
121
117
  hash_node.children.each_with_index do |pair_node, index|
122
118
  key_node, value_node = *pair_node
123
119
  expression = build_allow_to_receive(key_node, value_node, false)
124
- expression.prepend(indentation_of_line(@node)) if index > 0
120
+ expression.prepend(indentation_of_line(node)) if index > 0
125
121
  expressions << expression
126
122
  end
127
123
 
@@ -182,7 +178,7 @@ module Transpec
182
178
  else
183
179
  AllowRecord
184
180
  end
185
- @report.records << record_class.new(self, conversion_type)
181
+ report.records << record_class.new(self, conversion_type)
186
182
  end
187
183
 
188
184
  class AllowRecord < Record
@@ -25,7 +25,7 @@ module Transpec
25
25
  return instance_variable_get(matcher_ivar_name)
26
26
  end
27
27
 
28
- if matcher_class.conversion_target_node?(matcher_node, @runtime_data)
28
+ if matcher_class.conversion_target_node?(matcher_node, runtime_data)
29
29
  instance_variable_set(matcher_ivar_name, send(matcher_creator_name))
30
30
  else
31
31
  instance_variable_set(matcher_ivar_name, nil)
@@ -33,7 +33,7 @@ module Transpec
33
33
  end
34
34
 
35
35
  define_method(matcher_creator_name) do
36
- matcher_class.new(matcher_node, self, @source_rewriter, @runtime_data, @report)
36
+ matcher_class.new(matcher_node, self, source_rewriter, runtime_data, report)
37
37
  end
38
38
  end
39
39
  end