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
@@ -17,14 +17,12 @@ module Transpec
17
17
  code << " && respond_to?(#{method.inspect})"
18
18
  end
19
19
 
20
- rewriter.register_request(@node, key, code, :context)
20
+ rewriter.register_request(node, key, code, :context)
21
21
  end
22
22
 
23
23
  def check_syntax_availability(key)
24
- node_data = runtime_node_data(@node)
25
-
26
- if node_data
27
- node_data[key].result
24
+ if runtime_data.present?(node, key)
25
+ runtime_data[node, key]
28
26
  else
29
27
  static_context_inspector.send(key)
30
28
  end
@@ -32,9 +32,7 @@ module Transpec
32
32
 
33
33
  def any_instance?
34
34
  return true unless any_instance_target_node.nil?
35
- node_data = runtime_node_data(subject_node)
36
- return false unless node_data && node_data[:any_instance_target_class_name]
37
- !node_data[:any_instance_target_class_name].result.nil?
35
+ !runtime_data[subject_node, :any_instance_target_class_name].nil?
38
36
  end
39
37
 
40
38
  private
@@ -45,7 +43,7 @@ module Transpec
45
43
  if any_instance_target_node
46
44
  any_instance_target_node.loc.expression.source
47
45
  else
48
- runtime_node_data(subject_node)[:any_instance_target_class_name].result
46
+ runtime_data[subject_node, :any_instance_target_class_name]
49
47
  end
50
48
  end
51
49
 
@@ -19,11 +19,8 @@ module Transpec
19
19
  end
20
20
 
21
21
  def initialize(node, expectation, source_rewriter = nil, runtime_data = nil, report = nil)
22
- @node = node
22
+ super(node, source_rewriter, runtime_data, report)
23
23
  @expectation = expectation
24
- @source_rewriter = source_rewriter
25
- @runtime_data = runtime_data
26
- @report = report || Report.new
27
24
  end
28
25
  end
29
26
  end
@@ -34,9 +34,7 @@ module Transpec
34
34
  return unless runtime_data
35
35
  receiver_node, method_name, *_ = *node
36
36
  target_node = receiver_node ? receiver_node : node
37
- return unless (node_data = runtime_data[target_node])
38
- return unless (eval_data = node_data[source_location_key(method_name)])
39
- eval_data.result
37
+ runtime_data[target_node, source_location_key(method_name)]
40
38
  end
41
39
 
42
40
  def source_location_key(method_name)
@@ -50,7 +48,7 @@ module Transpec
50
48
  target_node = receiver_node
51
49
  target_object_type = :object
52
50
  else
53
- target_node = @node
51
+ target_node = node
54
52
  target_object_type = :context
55
53
  end
56
54
 
@@ -61,23 +59,23 @@ module Transpec
61
59
  end
62
60
 
63
61
  def receiver_node
64
- @node.children[0]
62
+ node.children[0]
65
63
  end
66
64
 
67
65
  def method_name
68
- @node.children[1]
66
+ node.children[1]
69
67
  end
70
68
 
71
69
  def arg_node
72
- @node.children[2]
70
+ node.children[2]
73
71
  end
74
72
 
75
73
  def arg_nodes
76
- @node.children[2..-1]
74
+ node.children[2..-1]
77
75
  end
78
76
 
79
77
  def selector_range
80
- @node.loc.selector
78
+ node.loc.selector
81
79
  end
82
80
 
83
81
  def receiver_range
@@ -43,7 +43,7 @@ module Transpec
43
43
 
44
44
  have_matcher.convert_to_standard_expectation!
45
45
 
46
- @report.records << OnelinerShouldHaveRecord.new(self, have_matcher)
46
+ report.records << OnelinerShouldHaveRecord.new(self, have_matcher)
47
47
  end
48
48
 
49
49
  # rubocop:disable LineLength
@@ -61,7 +61,7 @@ module Transpec
61
61
  @current_syntax_type = :expect
62
62
  have_matcher.convert_to_standard_expectation!
63
63
 
64
- @report.records << OnelinerShouldHaveRecord.new(self, have_matcher, negative_form)
64
+ report.records << OnelinerShouldHaveRecord.new(self, have_matcher, negative_form)
65
65
  end
66
66
 
67
67
  def example_has_description?
@@ -111,7 +111,7 @@ module Transpec
111
111
  def example_block_node
112
112
  return @example_block_node if instance_variable_defined?(:@example_block_node)
113
113
 
