transpec 1.10.4 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/CHANGELOG.md +9 -0
  4. data/README.md +96 -18
  5. data/README.md.erb +117 -51
  6. data/lib/transpec/base_rewriter.rb +25 -28
  7. data/lib/transpec/cli.rb +27 -14
  8. data/lib/transpec/configuration.rb +2 -1
  9. data/lib/transpec/converter.rb +54 -32
  10. data/lib/transpec/dynamic_analyzer/helper.rb.erb +44 -0
  11. data/lib/transpec/dynamic_analyzer/node_util.rb +17 -0
  12. data/lib/transpec/dynamic_analyzer/rewriter.rb +12 -4
  13. data/lib/transpec/dynamic_analyzer/runtime_data.rb +3 -4
  14. data/lib/transpec/dynamic_analyzer.rb +14 -52
  15. data/lib/transpec/option_parser.rb +89 -81
  16. data/lib/transpec/processed_source.rb +48 -0
  17. data/lib/transpec/record.rb +2 -2
  18. data/lib/transpec/report.rb +5 -1
  19. data/lib/transpec/rspec_dsl.rb +12 -2
  20. data/lib/transpec/rspec_version.rb +8 -7
  21. data/lib/transpec/spec_suite.rb +114 -0
  22. data/lib/transpec/syntax/example.rb +4 -20
  23. data/lib/transpec/syntax/example_group.rb +38 -0
  24. data/lib/transpec/syntax/have.rb +6 -9
  25. data/lib/transpec/syntax/its.rb +5 -7
  26. data/lib/transpec/syntax/method_stub.rb +12 -13
  27. data/lib/transpec/syntax/mixin/any_instance_block.rb +14 -6
  28. data/lib/transpec/syntax/mixin/context_sensitive.rb +41 -0
  29. data/lib/transpec/syntax/mixin/matcher_owner.rb +16 -26
  30. data/lib/transpec/syntax/mixin/monkey_patch_any_instance.rb +1 -1
  31. data/lib/transpec/syntax/mixin/no_message_allowance.rb +2 -2
  32. data/lib/transpec/syntax/mixin/useless_and_return.rb +2 -2
  33. data/lib/transpec/syntax/oneliner_should.rb +9 -13
  34. data/lib/transpec/syntax/operator.rb +3 -7
  35. data/lib/transpec/syntax/pending.rb +4 -20
  36. data/lib/transpec/syntax/rspec_configure/configuration_modification.rb +86 -0
  37. data/lib/transpec/syntax/rspec_configure/framework.rb +45 -86
  38. data/lib/transpec/syntax/rspec_configure.rb +18 -6
  39. data/lib/transpec/syntax/should.rb +3 -5
  40. data/lib/transpec/syntax/should_receive.rb +1 -1
  41. data/lib/transpec/syntax.rb +8 -0
  42. data/lib/transpec/util.rb +0 -8
  43. data/lib/transpec/version.rb +2 -2
  44. data/spec/acceptance/configuration_modification_spec.rb +132 -0
  45. data/spec/acceptance/conversion_spec.rb +114 -0
  46. data/spec/support/shared_context.rb +6 -12
  47. data/spec/transpec/cli_spec.rb +21 -23
  48. data/spec/transpec/configuration_spec.rb +2 -1
  49. data/spec/transpec/converter_spec.rb +292 -213
  50. data/spec/transpec/dynamic_analyzer/rewriter_spec.rb +3 -3
  51. data/spec/transpec/dynamic_analyzer_spec.rb +8 -2
  52. data/spec/transpec/option_parser_spec.rb +42 -4
  53. data/spec/transpec/processed_source_spec.rb +67 -0
  54. data/spec/transpec/rspec_version_spec.rb +8 -2
  55. data/spec/transpec/spec_suite_spec.rb +107 -0
  56. data/spec/transpec/syntax/allow_spec.rb +9 -27
  57. data/spec/transpec/syntax/example_group_spec.rb +172 -0
  58. data/spec/transpec/syntax/expect_spec.rb +18 -54
  59. data/spec/transpec/syntax/have_spec.rb +35 -14
  60. data/spec/transpec/syntax/its_spec.rb +27 -7
  61. data/spec/transpec/syntax/method_stub_spec.rb +31 -8
  62. data/spec/transpec/syntax/oneliner_should_spec.rb +22 -131
  63. data/spec/transpec/syntax/rspec_configure_spec.rb +118 -15
  64. data/spec/transpec/syntax/should_spec.rb +51 -82
  65. data/tasks/fixtures/guard/COMMIT_EDITMSG +80 -0
  66. data/tasks/fixtures/mail/COMMIT_EDITMSG +84 -0
  67. data/tasks/fixtures/twitter/COMMIT_EDITMSG +36 -0
  68. data/tasks/lib/transpec_test.rb +23 -2
  69. data/tasks/readme.rake +35 -0
  70. metadata +22 -4
  71. data/lib/transpec/parser.rb +0 -9
