metamorpher 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d16003d62069703538fe00d09537221c30a8b50
4
- data.tar.gz: 2be984b2778ce18e4085795895d23df4d5407f9d
3
+ metadata.gz: 07d706738851b486c6fc3a58140b5046a1569a6e
4
+ data.tar.gz: 77e1d643b5816016c0d054954af1d9f7f9e6bbae
5
5
  SHA512:
6
- metadata.gz: 2a9745cbf4788895da970497de3b8f15e28c9866b74c899e468a36a13f86bf804400be34db79e20f0ae35437fcd0487e0f4ffd032c2744bb3193050897595e32
7
- data.tar.gz: bf3e827ea391a666826a816d4338b8bd4afbebd426f53e7977ded9bf3238f5371c41dac6e950440d066269244c5677e54fb98ae14fe664d14b82728ebbbc7753
6
+ metadata.gz: 6a15ddbe30e40c36ec9d4ea427d6e5558650b00d6aabb907d2954cd5327abe5effc232355fc249fe0faa59191fe9546dadb2846ae4ef7e7f56796b52446ed3fe
7
+ data.tar.gz: 063d0829bb4dbc7de52d3a50b78599d752d395a94f0b3e5cb9dd9067dfb3f8eae389ec6d3380db1a6b865f9b13f2fb9fc75c0bf1fa7606c427de4cfb4ed3d115
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .DS_Store
1
2
  *.gem
2
3
  *.rbc
3
4
  .bundle
data/README.md CHANGED
@@ -36,7 +36,7 @@ This simple example is short, but terse! To fully understand it, you might now w
36
36
 
