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
@@ -0,0 +1,84 @@
|
|
1
|
+
require "metamorpher/refactorer/merger"
|
2
|
+
require "metamorpher/refactorer/site"
|
3
|
+
|
4
|
+
module Metamorpher
|
5
|
+
module Refactorer
|
6
|
+
describe Merger do
|
7
|
+
let(:original) { "The quick brown fox jumps over the lazy dog." }
|
8
|
+
subject { Merger.new(original) }
|
9
|
+
|
10
|
+
describe "for a single replacement" do
|
11
|
+
it "should be able to rewrite at the start of the string" do
|
12
|
+
merged = merge(Site.new(0..2, "The", "A"))
|
13
|
+
|
14
|
+
expect(merged).to eq("A quick brown fox jumps over the lazy dog.")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be able to rewrite in the middle of the string" do
|
18
|
+
merged = merge(Site.new(4..8, "quick", "swift"))
|
19
|
+
|
20
|
+
expect(merged).to eq("The swift brown fox jumps over the lazy dog.")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be able to rewrite at the end of the string" do
|
24
|
+
merged = merge(Site.new(43..43, ".", "!"))
|
25
|
+
|
26
|
+
expect(merged).to eq("The quick brown fox jumps over the lazy dog!")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not alter the original string" do
|
30
|
+
merge(Site.new(0..2, "The", "A"))
|
31
|
+
|
32
|
+
expect(original).to eq("The quick brown fox jumps over the lazy dog.")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should yield before performing the replacement" do
|
36
|
+
replacement = Site.new(0..2, "The", "A")
|
37
|
+
|
38
|
+
expect { |b| merge(replacement, &b) }.to yield_with_args(replacement)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "for multiple replacements" do
|
43
|
+
it "should merge all replacements" do
|
44
|
+
merged = merge(
|
45
|
+
Site.new(4..8, "quick", "swift"),
|
46
|
+
Site.new(20..24, "jumps", "walks"),
|
47
|
+
Site.new(40..42, "dog", "cat")
|
48
|
+
)
|
49
|
+
|
50
|
+
expect(merged).to eq("The swift brown fox walks over the lazy cat.")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should determine position of all replacements based on the original string" do
|
54
|
+
merged = merge(
|
55
|
+
Site.new(4..8, "quick", "fast"),
|
56
|
+
Site.new(20..24, "jumps", "springs"),
|
57
|
+
Site.new(40..42, "dog", "cat")
|
58
|
+
)
|
59
|
+
|
60
|
+
# note that "fast" is 1 char shorter than its replacee "quick"
|
61
|
+
# and hence the second substring has to be repositioned by -1 char
|
62
|
+
# and that "springs" is 2 chars longer than its replacee "jumps"
|
63
|
+
# and hence the third substring has to be repositioned by +1 char (as -1 + +2 = +1)
|
64
|
+
|
65
|
+
expect(merged).to eq("The fast brown fox springs over the lazy cat.")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should yield before performing each replacement" do
|
69
|
+
replacements = [
|
70
|
+
Site.new(4..8, "quick", "fast"),
|
71
|
+
Site.new(20..24, "jumps", "springs"),
|
72
|
+
Site.new(40..42, "dog", "cat")
|
73
|
+
]
|
74
|
+
|
75
|
+
expect { |b| merge(*replacements, &b) }.to yield_successive_args(*replacements)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def merge(*replacements, &block)
|
80
|
+
subject.merge(*replacements, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "metamorpher/refactorer/site"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Refactorer
|
5
|
+
describe Site do
|
6
|
+
subject { Site.new(4..6, "foo", "bar") }
|
7
|
+
|
8
|
+
describe "slide" do
|
9
|
+
it "should return a replacement with the new position" do
|
10
|
+
expect(subject.slide(2).original_position).to eq(6..8)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not alter the code" do
|
14
|
+
expect(subject.slide(2).original_code).to eq("foo")
|
15
|
+
expect(subject.slide(2).refactored_code).to eq("bar")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should be chainable" do
|
19
|
+
expect(subject.slide(2).slide(10).original_position).to eq(16..18)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "merge_into" do
|
24
|
+
it "should apply change to argument" do
|
25
|
+
expect(subject.merge_into("foo foo")).to eq("foo bar")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise error when mergee is shorter than start of position" do
|
29
|
+
expect { subject.merge_into("foo") }.to raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not raise error when mergee is same length as start of position" do
|
33
|
+
expect { subject.merge_into("foo ") }.to_not raise_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "offset" do
|
38
|
+
it "should be 0 when position and value are the same size" do
|
39
|
+
expect(subject.offset).to eq(0)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should be -ve when position's size is larger than value's size" do
|
43
|
+
expect(Site.new(4..6, "foo", "b").offset).to eq(-2)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be +ve when position's size is smaller than value's size" do
|
47
|
+
expect(Site.new(4..6, "foo", "baaz").offset).to eq(1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "metamorpher/terms/variable"
|
2
|
+
require "metamorpher/terms/derived"
|
3
|
+
require "metamorpher/terms/literal"
|
4
|
+
|
5
|
+
module Metamorpher
|
6
|
+
module Terms
|
7
|
+
describe "replace" do
|
8
|
+
describe "with no children" do
|
9
|
+
subject { Literal.new(name: :top) }
|
10
|
+
|
11
|
+
it "should be possible to replace at the top" do
|
12
|
+
replacement = Literal.new(name: :root)
|
13
|
+
|
14
|
+
expect(subject.replace(subject.path, replacement)).to eq(replacement)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "with children" do
|
19
|
+
subject do
|
20
|
+
Literal.new(
|
21
|
+
name: :root,
|
22
|
+
children: [
|
23
|
+
Literal.new(name: :first_child),
|
24
|
+
Variable.new(name: :second_child),
|
25
|
+
Derived.new(name: :third_child)
|
26
|
+
]
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:replacement) { Literal.new(name: :root) }
|
31
|
+
|
32
|
+
it "should be possible to replace literal child" do
|
33
|
+
expect(subject.replace(subject.children[0].path, replacement)).to eq(
|
34
|
+
Literal.new(
|
35
|
+
name: :root,
|
36
|
+
children: [
|
37
|
+
replacement,
|
38
|
+
Variable.new(name: :second_child),
|
39
|
+
Derived.new(name: :third_child)
|
40
|
+
]
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be possible to replace literal child" do
|
46
|
+
expect(subject.replace(subject.children[1].path, replacement)).to eq(
|
47
|
+
Literal.new(
|
48
|
+
name: :root,
|
49
|
+
children: [
|
50
|
+
Literal.new(name: :first_child),
|
51
|
+
replacement,
|
52
|
+
Derived.new(name: :third_child)
|
53
|
+
]
|
54
|
+
)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should be possible to replace derived child" do
|
59
|
+
expect(subject.replace(subject.children[2].path, replacement)).to eq(
|
60
|
+
Literal.new(
|
61
|
+
name: :root,
|
62
|
+
children: [
|
63
|
+
Literal.new(name: :first_child),
|
64
|
+
Variable.new(name: :second_child),
|
65
|
+
replacement
|
66
|
+
]
|
67
|
+
)
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require "metamorpher/terms/variable"
|
2
|
+
require "metamorpher/terms/derived"
|
3
|
+
require "metamorpher/terms/literal"
|
4
|
+
|
5
|
+
module Metamorpher
|
6
|
+
module Terms
|
7
|
+
describe Variable do
|
8
|
+
subject { Variable.new(name: :type) }
|
9
|
+
|
10
|
+
it "should return the element of the substitution with the correct name" do
|
11
|
+
substitution = { type: Literal.new(name: :sub) }
|
12
|
+
expect(subject.substitute(substitution)).to eq(substitution[:type])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should raise if the substitution contains no value for variable's name" do
|
16
|
+
expect { subject.substitute({}) }.to raise_error(Rewriter::SubstitutionError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Derived do
|
21
|
+
subject do
|
22
|
+
Derived.new(
|
23
|
+
base: [:type],
|
24
|
+
derivation: -> (type) { Literal.new(name: type.name.reverse) }
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return the element of the substitution after calling derivation" do
|
29
|
+
substitution = { type: Literal.new(name: "reverse_me") }
|
30
|
+
|
31
|
+
expect(subject.substitute(substitution)).to eq(
|
32
|
+
Literal.new(name: "reverse_me".reverse)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise if the substitution contains no value for variable's name" do
|
37
|
+
expect { subject.substitute({}) }.to raise_error(Rewriter::SubstitutionError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe Literal do
|
42
|
+
describe "with no children" do
|
43
|
+
subject { Literal.new(name: :root) }
|
44
|
+
|
45
|
+
it "should return the original literal" do
|
46
|
+
expect(subject.substitute({})).to eq(subject)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "with children" do
|
51
|
+
subject do
|
52
|
+
Literal.new(
|
53
|
+
name: :root,
|
54
|
+
children: [
|
55
|
+
Literal.new(name: :child, children: [Variable.new(name: :foo)])
|
56
|
+
]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:child) { literal.children.first }
|
61
|
+
let(:grandchild) { child.children.first }
|
62
|
+
|
63
|
+
it "should return the original literal with substituted descendants" do
|
64
|
+
substitution = { foo: Literal.new(name: :bar) }
|
65
|
+
|
66
|
+
expect(subject.substitute(substitution)).to eq(
|
67
|
+
Literal.new(
|
68
|
+
name: :root,
|
69
|
+
children: [
|
70
|
+
Literal.new(
|
71
|
+
name: :child,
|
72
|
+
children: [Literal.new(name: :bar)]
|
73
|
+
)
|
74
|
+
]
|
75
|
+
)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should contain all elements if the substituted value is an array" do
|
80
|
+
substitution = { foo: [Literal.new(name: :bar), Literal.new(name: :baz)] }
|
81
|
+
|
82
|
+
expect(subject.substitute(substitution)).to eq(
|
83
|
+
Literal.new(
|
84
|
+
name: :root,
|
85
|
+
children: [
|
86
|
+
Literal.new(
|
87
|
+
name: :child,
|
88
|
+
children: [Literal.new(name: :bar), Literal.new(name: :baz)]
|
89
|
+
)
|
90
|
+
]
|
91
|
+
)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "metamorpher/rewriter/traverser"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Rewriter
|
5
|
+
describe Traverser do
|
6
|
+
describe "traversing a flat tree" do
|
7
|
+
let(:tree) { t(1, 2, 3, 4) }
|
8
|
+
|
9
|
+
it "correctly reports number of nodes" do
|
10
|
+
expect(subject.traverse(tree).size).to eq(5)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns nodes in left-to-right order" do
|
14
|
+
expect(subject.traverse(tree).take(5)).to eq([tree, 1, 2, 3, 4])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "traversing a skinny tree" do
|
19
|
+
let(:tree) { t(1, t(2, t(3))) }
|
20
|
+
|
21
|
+
it "correctly reports number of nodes" do
|
22
|
+
expect(subject.traverse(tree).size).to eq(6)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns nodes in outermost (root-to-leaves) order" do
|
26
|
+
expect(subject.traverse(tree).take(6)).to eq(
|
27
|
+
[tree, 1, tree.children.last, 2, tree.children.last.children.last, 3]
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "traversing the empty tree" do
|
33
|
+
let(:tree) { t }
|
34
|
+
|
35
|
+
it "correctly reports number of nodes" do
|
36
|
+
expect(subject.traverse(tree).size).to eq(1)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns only the original tree" do
|
40
|
+
expect(subject.traverse(tree).take(1)).to eq([tree])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def t(*children)
|
45
|
+
Tree.new(children)
|
46
|
+
end
|
47
|
+
|
48
|
+
class Tree < Struct.new(:children); end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "metamorpher/support/map_at"
|
2
|
+
|
3
|
+
describe Enumerable do
|
4
|
+
subject { %w(foo bar baz) }
|
5
|
+
|
6
|
+
describe "map_at" do
|
7
|
+
it "should return a new array with the specified replacement" do
|
8
|
+
expect(subject.map_at(0) { |w| w.reverse }).to eq(%w(oof bar baz))
|
9
|
+
expect(subject.map_at(1) { |w| w.reverse }).to eq(%w(foo rab baz))
|
10
|
+
expect(subject.map_at(2) { |w| w.reverse }).to eq(%w(foo bar zab))
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should raise when index is out of range" do
|
14
|
+
expect { subject.map_at(-1) }.to raise_error(IndexError)
|
15
|
+
expect { subject.map_at(3) }.to raise_error(IndexError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "metamorpher/terms/literal"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Terms
|
5
|
+
describe Literal do
|
6
|
+
describe "leaf? and branch?" do
|
7
|
+
let(:parent) { Literal.new(name: :parent, children: [Literal.new(name: :child)]) }
|
8
|
+
let(:child) { parent.children.first }
|
9
|
+
|
10
|
+
it "should correctly identify childless literals as leaves not branches" do
|
11
|
+
expect(child).to be_leaf
|
12
|
+
expect(child).not_to be_branch
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should correctly identify literals with children as branches not leaves" do
|
16
|
+
expect(parent).to be_branch
|
17
|
+
expect(parent).not_to be_leaf
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "child_of?" do
|
22
|
+
let(:parent) { Literal.new(name: :parent, children: [Literal.new(name: :child)]) }
|
23
|
+
let(:child) { parent.children.first }
|
24
|
+
|
25
|
+
it "should return true when parent's name is parameter" do
|
26
|
+
expect(child).to be_child_of(:parent)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return false when parent's name is not parameter" do
|
30
|
+
expect(child).not_to be_child_of(:root)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should false when literal has no parent" do
|
34
|
+
expect(parent).not_to be_child_of(:root)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "children younger than or equal to" do
|
39
|
+
let(:eldest) { Term.new(name: :eldest) }
|
40
|
+
let(:middle) { Term.new(name: :middle) }
|
41
|
+
let(:youngest) { Term.new(name: :youngest) }
|
42
|
+
|
43
|
+
subject { Literal.new(name: :parent, children: [eldest, middle, youngest]) }
|
44
|
+
|
45
|
+
it "should return all children not to the 'left' of argument" do
|
46
|
+
expect(subject.children_younger_than_or_equal_to(middle)).to eq([middle, youngest])
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return an only argument when argument is the youngest" do
|
50
|
+
expect(subject.children_younger_than_or_equal_to(youngest)).to eq([youngest])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should raise when argument is not a child" do
|
54
|
+
expect { subject.children_younger_than_or_equal_to(Term.new(name: :unknown)) }
|
55
|
+
.to raise_error(ArgumentError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "metamorpher/terms/term"
|
2
|
+
require "metamorpher/terms/literal"
|
3
|
+
|
4
|
+
module Metamorpher
|
5
|
+
module Terms
|
6
|
+
describe Term do
|
7
|
+
describe "path" do
|
8
|
+
let(:root) do
|
9
|
+
Literal.new(
|
10
|
+
name: :root,
|
11
|
+
children: [
|
12
|
+
Literal.new(
|
13
|
+
name: :child,
|
14
|
+
children: [
|
15
|
+
Term.new(name: :grandchild),
|
16
|
+
Term.new(name: :grandchild)
|
17
|
+
]
|
18
|
+
),
|
19
|
+
Literal.new(
|
20
|
+
name: :child,
|
21
|
+
children: [
|
22
|
+
Term.new(name: :grandchild),
|
23
|
+
Term.new(name: :grandchild),
|
24
|
+
Term.new(name: :grandchild)
|
25
|
+
]
|
26
|
+
)
|
27
|
+
]
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:first_child) { root.children.first }
|
32
|
+
let(:second_child) { root.children.last }
|
33
|
+
|
34
|
+
let(:leftmost_grandchild) { first_child.children.first }
|
35
|
+
let(:rightmost_grandchild) { second_child.children.last }
|
36
|
+
|
37
|
+
it "should return [] for root" do
|
38
|
+
expect(root.path).to eq([])
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return [0] for first child" do
|
42
|
+
expect(first_child.path).to eq([0])
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return [1] for second child" do
|
46
|
+
expect(second_child.path).to eq([1])
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return [0, 0] for leftmost grandchild" do
|
50
|
+
expect(leftmost_grandchild.path).to eq([0, 0])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return [1, 2] for rightmost grandchild" do
|
54
|
+
expect(rightmost_grandchild.path).to eq([1, 2])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "metamorpher/visitable/visitor"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Visitable
|
5
|
+
describe Visitor do
|
6
|
+
it "should call visitor based on the type of the visitee" do
|
7
|
+
subject = Visitor.new
|
8
|
+
subject.stub(visit_string: true)
|
9
|
+
subject.visit("foo")
|
10
|
+
expect(subject).to have_received(:visit_string)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should call visitor on ancestor of visitee if necessary" do
|
14
|
+
subject = Visitor.new
|
15
|
+
subject.stub(visit_numeric: true)
|
16
|
+
subject.visit(3) # Fixnum < Integer < Numeric
|
17
|
+
expect(subject).to have_received(:visit_numeric)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should call visitor based on unqualified type of the visitee" do
|
21
|
+
subject = Visitor.new
|
22
|
+
subject.stub(visit_dummy: true)
|
23
|
+
subject.visit(Dummy.new)
|
24
|
+
expect(subject).to have_received(:visit_dummy)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise if no appropriate visit method is defined" do
|
28
|
+
subject = Visitor.new
|
29
|
+
expect { subject.visit("foo") }.to raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Dummy; end
|
34
|
+
end
|
35
|
+
end
|