data/lib/transpec/cli.rb CHANGED
@@ -4,10 +4,10 @@ require 'transpec/commit_message'
4
4
  require 'transpec/configuration'
5
5
  require 'transpec/converter'
6
6
  require 'transpec/dynamic_analyzer'
7
- require 'transpec/file_finder'
8
7
  require 'transpec/option_parser'
9
8
  require 'transpec/project'
10
9
  require 'transpec/report'
10
+ require 'transpec/spec_suite'
11
11
  require 'rainbow'
12
12
  require 'rainbow/ext/string' unless String.method_defined?(:color)
13
13
 
@@ -34,7 +34,12 @@ module Transpec
34
34
  return false
35
35
  end
36
36
 
37
- process(paths)
37
+ begin
38
+ process(paths)
39
+ rescue DynamicAnalyzer::AnalysisError
40
+ warn_dynamic_analysis_error
41
+ return false
42
+ end
38
43
 
39
44
  display_summary
40
45
  generate_commit_message
@@ -50,16 +55,23 @@ module Transpec
50
55
  runtime_data = run_dynamic_analysis(paths)
51
56
  end
52
57
 
53
- FileFinder.find(paths).each do |file_path|
54
- convert_file(file_path, runtime_data)
58
+ spec_suite = SpecSuite.new(paths, runtime_data)
59
+ # Actually #analyze does not need to be invoked here, but doing this will avoid long freeze
60
+ # while conversion of files.
61
+ puts 'Aggregating spec suite data...'
62
+ spec_suite.analyze
63
+ puts
64
+
65
+ spec_suite.specs.each do |spec|
66
+ convert_spec(spec, spec_suite)
55
67
  end
56
68
  end
57
69
 
58
- def convert_file(file_path, runtime_data = nil)
59
- puts "Converting #{file_path}"
70
+ def convert_spec(spec, spec_suite)
71
+ puts "Converting #{spec.path}"
60
72
 
61
- converter = Converter.new(configuration, project.rspec_version, runtime_data)
62
- converter.convert_file!(file_path)
73
+ converter = Converter.new(spec_suite, configuration, project.rspec_version)
74
+ converter.convert_file!(spec)
63
75
 
64
76
  warn_annotations(converter.report)
65
77
  report << converter.report
@@ -96,12 +108,6 @@ module Transpec
96
108
  puts
97
109
 
98
110
  runtime_data
99
- rescue DynamicAnalyzer::AnalysisError
100
- warn 'Failed running dynamic analysis. ' \
101
- 'Transpec needs to run your specs in a copied project directory for dynamic analysis. ' \
102
- 'If your project requires some special setup or commands to run specs, ' \
103
- 'use -c/--rspec-command option.'
104
- exit(1)
105
111
  end
106
112
 
107
113
  def display_summary
@@ -138,6 +144,13 @@ module Transpec
138
144
  puts "Done! Now run #{'rspec'.bright} and check if everything is green."
139
145
  end
140
146
 
147
+ def warn_dynamic_analysis_error
148
+ warn 'Failed running dynamic analysis. ' \
149
+ 'Transpec runs your specs in a copied project directory. ' \
150
+ 'If your project requires some special setup or commands to run specs, ' \
151
+ 'use -c/--rspec-command option.'
152
+ end
153
+
141
154
  def warn_syntax_error(error)
142
155
  warn "Syntax error at #{error.diagnostic.location}. Skipping the file.".color(:red)
