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
@@ -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