transpec 1.10.4 → 1.11.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 (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)