143
156
  end
@@ -17,7 +17,8 @@ module Transpec
17
17
  [:convert_deprecated_method, true],
18
18
  [:parenthesize_matcher_arg, true],
19
19
  [:add_receiver_arg_to_any_instance_implementation_block, true],
20
- [:convert_stub_with_hash_to_stub_and_return, false],
20
+ [:convert_stub_with_hash_to_allow_to_receive_and_return, false],
21
+ [:convert_example_group, false],
21
22
  [:forced, false],
22
23
  [:skip_dynamic_analysis, false]
23
24
  ].freeze
@@ -4,24 +4,30 @@ require 'transpec/base_rewriter'
4
4
  require 'transpec/configuration'
5
5
  require 'transpec/report'
6
6
  require 'transpec/rspec_version'
7
+ require 'transpec/spec_suite'
7
8
  require 'transpec/syntax'
8
9
 
9
10
  Transpec::Syntax.require_all
10
11
 
11
12
  module Transpec
12
13
  class Converter < BaseRewriter # rubocop:disable ClassLength
13
- attr_reader :configuration, :rspec_version, :runtime_data, :report
14
+ attr_reader :spec_suite, :configuration, :rspec_version, :report
14
15
 
15
16
  alias_method :convert_file!, :rewrite_file!
17
+ alias_method :convert_source, :rewrite_source
16
18
  alias_method :convert, :rewrite
17
19
 
18
- def initialize(configuration = nil, rspec_version = nil, runtime_data = nil)
20
+ def initialize(spec_suite = nil, configuration = nil, rspec_version = nil)
21
+ @spec_suite = spec_suite || SpecSuite.new
19
22
  @configuration = configuration || Configuration.new
20
23
  @rspec_version = rspec_version || Transpec.required_rspec_version
21
- @runtime_data = runtime_data
22
24
  @report = Report.new
23
25
  end
24
26
 
27
+ def runtime_data
28
+ spec_suite.runtime_data
29
+ end
30
+
25
31
  def process(ast, source_rewriter)
26
32
  return unless ast
27
33
  ast.each_node do |node|
@@ -33,10 +39,7 @@ module Transpec
33
39
  Syntax.standalone_syntaxes.each do |syntax_class|
34
40
  syntax = syntax_class.new(node, source_rewriter, runtime_data, report)
35
41
  next unless syntax.conversion_target?
36
-
37
- handler_name = "process_#{syntax_class.snake_case_name}"
38
- send(handler_name, syntax)
39
-
42
+ dispatch_syntax(syntax)
40
43
  break
41
44
  end
42
45
  rescue OverlappedRewriteError # rubocop:disable HandleExceptions
@@ -44,49 +47,41 @@ module Transpec
44
47
  report.conversion_errors << error
45
48
  end
46
49
 
47
- def process_should(should)
48
- if configuration.convert_should?
49
- should.expectize!(
50
- configuration.negative_form_of_to,
51
- configuration.parenthesize_matcher_arg?
52
- )
50
+ def dispatch_syntax(syntax)
51
+ handler_name = "process_#{syntax.class.snake_case_name}"
52
+ send(handler_name, syntax)
53
+
54
+ syntax.dependent_syntaxes.each do |dependent_syntax|
55
+ next unless dependent_syntax.conversion_target?
56
+ dispatch_syntax(dependent_syntax)
53
57
  end
58
+ end
54
59
 
55
- process_have(should.have_matcher)
56
- process_raise_error(should.raise_error_matcher)
60
+ def process_should(should)
61
+ return unless configuration.convert_should?
62
+ should.expectize!(configuration.negative_form_of_to)
57
63
  end
58
64
 
59
65
  def process_oneliner_should(oneliner_should)
60
66
  negative_form = configuration.negative_form_of_to
61
- parenthesize = configuration.parenthesize_matcher_arg?
62
-
63
- # TODO: Referencing oneliner_should.have_matcher.project_requires_collection_matcher?
64
- # from this converter is considered bad design.
65
67
  should_convert_have_items = configuration.convert_have_items? &&
66
- oneliner_should.have_matcher &&
67
- !oneliner_should.have_matcher.project_requires_collection_matcher?
68
+ oneliner_should.have_matcher.conversion_target?
68
69
 
