evilution 0.13.0 → 0.14.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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/.migration-hint-ts +1 -1
  3. data/.beads/issues.jsonl +8 -8
  4. data/CHANGELOG.md +17 -0
  5. data/lib/evilution/ast/parser.rb +69 -68
  6. data/lib/evilution/ast/source_surgeon.rb +7 -9
  7. data/lib/evilution/ast.rb +4 -0
  8. data/lib/evilution/baseline.rb +73 -75
  9. data/lib/evilution/cache.rb +75 -77
  10. data/lib/evilution/cli.rb +408 -173
  11. data/lib/evilution/config.rb +141 -136
  12. data/lib/evilution/equivalent/detector.rb +25 -27
  13. data/lib/evilution/equivalent/heuristic/alias_swap.rb +29 -33
  14. data/lib/evilution/equivalent/heuristic/dead_code.rb +41 -45
  15. data/lib/evilution/equivalent/heuristic/method_body_nil.rb +11 -15
  16. data/lib/evilution/equivalent/heuristic/noop_source.rb +5 -9
  17. data/lib/evilution/equivalent/heuristic.rb +6 -0
  18. data/lib/evilution/equivalent.rb +4 -0
  19. data/lib/evilution/git/changed_files.rb +35 -37
  20. data/lib/evilution/git.rb +4 -0
  21. data/lib/evilution/integration/base.rb +5 -7
  22. data/lib/evilution/integration/rspec.rb +114 -116
  23. data/lib/evilution/integration.rb +4 -0
  24. data/lib/evilution/isolation/fork.rb +98 -100
  25. data/lib/evilution/isolation/in_process.rb +59 -61
  26. data/lib/evilution/isolation.rb +4 -0
  27. data/lib/evilution/mcp/mutate_tool.rb +172 -143
  28. data/lib/evilution/mcp/server.rb +12 -11
  29. data/lib/evilution/mcp/session_diff_tool.rb +89 -0
  30. data/lib/evilution/mcp/session_list_tool.rb +46 -0
  31. data/lib/evilution/mcp/session_show_tool.rb +53 -0
  32. data/lib/evilution/mcp.rb +4 -0
  33. data/lib/evilution/memory/leak_check.rb +80 -84
  34. data/lib/evilution/memory.rb +34 -36
  35. data/lib/evilution/mutation.rb +40 -42
  36. data/lib/evilution/mutator/base.rb +46 -48
  37. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +32 -36
  38. data/lib/evilution/mutator/operator/argument_removal.rb +32 -36
  39. data/lib/evilution/mutator/operator/arithmetic_replacement.rb +26 -30
  40. data/lib/evilution/mutator/operator/array_literal.rb +18 -22
  41. data/lib/evilution/mutator/operator/block_removal.rb +16 -20
  42. data/lib/evilution/mutator/operator/boolean_literal_replacement.rb +38 -42
  43. data/lib/evilution/mutator/operator/boolean_operator_replacement.rb +41 -45
  44. data/lib/evilution/mutator/operator/collection_replacement.rb +32 -36
  45. data/lib/evilution/mutator/operator/comparison_replacement.rb +24 -28
  46. data/lib/evilution/mutator/operator/compound_assignment.rb +114 -118
  47. data/lib/evilution/mutator/operator/conditional_branch.rb +25 -29
  48. data/lib/evilution/mutator/operator/conditional_flip.rb +26 -30
  49. data/lib/evilution/mutator/operator/conditional_negation.rb +25 -29
  50. data/lib/evilution/mutator/operator/float_literal.rb +22 -26
  51. data/lib/evilution/mutator/operator/hash_literal.rb +18 -22
  52. data/lib/evilution/mutator/operator/integer_literal.rb +2 -0
  53. data/lib/evilution/mutator/operator/method_body_replacement.rb +12 -16
  54. data/lib/evilution/mutator/operator/method_call_removal.rb +12 -16
  55. data/lib/evilution/mutator/operator/negation_insertion.rb +12 -16
  56. data/lib/evilution/mutator/operator/nil_replacement.rb +13 -17
  57. data/lib/evilution/mutator/operator/range_replacement.rb +12 -16
  58. data/lib/evilution/mutator/operator/receiver_replacement.rb +16 -20
  59. data/lib/evilution/mutator/operator/regexp_mutation.rb +15 -19
  60. data/lib/evilution/mutator/operator/return_value_removal.rb +12 -16
  61. data/lib/evilution/mutator/operator/send_mutation.rb +36 -40
  62. data/lib/evilution/mutator/operator/statement_deletion.rb +13 -17
  63. data/lib/evilution/mutator/operator/string_literal.rb +18 -22
  64. data/lib/evilution/mutator/operator/symbol_literal.rb +17 -21
  65. data/lib/evilution/mutator/operator.rb +6 -0
  66. data/lib/evilution/mutator/registry.rb +54 -56
  67. data/lib/evilution/mutator.rb +4 -0
  68. data/lib/evilution/parallel/pool.rb +56 -58
  69. data/lib/evilution/parallel.rb +4 -0
  70. data/lib/evilution/reporter/cli.rb +99 -101
  71. data/lib/evilution/reporter/html.rb +242 -244
  72. data/lib/evilution/reporter/json.rb +57 -59
  73. data/lib/evilution/reporter/suggestion.rb +326 -328
  74. data/lib/evilution/reporter.rb +4 -0
  75. data/lib/evilution/result/mutation_result.rb +43 -46
  76. data/lib/evilution/result/summary.rb +80 -81
  77. data/lib/evilution/result.rb +4 -0
  78. data/lib/evilution/runner.rb +334 -323
  79. data/lib/evilution/session/store.rb +147 -0
  80. data/lib/evilution/session.rb +4 -0
  81. data/lib/evilution/spec_resolver.rb +49 -47
  82. data/lib/evilution/subject.rb +14 -16
  83. data/lib/evilution/version.rb +1 -1
  84. data/lib/evilution.rb +13 -0
  85. metadata +19 -2
