metamorpher 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +541 -0
- data/Rakefile +23 -0
- data/examples/refactorings/rails/where_first/app.rb +50 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_mocks.rb +31 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_not_called_expectations.rb +14 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_strict_mocks.rb +27 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_to_find_by.rb +14 -0
- data/examples/refactorings/rails/where_first/sample_controller.rb +184 -0
- data/lib/metamorpher/builders/ast/builder.rb +50 -0
- data/lib/metamorpher/builders/ast/derivation_builder.rb +20 -0
- data/lib/metamorpher/builders/ast/greedy_variable_builder.rb +29 -0
- data/lib/metamorpher/builders/ast/literal_builder.rb +31 -0
- data/lib/metamorpher/builders/ast/variable_builder.rb +29 -0
- data/lib/metamorpher/builders/ast.rb +11 -0
- data/lib/metamorpher/builders/ruby/builder.rb +38 -0
- data/lib/metamorpher/builders/ruby/deriving_visitor.rb +13 -0
- data/lib/metamorpher/builders/ruby/ensuring_visitor.rb +13 -0
- data/lib/metamorpher/builders/ruby/term.rb +35 -0
- data/lib/metamorpher/builders/ruby/uppercase_constant_rewriter.rb +31 -0
- data/lib/metamorpher/builders/ruby/uppercase_rewriter.rb +28 -0
- data/lib/metamorpher/builders/ruby/variable_replacement_visitor.rb +32 -0
- data/lib/metamorpher/builders/ruby.rb +11 -0
- data/lib/metamorpher/drivers/parse_error.rb +5 -0
- data/lib/metamorpher/drivers/ruby.rb +78 -0
- data/lib/metamorpher/matcher/match.rb +26 -0
- data/lib/metamorpher/matcher/matching.rb +61 -0
- data/lib/metamorpher/matcher/no_match.rb +18 -0
- data/lib/metamorpher/matcher.rb +6 -0
- data/lib/metamorpher/refactorer/merger.rb +18 -0
- data/lib/metamorpher/refactorer/site.rb +29 -0
- data/lib/metamorpher/refactorer.rb +48 -0
- data/lib/metamorpher/rewriter/replacement.rb +18 -0
- data/lib/metamorpher/rewriter/rule.rb +38 -0
- data/lib/metamorpher/rewriter/substitution.rb +45 -0
- data/lib/metamorpher/rewriter/traverser.rb +26 -0
- data/lib/metamorpher/rewriter.rb +12 -0
- data/lib/metamorpher/support/map_at.rb +8 -0
- data/lib/metamorpher/terms/derived.rb +13 -0
- data/lib/metamorpher/terms/literal.rb +47 -0
- data/lib/metamorpher/terms/term.rb +40 -0
- data/lib/metamorpher/terms/variable.rb +17 -0
- data/lib/metamorpher/version.rb +3 -0
- data/lib/metamorpher/visitable/visitable.rb +7 -0
- data/lib/metamorpher/visitable/visitor.rb +21 -0
- data/lib/metamorpher.rb +30 -0
- data/metamorpher.gemspec +30 -0
- data/spec/integration/ast/builder_spec.rb +13 -0
- data/spec/integration/ast/matcher_spec.rb +132 -0
- data/spec/integration/ast/rewriter_spec.rb +138 -0
- data/spec/integration/ruby/builder_spec.rb +125 -0
- data/spec/integration/ruby/refactorer_spec.rb +192 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/helpers/silence_stream.rb +10 -0
- data/spec/support/matchers/have_matched_matcher.rb +22 -0
- data/spec/support/matchers/have_substitution_matcher.rb +15 -0
- data/spec/support/shared_examples/shared_examples_for_derivation_builders.rb +53 -0
- data/spec/support/shared_examples/shared_examples_for_greedy_variable_builders.rb +49 -0
- data/spec/support/shared_examples/shared_examples_for_literal_builders.rb +93 -0
- data/spec/support/shared_examples/shared_examples_for_variable_builders.rb +49 -0
- data/spec/unit/builders/ast/derivation_builder_spec.rb +5 -0
- data/spec/unit/builders/ast/greedy_variable_builder_spec.rb +9 -0
- data/spec/unit/builders/ast/literal_builder_spec.rb +9 -0
- data/spec/unit/builders/ast/variable_builder_spec.rb +9 -0
- data/spec/unit/builders/ruby/variable_replacement_visitor_spec.rb +48 -0
- data/spec/unit/drivers/ruby_spec.rb +91 -0
- data/spec/unit/matcher/matching_spec.rb +230 -0
- data/spec/unit/metamorpher_spec.rb +22 -0
- data/spec/unit/refactorer/merger_spec.rb +84 -0
- data/spec/unit/refactorer/site_spec.rb +52 -0
- data/spec/unit/rewriter/replacement_spec.rb +73 -0
- data/spec/unit/rewriter/substitution_spec.rb +97 -0
- data/spec/unit/rewriter/traverser_spec.rb +51 -0
- data/spec/unit/support/map_at_spec.rb +18 -0
- data/spec/unit/terms/literal_spec.rb +60 -0
- data/spec/unit/terms/term_spec.rb +59 -0
- data/spec/unit/visitable/visitor_spec.rb +35 -0
- metadata +269 -0
data/metamorpher.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'metamorpher/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "metamorpher"
|
8
|
+
spec.version = Metamorpher::VERSION
|
9
|
+
spec.authors = ["Louis Rose"]
|
10
|
+
spec.email = ["louis.rose@york.ac.uk"]
|
11
|
+
spec.description = %q{Provides structures that support program transformations, such as refactoring or program mutation.}
|
12
|
+
spec.summary = %q{A term rewriting library for transforming (Ruby) programs}
|
13
|
+
spec.homepage = "https://github.com/mutiny/metamorpher"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "attributable", "~> 0.1.0"
|
22
|
+
spec.add_runtime_dependency "parser", "~> 2.1.4"
|
23
|
+
spec.add_runtime_dependency "unparser", "~> 0.1.9"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.1.1"
|
27
|
+
spec.add_development_dependency "rspec", "~> 2.14.1"
|
28
|
+
spec.add_development_dependency "coveralls", "~> 0.7.0"
|
29
|
+
spec.add_development_dependency "rubocop", "~> 0.19.1"
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
describe Metamorpher do
|
4
|
+
subject { Metamorpher.builder }
|
5
|
+
|
6
|
+
before { Metamorpher.configure(builder: :ast) }
|
7
|
+
after { Metamorpher.configure(builder: :ruby) }
|
8
|
+
|
9
|
+
it_behaves_like "a literal builder"
|
10
|
+
it_behaves_like "a variable builder"
|
11
|
+
it_behaves_like "a greedy variable builder"
|
12
|
+
it_behaves_like "a derivation builder"
|
13
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
require "metamorpher/builders/ast"
|
3
|
+
|
4
|
+
describe "Matching" do
|
5
|
+
let(:builder) { Metamorpher::Builders::AST::Builder.new }
|
6
|
+
|
7
|
+
describe "literals" do
|
8
|
+
class SuccZeroMatcher
|
9
|
+
include Metamorpher::Matcher
|
10
|
+
include Metamorpher::Builders::AST
|
11
|
+
|
12
|
+
def pattern
|
13
|
+
builder.succ(0)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { SuccZeroMatcher.new }
|
18
|
+
|
19
|
+
it "should return a match for a matching expression" do
|
20
|
+
expression = builder.succ(0)
|
21
|
+
|
22
|
+
expect(subject.run(expression)).to have_matched(expression)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return no match for a non-matching expression" do
|
26
|
+
expression = builder.succ(1)
|
27
|
+
|
28
|
+
expect(subject.run(expression)).not_to have_matched
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "variables" do
|
33
|
+
class SuccMatcher
|
34
|
+
include Metamorpher::Matcher
|
35
|
+
include Metamorpher::Builders::AST
|
36
|
+
|
37
|
+
def pattern
|
38
|
+
builder.succ(builder.X)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
subject { SuccMatcher.new }
|
43
|
+
|
44
|
+
it "should return a match for matching expressions" do
|
45
|
+
expressions = [
|
46
|
+
builder.succ(0),
|
47
|
+
builder.succ(1),
|
48
|
+
builder.succ(:n),
|
49
|
+
builder.succ(builder.succ(:n))
|
50
|
+
]
|
51
|
+
|
52
|
+
expressions.each do |expression|
|
53
|
+
expect(subject.run(expression)).to have_matched(expression)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return no match for a non-matching expression" do
|
58
|
+
expression = builder.pred(1)
|
59
|
+
|
60
|
+
expect(subject.run(expression)).not_to have_matched
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "conditional variables" do
|
65
|
+
class DynamicFinderMatcher
|
66
|
+
include Metamorpher::Matcher
|
67
|
+
include Metamorpher::Builders::AST
|
68
|
+
|
69
|
+
def pattern
|
70
|
+
builder.literal!(
|
71
|
+
:".",
|
72
|
+
:User,
|
73
|
+
builder.METHOD { |literal| literal.name =~ /^find_by_/ }
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
subject { DynamicFinderMatcher.new }
|
79
|
+
|
80
|
+
it "should return a match for matching expression" do
|
81
|
+
expression = builder.literal!(:".", :User, :find_by_name)
|
82
|
+
|
83
|
+
expect(subject.run(expression)).to have_matched(expression)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should return no match when the condition is not satisfied" do
|
87
|
+
expression = builder.literal!(:".", :User, :find)
|
88
|
+
|
89
|
+
expect(subject.run(expression)).not_to have_matched
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "greedy variables" do
|
94
|
+
class MultiAddMatcher
|
95
|
+
include Metamorpher::Matcher
|
96
|
+
include Metamorpher::Builders::AST
|
97
|
+
|
98
|
+
def pattern
|
99
|
+
builder.add(
|
100
|
+
builder.ARGS_
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
subject { MultiAddMatcher.new }
|
106
|
+
|
107
|
+
it "should return a match for matching expressions" do
|
108
|
+
expressions = [
|
109
|
+
builder.add(1),
|
110
|
+
builder.add(1, 2),
|
111
|
+
builder.add(1, 2, 3),
|
112
|
+
builder.add(1, builder.succ(:n), 2)
|
113
|
+
]
|
114
|
+
|
115
|
+
expressions.each do |expression|
|
116
|
+
expect(subject.run(expression)).to have_matched(expression)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return no match when there are no arguments" do
|
121
|
+
expression = builder.add
|
122
|
+
|
123
|
+
expect(subject.run(expression)).not_to have_matched
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should return no match when names do not match" do
|
127
|
+
expression = builder.multiply(1, 2, 3)
|
128
|
+
|
129
|
+
expect(subject.run(expression)).not_to have_matched
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
require "metamorpher/builders/ast"
|
3
|
+
|
4
|
+
describe "Rewriting" do
|
5
|
+
let(:builder) { Metamorpher::Builders::AST::Builder.new }
|
6
|
+
|
7
|
+
describe "literals" do
|
8
|
+
class SuccZeroRewriter
|
9
|
+
include Metamorpher::Rewriter
|
10
|
+
include Metamorpher::Builders::AST
|
11
|
+
|
12
|
+
def pattern
|
13
|
+
builder.literal! :succ, 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def replacement
|
17
|
+
builder.literal! 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { SuccZeroRewriter.new }
|
22
|
+
|
23
|
+
describe "with reduce" do
|
24
|
+
it "should rewrite a matching expression" do
|
25
|
+
expression = builder.succ(0)
|
26
|
+
reduced = builder.literal!(1)
|
27
|
+
|
28
|
+
expect(subject.reduce(expression)).to eq(reduced)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should completely rewrite all matching expressions" do
|
32
|
+
expression = builder.add(builder.succ(0), builder.succ(0))
|
33
|
+
reduced = builder.add(builder.literal!(1), builder.literal!(1))
|
34
|
+
|
35
|
+
expect(subject.reduce(expression)).to eq(reduced)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should yield the original and replacement literals" do
|
39
|
+
expression = builder.add(builder.succ(0), builder.succ(0))
|
40
|
+
reduced = builder.add(builder.literal!(1), builder.literal!(1))
|
41
|
+
|
42
|
+
expect { |b| subject.reduce(expression, &b) }.to yield_successive_args(
|
43
|
+
[expression.children.first, reduced.children.first],
|
44
|
+
[expression.children.last, reduced.children.last]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not change a non-matching expression" do
|
49
|
+
expression = builder.succ(1)
|
50
|
+
|
51
|
+
expect(subject.reduce(expression)).to eq(expression)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "with apply" do
|
56
|
+
it "should rewrite a matching expression" do
|
57
|
+
expression = builder.succ(0)
|
58
|
+
reduced = builder.literal!(1)
|
59
|
+
|
60
|
+
expect(subject.apply(expression)).to eq(reduced)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should yield the original and replacement literal" do
|
64
|
+
expression = builder.succ(0)
|
65
|
+
reduced = builder.literal!(1)
|
66
|
+
|
67
|
+
expect { |b| subject.apply(expression, &b) }.to yield_with_args(expression, reduced)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should rewrite only the first matching expressions" do
|
71
|
+
expression = builder.add(builder.succ(0), builder.succ(0))
|
72
|
+
reduced = builder.add(builder.literal!(1), builder.succ(0))
|
73
|
+
|
74
|
+
expect(subject.apply(expression)).to eq(reduced)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should not change a non-matching expression" do
|
78
|
+
expression = builder.succ(1)
|
79
|
+
|
80
|
+
expect(subject.apply(expression)).to eq(expression)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "derivations" do
|
86
|
+
describe "from a single variable" do
|
87
|
+
class PluraliseRewriter
|
88
|
+
include Metamorpher::Rewriter
|
89
|
+
include Metamorpher::Builders::AST
|
90
|
+
|
91
|
+
def pattern
|
92
|
+
builder.SINGULAR
|
93
|
+
end
|
94
|
+
|
95
|
+
def replacement
|
96
|
+
builder.derivation! :singular do |singular|
|
97
|
+
builder.literal!(singular.name + "s")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
subject { PluraliseRewriter.new }
|
103
|
+
|
104
|
+
it "should rewrite using the derivation logic" do
|
105
|
+
expression = builder.literal! "dog"
|
106
|
+
reduced = builder.literal! "dogs"
|
107
|
+
|
108
|
+
expect(subject.reduce(expression)).to eq(reduced)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "from multiple variables" do
|
113
|
+
class RocketRewriter
|
114
|
+
include Metamorpher::Rewriter
|
115
|
+
include Metamorpher::Builders::AST
|
116
|
+
|
117
|
+
def pattern
|
118
|
+
builder.literal!(:"=>", builder.KEY, builder.VALUE)
|
119
|
+
end
|
120
|
+
|
121
|
+
def replacement
|
122
|
+
builder.derivation!(:key, :value) do |key, value|
|
123
|
+
builder.pair(key, value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
subject { RocketRewriter.new }
|
129
|
+
|
130
|
+
it "should rewrite using the derivation logic" do
|
131
|
+
expression = builder.literal! :"=>", :foo, :bar
|
132
|
+
reduced = builder.pair(:foo, :bar)
|
133
|
+
|
134
|
+
expect(subject.reduce(expression)).to eq(reduced)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
describe Metamorpher.builder do
|
4
|
+
let(:ast_builder) { Metamorpher::Builders::AST::Builder.new }
|
5
|
+
|
6
|
+
describe "when building literals" do
|
7
|
+
it "should produce literals from source" do
|
8
|
+
expect(subject.build("1 + 1")).to eq(
|
9
|
+
ast_builder.literal!(
|
10
|
+
:send,
|
11
|
+
ast_builder.int(1),
|
12
|
+
:+,
|
13
|
+
ast_builder.int(1)
|
14
|
+
)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should raise for invalid source" do
|
19
|
+
silence_stream(STDERR) do
|
20
|
+
expect { subject.build("1 + ") }.to raise_error(Metamorpher::Drivers::ParseError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "when building programs containing constants" do
|
26
|
+
it "should convert uppercase constants to variables" do
|
27
|
+
expect(subject.build("LEFT + RIGHT")).to eq(
|
28
|
+
ast_builder.literal!(
|
29
|
+
:send,
|
30
|
+
ast_builder.LEFT,
|
31
|
+
:+,
|
32
|
+
ast_builder.RIGHT
|
33
|
+
)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should convert uppercase messages to variables" do
|
38
|
+
expect(subject.build("User.METHOD")).to eq(
|
39
|
+
ast_builder.literal!(
|
40
|
+
:send,
|
41
|
+
ast_builder.literal!(:const, nil, :User),
|
42
|
+
ast_builder.METHOD
|
43
|
+
)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should convert uppercase constants ending with underscore to greedy variables" do
|
48
|
+
expect(subject.build("LEFT_ + RIGHT_")).to eq(
|
49
|
+
ast_builder.literal!(
|
50
|
+
:send,
|
51
|
+
ast_builder.LEFT_,
|
52
|
+
:+,
|
53
|
+
ast_builder.RIGHT_
|
54
|
+
)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not convert non-uppercase constants to variables" do
|
59
|
+
expect(subject.build("Left + RIGHt")).to eq(
|
60
|
+
ast_builder.literal!(
|
61
|
+
:send,
|
62
|
+
ast_builder.const(nil, :Left),
|
63
|
+
:+,
|
64
|
+
ast_builder.const(nil, :RIGHt)
|
65
|
+
)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when building programs with conditional variables" do
|
71
|
+
it "should create a conditional variable from a call to ensuring" do
|
72
|
+
built = subject.build("A").ensuring("A") { |n| n > 0 }
|
73
|
+
|
74
|
+
expect(built.name).to eq(:a)
|
75
|
+
expect(built.condition.call(1)).to be_true
|
76
|
+
expect(built.condition.call(-1)).to be_false
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should create several conditional variables from several calls to ensuring" do
|
80
|
+
built = subject
|
81
|
+
.build("A + B")
|
82
|
+
.ensuring("A") { |n| n > 0 }
|
83
|
+
.ensuring("B") { |n| n < 0 }
|
84
|
+
|
85
|
+
first_variable, _operator, last_variable = built.children
|
86
|
+
|
87
|
+
expect(first_variable.name).to eq(:a)
|
88
|
+
expect(first_variable.condition.call(1)).to be_true
|
89
|
+
expect(first_variable.condition.call(-1)).to be_false
|
90
|
+
|
91
|
+
expect(last_variable.name).to eq(:b)
|
92
|
+
expect(last_variable.condition.call(-1)).to be_true
|
93
|
+
expect(last_variable.condition.call(1)).to be_false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "when building programs with derivations" do
|
98
|
+
it "should create a derivation from a call to deriving" do
|
99
|
+
built = subject.build("PLURAL").deriving("PLURAL", "SINGULAR") do |constant|
|
100
|
+
subject.build(constant.children.last.name.to_s + "s")
|
101
|
+
end
|
102
|
+
|
103
|
+
expect(built.base).to eq([:singular])
|
104
|
+
expect(built.derivation.call(subject.build("dog"))).to eq(subject.build("dogs"))
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should create a derivation with multiple bases from a call to deriving" do
|
108
|
+
built = subject.build("HASH").deriving("HASH", "KEY", "VALUE") {}
|
109
|
+
|
110
|
+
expect(built.base).to eq([:key, :value])
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should create several derivations from several calls to deriving" do
|
114
|
+
built = subject
|
115
|
+
.build("NEW_FIRST; NEW_LAST")
|
116
|
+
.deriving("NEW_FIRST", "FIRST") {}
|
117
|
+
.deriving("NEW_LAST", "LAST") {}
|
118
|
+
|
119
|
+
first_derived, last_derived = built.children
|
120
|
+
|
121
|
+
expect(first_derived.base).to eq([:first])
|
122
|
+
expect(last_derived.base).to eq([:last])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
describe "Refactorer" do
|
4
|
+
describe "for Ruby" do
|
5
|
+
class UnnecessaryConditionalRefactorer
|
6
|
+
include Metamorpher::Refactorer
|
7
|
+
include Metamorpher::Builders::Ruby
|
8
|
+
|
9
|
+
def pattern
|
10
|
+
builder.build("if CONDITION then true else false end")
|
11
|
+
end
|
12
|
+
|
13
|
+
def replacement
|
14
|
+
builder.build("CONDITION")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
subject { UnnecessaryConditionalRefactorer.new }
|
19
|
+
|
20
|
+
let(:refactorable) do
|
21
|
+
"def run\n" \
|
22
|
+
" a = #{refactorable_code_for("some_predicate")}\n" \
|
23
|
+
" b = #{refactorable_code_for("some_other_predicate")}\n" \
|
24
|
+
"end"
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:refactored) do
|
28
|
+
"def run\n" \
|
29
|
+
" a = some_predicate\n" \
|
30
|
+
" b = some_other_predicate\n" \
|
31
|
+
"end"
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:not_refactorable) { "nothing_to_see_here = 42" }
|
35
|
+
|
36
|
+
describe "by calling refactor" do
|
37
|
+
describe "for code that can be refactored"do
|
38
|
+
it "should return the refactored code" do
|
39
|
+
expect(subject.refactor(refactorable)).to eq(refactored)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should yield for each refactoring site" do
|
43
|
+
expect { |b| subject.refactor(refactorable, &b) }.to yield_successive_args(
|
44
|
+
site_for(14..55, "some_predicate"),
|
45
|
+
site_for(63.. 110, "some_other_predicate")
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "for code that cannot be refactored" do
|
51
|
+
it "should return the original code" do
|
52
|
+
expect(subject.refactor(not_refactorable)).to eq(not_refactorable)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not yield when there are no refactoring site" do
|
56
|
+
expect { |b| subject.refactor(not_refactorable, &b) }.not_to yield_control
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "refactor_file" do
|
62
|
+
describe "for code that can be refactored" do
|
63
|
+
let(:refactorable_file) { create_temporary_ruby_file("refactorable", refactorable) }
|
64
|
+
|
65
|
+
it "should return the refactored code" do
|
66
|
+
expect(subject.refactor_file(refactorable_file)).to eq(refactored)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should yield for each refactoring site" do
|
70
|
+
expect { |b| subject.refactor_file(refactorable_file, &b) }.to yield_successive_args(
|
71
|
+
site_for(14..55, "some_predicate"),
|
72
|
+
site_for(63.. 110, "some_other_predicate")
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "for code that cannot be refactored" do
|
78
|
+
let(:not_refactorable_file) do
|
79
|
+
create_temporary_ruby_file("not_refactorable", not_refactorable)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return the original code" do
|
83
|
+
expect(subject.refactor_file(not_refactorable_file)).to eq(not_refactorable)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not yield when there are no refactoring site" do
|
87
|
+
expect { |b| subject.refactor_file(not_refactorable_file, &b) }.not_to yield_control
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "refactor_files" do
|
93
|
+
let(:refactorable_file) { create_temporary_ruby_file("refactorable", refactorable) }
|
94
|
+
let(:clone_of_refactorable_file) { create_temporary_ruby_file("refactorable", refactorable) }
|
95
|
+
|
96
|
+
let(:different_refactoring_sites_file) do
|
97
|
+
create_temporary_ruby_file(
|
98
|
+
"differently_refactorable",
|
99
|
+
"c = if yet_another_predicate then true else false end"
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
let(:not_refactorable_file) do
|
104
|
+
create_temporary_ruby_file(
|
105
|
+
"not_refactorable",
|
106
|
+
"nothing_to_see_here = 42"
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
let(:files) do
|
111
|
+
[
|
112
|
+
refactorable_file,
|
113
|
+
clone_of_refactorable_file,
|
114
|
+
different_refactoring_sites_file,
|
115
|
+
not_refactorable_file
|
116
|
+
]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should return a map of the paths and refactored code" do
|
120
|
+
refactored_files = {
|
121
|
+
refactorable_file => refactored,
|
122
|
+
clone_of_refactorable_file => refactored,
|
123
|
+
different_refactoring_sites_file => "c = yet_another_predicate",
|
124
|
+
not_refactorable_file => "nothing_to_see_here = 42"
|
125
|
+
}
|
126
|
+
|
127
|
+
expect(subject.refactor_files(files)).to eq(refactored_files)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should yield for each file" do
|
131
|
+
refactorable_file_details = [
|
132
|
+
refactorable_file,
|
133
|
+
refactored,
|
134
|
+
[
|
135
|
+
site_for(14..55, "some_predicate"),
|
136
|
+
site_for(63.. 110, "some_other_predicate")
|
137
|
+
]
|
138
|
+
]
|
139
|
+
|
140
|
+
clone_of_refactorable_file_details = [
|
141
|
+
clone_of_refactorable_file,
|
142
|
+
refactored,
|
143
|
+
[
|
144
|
+
site_for(14..55, "some_predicate"),
|
145
|
+
site_for(63.. 110, "some_other_predicate")
|
146
|
+
]
|
147
|
+
]
|
148
|
+
|
149
|
+
different_refactoring_sites_file_details = [
|
150
|
+
different_refactoring_sites_file,
|
151
|
+
"c = yet_another_predicate",
|
152
|
+
[
|
153
|
+
site_for(4..52, "yet_another_predicate")
|
154
|
+
]
|
155
|
+
]
|
156
|
+
|
157
|
+
not_refactorable_file_details = [
|
158
|
+
not_refactorable_file,
|
159
|
+
"nothing_to_see_here = 42",
|
160
|
+
[]
|
161
|
+
]
|
162
|
+
|
163
|
+
summary = []
|
164
|
+
subject.refactor_files(files) { |*args| summary << args }
|
165
|
+
|
166
|
+
expect(summary[0]).to eq(refactorable_file_details)
|
167
|
+
expect(summary[1]).to eq(clone_of_refactorable_file_details)
|
168
|
+
expect(summary[2]).to eq(different_refactoring_sites_file_details)
|
169
|
+
expect(summary[3]).to eq(not_refactorable_file_details)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def refactorable_code_for(predicate)
|
174
|
+
"if #{predicate} then true else false end"
|
175
|
+
end
|
176
|
+
|
177
|
+
def site_for(original_position, predicate)
|
178
|
+
Metamorpher::Refactorer::Site.new(
|
179
|
+
original_position,
|
180
|
+
refactorable_code_for(predicate),
|
181
|
+
predicate
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def create_temporary_ruby_file(filename, contents)
|
186
|
+
Tempfile.new([filename, ".rb"]).tap do |tempfile|
|
187
|
+
tempfile.write(contents)
|
188
|
+
tempfile.close
|
189
|
+
end.path
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "coveralls"
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
Dir["./spec/support/**/*.rb"].each { |f| require f }
|
5
|
+
|
6
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
7
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
8
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
9
|
+
# loaded once.
|
10
|
+
#
|
11
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.run_all_when_everything_filtered = true
|
15
|
+
config.filter_run :focus
|
16
|
+
|
17
|
+
# Run specs in random order to surface order dependencies. If you find an
|
18
|
+
# order dependency and want to debug it, you can fix the order by providing
|
19
|
+
# the seed, which is printed after each run.
|
20
|
+
# --seed 1234
|
21
|
+
config.order = "random"
|
22
|
+
|
23
|
+
config.expect_with :rspec do |c|
|
24
|
+
# Disable old "should" syntax for expressions
|
25
|
+
c.syntax = :expect
|
26
|
+
end
|
27
|
+
|
28
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true # Prepare for RSpec 3
|
29
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Taken from ActiveSupport: http://api.rubyonrails.org/classes/Kernel.html#method-i-silence_stream
|
2
|
+
def silence_stream(stream)
|
3
|
+
old_stream = stream.dup
|
4
|
+
stream.reopen(RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ ? "NUL:" : "/dev/null")
|
5
|
+
stream.sync = true
|
6
|
+
yield
|
7
|
+
ensure
|
8
|
+
stream.reopen(old_stream)
|
9
|
+
old_stream.close
|
10
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rspec/expectations"
|
2
|
+
|
3
|
+
RSpec::Matchers.define :have_matched do |expected_root|
|
4
|
+
match do |actual|
|
5
|
+
actual.matches? && (expected_root.nil? || actual.root == expected_root)
|
6
|
+
end
|
7
|
+
|
8
|
+
failure_message_for_should do |actual|
|
9
|
+
if actual.matches?
|
10
|
+
"expected a match against '#{expected_root.inspect}', " \
|
11
|
+
"but got a match against '#{actual.root.inspect}'"
|
12
|
+
else
|
13
|
+
"expected a match, but got none"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message_for_should_not do |actual|
|
18
|
+
if actual.matches?
|
19
|
+
"expected no match, but got a match against #{actual.root.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|