metamorpher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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