@@ -1,39 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class ArithmeticReplacement < Base
7
- REPLACEMENTS = {
8
- :+ => [:-],
9
- :- => [:+],
10
- :* => [:/],
11
- :/ => [:*],
12
- :% => [:*],
13
- :** => [:*],
14
- :<< => [:>>],
15
- :>> => [:<<]
16
- }.freeze
3
+ require_relative "../operator"
17
4
 
18
- def visit_call_node(node)
19
- replacements = REPLACEMENTS[node.name]
20
- return super unless replacements
5
+ class Evilution::Mutator::Operator::ArithmeticReplacement < Evilution::Mutator::Base
6
+ REPLACEMENTS = {
7
+ :+ => [:-],
8
+ :- => [:+],
9
+ :* => [:/],
10
+ :/ => [:*],
11
+ :% => [:*],
12
+ :** => [:*],
13
+ :<< => [:>>],
14
+ :>> => [:<<]
15
+ }.freeze
21
16
 
22
- loc = node.message_loc
23
- return super unless loc
17
+ def visit_call_node(node)
18
+ replacements = REPLACEMENTS[node.name]
19
+ return super unless replacements
24
20
 
25
- replacements.each do |replacement|
26
- add_mutation(
27
- offset: loc.start_offset,
28
- length: loc.length,
29
- replacement: replacement.to_s,
30
- node: node
31
- )
32
- end
21
+ loc = node.message_loc
22
+ return super unless loc
33
23
 
34
- super
35
- end
36
- end
24
+ replacements.each do |replacement|
25
+ add_mutation(
26
+ offset: loc.start_offset,
27
+ length: loc.length,
28
+ replacement: replacement.to_s,
29
+ node: node
30
+ )
37
31
  end
32
+
33
+ super
38
34
  end
39
35
  end
@@ -1,29 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class ArrayLiteral < Base
7
- def visit_array_node(node)
8
- if node.opening_loc && node.elements.any?
9
- add_mutation(
10
- offset: node.location.start_offset,
11
- length: node.location.length,
12
- replacement: "[]",
13
- node: node
14
- )
3
+ require_relative "../operator"
15
4
 
16
- add_mutation(
17
- offset: node.location.start_offset,
18
- length: node.location.length,
19
- replacement: "nil",
20
- node: node
21
- )
22
- end
5
+ class Evilution::Mutator::Operator::ArrayLiteral < Evilution::Mutator::Base
6
+ def visit_array_node(node)
7
+ if node.opening_loc && node.elements.any?
8
+ add_mutation(
9
+ offset: node.location.start_offset,
10
+ length: node.location.length,
11
+ replacement: "[]",
12
+ node: node
13
+ )
23
14
 
24
- super
25
- end
26
- end
15
+ add_mutation(
16
+ offset: node.location.start_offset,
17
+ length: node.location.length,
18
+ replacement: "nil",
19
+ node: node
20
+ )
27
21
  end
22
+
23
+ super
28
24
  end
29
25
  end
