metamorpher 0.1.1 → 0.2.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 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