69
70
  if should_convert_have_items
70
71
  if configuration.convert_should?
71
- oneliner_should.convert_have_items_to_standard_expect!(negative_form, parenthesize)
72
+ oneliner_should.convert_have_items_to_standard_expect!(negative_form)
72
73
  else
73
74
  oneliner_should.convert_have_items_to_standard_should!
74
75
  end
75
76
  elsif configuration.convert_oneliner? && rspec_version.oneliner_is_expected_available?
76
- oneliner_should.expectize!(negative_form, parenthesize)
77
+ oneliner_should.expectize!(negative_form)
77
78
  end
78
-
79
- process_raise_error(oneliner_should.raise_error_matcher)
80
79
  end
81
80
 
82
81
  def process_expect(expect)
83
- process_have(expect.have_matcher)
84
- process_raise_error(expect.raise_error_matcher)
85
- process_messaging_host(expect.receive_matcher)
86
82
  end
87
83
 
88
84
  def process_allow(allow)
89
- process_messaging_host(allow.receive_matcher)
90
85
  end
91
86
 
92
87
  def process_should_receive(should_receive)
@@ -115,7 +110,7 @@ module Transpec
115
110
  if configuration.convert_stub?
116
111
  if !method_stub.hash_arg? ||
117
112
  rspec_version.receive_messages_available? ||
118
- configuration.convert_stub_with_hash_to_stub_and_return?
113
+ configuration.convert_stub_with_hash_to_allow_to_receive_and_return?
119
114
  method_stub.allowize!(rspec_version)
120
115
  elsif configuration.convert_deprecated_method?
121
116
  method_stub.convert_deprecated_method!
@@ -129,6 +124,17 @@ module Transpec
129
124
  process_messaging_host(method_stub)
130
125
  end
131
126
 
127
+ def process_operator(operator)
128
+ return unless configuration.convert_should?
129
+ return if operator.expectation.is_a?(Syntax::OnelinerShould) &&
130
+ !rspec_version.oneliner_is_expected_available?
131
+ operator.convert_operator!(configuration.parenthesize_matcher_arg?)
132
+ end
133
+
134
+ def process_receive(receive)
135
+ process_messaging_host(receive)
136
+ end
137
+
132
138
  def process_be_boolean(be_boolean)
133
139
  return unless rspec_version.be_truthy_available?
134
140
  return unless configuration.convert_deprecated_method?
@@ -176,7 +182,14 @@ module Transpec
176
182
  matcher_definition.convert_deprecated_method! if configuration.convert_deprecated_method?
177
183
  end
178
184
 
185
+ def process_example_group(example_group)
186
+ return unless rspec_version.non_monkey_patch_example_group_available?
187
+ example_group.convert_to_non_monkey_patch! if configuration.convert_example_group?
188
+ end
189
+
179
190
  def process_rspec_configure(rspec_configure)
191
+ return unless spec_suite.main_rspec_configure_node?(rspec_configure.node)
192
+
180
193
  if need_to_modify_expectation_syntax_configuration?(rspec_configure)
181
194
  rspec_configure.expectations.syntaxes = :expect
182
195
  end
@@ -185,8 +198,12 @@ module Transpec
185
198
  rspec_configure.mocks.syntaxes = :expect
186
199
  end
187
200
 
188
- if rspec_version.rspec_2_99? &&
189
- configuration.convert_deprecated_method?
201
+ if rspec_version.non_monkey_patch_example_group_available? &&
202
+ configuration.convert_example_group?
203
+ rspec_configure.expose_dsl_globally = false
204
+ end
205
+
206
+ if need_to_modify_yield_receiver_to_any_instance_implementation_blocks_config?
190
207
  should_yield = configuration.add_receiver_arg_to_any_instance_implementation_block?
191
208
  rspec_configure.mocks.yield_receiver_to_any_instance_implementation_blocks = should_yield
192
209
  end
@@ -216,6 +233,11 @@ module Transpec
216
233
  messaging_host.add_receiver_arg_to_any_instance_implementation_block!
217
234
  end
218
235
 