@@ -1,27 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class BlockRemoval < Base
7
- def visit_call_node(node)
8
- if node.block
9
- block_node = node.block
10
- call_end = block_node.location.start_offset
11
- call_start = node.location.start_offset
12
- call_without_block = @file_source.byteslice(call_start...call_end).rstrip
3
+ require_relative "../operator"
13
4
 
14
- add_mutation(
15
- offset: call_start,
16
- length: node.location.length,
17
- replacement: call_without_block,
18
- node: node
19
- )
20
- end
5
+ class Evilution::Mutator::Operator::BlockRemoval < Evilution::Mutator::Base
6
+ def visit_call_node(node)
7
+ if node.block
8
+ block_node = node.block
9
+ call_end = block_node.location.start_offset
10
+ call_start = node.location.start_offset
11
+ call_without_block = @file_source.byteslice(call_start...call_end).rstrip
21
12
 
22
- super
23
- end
24
- end
13
+ add_mutation(
14
+ offset: call_start,
15
+ length: node.location.length,
16
+ replacement: call_without_block,
17
+ node: node
18
+ )
25
19
  end
20
+
21
+ super
26
22
  end
27
23
  end
@@ -1,46 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class BooleanLiteralReplacement < Base
7
- def visit_true_node(node)
8
- add_mutation(
9
- offset: node.location.start_offset,
10
- length: node.location.length,
11
- replacement: "false",
12
- node: node
13
- )
14
-
15
- add_nil_mutation(node)
16
-
17
- super
18
- end
19
-
20
- def visit_false_node(node)
21
- add_mutation(
22
- offset: node.location.start_offset,
23
- length: node.location.length,
24
- replacement: "true",
25
- node: node
26
- )
27
-
28
- add_nil_mutation(node)
29
-
30
- super
31
- end
32
-
33
- private
34
-
35
- def add_nil_mutation(node)
36
- add_mutation(
37
- offset: node.location.start_offset,
38
- length: node.location.length,
39
- replacement: "nil",
40
- node: node
41
- )
42
- end
43
- end
44
- end
3
+ require_relative "../operator"
4
+
5
+ class Evilution::Mutator::Operator::BooleanLiteralReplacement < Evilution::Mutator::Base
6
+ def visit_true_node(node)
7
+ add_mutation(
8
+ offset: node.location.start_offset,
9
+ length: node.location.length,
10
+ replacement: "false",
11
+ node: node
12
+ )
13
+
14
+ add_nil_mutation(node)
15
+
16
+ super
17
+ end
18
+
19
+ def visit_false_node(node)
20
+ add_mutation(
21
+ offset: node.location.start_offset,
22
+ length: node.location.length,
23
+ replacement: "true",
24
+ node: node
25
+ )
26
+
27
+ add_nil_mutation(node)
28
+
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ def add_nil_mutation(node)
35
+ add_mutation(
36
+ offset: node.location.start_offset,
37
+ length: node.location.length,
38
+ replacement: "nil",
39
+ node: node
40
+ )
45
41
  end
46
42
  end
@@ -1,50 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class BooleanOperatorReplacement < Base
7
- REPLACEMENTS = {
8
- "&&" => "||",
9
- "||" => "&&",
10
- "and" => "or",
11
- "or" => "and"
12
- }.freeze
13
-
14
- def visit_and_node(node)
15
- loc = node.operator_loc
16
- operator = loc.slice
17
- replacement = REPLACEMENTS[operator]
18
-
19
- if replacement
20
- add_mutation(
21
- offset: loc.start_offset,
22
- length: loc.length,
23
- replacement: replacement,
24
- node: node
25
- )
26
- end
27
-
28
- super
29
- end
30
-
31
- def visit_or_node(node)
32
- loc = node.operator_loc
33
- operator = loc.slice
34
- replacement = REPLACEMENTS[operator]
35
-
36
- if replacement
37
- add_mutation(
38
- offset: loc.start_offset,
39
- length: loc.length,
40
- replacement: replacement,
41
- node: node
42
- )
43
- end
44
-
45
- super
46
- end
47
- end
3
+ require_relative "../operator"
4
+
5
+ class Evilution::Mutator::Operator::BooleanOperatorReplacement < Evilution::Mutator::Base
6
+ REPLACEMENTS = {
7
+ "&&" => "||",
8
+ "||" => "&&",
9
+ "and" => "or",
10
+ "or" => "and"
11
+ }.freeze
12
+
13
+ def visit_and_node(node)
14
+ loc = node.operator_loc
15
+ operator = loc.slice
16
+ replacement = REPLACEMENTS[operator]
17
+
18
+ if replacement
19
+ add_mutation(
20
+ offset: loc.start_offset,
21
+ length: loc.length,
22
+ replacement: replacement,
23
+ node: node
24
+ )
48
25
  end
