metamorpher 0.1.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 +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
|