114
- @example_block_node = @node.each_ancestor_node.find do |node|
114
+ @example_block_node = node.each_ancestor_node.find do |node|
115
115
  next false unless node.block_type?
116
116
  send_node = node.children.first
117
117
  receiver_node, method_name, = *send_node
@@ -157,7 +157,7 @@ module Transpec
157
157
  syntax << ' ... }'
158
158
  end
159
159
 
160
- @report.records << Record.new(original_syntax, converted_syntax)
160
+ report.records << Record.new(original_syntax, converted_syntax)
161
161
  end
162
162
 
163
163
  class OnelinerShouldHaveRecord < Have::HaveRecord
@@ -19,12 +19,12 @@ module Transpec
19
19
  false
20
20
  end
21
21
 
22
- def self.target_node?(node, runtime_data = nil)
22
+ def self.check_target_node_statically(node)
23
23
  node = node.parent_node if node == BE_NODE
24
+ return false unless node && node.send_type?
24
25
  receiver_node, method_name, *_ = *node
25
26
  return false if receiver_node.nil?
26
- return false unless OPERATORS.include?(method_name)
27
- check_target_node_dynamically(node, runtime_data)
27
+ OPERATORS.include?(method_name)
28
28
  end
29
29
 
30
30
  define_dynamic_analysis_request do |rewriter|
@@ -94,10 +94,12 @@ module Transpec
94
94
 
95
95
  # Need to register record after all source rewrites are done
96
96
  # to avoid false record when failed with overlapped rewrite.
97
+ accurate = !arg_is_enumerable?.nil?
98
+
97
99
  if arg_is_enumerable?
98
- register_record('=~ [1, 2]', 'match_array([1, 2])')
100
+ register_record('=~ [1, 2]', 'match_array([1, 2])', accurate)
99
101
  else
100
- register_record('=~ /pattern/', 'match(/pattern/)')
102
+ register_record('=~ /pattern/', 'match(/pattern/)', accurate)
101
103
  end
102
104
  end
103
105
 
@@ -110,9 +112,14 @@ module Transpec
110
112
  end
111
113
 
112
114
  def arg_is_enumerable?
113
- return true if arg_node.array_type?
114
- node_data = runtime_node_data(arg_node)
115
- node_data && node_data[:arg_is_enumerable?].result
115
+ case arg_node.type
116
+ when :array
117
+ true
118
+ when :regexp
119
+ false
120
+ else
121
+ runtime_data[arg_node, :arg_is_enumerable?]
122
+ end
116
123
  end
117
124
 
118
125
  def parenthesize_single_line!(always)
@@ -128,7 +135,7 @@ module Transpec
128
135
 
129
136
  def parenthesize_multi_line!(linefeed)
130
137
  insert_before(range_in_between_selector_and_arg, '(')
131
- matcher_line_indentation = indentation_of_line(@node)
138
+ matcher_line_indentation = indentation_of_line(node)
132
139
  right_parenthesis = "#{linefeed}#{matcher_line_indentation})"
133
140
  insert_after(expression_range, right_parenthesis)
134
141
  end
@@ -150,9 +157,18 @@ module Transpec
150
157
  end
151
158
  end
152
159
 
153
- def register_record(original_syntax, converted_syntax)
160
+ def matcher_range
161
+ if be_node
162
+ expression_range
163
+ else
164
+ selector_range.join(expression_range.end)
165
+ end
166
+ end
167
+
168
+ def register_record(original_syntax, converted_syntax, accurate = true)
154
169
  original_syntax ||= "#{method_name} expected"
155
- @report.records << Record.new(original_syntax, converted_syntax)
170
+ annotation = AccuracyAnnotation.new(matcher_range) unless accurate
171
+ report.records << Record.new(original_syntax, converted_syntax, annotation)
156
172
  end
157
173
  end
158
174
  end
