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
|
@@ -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
|