transpec 0.0.1
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 +7 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +6 -0
- data/Gemfile +9 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +27 -0
- data/bin/transpec +8 -0
- data/lib/transpec/ast/scanner.rb +51 -0
- data/lib/transpec/ast/scope_stack.rb +76 -0
- data/lib/transpec/cli.rb +162 -0
- data/lib/transpec/configuration.rb +40 -0
- data/lib/transpec/git.rb +24 -0
- data/lib/transpec/rewriter.rb +109 -0
- data/lib/transpec/syntax/double.rb +21 -0
- data/lib/transpec/syntax/matcher.rb +60 -0
- data/lib/transpec/syntax/method_stub.rb +142 -0
- data/lib/transpec/syntax/send_node_syntax.rb +39 -0
- data/lib/transpec/syntax/should.rb +49 -0
- data/lib/transpec/syntax/should_receive.rb +120 -0
- data/lib/transpec/syntax.rb +58 -0
- data/lib/transpec/util.rb +50 -0
- data/lib/transpec/version.rb +14 -0
- data/lib/transpec.rb +17 -0
- data/spec/.rubocop.yml +19 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/spec_spec.rb +54 -0
- data/spec/support/file_helper.rb +25 -0
- data/spec/support/shared_context.rb +63 -0
- data/spec/transpec/ast/scanner_spec.rb +177 -0
- data/spec/transpec/ast/scope_stack_spec.rb +94 -0
- data/spec/transpec/cli_spec.rb +290 -0
- data/spec/transpec/configuration_spec.rb +52 -0
- data/spec/transpec/git_spec.rb +85 -0
- data/spec/transpec/rewriter_spec.rb +203 -0
- data/spec/transpec/syntax/double_spec.rb +88 -0
- data/spec/transpec/syntax/matcher_spec.rb +407 -0
- data/spec/transpec/syntax/method_stub_spec.rb +386 -0
- data/spec/transpec/syntax/should_receive_spec.rb +286 -0
- data/spec/transpec/syntax/should_spec.rb +262 -0
- data/spec/transpec/util_spec.rb +48 -0
- data/transpec.gemspec +32 -0
- metadata +233 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c98c4880892b0c69338d5a58b145fb47e6efbc14
|
4
|
+
data.tar.gz: 27d7e201e95c233d9e13887a9d22642e99034952
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 265a268cf2858c97c587c561c58cb0b129951d6f8b69acec44ac652457ad9eb11ff01e25d506914f34661239e792c37b0e9443448a92f5587a219b43af37e4cd
|
7
|
+
data.tar.gz: 901f0dcc5290387aec5377f3514c61134199351000621520090d35aecd46be78063239a47c66f7ca3cd414ef6182f7aba845b067761cce0ee6774e999bc4aef3
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :rspec, all_after_pass: true, all_on_start: true, keep_failed: true do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
watch(%r{^spec/support/.+\.rb$}) { "spec" }
|
9
|
+
end
|
10
|
+
|
11
|
+
guard :rubocop do
|
12
|
+
watch(%r{.+\.rb$})
|
13
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Yuji Nakayama
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Transpec
|
2
|
+
|
3
|
+
**Transpec** automatically converts your specs into latest [RSpec](http://rspec.info/) syntax with static analysis.
|
4
|
+
|
5
|
+
See the following pages for new RSpec syntax:
|
6
|
+
|
7
|
+
* [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax)
|
8
|
+
* [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
|
9
|
+
* [Myron Marston » The Plan for RSpec 3](http://myronmars.to/n/dev-blog/2013/07/the-plan-for-rspec-3)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
```bash
|
14
|
+
$ gem install transpec
|
15
|
+
```
|
16
|
+
|
17
|
+
## Basic Usage
|
18
|
+
|
19
|
+
Run `transpec` with no arguments in your project directory:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ transpec
|
23
|
+
```
|
24
|
+
|
25
|
+
This will inspect and overwrite all spec files in the `spec` directory.
|
26
|
+
|
27
|
+
For more information, please see the help with `--help` option.
|
28
|
+
|
29
|
+
**TODO:** Add more description
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
1. Fork it
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
37
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
namespace :ci do
|
7
|
+
desc "#{Rake::Task['spec'].comment} for CI environment"
|
8
|
+
task :spec do
|
9
|
+
ENV['CI'] = 'true'
|
10
|
+
|
11
|
+
ENV['CI_REPORTS'] = 'spec/reports'
|
12
|
+
require 'ci/reporter/rake/rspec'
|
13
|
+
Rake::Task['ci:setup:rspec'].invoke
|
14
|
+
|
15
|
+
Rake::Task['spec'].invoke
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Check code style with RuboCop'
|
20
|
+
task :style do
|
21
|
+
sh('rubocop')
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Run RSpec and RuboCop'
|
25
|
+
task all: [:spec, :style]
|
26
|
+
|
27
|
+
task default: :all
|
data/bin/transpec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Transpec
|
4
|
+
module AST
|
5
|
+
class Scanner
|
6
|
+
SCOPE_TYPES = [:module, :class, :sclass, :def, :defs, :block].freeze
|
7
|
+
|
8
|
+
attr_reader :scope_stack
|
9
|
+
|
10
|
+
def self.scan(origin_node, &block)
|
11
|
+
instance = new(&block)
|
12
|
+
instance.scan(origin_node, true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(&block)
|
16
|
+
@callback = block
|
17
|
+
@ancestor_nodes = []
|
18
|
+
@scope_stack = ScopeStack.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def scan(origin_node, yield_origin_node = false)
|
22
|
+
return unless origin_node
|
23
|
+
|
24
|
+
yield_node(origin_node) if yield_origin_node
|
25
|
+
|
26
|
+
@ancestor_nodes.push(origin_node)
|
27
|
+
@scope_stack.push_scope(origin_node) if scope_node?(origin_node)
|
28
|
+
|
29
|
+
origin_node.children.each_with_index do |child, index|
|
30
|
+
next unless child.is_a?(Parser::AST::Node)
|
31
|
+
node = child
|
32
|
+
yield_node(node)
|
33
|
+
scan(node)
|
34
|
+
end
|
35
|
+
|
36
|
+
@scope_stack.pop_scope if scope_node?(origin_node)
|
37
|
+
@ancestor_nodes.pop
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def yield_node(node)
|
43
|
+
@callback.call(node, @ancestor_nodes, @scope_stack.in_example_group_context?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def scope_node?(node)
|
47
|
+
SCOPE_TYPES.include?(node.type)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Transpec
|
4
|
+
module AST
|
5
|
+
class ScopeStack < Array
|
6
|
+
EXAMPLE_GROUP_METHOD_NAMES = [
|
7
|
+
:describe, :context,
|
8
|
+
:shared_examples, :shared_context, :share_examples_for, :shared_examples_for
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def push_scope(node)
|
12
|
+
push(scope_type(node))
|
13
|
+
end
|
14
|
+
|
15
|
+
def pop_scope
|
16
|
+
pop
|
17
|
+
end
|
18
|
+
|
19
|
+
def in_example_group_context?
|
20
|
+
if include?(:example_group)
|
21
|
+
scopes_in_example_group = inner_scopes_in_scope(:example_group)
|
22
|
+
return false if include_class_scope?(scopes_in_example_group)
|
23
|
+
include_method_or_block_scope?(scopes_in_example_group)
|
24
|
+
elsif include?(:rspec_configure)
|
25
|
+
scopes_in_rspec_configure = inner_scopes_in_scope(:rspec_configure)
|
26
|
+
return false if include_class_scope?(scopes_in_rspec_configure)
|
27
|
+
include_method_or_block_scope?(scopes_in_rspec_configure)
|
28
|
+
elsif first == :def
|
29
|
+
scopes_in_method = self[1..-1]
|
30
|
+
!include_class_scope?(scopes_in_method)
|
31
|
+
elsif include?(:module)
|
32
|
+
scopes_in_module = inner_scopes_in_scope(:module)
|
33
|
+
return false if include_class_scope?(scopes_in_module)
|
34
|
+
scopes_in_module.include?(:def)
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def scope_type(node)
|
43
|
+
return node.type unless node.type == :block
|
44
|
+
|
45
|
+
send_node = node.children.first
|
46
|
+
receiver_node, method_name, *_ = *send_node
|
47
|
+
|
48
|
+
if Util.const_name(receiver_node) == 'RSpec' && method_name == :configure
|
49
|
+
:rspec_configure
|
50
|
+
elsif receiver_node
|
51
|
+
node.type
|
52
|
+
elsif EXAMPLE_GROUP_METHOD_NAMES.include?(method_name)
|
53
|
+
:example_group
|
54
|
+
else
|
55
|
+
node.type
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def inner_scopes_in_scope(scope_type)
|
60
|
+
index = rindex(scope_type)
|
61
|
+
return nil unless index
|
62
|
+
self[Range.new(index + 1, -1)]
|
63
|
+
end
|
64
|
+
|
65
|
+
def include_class_scope?(scopes)
|
66
|
+
!(scopes & [:class, :sclass]).empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
def include_method_or_block_scope?(scopes)
|
70
|
+
# TODO: Should validate whether the method taking the block is RSpec's
|
71
|
+
# special method. (e.g. #subject, #let, #before, #after)
|
72
|
+
!(scopes & [:def, :block]).empty?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/transpec/cli.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
module Transpec
|
7
|
+
class CLI
|
8
|
+
CONFIG_ATTRS_FOR_CLI_TYPES = {
|
9
|
+
expect_to_matcher: :convert_to_expect_to_matcher=,
|
10
|
+
expect_to_receive: :convert_to_expect_to_receive=,
|
11
|
+
allow_to_receive: :convert_to_allow_to_receive=,
|
12
|
+
deprecated: :replace_deprecated_method=
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_reader :configuration, :forced
|
16
|
+
alias_method :forced?, :forced
|
17
|
+
|
18
|
+
def self.run(args = ARGV)
|
19
|
+
new.run(args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@configuration = Configuration.new
|
24
|
+
@forced = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def run(args)
|
28
|
+
non_option_args = parse_options(args)
|
29
|
+
|
30
|
+
fail_if_should_not_continue!
|
31
|
+
|
32
|
+
paths = non_option_args
|
33
|
+
|
34
|
+
if paths.empty?
|
35
|
+
if Dir.exists?('spec')
|
36
|
+
paths = ['spec']
|
37
|
+
else
|
38
|
+
fail ArgumentError, 'Specify target files or directories.'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
target_files(paths).each do |file_path|
|
43
|
+
puts "Processing #{file_path}"
|
44
|
+
rewriter = Rewriter.new(@configuration)
|
45
|
+
rewriter.rewrite_file!(file_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO: Print summary
|
49
|
+
|
50
|
+
true
|
51
|
+
rescue => error
|
52
|
+
warn error.message
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:disable MethodLength
|
57
|
+
def parse_options(args)
|
58
|
+
parser = OptionParser.new
|
59
|
+
parser.banner = "Usage: transpec [options] [files or directories]\n\n"
|
60
|
+
|
61
|
+
parser.on(
|
62
|
+
'--force',
|
63
|
+
'Force processing even if the current Git',
|
64
|
+
'repository is not clean.'
|
65
|
+
) do
|
66
|
+
@forced = true
|
67
|
+
end
|
68
|
+
|
69
|
+
parser.on(
|
70
|
+
'-d', '--disable TYPE[,TYPE...]',
|
71
|
+
'Disable specific conversions.',
|
72
|
+
'Available conversion types:',
|
73
|
+
' expect_to_matcher (from `should`)',
|
74
|
+
' expect_to_receive (from `should_receive`)',
|
75
|
+
' allow_to_receive (from `stub`)',
|
76
|
+
' deprecated (e.g. from `stub!` to `stub`)',
|
77
|
+
'These are all enabled by default.'
|
78
|
+
) do |types|
|
79
|
+
types.split(',').each do |type|
|
80
|
+
config_attr = CONFIG_ATTRS_FOR_CLI_TYPES[type.to_sym]
|
81
|
+
fail ArgumentError, "Unknown conversion type #{type.inspect}" unless config_attr
|
82
|
+
@configuration.send(config_attr, false)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
parser.on(
|
87
|
+
'-n', '--negative-form FORM',
|
88
|
+
'Specify negative form of `to` that is used',
|
89
|
+
'in `expect(...).to` syntax.',
|
90
|
+
'Either `not_to` or `to_not`.',
|
91
|
+
'Default: not_to'
|
92
|
+
) do |form|
|
93
|
+
@configuration.negative_form_of_to = form
|
94
|
+
end
|
95
|
+
|
96
|
+
parser.on(
|
97
|
+
'-p', '--no-parentheses-matcher-arg',
|
98
|
+
'Suppress parenthesizing argument of matcher',
|
99
|
+
'when converting operator to non-operator',
|
100
|
+
'in `expect` syntax. Note that it will be',
|
101
|
+
'parenthesized even if this option is',
|
102
|
+
'specified when parentheses are necessary to',
|
103
|
+
'keep the meaning of the expression.',
|
104
|
+
'By default, arguments of the following',
|
105
|
+
'operator matchers will be parenthesized.',
|
106
|
+
' `== 10` to `eq(10)`',
|
107
|
+
' `=~ /pattern/` to `match(/pattern/)`',
|
108
|
+
' `=~ [1, 2]` to `match_array([1, 2])`'
|
109
|
+
) do
|
110
|
+
@configuration.parenthesize_matcher_arg = false
|
111
|
+
end
|
112
|
+
|
113
|
+
parser.on('--version', 'Show Transpec version.') do
|
114
|
+
puts Version.to_s
|
115
|
+
exit
|
116
|
+
end
|
117
|
+
|
118
|
+
args = args.dup
|
119
|
+
parser.parse!(args)
|
120
|
+
args
|
121
|
+
end
|
122
|
+
# rubocop:enable MethodLength
|
123
|
+
|
124
|
+
def target_files(paths)
|
125
|
+
paths.reduce([]) do |file_paths, path|
|
126
|
+
if File.directory?(path)
|
127
|
+
file_paths.concat(ruby_files_in_directory(path))
|
128
|
+
elsif File.file?(path)
|
129
|
+
file_paths << path
|
130
|
+
elsif !File.exists?(path) # rubocop:disable FavorUnlessOverNegatedIf
|
131
|
+
fail ArgumentError, "No such file or directory #{path.inspect}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def ruby_files_in_directory(directory_path)
|
139
|
+
ruby_file_paths = []
|
140
|
+
|
141
|
+
Find.find(directory_path) do |path|
|
142
|
+
next unless File.file?(path)
|
143
|
+
next unless File.extname(path) == '.rb'
|
144
|
+
ruby_file_paths << path
|
145
|
+
end
|
146
|
+
|
147
|
+
ruby_file_paths
|
148
|
+
end
|
149
|
+
|
150
|
+
def fail_if_should_not_continue!
|
151
|
+
return if forced?
|
152
|
+
|
153
|
+
# TODO: Check each repository of target files / directories,
|
154
|
+
# not only the current working directory.
|
155
|
+
return unless Git.command_available?
|
156
|
+
return unless Git.inside_of_repository?
|
157
|
+
return if Git.clean?
|
158
|
+
|
159
|
+
fail 'The current Git repository is not clean. Aborting.'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Transpec
|
4
|
+
class Configuration
|
5
|
+
NEGATIVE_FORMS_OF_TO = ['not_to', 'to_not'].freeze
|
6
|
+
|
7
|
+
PREDICATES = [
|
8
|
+
:convert_to_expect_to_matcher,
|
9
|
+
:convert_to_expect_to_receive,
|
10
|
+
:convert_to_allow_to_receive,
|
11
|
+
:replace_deprecated_method,
|
12
|
+
:parenthesize_matcher_arg
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
PREDICATES.each do |predicate|
|
16
|
+
attr_accessor predicate
|
17
|
+
alias_method predicate.to_s + '?', predicate
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :negative_form_of_to
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
PREDICATES.each do |predicate|
|
24
|
+
instance_variable_set('@' + predicate.to_s, true)
|
25
|
+
end
|
26
|
+
|
27
|
+
self.negative_form_of_to = 'not_to'
|
28
|
+
end
|
29
|
+
|
30
|
+
def negative_form_of_to=(form)
|
31
|
+
unless NEGATIVE_FORMS_OF_TO.include?(form.to_s)
|
32
|
+
message = 'Negative form of "to" must be either '
|
33
|
+
message << NEGATIVE_FORMS_OF_TO.map(&:inspect).join(' or ')
|
34
|
+
fail ArgumentError, message
|
35
|
+
end
|
36
|
+
|
37
|
+
@negative_form_of_to = form.to_s.freeze
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/transpec/git.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Transpec
|
4
|
+
module Git
|
5
|
+
GIT = 'git'
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def command_available?
|
10
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path|
|
11
|
+
git_path = File.join(path, GIT)
|
12
|
+
File.exists?(git_path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def inside_of_repository?
|
17
|
+
system("#{GIT} rev-parse --is-inside-work-tree > /dev/null 2> /dev/null")
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean?
|
21
|
+
`#{GIT} status --porcelain`.empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'parser'
|
4
|
+
require 'parser/current'
|
5
|
+
|
6
|
+
module Transpec
|
7
|
+
class Rewriter
|
8
|
+
def initialize(configuration = Configuration.new)
|
9
|
+
@configuration = configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def rewrite_file!(file_path)
|
13
|
+
source = File.read(file_path)
|
14
|
+
rewritten_source = rewrite(source, file_path)
|
15
|
+
File.write(file_path, rewritten_source)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rewrite(source, name = '(string)')
|
19
|
+
source_buffer = create_source_buffer(source, name)
|
20
|
+
ast = parse(source_buffer)
|
21
|
+
|
22
|
+
@source_rewriter = Parser::Source::Rewriter.new(source_buffer)
|
23
|
+
failed_overlapping_rewrite = false
|
24
|
+
@source_rewriter.diagnostics.consumer = proc { failed_overlapping_rewrite = true }
|
25
|
+
|
26
|
+
AST::Scanner.scan(ast) do |node, ancestor_nodes, in_example_group_context|
|
27
|
+
dispatch_node(node, ancestor_nodes, in_example_group_context)
|
28
|
+
end
|
29
|
+
|
30
|
+
rewritten_source = @source_rewriter.process
|
31
|
+
|
32
|
+
if failed_overlapping_rewrite
|
33
|
+
rewriter = self.class.new(@configuration)
|
34
|
+
rewritten_source = rewriter.rewrite(rewritten_source, name)
|
35
|
+
end
|
36
|
+
|
37
|
+
rewritten_source
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_source_buffer(source, name)
|
41
|
+
source_buffer = Parser::Source::Buffer.new(name)
|
42
|
+
source_buffer.source = source
|
43
|
+
source_buffer
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse(source_buffer)
|
47
|
+
parser = Parser::CurrentRuby.new
|
48
|
+
ast = parser.parse(source_buffer)
|
49
|
+
ast
|
50
|
+
end
|
51
|
+
|
52
|
+
def dispatch_node(node, ancestor_nodes, in_example_group_context)
|
53
|
+
Syntax.all.each do |syntax_class|
|
54
|
+
next unless syntax_class.target_node?(node)
|
55
|
+
|
56
|
+
syntax = syntax_class.new(
|
57
|
+
node,
|
58
|
+
ancestor_nodes,
|
59
|
+
in_example_group_context,
|
60
|
+
@source_rewriter
|
61
|
+
)
|
62
|
+
|
63
|
+
handler_name = "process_#{syntax_class.snake_case_name}"
|
64
|
+
send(handler_name, syntax)
|
65
|
+
|
66
|
+
break
|
67
|
+
end
|
68
|
+
rescue Syntax::NotInExampleGroupContextError => error
|
69
|
+
warn_not_in_example_group_context_error(error)
|
70
|
+
end
|
71
|
+
|
72
|
+
def warn_not_in_example_group_context_error(error)
|
73
|
+
warn error.message
|
74
|
+
warn format(
|
75
|
+
'%s:%d:%s',
|
76
|
+
error.source_buffer.name,
|
77
|
+
error.source_range.line,
|
78
|
+
error.source_range.source_line
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_should(should)
|
83
|
+
if @configuration.convert_to_expect_to_matcher?
|
84
|
+
should.expectize!(
|
85
|
+
@configuration.negative_form_of_to,
|
86
|
+
@configuration.parenthesize_matcher_arg?
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def process_should_receive(should_receive)
|
92
|
+
if @configuration.convert_to_expect_to_receive?
|
93
|
+
should_receive.expectize!(@configuration.negative_form_of_to)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_double(double)
|
98
|
+
double.replace_deprecated_method! if @configuration.replace_deprecated_method?
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_method_stub(method_stub)
|
102
|
+
if @configuration.convert_to_allow_to_receive?
|
103
|
+
method_stub.allowize!
|
104
|
+
elsif @configuration.replace_deprecated_method?
|
105
|
+
method_stub.replace_deprecated_method!
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Transpec
|
4
|
+
class Syntax
|
5
|
+
class Double < Syntax
|
6
|
+
include SendNodeSyntax
|
7
|
+
|
8
|
+
def self.target_node?(node)
|
9
|
+
return false unless node.type == :send
|
10
|
+
receiver_node, method_name, *_ = *node
|
11
|
+
return false if receiver_node
|
12
|
+
[:double, :mock, :stub].include?(method_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace_deprecated_method!
|
16
|
+
return if method_name == :double
|
17
|
+
@source_rewriter.replace(selector_range, 'double')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|