transpec 1.9.3 → 1.10.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 (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