transpec 0.2.6 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +10 -0
- data/README.md +111 -56
- data/README.md.erb +117 -62
- data/lib/transpec/ast/node.rb +41 -0
- data/lib/transpec/base_rewriter.rb +55 -0
- data/lib/transpec/cli.rb +43 -153
- data/lib/transpec/configuration.rb +13 -9
- data/lib/transpec/{rewriter.rb → converter.rb} +44 -71
- data/lib/transpec/dynamic_analyzer/rewriter.rb +94 -0
- data/lib/transpec/dynamic_analyzer/runtime_data.rb +27 -0
- data/lib/transpec/dynamic_analyzer.rb +166 -0
- data/lib/transpec/file_finder.rb +53 -0
- data/lib/transpec/option_parser.rb +166 -0
- data/lib/transpec/{context.rb → static_context_inspector.rb} +2 -2
- data/lib/transpec/syntax/be_close.rb +7 -9
- data/lib/transpec/syntax/double.rb +6 -10
- data/lib/transpec/syntax/expect.rb +35 -0
- data/lib/transpec/syntax/have.rb +195 -0
- data/lib/transpec/syntax/method_stub.rb +22 -27
- data/lib/transpec/syntax/mixin/allow_no_message.rb +73 -0
- data/lib/transpec/syntax/mixin/any_instance.rb +22 -0
- data/lib/transpec/syntax/mixin/expectizable.rb +26 -0
- data/lib/transpec/syntax/mixin/have_matcher.rb +23 -0
- data/lib/transpec/syntax/mixin/monkey_patch.rb +37 -0
- data/lib/transpec/syntax/mixin/send.rb +109 -0
- data/lib/transpec/syntax/{matcher.rb → operator_matcher.rb} +27 -14
- data/lib/transpec/syntax/raise_error.rb +6 -10
- data/lib/transpec/syntax/rspec_configure.rb +29 -28
- data/lib/transpec/syntax/should.rb +45 -15
- data/lib/transpec/syntax/should_receive.rb +44 -16
- data/lib/transpec/syntax.rb +29 -21
- data/lib/transpec/util.rb +12 -2
- data/lib/transpec/version.rb +3 -3
- data/spec/spec_helper.rb +8 -6
- data/spec/support/cache_helper.rb +50 -0
- data/spec/support/shared_context.rb +49 -1
- data/spec/transpec/ast/node_spec.rb +65 -0
- data/spec/transpec/cli_spec.rb +33 -242
- data/spec/transpec/commit_message_spec.rb +2 -2
- data/spec/transpec/configuration_spec.rb +12 -8
- data/spec/transpec/{rewriter_spec.rb → converter_spec.rb} +198 -148
- data/spec/transpec/dynamic_analyzer/rewriter_spec.rb +183 -0
- data/spec/transpec/dynamic_analyzer_spec.rb +164 -0
- data/spec/transpec/file_finder_spec.rb +118 -0
- data/spec/transpec/option_parser_spec.rb +185 -0
- data/spec/transpec/{context_spec.rb → static_context_inspector_spec.rb} +27 -12
- data/spec/transpec/syntax/be_close_spec.rb +8 -4
- data/spec/transpec/syntax/double_spec.rb +105 -12
- data/spec/transpec/syntax/expect_spec.rb +83 -0
- data/spec/transpec/syntax/have_spec.rb +599 -0
- data/spec/transpec/syntax/method_stub_spec.rb +276 -115
- data/spec/transpec/syntax/{matcher_spec.rb → operator_matcher_spec.rb} +277 -98
- data/spec/transpec/syntax/raise_error_spec.rb +92 -46
- data/spec/transpec/syntax/should_receive_spec.rb +298 -92
- data/spec/transpec/syntax/should_spec.rb +230 -44
- data/spec/transpec/util_spec.rb +2 -9
- data/tasks/lib/transpec_demo.rb +1 -1
- data/tasks/lib/transpec_test.rb +5 -7
- data/tasks/test.rake +5 -1
- data/transpec.gemspec +1 -1
- metadata +46 -22
- data/lib/transpec/syntax/able_to_allow_no_message.rb +0 -73
- data/lib/transpec/syntax/able_to_target_any_instance.rb +0 -24
- data/lib/transpec/syntax/expectizable.rb +0 -27
- data/lib/transpec/syntax/send_node_syntax.rb +0 -57
data/lib/transpec/cli.rb
CHANGED
@@ -1,27 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
require 'transpec/configuration'
|
4
3
|
require 'transpec/commit_message'
|
5
|
-
require 'transpec/
|
4
|
+
require 'transpec/configuration'
|
5
|
+
require 'transpec/converter'
|
6
|
+
require 'transpec/dynamic_analyzer'
|
7
|
+
require 'transpec/file_finder'
|
8
|
+
require 'transpec/option_parser'
|
6
9
|
require 'transpec/report'
|
7
|
-
require 'transpec/rewriter'
|
8
|
-
require 'transpec/version'
|
9
|
-
require 'optparse'
|
10
|
-
require 'find'
|
11
10
|
require 'rainbow'
|
12
11
|
|
13
12
|
module Transpec
|
14
13
|
class CLI
|
15
|
-
|
16
|
-
expect_to_matcher: :convert_to_expect_to_matcher=,
|
17
|
-
expect_to_receive: :convert_to_expect_to_receive=,
|
18
|
-
allow_to_receive: :convert_to_allow_to_receive=,
|
19
|
-
deprecated: :replace_deprecated_method=
|
20
|
-
}
|
21
|
-
|
22
|
-
attr_reader :configuration, :forced, :generates_commit_message
|
23
|
-
alias_method :forced?, :forced
|
24
|
-
alias_method :generates_commit_message?, :generates_commit_message
|
14
|
+
attr_reader :configuration
|
25
15
|
|
26
16
|
def self.run(args = ARGV)
|
27
17
|
new.run(args)
|
@@ -29,172 +19,67 @@ module Transpec
|
|
29
19
|
|
30
20
|
def initialize
|
31
21
|
@configuration = Configuration.new
|
32
|
-
@forced = false
|
33
|
-
@generates_commit_message = false
|
34
22
|
@report = Report.new
|
35
23
|
end
|
36
24
|
|
37
25
|
def run(args)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
target_files(base_paths).each do |file_path|
|
45
|
-
process_file(file_path)
|
26
|
+
begin
|
27
|
+
paths = OptionParser.new(@configuration).parse(args)
|
28
|
+
fail_if_should_not_continue!
|
29
|
+
rescue => error
|
30
|
+
warn error.message
|
31
|
+
return false
|
46
32
|
end
|
47
33
|
|
34
|
+
process(paths)
|
35
|
+
|
48
36
|
display_summary
|
49
|
-
generate_commit_message if
|
37
|
+
generate_commit_message if @configuration.generate_commit_message?
|
38
|
+
display_final_guide
|
50
39
|
|
51
40
|
true
|
52
|
-
rescue => error
|
53
|
-
warn error.message
|
54
|
-
false
|
55
41
|
end
|
56
42
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
rewriter = Rewriter.new(@configuration, @report)
|
61
|
-
rewriter.rewrite_file!(file_path)
|
62
|
-
|
63
|
-
@report.invalid_context_errors.concat(rewriter.invalid_context_errors)
|
43
|
+
def process(paths)
|
44
|
+
runtime_data = nil
|
64
45
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
warn_syntax_error(error)
|
71
|
-
end
|
72
|
-
|
73
|
-
# rubocop:disable MethodLength
|
74
|
-
def parse_options(args)
|
75
|
-
parser = OptionParser.new
|
76
|
-
parser.banner = "Usage: transpec [options] [files or directories]\n\n"
|
77
|
-
|
78
|
-
parser.on(
|
79
|
-
'-f', '--force',
|
80
|
-
'Force processing even if the current Git',
|
81
|
-
'repository is not clean.'
|
82
|
-
) do
|
83
|
-
@forced = true
|
84
|
-
end
|
85
|
-
|
86
|
-
parser.on(
|
87
|
-
'-m', '--commit-message',
|
88
|
-
'Generate commit message that describes',
|
89
|
-
'conversion summary. Only Git is supported.'
|
90
|
-
) do
|
91
|
-
unless Git.inside_of_repository?
|
92
|
-
fail '-m/--commit-message option is specified but not in a Git repository'
|
93
|
-
end
|
94
|
-
|
95
|
-
@generates_commit_message = true
|
96
|
-
end
|
97
|
-
|
98
|
-
parser.on(
|
99
|
-
'-d', '--disable TYPE[,TYPE...]',
|
100
|
-
'Disable specific conversions.',
|
101
|
-
'Available conversion types:',
|
102
|
-
' expect_to_matcher (from `should`)',
|
103
|
-
' expect_to_receive (from `should_receive`)',
|
104
|
-
' allow_to_receive (from `stub`)',
|
105
|
-
' deprecated (e.g. from `stub!` to `stub`)',
|
106
|
-
'These are all enabled by default.'
|
107
|
-
) do |types|
|
108
|
-
types.split(',').each do |type|
|
109
|
-
config_attr = CONFIG_ATTRS_FOR_CLI_TYPES[type.to_sym]
|
110
|
-
fail ArgumentError, "Unknown conversion type #{type.inspect}" unless config_attr
|
111
|
-
@configuration.send(config_attr, false)
|
46
|
+
unless @configuration.skip_dynamic_analysis?
|
47
|
+
puts 'Copying project for dynamic analysis...'
|
48
|
+
DynamicAnalyzer.new(rspec_command: @configuration.rspec_command) do |analyzer|
|
49
|
+
puts "Running dynamic analysis with command \"#{analyzer.rspec_command}\"..."
|
50
|
+
runtime_data = analyzer.analyze(paths)
|
112
51
|
end
|
52
|
+
puts
|
113
53
|
end
|
114
54
|
|
115
|
-
|
116
|
-
|
117
|
-
'Specify negative form of `to` that is used',
|
118
|
-
'in `expect(...).to` syntax.',
|
119
|
-
'Either `not_to` or `to_not`.',
|
120
|
-
'Default: not_to'
|
121
|
-
) do |form|
|
122
|
-
@configuration.negative_form_of_to = form
|
55
|
+
FileFinder.find(paths).each do |file_path|
|
56
|
+
convert_file(file_path, runtime_data)
|
123
57
|
end
|
58
|
+
end
|
124
59
|
|
125
|
-
|
126
|
-
|
127
|
-
'Suppress parenthesizing argument of matcher',
|
128
|
-
'when converting operator to non-operator',
|
129
|
-
'in `expect` syntax. Note that it will be',
|
130
|
-
'parenthesized even if this option is',
|
131
|
-
'specified when parentheses are necessary to',
|
132
|
-
'keep the meaning of the expression.',
|
133
|
-
'By default, arguments of the following',
|
134
|
-
'operator matchers will be parenthesized.',
|
135
|
-
' `== 10` to `eq(10)`',
|
136
|
-
' `=~ /pattern/` to `match(/pattern/)`',
|
137
|
-
' `=~ [1, 2]` to `match_array([1, 2])`'
|
138
|
-
) do
|
139
|
-
@configuration.parenthesize_matcher_arg = false
|
140
|
-
end
|
60
|
+
def convert_file(file_path, runtime_data = nil)
|
61
|
+
puts "Converting #{file_path}"
|
141
62
|
|
142
|
-
|
143
|
-
|
144
|
-
end
|
63
|
+
converter = Converter.new(@configuration, runtime_data, @report)
|
64
|
+
converter.convert_file!(file_path)
|
145
65
|
|
146
|
-
|
147
|
-
puts Version.to_s
|
148
|
-
exit
|
149
|
-
end
|
66
|
+
@report.invalid_context_errors.concat(converter.invalid_context_errors)
|
150
67
|
|
151
|
-
|
152
|
-
|
153
|
-
args
|
154
|
-
end
|
155
|
-
# rubocop:enable MethodLength
|
156
|
-
|
157
|
-
def target_files(paths)
|
158
|
-
paths.reduce([]) do |file_paths, path|
|
159
|
-
if File.directory?(path)
|
160
|
-
file_paths.concat(ruby_files_in_directory(path))
|
161
|
-
elsif File.file?(path)
|
162
|
-
file_paths << path
|
163
|
-
elsif !File.exists?(path)
|
164
|
-
fail ArgumentError, "No such file or directory #{path.inspect}"
|
165
|
-
end
|
68
|
+
converter.invalid_context_errors.each do |error|
|
69
|
+
warn_invalid_context_error(error)
|
166
70
|
end
|
71
|
+
rescue Parser::SyntaxError => error
|
72
|
+
@report.syntax_errors << error
|
73
|
+
warn_syntax_error(error)
|
167
74
|
end
|
168
75
|
|
169
76
|
private
|
170
77
|
|
171
|
-
def base_target_paths(args)
|
172
|
-
return args unless args.empty?
|
173
|
-
return ['spec'] if Dir.exists?('spec')
|
174
|
-
fail ArgumentError, 'Specify target files or directories.'
|
175
|
-
end
|
176
|
-
|
177
|
-
def ruby_files_in_directory(directory_path)
|
178
|
-
ruby_file_paths = []
|
179
|
-
|
180
|
-
Find.find(directory_path) do |path|
|
181
|
-
next unless File.file?(path)
|
182
|
-
next unless File.extname(path) == '.rb'
|
183
|
-
ruby_file_paths << path
|
184
|
-
end
|
185
|
-
|
186
|
-
ruby_file_paths
|
187
|
-
end
|
188
|
-
|
189
78
|
def fail_if_should_not_continue!
|
190
|
-
return if forced?
|
191
|
-
|
192
|
-
# TODO: Check each repository of target files / directories,
|
193
|
-
# not only the current working directory.
|
79
|
+
return if @configuration.forced?
|
194
80
|
return unless Git.command_available?
|
195
81
|
return unless Git.inside_of_repository?
|
196
82
|
return if Git.clean?
|
197
|
-
|
198
83
|
fail 'The current Git repository is not clean. Aborting.'
|
199
84
|
end
|
200
85
|
|
@@ -223,6 +108,11 @@ module Transpec
|
|
223
108
|
puts ' git commit -eF .git/COMMIT_EDITMSG'
|
224
109
|
end
|
225
110
|
|
111
|
+
def display_final_guide
|
112
|
+
puts
|
113
|
+
puts "Done! Now run #{'rspec'.bright} and check if all the converted specs pass."
|
114
|
+
end
|
115
|
+
|
226
116
|
def warn_syntax_error(error)
|
227
117
|
warn "Syntax error at #{error.diagnostic.location}. Skipping the file.".color(:red)
|
228
118
|
end
|
@@ -5,23 +5,27 @@ module Transpec
|
|
5
5
|
NEGATIVE_FORMS_OF_TO = ['not_to', 'to_not'].freeze
|
6
6
|
|
7
7
|
PREDICATES = [
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
8
|
+
[:convert_should, true],
|
9
|
+
[:convert_should_receive, true],
|
10
|
+
[:convert_stub, true],
|
11
|
+
[:convert_have_items, true],
|
12
|
+
[:convert_deprecated_method, true],
|
13
|
+
[:parenthesize_matcher_arg, true],
|
14
|
+
[:forced, false],
|
15
|
+
[:skip_dynamic_analysis, false],
|
16
|
+
[:generate_commit_message, false]
|
13
17
|
].freeze
|
14
18
|
|
15
|
-
PREDICATES.each do |predicate|
|
19
|
+
PREDICATES.each do |predicate, _|
|
16
20
|
attr_accessor predicate
|
17
21
|
alias_method predicate.to_s + '?', predicate
|
18
22
|
end
|
19
23
|
|
20
|
-
attr_accessor :negative_form_of_to
|
24
|
+
attr_accessor :negative_form_of_to, :rspec_command
|
21
25
|
|
22
26
|
def initialize
|
23
|
-
PREDICATES.each do |predicate|
|
24
|
-
instance_variable_set('@' + predicate.to_s,
|
27
|
+
PREDICATES.each do |predicate, default_value|
|
28
|
+
instance_variable_set('@' + predicate.to_s, default_value)
|
25
29
|
end
|
26
30
|
|
27
31
|
self.negative_form_of_to = 'not_to'
|
@@ -1,83 +1,48 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
+
require 'transpec/base_rewriter'
|
4
|
+
require 'transpec/configuration'
|
3
5
|
require 'transpec/report'
|
4
|
-
require 'transpec/ast/builder'
|
5
6
|
require 'transpec/ast/scanner'
|
6
|
-
require 'transpec/configuration'
|
7
7
|
require 'transpec/syntax'
|
8
8
|
require 'transpec/syntax/be_close'
|
9
9
|
require 'transpec/syntax/double'
|
10
|
-
require 'transpec/syntax/
|
10
|
+
require 'transpec/syntax/expect'
|
11
11
|
require 'transpec/syntax/method_stub'
|
12
12
|
require 'transpec/syntax/raise_error'
|
13
13
|
require 'transpec/syntax/rspec_configure'
|
14
14
|
require 'transpec/syntax/should'
|
15
15
|
require 'transpec/syntax/should_receive'
|
16
|
-
require 'parser/current'
|
17
16
|
|
18
17
|
module Transpec
|
19
|
-
class
|
20
|
-
attr_reader :report, :invalid_context_errors
|
18
|
+
class Converter < BaseRewriter
|
19
|
+
attr_reader :configuration, :runtime_data, :report, :invalid_context_errors
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
@report = report
|
25
|
-
@invalid_context_errors = []
|
26
|
-
end
|
21
|
+
alias_method :convert_file!, :rewrite_file!
|
22
|
+
alias_method :convert, :rewrite
|
27
23
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
def initialize(configuration = nil, runtime_data = nil, report = nil)
|
25
|
+
@configuration = configuration || Configuration.new
|
26
|
+
@runtime_data = runtime_data
|
27
|
+
@report = report || Report.new
|
28
|
+
@invalid_context_errors = []
|
33
29
|
end
|
34
30
|
|
35
|
-
def
|
36
|
-
source_buffer = create_source_buffer(source, name)
|
37
|
-
ast = parse(source_buffer)
|
38
|
-
|
39
|
-
@source_rewriter = Parser::Source::Rewriter.new(source_buffer)
|
40
|
-
failed_overlapping_rewrite = false
|
41
|
-
@source_rewriter.diagnostics.consumer = proc do
|
42
|
-
failed_overlapping_rewrite = true
|
43
|
-
fail OverlappedRewriteError
|
44
|
-
end
|
45
|
-
|
31
|
+
def process(ast, source_rewriter)
|
46
32
|
AST::Scanner.scan(ast) do |node, ancestor_nodes|
|
47
|
-
dispatch_node(node, ancestor_nodes)
|
48
|
-
end
|
49
|
-
|
50
|
-
rewritten_source = @source_rewriter.process
|
51
|
-
|
52
|
-
if failed_overlapping_rewrite
|
53
|
-
rewriter = self.class.new(@configuration, @report)
|
54
|
-
rewritten_source = rewriter.rewrite(rewritten_source, name)
|
33
|
+
dispatch_node(node, ancestor_nodes, source_rewriter)
|
55
34
|
end
|
56
|
-
|
57
|
-
rewritten_source
|
58
|
-
end
|
59
|
-
|
60
|
-
def create_source_buffer(source, name)
|
61
|
-
source_buffer = Parser::Source::Buffer.new(name)
|
62
|
-
source_buffer.source = source
|
63
|
-
source_buffer
|
64
35
|
end
|
65
36
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
ast = parser.parse(source_buffer)
|
70
|
-
ast
|
71
|
-
end
|
72
|
-
|
73
|
-
def dispatch_node(node, ancestor_nodes)
|
74
|
-
Syntax.all.each do |syntax_class|
|
75
|
-
next unless syntax_class.target_node?(node)
|
37
|
+
def dispatch_node(node, ancestor_nodes, source_rewriter)
|
38
|
+
Syntax.standalone_syntaxes.each do |syntax_class|
|
39
|
+
next unless syntax_class.target_node?(node, @runtime_data)
|
76
40
|
|
77
41
|
syntax = syntax_class.new(
|
78
42
|
node,
|
79
43
|
ancestor_nodes,
|
80
|
-
|
44
|
+
source_rewriter,
|
45
|
+
@runtime_data,
|
81
46
|
@report
|
82
47
|
)
|
83
48
|
|
@@ -92,50 +57,60 @@ module Transpec
|
|
92
57
|
end
|
93
58
|
|
94
59
|
def process_should(should)
|
95
|
-
if @configuration.
|
60
|
+
if @configuration.convert_should?
|
96
61
|
should.expectize!(
|
97
62
|
@configuration.negative_form_of_to,
|
98
63
|
@configuration.parenthesize_matcher_arg?
|
99
64
|
)
|
100
65
|
end
|
66
|
+
|
67
|
+
if should.have_matcher && @configuration.convert_have_items?
|
68
|
+
should.have_matcher.convert_to_standard_expectation!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_expect(expect)
|
73
|
+
if expect.have_matcher && @configuration.convert_have_items?
|
74
|
+
expect.have_matcher.convert_to_standard_expectation!
|
75
|
+
end
|
101
76
|
end
|
102
77
|
|
103
78
|
def process_should_receive(should_receive)
|
104
79
|
if should_receive.useless_expectation?
|
105
|
-
if @configuration.
|
106
|
-
if @configuration.
|
80
|
+
if @configuration.convert_deprecated_method?
|
81
|
+
if @configuration.convert_stub?
|
107
82
|
should_receive.allowize_useless_expectation!(@configuration.negative_form_of_to)
|
108
83
|
else
|
109
84
|
should_receive.stubize_useless_expectation!
|
110
85
|
end
|
111
|
-
elsif @configuration.
|
86
|
+
elsif @configuration.convert_should_receive?
|
112
87
|
should_receive.expectize!(@configuration.negative_form_of_to)
|
113
88
|
end
|
114
|
-
elsif @configuration.
|
89
|
+
elsif @configuration.convert_should_receive?
|
115
90
|
should_receive.expectize!(@configuration.negative_form_of_to)
|
116
91
|
end
|
117
92
|
end
|
118
93
|
|
119
94
|
def process_double(double)
|
120
|
-
double.convert_to_double! if @configuration.
|
95
|
+
double.convert_to_double! if @configuration.convert_deprecated_method?
|
121
96
|
end
|
122
97
|
|
123
98
|
def process_method_stub(method_stub)
|
124
|
-
if @configuration.
|
99
|
+
if @configuration.convert_stub?
|
125
100
|
method_stub.allowize!
|
126
|
-
elsif @configuration.
|
127
|
-
method_stub.
|
101
|
+
elsif @configuration.convert_deprecated_method?
|
102
|
+
method_stub.convert_deprecated_method!
|
128
103
|
end
|
129
104
|
|
130
|
-
method_stub.remove_allowance_for_no_message! if @configuration.
|
105
|
+
method_stub.remove_allowance_for_no_message! if @configuration.convert_deprecated_method?
|
131
106
|
end
|
132
107
|
|
133
108
|
def process_be_close(be_close)
|
134
|
-
be_close.convert_to_be_within! if @configuration.
|
109
|
+
be_close.convert_to_be_within! if @configuration.convert_deprecated_method?
|
135
110
|
end
|
136
111
|
|
137
112
|
def process_raise_error(raise_error)
|
138
|
-
if @configuration.
|
113
|
+
if @configuration.convert_deprecated_method?
|
139
114
|
raise_error.remove_error_specification_with_negative_expectation!
|
140
115
|
end
|
141
116
|
end
|
@@ -151,20 +126,18 @@ module Transpec
|
|
151
126
|
end
|
152
127
|
|
153
128
|
def need_to_modify_expectation_syntax_configuration?(rspec_configure)
|
154
|
-
return false unless @configuration.
|
129
|
+
return false unless @configuration.convert_should?
|
155
130
|
rspec_configure.expectation_syntaxes == [:should]
|
156
131
|
rescue Syntax::RSpecConfigure::UnknownSyntaxError
|
157
132
|
false
|
158
133
|
end
|
159
134
|
|
160
135
|
def need_to_modify_mock_syntax_configuration?(rspec_configure)
|
161
|
-
return false if !@configuration.
|
162
|
-
!@configuration.
|
136
|
+
return false if !@configuration.convert_should_receive? &&
|
137
|
+
!@configuration.convert_stub?
|
163
138
|
rspec_configure.mock_syntaxes == [:should]
|
164
139
|
rescue Syntax::RSpecConfigure::UnknownSyntaxError
|
165
140
|
false
|
166
141
|
end
|
167
|
-
|
168
|
-
class OverlappedRewriteError < StandardError; end
|
169
142
|
end
|
170
143
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'transpec/base_rewriter'
|
4
|
+
require 'transpec/util'
|
5
|
+
require 'transpec/ast/scanner'
|
6
|
+
|
7
|
+
module Transpec
|
8
|
+
class DynamicAnalyzer
|
9
|
+
class Rewriter < BaseRewriter
|
10
|
+
include Util
|
11
|
+
|
12
|
+
def process(ast, source_rewriter)
|
13
|
+
# TODO: Currently multitheading is not considered...
|
14
|
+
clear_requests!
|
15
|
+
collect_requests(ast)
|
16
|
+
process_requests(source_rewriter)
|
17
|
+
end
|
18
|
+
|
19
|
+
def requests
|
20
|
+
@requests ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear_requests!
|
24
|
+
@requests = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_request(node, key, instance_eval_string, eval_target_type = :object)
|
28
|
+
unless EVAL_TARGET_TYPES.include?(eval_target_type)
|
29
|
+
fail "Target type must be any of #{EVAL_TARGET_TYPES}"
|
30
|
+
end
|
31
|
+
|
32
|
+
requests[node] ||= {}
|
33
|
+
requests[node][key] = [eval_target_type, instance_eval_string]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def collect_requests(ast)
|
39
|
+
AST::Scanner.scan(ast) do |node, ancestor_nodes|
|
40
|
+
Syntax.standalone_syntaxes.each do |syntax_class|
|
41
|
+
syntax_class.register_request_for_dynamic_analysis(node, self)
|
42
|
+
next unless syntax_class.target_node?(node)
|
43
|
+
syntax = syntax_class.new(node, ancestor_nodes)
|
44
|
+
syntax.register_request_for_dynamic_analysis(self)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_requests(source_rewriter)
|
50
|
+
requests.each do |node, analysis_codes|
|
51
|
+
inject_analysis_method(node, analysis_codes, source_rewriter)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def inject_analysis_method(node, analysis_codes, source_rewriter)
|
56
|
+
source_range = node.loc.expression
|
57
|
+
|
58
|
+
front = "#{ANALYSIS_METHOD}(("
|
59
|
+
rear = format(
|
60
|
+
'), self, %s, __FILE__, %d, %d)',
|
61
|
+
hash_literal(analysis_codes), source_range.begin_pos, source_range.end_pos
|
62
|
+
)
|
63
|
+
rear = "\n" + indentation_of_line(source_range.end) + rear if contain_here_document?(node)
|
64
|
+
|
65
|
+
parent_node = node.parent_node
|
66
|
+
|
67
|
+
if parent_node && parent_node.type == :block && parent_node.children.first.equal?(node)
|
68
|
+
source_range = node.parent_node.loc.expression
|
69
|
+
end
|
70
|
+
|
71
|
+
source_rewriter.insert_before(source_range, front)
|
72
|
+
source_rewriter.insert_after(source_range, rear)
|
73
|
+
rescue OverlappedRewriteError # rubocop:disable HandleExceptions
|
74
|
+
end
|
75
|
+
|
76
|
+
# Hash#inspect generates invalid literal with following example:
|
77
|
+
#
|
78
|
+
# > eval({ :predicate? => 1 }.inspect)
|
79
|
+
# SyntaxError: (eval):1: syntax error, unexpected =>
|
80
|
+
# {:predicate?=>1}
|
81
|
+
# ^
|
82
|
+
def hash_literal(hash)
|
83
|
+
literal = '{ '
|
84
|
+
|
85
|
+
hash.each_with_index do |(key, value), index|
|
86
|
+
literal << ', ' unless index == 0
|
87
|
+
literal << "#{key.inspect} => #{value.inspect}"
|
88
|
+
end
|
89
|
+
|
90
|
+
literal << ' }'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Transpec
|
6
|
+
class DynamicAnalyzer
|
7
|
+
class RuntimeData
|
8
|
+
attr_reader :hash
|
9
|
+
|
10
|
+
def initialize(hash = {})
|
11
|
+
@hash = hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](node)
|
15
|
+
@hash[node_id(node)]
|
16
|
+
end
|
17
|
+
|
18
|
+
def node_id(node)
|
19
|
+
source_range = node.loc.expression
|
20
|
+
source_buffer = source_range.source_buffer
|
21
|
+
absolute_path = File.expand_path(source_buffer.name)
|
22
|
+
relative_path = Pathname.new(absolute_path).relative_path_from(Pathname.pwd).to_s
|
23
|
+
[relative_path, source_range.begin_pos, source_range.end_pos].join('_')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|