236
+ def need_to_modify_yield_receiver_to_any_instance_implementation_blocks_config?
237
+ rspec_version.rspec_2_99? && configuration.convert_deprecated_method? &&
238
+ spec_suite.need_to_modify_yield_receiver_to_any_instance_implementation_blocks_config?
239
+ end
240
+
219
241
  def need_to_modify_expectation_syntax_configuration?(rspec_configure)
220
242
  return false unless configuration.convert_should?
221
243
  rspec_configure.expectations.syntaxes == [:should]
@@ -0,0 +1,44 @@
1
+ module TranspecAnalysis
2
+ @base_path = Dir.pwd
3
+
4
+ def self.global_data
5
+ @data ||= {}
6
+ end
7
+
8
+ def self.node_data
9
+ @data ||= {}
10
+ end
11
+
12
+ at_exit do
13
+ # Use JSON rather than Marshal so that:
14
+ # * Unknown third-party class information won't be serialized.
15
+ # (Such objects are stored as a string.)
16
+ # * Singleton method information won't be serialized.
17
+ # (With Marshal.load, `singleton can't be dumped (TypeError)` will be raised.)
18
+ require 'json'
19
+ path = File.join(@base_path, '<%= RESULT_FILE %>')
20
+ File.open(path, 'w') do |file|
21
+ JSON.dump(node_data, file)
22
+ end
23
+ end
24
+ end
25
+
26
+ def <%= ANALYSIS_METHOD %>(object, context, node_id, analysis_codes)
27
+ node_data = {}
28
+
29
+ analysis_codes.each do |key, (target_type, code)|
30
+ target = case target_type
31
+ when :object then object
32
+ when :context then context
33
+ end
34
+
35
+ begin
36
+ node_data[key] = target.instance_eval(code)
37
+ rescue Exception
38
+ end
39
+ end
40
+
41
+ TranspecAnalysis.node_data[node_id] = node_data
42
+
43
+ object
44
+ end
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+
3
+ require 'pathname'
4
+
5
+ module Transpec
6
+ class DynamicAnalyzer
7
+ module NodeUtil
8
+ def node_id(node)
9
+ source_range = node.loc.expression
10
+ source_buffer = source_range.source_buffer
11
+ absolute_path = File.expand_path(source_buffer.name)
12
+ relative_path = Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
13
+ [relative_path, source_range.begin_pos, source_range.end_pos].join('_')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,15 +2,16 @@
2
2
 
3
3
  require 'transpec/base_rewriter'
4
4
  require 'transpec/dynamic_analyzer'
5
- require 'transpec/syntax'
5
+ require 'transpec/dynamic_analyzer/node_util'
6
6
  require 'transpec/util'
7
+ require 'transpec/syntax'
7
8
 
8
9
  Transpec::Syntax.require_all
9
10
 
10
11
  module Transpec
11
12
  class DynamicAnalyzer
12
13
  class Rewriter < BaseRewriter
13
- include Util
14
+ include NodeUtil, Util
14
15
 
15
16
  EVAL_TARGET_TYPES = [:object, :context]
16
17
 
@@ -46,12 +47,19 @@ module Transpec
46
47
  ast.each_node do |node|
47
48
  Syntax.standalone_syntaxes.each do |syntax_class|
48
49
  syntax = syntax_class.new(node)
49
- next unless syntax.dynamic_analysis_target?
50
- syntax.register_dynamic_analysis_request(self)
50
+ collect_requests_of_syntax(syntax)
51
51
  end
52
52
  end
53
53
  end
54
54
 
55
+ def collect_requests_of_syntax(syntax)
56
+ return unless syntax.dynamic_analysis_target?
57
+ syntax.register_dynamic_analysis_request(self)
58
+ syntax.dependent_syntaxes.each do |dependent_syntax|
59
+ collect_requests_of_syntax(dependent_syntax)
60
+ end
61
+ end
62
+
55
63
  def process_requests(source_rewriter)
56
64
  requests.each do |node, analysis_codes|
57
65
  inject_analysis_code(node, analysis_codes, source_rewriter)
@@ -1,14 +1,13 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'transpec/util'
3
+ require 'transpec/dynamic_analyzer/node_util'
4
4
  require 'json'
5
5
  require 'ostruct'
6
- require 'pathname'
7
6
 