26
+
27
+ super
28
+ end
29
+
30
+ def visit_or_node(node)
31
+ loc = node.operator_loc
32
+ operator = loc.slice
33
+ replacement = REPLACEMENTS[operator]
34
+
35
+ if replacement
36
+ add_mutation(
37
+ offset: loc.start_offset,
38
+ length: loc.length,
39
+ replacement: replacement,
40
+ node: node
41
+ )
42
+ end
43
+
44
+ super
49
45
  end
50
46
  end
@@ -1,45 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class CollectionReplacement < Base
7
- REPLACEMENTS = {
8
- map: [:each],
9
- each: [:map],
10
- select: [:reject],
11
- reject: [:select],
12
- flat_map: [:map],
13
- collect: [:each],
14
- sort: [:sort_by],
15
- sort_by: [:sort],
16
- find: [:detect],
17
- detect: [:find],
18
- any?: [:all?],
19
- all?: [:any?],
20
- count: [:length],
21
- length: [:count]
22
- }.freeze
3
+ require_relative "../operator"
23
4
 
24
- def visit_call_node(node)
25
- replacements = REPLACEMENTS[node.name]
26
- return super unless replacements
5
+ class Evilution::Mutator::Operator::CollectionReplacement < Evilution::Mutator::Base
6
+ REPLACEMENTS = {
7
+ map: [:each],
8
+ each: [:map],
9
+ select: [:reject],
10
+ reject: [:select],
11
+ flat_map: [:map],
12
+ collect: [:each],
13
+ sort: [:sort_by],
14
+ sort_by: [:sort],
15
+ find: [:detect],
16
+ detect: [:find],
17
+ any?: [:all?],
18
+ all?: [:any?],
19
+ count: [:length],
20
+ length: [:count]
21
+ }.freeze
27
22
 
28
- loc = node.message_loc
29
- return super unless loc
23
+ def visit_call_node(node)
24
+ replacements = REPLACEMENTS[node.name]
25
+ return super unless replacements
30
26
 
31
- replacements.each do |replacement|
32
- add_mutation(
33
- offset: loc.start_offset,
34
- length: loc.length,
35
- replacement: replacement.to_s,
36
- node: node
37
- )
38
- end
27
+ loc = node.message_loc
28
+ return super unless loc
39
29
 
40
- super
41
- end
42
- end
30
+ replacements.each do |replacement|
31
+ add_mutation(
32
+ offset: loc.start_offset,
33
+ length: loc.length,
34
+ replacement: replacement.to_s,
35
+ node: node
36
+ )
43
37
  end
38
+
39
+ super
44
40
  end
45
41
  end
@@ -1,37 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Evilution
4
- module Mutator
5
- module Operator
6
- class ComparisonReplacement < Base
7
- REPLACEMENTS = {
8
- :> => %i[>= == <],
9
- :< => %i[<= == >],
10
- :>= => %i[> == <=],
11
- :<= => %i[< == >=],
12
- :== => [:!=],
13
- :!= => [:==]
14
- }.freeze
3
+ require_relative "../operator"
15
4
 
16
- def visit_call_node(node)
17
- replacements = REPLACEMENTS[node.name]
18
- return super unless replacements
5
+ class Evilution::Mutator::Operator::ComparisonReplacement < Evilution::Mutator::Base
6
+ REPLACEMENTS = {
7
+ :> => %i[>= == <],
8
+ :< => %i[<= == >],
9
+ :>= => %i[> == <=],
10
+ :<= => %i[< == >=],
11
+ :== => [:!=],
12
+ :!= => [:==]
13
+ }.freeze
19
14
 
20
- loc = node.message_loc
21
- return super unless loc
15
+ def visit_call_node(node)
16
+ replacements = REPLACEMENTS[node.name]
17
+ return super unless replacements
22
18
 
23
- replacements.each do |replacement|
24
- add_mutation(
25
- offset: loc.start_offset,
26
- length: loc.length,
27
- replacement: replacement.to_s,
28
- node: node
29
- )
30
- end
19
+ loc = node.message_loc
20
+ return super unless loc
31
21
 
32
- super
33
- end
34
- end
22
+ replacements.each do |replacement|
23
+ add_mutation(
24
+ offset: loc.start_offset,
25
+ length: loc.length,
26
+ replacement: replacement.to_s,
27
+ node: node
28
+ )
35
29
  end
30
+
31
+ super
36
32
  end
37
33
  end