@@ -0,0 +1,110 @@
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 Pending < Syntax
10
+ include Mixin::Send, Util
11
+
12
+ def self.conversion_target_node?(node, runtime_data = nil)
13
+ return false unless target_node?(node, runtime_data)
14
+
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_context?]
21
+ else
22
+ # Otherwise check statically.
23
+ inspector = StaticContextInspector.new(node)
24
+ inspector.scopes.last == :example
25
+ end
26
+ end
27
+
28
+ def self.target_method?(receiver_node, method_name)
29
+ receiver_node.nil? && method_name == :pending
30
+ end
31
+
32
+ define_dynamic_analysis_request do |rewriter|
33
+ code = 'is_a?(RSpec::Core::ExampleGroup)'
34
+ rewriter.register_request(node, :example_context?, code, :context)
35
+ end
36
+
37
+ def convert_deprecated_syntax!
38
+ if block_node
39
+ unblock!
40
+ else
41
+ convert_to_skip!
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def convert_to_skip!
48
+ replace(selector_range, 'skip')
49
+ register_record('pending', 'skip')
50
+ end
51
+
52
+ def unblock!
53
+ if block_beginning_line == block_body_line
54
+ range_between_pending_and_body =
55
+ expression_range.end.join(block_body_node.loc.expression.begin)
56
+ replace(range_between_pending_and_body, "\n" + indentation_of_line(node))
57
+ else
58
+ remove(expression_range.end.join(block_node.loc.begin))
59
+ outdent!(block_body_node, node)
60
+ end
61
+
62
+ if block_body_line == block_end_line
63
+ remove(block_body_node.loc.expression.end.join(block_node.loc.end))
64
+ else
65
+ remove(line_range(block_node.loc.end))
66
+ end
67
+
68
+ register_record('pending { do_something_fail }', 'pending; do_something_fail')
69
+ end
70
+
71
+ def outdent!(target_node, base_node)
72
+ indentation_width = indentation_width(target_node, base_node)
73
+
74
+ return unless indentation_width > 0
75
+
76
+ each_line_range(target_node) do |line_range|
77
+ remove(line_range.resize(indentation_width))
78
+ end
79
+ end
80
+
81
+ def indentation_width(target, base)
82
+ indentation_of_line(target).size - indentation_of_line(base).size
83
+ end
84
+
85
+ def block_node
86
+ block_node_taken_by_method(node)
87
+ end
88
+
89
+ def block_body_node
90
+ block_node.children[2]
91
+ end
92
+
93
+ def block_beginning_line
94
+ block_node.loc.begin.line
95
+ end
96
+
97
+ def block_body_line
98
+ block_body_node.loc.expression.line
99
+ end
100
+
101
+ def block_end_line
102
+ block_node.loc.end.line
103
+ end
104
+
105
+ def register_record(original_syntax, converted_syntax)
106
+ report.records << Record.new(original_syntax, converted_syntax)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -38,7 +38,7 @@ module Transpec
38
38
 
39
39
  original_syntax << ')'
40
40
 
41
- @report.records << Record.new(original_syntax, 'expect { }.not_to raise_error')
41
+ report.records << Record.new(original_syntax, 'expect { }.not_to raise_error')
42
42
  end
43
43
  end
44
44
  end
@@ -17,22 +17,22 @@ module Transpec
17
17
  def remove_useless_and_return!
18
18
  removed = super
19
19
  return unless removed
20
- @report.records << ReceiveUselessAndReturnRecord.new(self)
20
+ report.records << ReceiveUselessAndReturnRecord.new(self)
21
21
  end
22
22
 
23
23
  def add_receiver_arg_to_any_instance_implementation_block!
24
24
  added = super
25
25
  return unless added
26
- @report.records << ReceiveAnyInstanceBlockRecord.new(self)
26
+ report.records << ReceiveAnyInstanceBlockRecord.new(self)
27
27
  end
28
28
 
29
29
  def any_instance?
30
- @expectation.any_instance?
30
+ expectation.any_instance?
31
31
  end
32
32
 
33
33
  def any_instance_block_node
34
34
  return unless any_instance?
35
- super || @expectation.block_node
35
+ super || expectation.block_node
36
36
  end
37
37
 
38
38
  class ReceiveAnyInstanceBlockRecord < AnyInstanceBlockRecord
@@ -9,7 +9,7 @@ module Transpec
9
9
  class Framework
10
10
  include Util, ::AST::Sexp
11
11
 
12
- attr_reader :rspec_configure
12
+ attr_reader :rspec_configure, :source_rewriter
13
13
 
14
14
  def initialize(rspec_configure, source_rewriter)
15
15
  @rspec_configure = rspec_configure
@@ -27,7 +27,7 @@ module Transpec
27
27
 
28
28
  if setter_node
29
29
  arg_node = setter_node.children[2]
30
- @source_rewriter.replace(arg_node.loc.expression, value.to_s)
30
+ source_rewriter.replace(arg_node.loc.expression, value.to_s)
31
31
  else
32
32
  add_configuration!(config_name, value)
33
33
  end
@@ -77,7 +77,7 @@ module Transpec
77
77
  lines.map! { |line| line + "\n" }
78
78
 
79
79
  insertion_position = beginning_of_line_range(block_node_to_insert_code.loc.end)