37
37
  * [Practicalities](#practicalities) - Information on how to use metamorpher to manipulate (Ruby) programs:
38
38
  * [Building Ruby terms](#building-ruby-terms) - how to concisely create terms that represent Ruby programs.
39
- * [Refactorers](#refactorers) - how to use rewriters to refactor Ruby programs.
39
+ * [Transformers](#transformers) - how to use rewriters to transform Ruby programs.
40
40
 
41
41
 
42
42
  ## Fundamentals
@@ -54,7 +54,7 @@ The primary data structure used for [rewriting](#rewriters) and for [matching](#
54
54
  * Greedy variable - a variable that is bound to a set of subterms during [matching](#matchers).
55
55
  * Derivation - a placeholder node, which is replaced during [rewriting](#rewriters).
56
56
 
57
- To simplify the construction of terms, metamorpher provides the `Metamorpher::Builders::AST::Builder` class, which is demonstrated below.
57
+ To simplify the construction of terms, metamorpher provides the `Metamorpher::Builders::AST::Builder` class, which is demonstrated below.
58
58
 
59
59
  ```ruby
60
60
  require "metamorpher"
@@ -71,7 +71,7 @@ builder.derivation! :singular do |singular, builder|
71
71
  builder.literal!(singular.name + "s")
72
72
  end
73
73
  # [SINGULAR] -> ...
74
-
74
+
75
75
  builder.derivation! :key, :value do |key, value, builder|
76
76
  builder.pair(key, value)
77
77
  end
@@ -91,7 +91,7 @@ The builder provides a method missing shorthand for constructing literals, varia
91
91
 
92
92
  ```ruby
93
93
  builder.succ # => succ
94
- builder.N # => N
94
+ builder.N # => N
95
95
  builder.N_ # => N+
96
96
  ```
97
97
 
@@ -137,7 +137,7 @@ require "metamorpher"
137
137
  class SuccZeroMatcher
138
138
  include Metamorpher::Matcher
139
139
  include Metamorpher::Builders::AST
140
-
140
+
141
141
  def pattern
142
142
  builder.succ(0)
143
143
  end
@@ -145,7 +145,7 @@ end
145
145
 
146
146
  expression = Metamorpher.builder.succ(0) # => succ(0)
147
147
  result = SuccZeroMatcher.new.run(expression)
148
- # => <Metamorpher::Matcher::Match root=succ(0), substitution={}>
148
+ # => <Metamorpher::Matcher::Match root=succ(0), substitution={}>
149
149
  result.matches? # => true
150
150
 
151
151
  expression = Metamorpher.builder.succ(1) # => succ(1)
@@ -164,7 +164,7 @@ For example, suppose we wish to match expressions of the form `succ(X)` where X
164
164
  class SuccMatcher
165
165
  include Metamorpher::Matcher
166
166
  include Metamorpher::Builders::AST
167
-
167
+
168
168
  def pattern
169
169
  builder.succ(builder.X)
170
170
  end
@@ -172,21 +172,21 @@ end
172
172
 
173
173
  expression = Metamorpher.builder.succ(0) # => succ(0)
174
174
  SuccMatcher.new.run(expression)
175
- # => <Metamorpher::Matcher::Match root=succ(0), substitution={:x=>0}>
175
+ # => <Metamorpher::Matcher::Match root=succ(0), substitution={:x=>0}>
176
176
 
177
177
  expression = Metamorpher.builder.succ(1) # => succ(1)
178
178
  SuccMatcher.new.run(expression)
179
179
  # => <Metamorpher::Matcher::Match root=succ(0), substitution={:x=>1}>
180
-
180
+
181
181
  expression = Metamorpher.builder.succ(:n) # => succ(n)
182
182
  SuccMatcher.new.run(expression)
183
183
  # => <Metamorpher::Matcher::Match root=succ(n), substitution={:x=>n}>
184
184
 
185
185
  expression = Metamorpher.builder.succ(Metamorpher.builder.succ(:n)) # => succ(succ(n))
186
186
  SuccMatcher.new.run(expression)
187
- # => <Metamorpher::Matcher::Match root=succ(succ(n)), substitution={:x=>succ(n)}>
187
+ # => <Metamorpher::Matcher::Match root=succ(succ(n)), substitution={:x=>succ(n)}>
188
188
  ```
189
-
189
+
190
190
  #### Conditional variables
191
191
 
192
192
  By default, a variable matches any literal. Matching is more powerful when variables are able to match only those literals that satisfy some condition. Metamorpher provides conditional variables for this purpose.
@@ -197,7 +197,7 @@ For example, suppose that we wish to create a matcher that only matches method c
197
197
  class DynamicFinderMatcher
198
198
  include Metamorpher::Matcher
199
199
  include Metamorpher::Builders::AST
200
-
200
+
201
201
  def pattern
202
202
  builder.literal!(
203
203
  :".",
@@ -209,7 +209,7 @@ end
209
209
 
210
210
  expression = Metamorpher.builder.literal!(:".", :User, :find_by_name) # => .(User, find_by_name)
211
211
  DynamicFinderMatcher.new.run(expression)
212
- # => #<Metamorpher::Matcher::Match root=.(User, find_by_name), substitution={:method=>find_by_name}>
212
+ # => #<Metamorpher::Matcher::Match root=.(User, find_by_name), substitution={:method=>find_by_name}>
213
213
 
214
214
  expression = Metamorpher.builder.literal!(:".", :User, :find) # => .(User, find)
215
215
  DynamicFinderMatcher.new.run(expression)
@@ -226,7 +226,7 @@ For example, suppose that we wish to create a matcher that works for an expressi
226
226
  class MultiAddMatcher
227
227
  include Metamorpher::Matcher
228
228
  include Metamorpher::Builders::AST
229
-
229
+
230
230
  def pattern
231
231
  builder.add(
232
232
  builder.ARGS_
@@ -235,10 +235,10 @@ class MultiAddMatcher
235
235
  end
236
236
 
237
237
  MultiAddMatcher.new.run(Metamorpher.builder.add(1,2))
238
- # => #<Metamorpher::Matcher::Match root=add(1,2), substitution={:args=>[1, 2]}>
238
+ # => #<Metamorpher::Matcher::Match root=add(1,2), substitution={:args=>[1, 2]}>
239
239
 
240
240
  MultiAddMatcher.new.run(Metamorpher.builder.add(1,2,3))
241
- # => #<Metamorpher::Matcher::Match root=add(1,2,3), substitution={:args=>[1, 2, 3]}>
241
+ # => #<Metamorpher::Matcher::Match root=add(1,2,3), substitution={:args=>[1, 2, 3]}>
242
242
  ```
243
243
 
244
244
  ### Rewriters
@@ -253,11 +253,11 @@ require "metamorpher"
253
253
  class SuccZeroRewriter
254
254
  include Metamorpher::Rewriter
255
255
  include Metamorpher::Builders::AST
256
-
256
+
257
257
  def pattern
258
258
  builder.literal! :succ, 0
259
259
  end
260
-
260
+
261
261
  def replacement
262
262
  builder.literal! 1
263
263
  end
@@ -300,7 +300,7 @@ SuccZeroRewriter.new.reduce(expression) do |matching, rewritten|
300
300
  end
301
301
  # About to replace 'succ(0)' at position [0] with '1'
302
302
  # About to replace 'succ(0)' at position [1] with '1'
303
- # =>
303
+ # =>
304
304
  ```
305
305
 
306
306
  #### Derivations
@@ -313,11 +313,11 @@ For example, suppose that we wish to create a rewriter that pluralises any liter
313
313
  class PluraliseRewriter
314
314
  include Metamorpher::Rewriter
315
315
  include Metamorpher::Builders::AST
316
-
316
+
317
317
  def pattern
318
318
  builder.SINGULAR
319
319
  end
320
-
320
+
321
321
  def replacement
322
322
  builder.derivation! :singular do |singular|
323
323
  builder.literal!(singular.name + "s")
@@ -398,11 +398,23 @@ builder
398
398
  # [KEY, VALUE] -> ...
399
399
  ```
400
400
 
401
- ### Refactorers
401
+ ### Transformers
402
+
403
+ Transformers are [rewriters](#rewriters) that are specialised for rewriting program source code. A transformer parses a program's source code, rewrites the source code, and returns the unparsed, rewritten source code.
404
+
405
+ Metamorpher provides two types of transformers:
406
+
407
+ * Refactorer - produces a single transformed program that contains all rewritings together
408
+ * Mutator - produces a set of transformed programs where each program contains a single rewriting
409
+
410
+ For example, for the pattern `1 + 1`, the replacement `2` and the input program `(1 + 1) * (1 + 1)`:
402
411
 
403
- Refactorers are [rewriters](#rewriters) that are specialised for rewriting program source code. A refactorer parses a program's source code, rewrites the source code, and returns the unparsed, rewritten source code.
412
+ * A refactorer will produce `(2) * (2)`
413
+ * A mutator will produce `[(2) * (1 + 1), (1 + 1) * (2)].`
404
414
 
405
- Metamorpher provides the `Metamorpher::Refactorer` module for constructing classes that perform refactorings. Include it, specify a `pattern` and a `replacement`, and then call `refactor(src)`:
415
+ Metamorpher provides the `Metamorpher::Refactorer` and `Metamorpher::Mutator` modules for constructing classes that perform refactorings or mutations.
416
+
417
+ To construct a refactorer, include the relevant module, specify a `pattern` and a `replacement`, and then call `refactor(src)`:
406
418
 
407
419
  ```ruby
408
420
  require "metamorpher"
@@ -420,11 +432,42 @@ class UnnecessaryConditionalRefactorer
420
432
  end
421
433
  end
422
434
 
423
- program = "a = if some_predicate then true else false end"
435
+ program = "a = if some_predicate then true else false end; " \
436
+ "b = if another_predicate then true else false end"
424
437
  UnnecessaryConditionalRefactorer.new.refactor(program)
425
- # => "a = some_predicate"
438
+ # => "a = some_predicate; b = another_predicate"
426
439
  ```
427
440
 
441
+ Similarly to construct a mutator, include the relevant module, specify a `pattern` and an array of `replacements`, and then call `mutate(src)`:
442
+
443
+ ```ruby
444
+ require "metamorpher"
445
+
446
+ class LessThanMutator
447
+ include Metamorpher::Mutator
448
+ include Metamorpher::Builders::Ruby
449
+
450
+ def pattern
451
+ builder.build("A < B")
452
+ end
453
+
454
+ def replacements
455
+ builder.build_all("A > B", "A == B")
456
+ end
457
+ end
458
+
459
+ program = "a = foo < bar; b = bar < baz"
460
+ LessThanMutator.new.mutate(program)
461
+ # => [
462
+ # "a = foo > bar; b = bar < baz",
463
+ # "a = foo == bar; b = bar < baz",
464
+ # "a = foo < bar; b = bar > baz",
465
+ # "a = foo < bar; b = bar == baz"
466
+ # ]
467
+ ```
468
+
469
+ The remainder of this section discusses only refactorers, but note that mutators have all of the same functionality as refactorers (but provides methods prefixed with `mutate` rather than `refactor`).
470
+
428
471
  The `refactor` method can optionally take a block, which is called immediately before the matching code is replaced with the refactored code:
429
472
 
430
473
  ```ruby
@@ -445,11 +488,11 @@ The `Metamorpher::Refactorer` module also defines a `refactor_file(path)` method
445
488
 
446
489
  ```ruby
447
490
  path = File.expand_path("refactorable.rb", "/Users/louis/code/mutiny")
448
- # => "/Users/louis/code/mutiny/refactorable.rb"
491
+ # => "/Users/louis/code/mutiny/refactorable.rb"
449
492
 
450
493
  UnnecessaryConditionalRefactorer.new.refactor_file(path)
451
494
  # => ... (refactored code)
452
-
495
+
453
496
  UnnecessaryConditionalRefactorer.new.refactor_file(path) do |refactoring|
454
497
  # works just like the block passed to refactor
455
498
  end
@@ -460,7 +503,7 @@ You might prefer the `refactor_files(paths)` method, if you'd like to refactor s
460
503
 
461
504
  ```ruby
462
505
  paths = Dir.glob(File.expand_path(File.join("**", "*.rb"), "/Users/louis/code/mutiny"))
463
- # => ["/Users/louis/code/mutiny/lib/mutiny.rb", ...]
506
+ # => ["/Users/louis/code/mutiny/lib/mutiny.rb", ...]
464
507
 
465
508
  # Note that refactor_files returns a Hash rather than a String
466
509
  UnnecessaryConditionalRefactorer.new.refactor_files(paths)
@@ -489,22 +532,22 @@ By default, `Metamorpher::Refactorer` assumes that you wish to refactor Ruby pro
489
532
  ```ruby
490
533
  class JavaRefactorer
491
534
  include Metamorpher::Refactorer
492
-
535
+
493
536
  def driver
494
537
  YourTool::MetamorpherDrivers::Java.new
495
538
  end
496
-
539
+
497
540
  def pattern
498
541
  ...
499
542
  end
500
-
543
+
501
544
  def replacement
502
545
  ...
503
546
  end
504
547
  end
505
548
  ```
506
549
 
507
- #### Examples
550
+ #### Examples
508
551
 
509
552
  Below are a few examples of using metamorpher to perform refactorings on Ruby code.
510
553
 
@@ -558,21 +601,21 @@ class RefactorWhereFirstToFindBy
558
601
  builder.build(create_hash_string(keys, values))
559
602
  end
560
603
  end
561
-
604
+
562
605
  private
563
-
606
+
564
607
  # Extracts an array of attributes from the name of a dynamic
565
608
  # finder, such as find_by_asset_id_and_object_path.
566
609
  def attributes_from_dynamic_finder(dynamic_finder)
567
610
  dynamic_finder["find_by_".length..-1].split("_and_")
568
611
  end
569
-
612
+
570
613
  # Builds a string representation of a hash from a set of keys
571
614
  # and a corresponding set of values
572
615
  def create_hash_string(keys, values)
573
616
  "{" + create_pairs_string(keys, values) + "}"
574
617
  end
575
-
618
+
576
619
  def create_pairs_string(keys, values)
577
620
  keys
578
621
  .zip(values)
data/RELEASES.md ADDED
@@ -0,0 +1,14 @@
1
+ # Release History
2
+
3
+ ## v0.2.0 (9 May 2015)
4
+ * Provide support for mutators which are similar to refactorers but produce multiple transformed programs
5
+
6
+ ## v0.1.1 (8 May 2015)
7
+ * Update dependencies
8
+
9
+ ## v0.1.0 (6 May 2014)
10
+ Provide support for:
11
+ * A generic mechanism for specifying terms
12
+ * Language-independent term matching and rewriting
13
+ * Language-specific refactoring
14
+ * A Ruby builder that enables program matching, rewriting and refactoring of Ruby programs.
data/lib/metamorpher.rb CHANGED
@@ -6,6 +6,7 @@ require "metamorpher/support/map_at"
6
6
  require "metamorpher/matcher"
7
7
  require "metamorpher/rewriter"
8
8
  require "metamorpher/refactorer"
9
+ require "metamorpher/mutator"
9
10
 
10
11
  module Metamorpher
11
12
  def self.builder
@@ -11,6 +11,10 @@ module Metamorpher
11
11
  decorate(rewrite(parse(source)))
12
12
  end
13
13
 
14
+ def build_all(*sources)
15
+ sources.map(&method(:build))
16
+ end
17
+
14
18
  private
15
19
 
16
20
  def decorate(term)
@@ -1,29 +1,15 @@
1
1
  require "metamorpher/builders/ast"
2
+ require "metamorpher/builders/ruby/uppercase_rewriter"
2
3
 
3
4
  module Metamorpher
4
5
  module Builders
5
6
  module Ruby
6
- class UppercaseConstantRewriter
7
+ class UppercaseConstantRewriter < UppercaseRewriter
7
8
  include Metamorpher::Rewriter
8
9
  include Metamorpher::Builders::AST
9
10
 
10
11
  def pattern
11
- builder.const(
12
- builder.literal!(nil),
13
- builder.VARIABLE_TO_BE { |v| v.name && v.name.to_s[/^[A-Z_]*$/] }
14
- )
15
- end
16
-
17
- def replacement
18
- builder.derivation!(:variable_to_be) do |variable_to_be, builder|
19
- name = variable_to_be.name.to_s
20
-
21
- if name.end_with?("_")
22
- builder.greedy_variable! name.chomp("_").downcase.to_sym
23
- else
24
- builder.variable! name.downcase.to_sym
25
- end
26
- end
12
+ builder.const(builder.literal!(nil), super)
27
13
  end
28
14
  end
29
15
  end
@@ -0,0 +1,15 @@
1
+ require "metamorpher/transformer/base"
2
+ require "metamorpher/transformer/merger"
3
+
4
+ module Metamorpher
5
+ module Mutator
6
+ include Transformer::Base
7
+ alias_method :mutate, :transform
8
+ alias_method :mutate_file, :transform_file
9
+ alias_method :mutate_files, :transform_files
10
+
11
+ def merge(src, replacements, &block)
12
+ replacements.map { |replacement| Transformer::Merger.new(src).merge(replacement, &block) }
13
+ end
14
+ end
15
+ end
@@ -1,47 +1,21 @@
1
- require "metamorpher/refactorer/merger"
2
- require "metamorpher/refactorer/site"
3
- require "metamorpher/rewriter/rule"
4
- require "metamorpher/drivers/ruby"
1
+ require "metamorpher/transformer/base"
2
+ require "metamorpher/transformer/merger"
5
3
 
6
4
  module Metamorpher
7
5
  module Refactorer
8
- def refactor(src, &block)
9
- literal = driver.parse(src)
10
- replacements = reduce_to_replacements(src, literal)
11
- Merger.new(src).merge(*replacements, &block)
12
- end
13
-
14
- def refactor_file(path, &block)
15
- refactor(File.read(path), &block)
16
- end
6
+ include Transformer::Base
7
+ alias_method :refactor, :transform
8
+ alias_method :refactor_file, :transform_file
9
+ alias_method :refactor_files, :transform_files
17
10
 
18
- def refactor_files(paths, &block)
19
- paths.each_with_object({}) do |path, result|
20
- changes = []
21
- result[path] = refactor_file(path) { |change| changes << change }
22
- block.call(path, result[path], changes) if block
23
- end
24
- end
25
-
26
- def driver
27
- @driver ||= Metamorpher::Drivers::Ruby.new
11
+ def merge(src, replacements, &block)
12
+ Transformer::Merger.new(src).merge(*replacements, &block)
28
13
  end
29
14
 
30
15
  private
31
16
 
32
- def reduce_to_replacements(src, literal)
33
- [].tap do |replacements|
34
- rule.reduce(literal) do |original, rewritten|
35
- original_position = driver.source_location_for(original)
36
- original_code = src[original_position]
37
- refactored_code = driver.unparse(rewritten)
38
- replacements << Site.new(original_position, original_code, refactored_code)
39
- end
40
- end
41
- end
42
-
43
- def rule
44
- @rule ||= Rewriter::Rule.new(pattern: pattern, replacement: replacement)
17
+ def replacements
18
+ @replacements ||= [replacement]
45
19
  end
46
20
  end
47
21
  end
@@ -0,0 +1,51 @@
1
+ require "metamorpher/transformer/merger"
2
+ require "metamorpher/transformer/site"
3
+ require "metamorpher/rewriter/rule"
4
+ require "metamorpher/drivers/ruby"
5
+
6
+ module Metamorpher
7
+ module Transformer
8
+ module Base
9
+ def transform(src, &block)
10
+ literal = driver.parse(src)
11
+ replacements = reduce_to_replacements(src, literal)
12
+ merge(src, replacements, &block)
13
+ end
14
+
15
+ def transform_file(path, &block)
16
+ transform(File.read(path), &block)
17
+ end
18
+
19
+ def transform_files(paths, &block)
20
+ paths.each_with_object({}) do |path, result|
21
+ changes = []
22
+ result[path] = transform_file(path) { |change| changes << change }
23
+ block.call(path, result[path], changes) if block
24
+ end
25
+ end
26
+
27
+ def driver
28
+ @driver ||= Metamorpher::Drivers::Ruby.new
29
+ end
30
+
31
+ private
32
+
33
+ def reduce_to_replacements(src, literal)
34
+ [].tap do |replacements|
35
+ rules.each do |rule| # FIXME : change to inject?
36
+ rule.reduce(literal) do |original, rewritten|
37
+ original_position = driver.source_location_for(original)
38
+ original_code = src[original_position]
39
+ transformed_code = driver.unparse(rewritten)
40
+ replacements << Site.new(original_position, original_code, transformed_code)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def rules
47
+ @rules ||= replacements.map { |r| Rewriter::Rule.new(pattern: pattern, replacement: r) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,7 +1,7 @@
1
1
  require "attributable"
2
2
 
3
3
  module Metamorpher
4
- module Refactorer
4
+ module Transformer
5
5
  Merger = Struct.new(:original) do
6
6
  def merge(*replacements, &block)
7
7
  original.dup.tap do |merged|
@@ -1,7 +1,7 @@
1
1
  require "attributable"
2
2
 
3
3
  module Metamorpher
4
- module Refactorer
4
+ module Transformer
5
5
  Site = Struct.new(:original_position, :original_code, :refactored_code) do
6
6
  def slide(offset)
7
7
  new_position = (original_position.begin + offset)..(original_position.end + offset)
@@ -1,3 +1,3 @@
1
1
  module Metamorpher
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,215 @@
1
+ require "metamorpher"
2
+ require "tempfile"
3
+
4
+ describe "Mutator" do
5
+ describe "for Ruby" do
6
+ class LessThanMutator
7
+ include Metamorpher::Mutator
8
+ include Metamorpher::Builders::Ruby
9
+
10
+ def pattern
11
+ builder.build("A < B")
12
+ end
13
+
14
+ def replacements
15
+ builder.build_all("A > B", "A == B")
16
+ end
17
+ end
18
+
19
+ subject { LessThanMutator.new }
20
+
21
+ let(:mutatable) do
22
+ "def compare\n" \
23
+ " foo < bar\n" \
24
+ " bar < baz\n" \
25
+ "end"
26
+ end
27
+
28
+ let(:mutated) do
29
+ [
30
+ "def compare\n" \
31
+ " foo > bar\n" \
32
+ " bar < baz\n" \
33
+ "end",
34
+
35
+ "def compare\n" \
36
+ " foo < bar\n" \
37
+ " bar > baz\n" \
38
+ "end",
39
+
40
+ "def compare\n" \
41
+ " foo == bar\n" \
42
+ " bar < baz\n" \
43
+ "end",
44
+
45
+ "def compare\n" \
46
+ " foo < bar\n" \
47
+ " bar == baz\n" \
48
+ "end"
49
+ ]
50
+ end
51
+
52
+ let(:not_mutatable) { "foo == bar" }
53
+
54
+ describe "by calling mutate" do
55
+ describe "for code that can be mutated"do
56
+ it "should return the mutated code" do
57
+ expect(subject.mutate(mutatable)).to eq(mutated)
58
+ end
59
+
60
+ it "should yield for each mutation site" do
61
+ expect { |b| subject.mutate(mutatable, &b) }.to yield_successive_args(
62
+ site_for(14..22, "foo < bar", "foo > bar"),
63
+ site_for(26..34, "bar < baz", "bar > baz"),
64
+ site_for(14..22, "foo < bar", "foo == bar"),
65
+ site_for(26..34, "bar < baz", "bar == baz")
66
+ )
67
+ end
68
+ end
69
+
70
+ describe "for code that cannot be mutated" do
71
+ it "should return no mutants" do
72
+ expect(subject.mutate(not_mutatable)).to eq([])
73
+ end
74
+
75
+ it "should not yield when there are no mutants" do
76
+ expect { |b| subject.mutate(not_mutatable, &b) }.not_to yield_control
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "mutate_file" do
82
+ describe "for code that can be mutated" do
83
+ let(:mutatable_file) { create_temporary_ruby_file("mutatable", mutatable) }
84
+
85
+ it "should return the mutated code" do
86
+ expect(subject.mutate_file(mutatable_file)).to eq(mutated)
87
+ end
88
+
89
+ it "should yield for each mutating site" do
90
+ expect { |b| subject.mutate_file(mutatable_file, &b) }.to yield_successive_args(
91
+ site_for(14..22, "foo < bar", "foo > bar"),
92
+ site_for(26..34, "bar < baz", "bar > baz"),
93
+ site_for(14..22, "foo < bar", "foo == bar"),
94
+ site_for(26..34, "bar < baz", "bar == baz")
95
+ )
96
+ end
97
+ end
98
+
99
+ describe "for code that cannot be mutated" do
100
+ let(:not_mutatable_file) do
101
+ create_temporary_ruby_file("not_mutatable", not_mutatable)
102
+ end
103
+
104
+ it "should return no mutants" do
105
+ expect(subject.mutate_file(not_mutatable_file)).to eq([])
106
+ end
107
+
108
+ it "should not yield when there are no mutating site" do
109
+ expect { |b| subject.mutate_file(not_mutatable_file, &b) }.not_to yield_control
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "mutate_files" do
115
+ let(:mutatable_file) { create_temporary_ruby_file("mutatable", mutatable) }
116
+ let(:clone_of_mutatable_file) { create_temporary_ruby_file("mutatable", mutatable) }
117
+
118
+ let(:different_mutating_sites_file) do
119
+ create_temporary_ruby_file(
120
+ "differently_mutatable",
121
+ "result = foo < bar"
122
+ )
123
+ end
124
+
125
+ let(:not_mutatable_file) do
126
+ create_temporary_ruby_file(
127
+ "not_mutatable",
128
+ "nothing_to_see_here = 42"
129
+ )
130
+ end
131
+
132
+ let(:files) do
133
+ [
134
+ mutatable_file,
135
+ clone_of_mutatable_file,
136
+ different_mutating_sites_file,
137
+ not_mutatable_file
138
+ ]
139
+ end
140
+
141
+ it "should return a map of the paths and mutated code" do
142
+ mutated_files = {
143
+ mutatable_file => mutated,
144
+ clone_of_mutatable_file => mutated,
145
+ different_mutating_sites_file => ["result = foo > bar", "result = foo == bar"],
146
+ not_mutatable_file => []
147
+ }
148
+
149
+ expect(subject.mutate_files(files)).to eq(mutated_files)
150
+ end
151
+
152
+ it "should yield for each file" do
153
+ mutatable_file_details = [
154
+ mutatable_file,
155
+ mutated,
156
+ [
157
+ site_for(14..22, "foo < bar", "foo > bar"),
158
+ site_for(26..34, "bar < baz", "bar > baz"),
159
+ site_for(14..22, "foo < bar", "foo == bar"),
160
+ site_for(26..34, "bar < baz", "bar == baz")
161
+ ]
162
+ ]
163
+
164
+ clone_of_mutatable_file_details = [
165
+ clone_of_mutatable_file,
166
+ mutated,
167
+ [
168
+ site_for(14..22, "foo < bar", "foo > bar"),
169
+ site_for(26..34, "bar < baz", "bar > baz"),
170
+ site_for(14..22, "foo < bar", "foo == bar"),
171
+ site_for(26..34, "bar < baz", "bar == baz")
172
+ ]
173
+ ]
174
+
175
+ different_mutating_sites_file_details = [
176
+ different_mutating_sites_file,
177
+ ["result = foo > bar", "result = foo == bar"],
178
+ [
179
+ site_for(9..17, "foo < bar", "foo > bar"),
180
+ site_for(9..17, "foo < bar", "foo == bar")
181
+ ]
182
+ ]
183
+
184
+ not_mutatable_file_details = [
185
+ not_mutatable_file,
186
+ [],
187
+ []
188
+ ]
189
+
190
+ summary = []
191
+ subject.mutate_files(files) { |*args| summary << args }
192
+
193
+ expect(summary[0]).to eq(mutatable_file_details)
194
+ expect(summary[1]).to eq(clone_of_mutatable_file_details)
195
+ expect(summary[2]).to eq(different_mutating_sites_file_details)
196
+ expect(summary[3]).to eq(not_mutatable_file_details)
197
+ end
198
+ end
199
+
200
+ def site_for(original_position, original_code, mutated_code)
201
+ Metamorpher::Transformer::Site.new(
202
+ original_position,
203
+ original_code,
204
+ mutated_code
205
+ )
206
+ end
207
+
208
+ def create_temporary_ruby_file(filename, contents)
209
+ Tempfile.new([filename, ".rb"]).tap do |tempfile|
210
+ tempfile.write(contents)
211
+ tempfile.close
212
+ end.path
213
+ end
214
+ end
215
+ end
@@ -176,7 +176,7 @@ describe "Refactorer" do
176
176
  end
177
177
 
178
178
  def site_for(original_position, predicate)
179
- Metamorpher::Refactorer::Site.new(
179
+ Metamorpher::Transformer::Site.new(
180
180
  original_position,
181
181
  refactorable_code_for(predicate),
182
182
  predicate
@@ -1,8 +1,8 @@
1
- require "metamorpher/refactorer/merger"
2
- require "metamorpher/refactorer/site"
1
+ require "metamorpher/transformer/merger"
2
+ require "metamorpher/transformer/site"
3
3
 
4
4
  module Metamorpher
5
- module Refactorer
5
+ module Transformer
6
6
  describe Merger do
7
7
  let(:original) { "The quick brown fox jumps over the lazy dog." }
8
8
  subject { Merger.new(original) }
@@ -1,7 +1,7 @@
1
- require "metamorpher/refactorer/site"
1
+ require "metamorpher/transformer/site"
2
2
 
3
3
  module Metamorpher
4
- module Refactorer
4
+ module Transformer
5
5
  describe Site do
6
6
  subject { Site.new(4..6, "foo", "bar") }
7
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metamorpher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis Rose
@@ -138,6 +138,7 @@ files:
138
138
  - Gemfile
139
139
  - LICENSE.txt
140
140
  - README.md
141
+ - RELEASES.md
141
142
  - Rakefile
142
143
  - examples/refactorings/rails/where_first/app.rb
143
144
  - examples/refactorings/rails/where_first/refactorers/refactor_where_first_mocks.rb
@@ -166,9 +167,8 @@ files:
166
167
  - lib/metamorpher/matcher/match.rb
167
168
  - lib/metamorpher/matcher/matching.rb
168
169
  - lib/metamorpher/matcher/no_match.rb
170
+ - lib/metamorpher/mutator.rb
169
171
  - lib/metamorpher/refactorer.rb
170
- - lib/metamorpher/refactorer/merger.rb
171
- - lib/metamorpher/refactorer/site.rb
172
172
  - lib/metamorpher/rewriter.rb
173
173
  - lib/metamorpher/rewriter/replacement.rb
174
174
  - lib/metamorpher/rewriter/rule.rb
@@ -179,6 +179,9 @@ files:
179
179
  - lib/metamorpher/terms/literal.rb
180
180
  - lib/metamorpher/terms/term.rb
181
181
  - lib/metamorpher/terms/variable.rb
182
+ - lib/metamorpher/transformer/base.rb
183
+ - lib/metamorpher/transformer/merger.rb
184
+ - lib/metamorpher/transformer/site.rb
182
185
  - lib/metamorpher/version.rb
183
186
  - lib/metamorpher/visitable/visitable.rb
184
187
  - lib/metamorpher/visitable/visitor.rb
@@ -187,6 +190,7 @@ files:
187
190
  - spec/integration/ast/matcher_spec.rb
188
191
  - spec/integration/ast/rewriter_spec.rb
189
192
  - spec/integration/ruby/builder_spec.rb
193
+ - spec/integration/ruby/mutator_spec.rb
190
194
  - spec/integration/ruby/refactorer_spec.rb
191
195
  - spec/spec_helper.rb
192
196
  - spec/support/helpers/silence_stream.rb
@@ -204,14 +208,14 @@ files:
204
208
  - spec/unit/drivers/ruby_spec.rb
205
209
  - spec/unit/matcher/matching_spec.rb
206
210
  - spec/unit/metamorpher_spec.rb
207
- - spec/unit/refactorer/merger_spec.rb
208
- - spec/unit/refactorer/site_spec.rb
209
211
  - spec/unit/rewriter/replacement_spec.rb
210
212
  - spec/unit/rewriter/substitution_spec.rb
211
213
  - spec/unit/rewriter/traverser_spec.rb
212
214
  - spec/unit/support/map_at_spec.rb
213
215
  - spec/unit/terms/literal_spec.rb
214
216
  - spec/unit/terms/term_spec.rb
217
+ - spec/unit/transformer/merger_spec.rb
218
+ - spec/unit/transformer/site_spec.rb
215
219
  - spec/unit/visitable/visitor_spec.rb
216
220
  homepage: https://github.com/mutiny/metamorpher
217
221
  licenses:
@@ -242,6 +246,7 @@ test_files:
242
246
  - spec/integration/ast/matcher_spec.rb
243
247
  - spec/integration/ast/rewriter_spec.rb
244
248
  - spec/integration/ruby/builder_spec.rb
249
+ - spec/integration/ruby/mutator_spec.rb
245
250
  - spec/integration/ruby/refactorer_spec.rb
246
251
  - spec/spec_helper.rb
247
252
  - spec/support/helpers/silence_stream.rb
@@ -259,12 +264,12 @@ test_files:
259
264
  - spec/unit/drivers/ruby_spec.rb
260
265
  - spec/unit/matcher/matching_spec.rb
261
266
  - spec/unit/metamorpher_spec.rb
262
- - spec/unit/refactorer/merger_spec.rb
263
- - spec/unit/refactorer/site_spec.rb
264
267
  - spec/unit/rewriter/replacement_spec.rb
265
268
  - spec/unit/rewriter/substitution_spec.rb
266
269
  - spec/unit/rewriter/traverser_spec.rb
267
270
  - spec/unit/support/map_at_spec.rb
268
271
  - spec/unit/terms/literal_spec.rb
269
272
  - spec/unit/terms/term_spec.rb
273
+ - spec/unit/transformer/merger_spec.rb
274
+ - spec/unit/transformer/site_spec.rb
270
275
  - spec/unit/visitable/visitor_spec.rb