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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +16 -0
  5. data/.travis.yml +3 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +541 -0
  9. data/Rakefile +23 -0
  10. data/examples/refactorings/rails/where_first/app.rb +50 -0
  11. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_mocks.rb +31 -0
  12. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_not_called_expectations.rb +14 -0
  13. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_strict_mocks.rb +27 -0
  14. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_to_find_by.rb +14 -0
  15. data/examples/refactorings/rails/where_first/sample_controller.rb +184 -0
  16. data/lib/metamorpher/builders/ast/builder.rb +50 -0
  17. data/lib/metamorpher/builders/ast/derivation_builder.rb +20 -0
  18. data/lib/metamorpher/builders/ast/greedy_variable_builder.rb +29 -0
  19. data/lib/metamorpher/builders/ast/literal_builder.rb +31 -0
  20. data/lib/metamorpher/builders/ast/variable_builder.rb +29 -0
  21. data/lib/metamorpher/builders/ast.rb +11 -0
  22. data/lib/metamorpher/builders/ruby/builder.rb +38 -0
  23. data/lib/metamorpher/builders/ruby/deriving_visitor.rb +13 -0
  24. data/lib/metamorpher/builders/ruby/ensuring_visitor.rb +13 -0
  25. data/lib/metamorpher/builders/ruby/term.rb +35 -0
  26. data/lib/metamorpher/builders/ruby/uppercase_constant_rewriter.rb +31 -0
  27. data/lib/metamorpher/builders/ruby/uppercase_rewriter.rb +28 -0
  28. data/lib/metamorpher/builders/ruby/variable_replacement_visitor.rb +32 -0
  29. data/lib/metamorpher/builders/ruby.rb +11 -0
  30. data/lib/metamorpher/drivers/parse_error.rb +5 -0
  31. data/lib/metamorpher/drivers/ruby.rb +78 -0
  32. data/lib/metamorpher/matcher/match.rb +26 -0
  33. data/lib/metamorpher/matcher/matching.rb +61 -0
  34. data/lib/metamorpher/matcher/no_match.rb +18 -0
  35. data/lib/metamorpher/matcher.rb +6 -0
  36. data/lib/metamorpher/refactorer/merger.rb +18 -0
  37. data/lib/metamorpher/refactorer/site.rb +29 -0
  38. data/lib/metamorpher/refactorer.rb +48 -0
  39. data/lib/metamorpher/rewriter/replacement.rb +18 -0
  40. data/lib/metamorpher/rewriter/rule.rb +38 -0
  41. data/lib/metamorpher/rewriter/substitution.rb +45 -0
  42. data/lib/metamorpher/rewriter/traverser.rb +26 -0
  43. data/lib/metamorpher/rewriter.rb +12 -0
  44. data/lib/metamorpher/support/map_at.rb +8 -0
  45. data/lib/metamorpher/terms/derived.rb +13 -0
  46. data/lib/metamorpher/terms/literal.rb +47 -0
  47. data/lib/metamorpher/terms/term.rb +40 -0
  48. data/lib/metamorpher/terms/variable.rb +17 -0
  49. data/lib/metamorpher/version.rb +3 -0
  50. data/lib/metamorpher/visitable/visitable.rb +7 -0
  51. data/lib/metamorpher/visitable/visitor.rb +21 -0
  52. data/lib/metamorpher.rb +30 -0
  53. data/metamorpher.gemspec +30 -0
  54. data/spec/integration/ast/builder_spec.rb +13 -0
  55. data/spec/integration/ast/matcher_spec.rb +132 -0
  56. data/spec/integration/ast/rewriter_spec.rb +138 -0
  57. data/spec/integration/ruby/builder_spec.rb +125 -0
  58. data/spec/integration/ruby/refactorer_spec.rb +192 -0
  59. data/spec/spec_helper.rb +29 -0
  60. data/spec/support/helpers/silence_stream.rb +10 -0
  61. data/spec/support/matchers/have_matched_matcher.rb +22 -0
  62. data/spec/support/matchers/have_substitution_matcher.rb +15 -0
  63. data/spec/support/shared_examples/shared_examples_for_derivation_builders.rb +53 -0
  64. data/spec/support/shared_examples/shared_examples_for_greedy_variable_builders.rb +49 -0
  65. data/spec/support/shared_examples/shared_examples_for_literal_builders.rb +93 -0
  66. data/spec/support/shared_examples/shared_examples_for_variable_builders.rb +49 -0
  67. data/spec/unit/builders/ast/derivation_builder_spec.rb +5 -0
  68. data/spec/unit/builders/ast/greedy_variable_builder_spec.rb +9 -0
  69. data/spec/unit/builders/ast/literal_builder_spec.rb +9 -0
  70. data/spec/unit/builders/ast/variable_builder_spec.rb +9 -0
  71. data/spec/unit/builders/ruby/variable_replacement_visitor_spec.rb +48 -0
  72. data/spec/unit/drivers/ruby_spec.rb +91 -0
  73. data/spec/unit/matcher/matching_spec.rb +230 -0
  74. data/spec/unit/metamorpher_spec.rb +22 -0
  75. data/spec/unit/refactorer/merger_spec.rb +84 -0
  76. data/spec/unit/refactorer/site_spec.rb +52 -0
  77. data/spec/unit/rewriter/replacement_spec.rb +73 -0
  78. data/spec/unit/rewriter/substitution_spec.rb +97 -0
  79. data/spec/unit/rewriter/traverser_spec.rb +51 -0
  80. data/spec/unit/support/map_at_spec.rb +18 -0
  81. data/spec/unit/terms/literal_spec.rb +60 -0
  82. data/spec/unit/terms/term_spec.rb +59 -0
  83. data/spec/unit/visitable/visitor_spec.rb +35 -0
  84. metadata +269 -0
@@ -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
@@ -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