80
- @source_rewriter.insert_before(insertion_position, lines.join(''))
80
+ source_rewriter.insert_before(insertion_position, lines.join(''))
81
81
  end
82
82
 
83
83
  def config_variable_name
@@ -35,7 +35,7 @@ module Transpec
35
35
  end
36
36
 
37
37
  def expectize!(negative_form = 'not_to', parenthesize_matcher_arg = true)
38
- fail ContextError.new(selector_range, "##{method_name}", '#expect') unless expect_available?
38
+ fail ContextError.new("##{method_name}", '#expect', selector_range) unless expect_available?
39
39
 
40
40
  if proc_literal?(subject_node)
41
41
  replace(range_of_subject_method_taking_block, 'expect')
@@ -74,7 +74,7 @@ module Transpec
74
74
  converted_syntax << negative_form_of_to
75
75
  end
76
76
 
77
- @report.records << Record.new(original_syntax, converted_syntax)
77
+ report.records << Record.new(original_syntax, converted_syntax)
78
78
  end
79
79
  end
80
80
  end
@@ -45,7 +45,7 @@ module Transpec
45
45
 
46
46
  def expectize!(negative_form = 'not_to')
47
47
  unless expect_to_receive_available?
48
- fail ContextError.new(selector_range, "##{method_name}", '#expect')
48
+ fail ContextError.new("##{method_name}", '#expect', selector_range)
49
49
  end
50
50
 
51
51
  convert_to_syntax!('expect', negative_form)
@@ -56,7 +56,7 @@ module Transpec
56
56
  return unless useless_expectation?
57
57
 
58
58
  unless allow_to_receive_available?
59
- fail ContextError.new(selector_range, "##{method_name}", '#allow')
59
+ fail ContextError.new("##{method_name}", '#allow', selector_range)
60
60
  end
61
61
 
62
62
  convert_to_syntax!('allow', negative_form)
@@ -157,7 +157,7 @@ module Transpec
157
157
  end
158
158
 
159
159
  def register_record(record_class, negative_form_of_to = nil)
160
- @report.records << record_class.new(self, negative_form_of_to)
160
+ report.records << record_class.new(self, negative_form_of_to)
161
161
  end
162
162
 
163
163
  class ExpectBaseRecord < Record
@@ -12,6 +12,14 @@ module Transpec
12
12
 
13
13
  module_function
14
14
 
15
+ def node_id(node)
16
+ source_range = node.loc.expression
17
+ source_buffer = source_range.source_buffer
18
+ absolute_path = File.expand_path(source_buffer.name)
19
+ relative_path = Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
20
+ [relative_path, source_range.begin_pos, source_range.end_pos].join('_')
21
+ end
22
+
15
23
  def proc_literal?(node)
16
24
  return false unless node.block_type?
17
25
 
@@ -133,16 +141,40 @@ module Transpec
133
141
  end
134
142
 
135
143
  def beginning_of_line_range(arg)
136
- range = case arg
137
- when AST::Node then arg.loc.expression
138
- when Parser::Source::Range then arg
139
- else fail ArgumentError, "Invalid argument #{arg}"
140
- end
141
-
144
+ range = range_from_arg(arg)
142
145
  begin_pos = range.begin_pos - range.column
143
146
  Parser::Source::Range.new(range.source_buffer, begin_pos, begin_pos)
144
147
  end
145
148
 
149
+ def end_of_line_range(arg)
150
+ range = range_from_arg(arg)
151
+ begin_pos = beginning_of_line_range(range).begin_pos + range.source_line.size
152
+ Parser::Source::Range.new(range.source_buffer, begin_pos, begin_pos)
153
+ end
154
+
155
+ def line_range(arg)
156
+ range = range_from_arg(arg)
157
+ beginning_of_line_range(range).resize(range.source_line.size + 1)
158
+ end
159
+
160
+ def each_line_range(arg)
161
+ multiline_range = range_from_arg(arg)
162
+ range = line_range(multiline_range)
163
+
164
+ while range.line <= multiline_range.end.line
165
+ yield range
166
+ range = line_range(range.end)
167
+ end
168
+ end
169
+
170
+ def range_from_arg(arg)
171
+ case arg
172
+ when AST::Node then arg.loc.expression
173
+ when Parser::Source::Range then arg
174
+ else fail ArgumentError, "Invalid argument #{arg}"
175
+ end
176
+ end
177
+
146
178
  def literal?(node)
147
179
  case node.type
148
180
  when *LITERAL_TYPES