8
7
  module Transpec
9
8
  class DynamicAnalyzer
10
9
  class RuntimeData
11
- include Util
10
+ include NodeUtil
12
11
 
13
12
  attr_reader :data
14
13
 
@@ -30,7 +29,7 @@ module Transpec
30
29
  end
31
30
 
32
31
  def run?(node)
33
- !self[node].nil?
32
+ self[node]
34
33
  end
35
34
 
36
35
  def present?(node, key)
@@ -2,13 +2,14 @@
2
2
 
3
3
  require 'transpec/dynamic_analyzer/rewriter'
4
4
  require 'transpec/dynamic_analyzer/runtime_data'
5
- require 'transpec/file_finder'
6
5
  require 'transpec/project'
6
+ require 'transpec/spec_suite'
7
7
  require 'tmpdir'
8
8
  require 'find'
9
9
  require 'pathname'
10
10
  require 'fileutils'
11
11
  require 'shellwords'
12
+ require 'erb'
12
13
  require 'English'
13
14
 
14
15
  module Transpec
@@ -16,50 +17,6 @@ module Transpec
16
17
  ANALYSIS_METHOD = 'transpec_analyze'
17
18
  HELPER_FILE = 'transpec_analysis_helper.rb'
18
19
  RESULT_FILE = 'transpec_analysis_result.json'
19
- HELPER_SOURCE = <<-END
20
- require 'pathname'
21
-
22
- module TranspecAnalysis
23
- @base_path = Dir.pwd
24
-
25
- def self.data
26
- @data ||= {}
27
- end
28
-
29
- at_exit do
30
- # Use JSON rather than Marshal so that:
31
- # * Unknown third-party class information won't be serialized.
32
- # (Such objects are stored as a string.)
33
- # * Singleton method information won't be serialized.
34
- # (With Marshal.load, `singleton can't be dumped (TypeError)` will be raised.)
35
- require 'json'
36
- path = File.join(@base_path, '#{RESULT_FILE}')
37
- File.open(path, 'w') do |file|
38
- JSON.dump(data, file)
39
- end
40
- end
41
- end
42
-
43
- def #{ANALYSIS_METHOD}(object, context, node_id, analysis_codes)
44
- node_data = {}
45
-
46
- analysis_codes.each do |key, (target_type, code)|
47
- target = case target_type
48
- when :object then object
49
- when :context then context
50
- end
51
-
52
- begin
53
- node_data[key] = target.instance_eval(code)
54
- rescue Exception
55
- end
56
- end
57
-
58
- TranspecAnalysis.data[node_id] = node_data
59
-
60
- object
61
- end
62
- END
63
20
 
64
21
  attr_reader :project, :rspec_command, :silent
65
22
  alias_method :silent?, :silent
@@ -88,7 +45,7 @@ module Transpec
88
45
  in_copied_project do
89
46
  rewrite_specs(paths)
90
47
 
91
- File.write(HELPER_FILE, HELPER_SOURCE)
48
+ File.write(HELPER_FILE, helper_source)
92
49
 
93
50
  run_rspec(paths)
94
51
 
@@ -153,15 +110,20 @@ module Transpec
153
110
  def rewrite_specs(paths)
154
111
  rewriter = Rewriter.new
155
112
 
156
- FileFinder.find(paths).each do |file_path|
157
- begin
158
- rewriter.rewrite_file!(file_path)
159
- rescue Parser::SyntaxError # rubocop:disable HandleExceptions
160
- # Syntax errors will be reported in CLI with Converter.
161
- end
113
+ spec_suite = SpecSuite.new(paths)
114
+
115
+ spec_suite.specs.each do |spec|
116
+ next if spec.syntax_error
117
+ rewriter.rewrite_file!(spec)
162
118
  end
163
119
  end
164
120
 
121
+ def helper_source
122
+ erb_path = File.join(File.dirname(__FILE__), 'dynamic_analyzer', 'helper.rb.erb')
123
+ erb = ERB.new(File.read(erb_path), nil)
124
+ erb.result(binding)
125
+ end
126
+
165
127
  def copy(source, destination)
166
128
  if File.symlink?(source)
167
129
  File.symlink(File.readlink(source), destination)