evilution 0.13.0 → 0.15.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 +4 -4
- data/.beads/.migration-hint-ts +1 -1
- data/.beads/issues.jsonl +17 -17
- data/CHANGELOG.md +39 -0
- data/lib/evilution/ast/inheritance_scanner.rb +70 -0
- data/lib/evilution/ast/parser.rb +73 -68
- data/lib/evilution/ast/source_surgeon.rb +7 -9
- data/lib/evilution/ast.rb +4 -0
- data/lib/evilution/baseline.rb +73 -75
- data/lib/evilution/cache.rb +75 -77
- data/lib/evilution/cli.rb +412 -173
- data/lib/evilution/config.rb +141 -136
- data/lib/evilution/equivalent/detector.rb +29 -27
- data/lib/evilution/equivalent/heuristic/alias_swap.rb +32 -33
- data/lib/evilution/equivalent/heuristic/arithmetic_identity.rb +30 -0
- data/lib/evilution/equivalent/heuristic/comment_marking.rb +21 -0
- data/lib/evilution/equivalent/heuristic/dead_code.rb +41 -45
- data/lib/evilution/equivalent/heuristic/method_body_nil.rb +11 -15
- data/lib/evilution/equivalent/heuristic/noop_source.rb +5 -9
- data/lib/evilution/equivalent/heuristic.rb +6 -0
- data/lib/evilution/equivalent.rb +4 -0
- data/lib/evilution/git/changed_files.rb +35 -37
- data/lib/evilution/git.rb +4 -0
- data/lib/evilution/integration/base.rb +5 -7
- data/lib/evilution/integration/rspec.rb +114 -116
- data/lib/evilution/integration.rb +4 -0
- data/lib/evilution/isolation/fork.rb +98 -100
- data/lib/evilution/isolation/in_process.rb +59 -61
- data/lib/evilution/isolation.rb +4 -0
- data/lib/evilution/mcp/mutate_tool.rb +172 -143
- data/lib/evilution/mcp/server.rb +12 -11
- data/lib/evilution/mcp/session_diff_tool.rb +89 -0
- data/lib/evilution/mcp/session_list_tool.rb +46 -0
- data/lib/evilution/mcp/session_show_tool.rb +53 -0
- data/lib/evilution/mcp.rb +4 -0
- data/lib/evilution/memory/leak_check.rb +80 -84
- data/lib/evilution/memory.rb +34 -36
- data/lib/evilution/mutation.rb +40 -42
- data/lib/evilution/mutator/base.rb +62 -48
- data/lib/evilution/mutator/operator/argument_nil_substitution.rb +32 -36
- data/lib/evilution/mutator/operator/argument_removal.rb +32 -36
- data/lib/evilution/mutator/operator/arithmetic_replacement.rb +26 -30
- data/lib/evilution/mutator/operator/array_literal.rb +18 -22
- data/lib/evilution/mutator/operator/block_removal.rb +16 -20
- data/lib/evilution/mutator/operator/boolean_literal_replacement.rb +38 -42
- data/lib/evilution/mutator/operator/boolean_operator_replacement.rb +41 -45
- data/lib/evilution/mutator/operator/collection_replacement.rb +32 -36
- data/lib/evilution/mutator/operator/comparison_replacement.rb +24 -28
- data/lib/evilution/mutator/operator/compound_assignment.rb +114 -118
- data/lib/evilution/mutator/operator/conditional_branch.rb +25 -29
- data/lib/evilution/mutator/operator/conditional_flip.rb +26 -30
- data/lib/evilution/mutator/operator/conditional_negation.rb +25 -29
- data/lib/evilution/mutator/operator/float_literal.rb +22 -26
- data/lib/evilution/mutator/operator/hash_literal.rb +18 -22
- data/lib/evilution/mutator/operator/integer_literal.rb +2 -0
- data/lib/evilution/mutator/operator/method_body_replacement.rb +12 -16
- data/lib/evilution/mutator/operator/method_call_removal.rb +12 -16
- data/lib/evilution/mutator/operator/mixin_removal.rb +80 -0
- data/lib/evilution/mutator/operator/negation_insertion.rb +12 -16
- data/lib/evilution/mutator/operator/nil_replacement.rb +13 -17
- data/lib/evilution/mutator/operator/range_replacement.rb +12 -16
- data/lib/evilution/mutator/operator/receiver_replacement.rb +16 -20
- data/lib/evilution/mutator/operator/regexp_mutation.rb +15 -19
- data/lib/evilution/mutator/operator/return_value_removal.rb +12 -16
- data/lib/evilution/mutator/operator/send_mutation.rb +36 -40
- data/lib/evilution/mutator/operator/statement_deletion.rb +13 -17
- data/lib/evilution/mutator/operator/string_literal.rb +18 -22
- data/lib/evilution/mutator/operator/superclass_removal.rb +65 -0
- data/lib/evilution/mutator/operator/symbol_literal.rb +17 -21
- data/lib/evilution/mutator/operator.rb +6 -0
- data/lib/evilution/mutator/registry.rb +56 -56
- data/lib/evilution/mutator.rb +4 -0
- data/lib/evilution/parallel/pool.rb +56 -58
- data/lib/evilution/parallel.rb +4 -0
- data/lib/evilution/reporter/cli.rb +99 -101
- data/lib/evilution/reporter/html.rb +242 -244
- data/lib/evilution/reporter/json.rb +57 -59
- data/lib/evilution/reporter/suggestion.rb +354 -328
- data/lib/evilution/reporter.rb +4 -0
- data/lib/evilution/result/mutation_result.rb +43 -46
- data/lib/evilution/result/summary.rb +80 -81
- data/lib/evilution/result.rb +4 -0
- data/lib/evilution/runner.rb +401 -316
- data/lib/evilution/session/store.rb +147 -0
- data/lib/evilution/session.rb +4 -0
- data/lib/evilution/spec_resolver.rb +49 -47
- data/lib/evilution/subject.rb +14 -16
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +16 -0
- metadata +24 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../operator"
|
|
6
|
+
|
|
7
|
+
class Evilution::Mutator::Operator::MixinRemoval < Evilution::Mutator::Base
|
|
8
|
+
MIXIN_METHODS = %i[include extend prepend].freeze
|
|
9
|
+
|
|
10
|
+
def call(subject)
|
|
11
|
+
@subject = subject
|
|
12
|
+
@file_source = File.read(subject.file_path)
|
|
13
|
+
@mutations = []
|
|
14
|
+
|
|
15
|
+
tree = self.class.parsed_tree_for(subject.file_path, @file_source)
|
|
16
|
+
enclosing = find_enclosing_scope(tree, subject.line_number)
|
|
17
|
+
return @mutations unless enclosing
|
|
18
|
+
|
|
19
|
+
first_method_line = find_first_method_line(enclosing)
|
|
20
|
+
return @mutations unless first_method_line == subject.line_number
|
|
21
|
+
|
|
22
|
+
find_mixin_calls(enclosing).each do |call_node|
|
|
23
|
+
add_mutation(
|
|
24
|
+
offset: call_node.location.start_offset,
|
|
25
|
+
length: call_node.location.length,
|
|
26
|
+
replacement: "",
|
|
27
|
+
node: call_node
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@mutations
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def find_enclosing_scope(tree, target_line)
|
|
37
|
+
finder = ScopeFinder.new(target_line)
|
|
38
|
+
finder.visit(tree)
|
|
39
|
+
finder.result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find_first_method_line(scope_node)
|
|
43
|
+
return nil unless scope_node.body
|
|
44
|
+
|
|
45
|
+
scope_node.body.body.each do |node|
|
|
46
|
+
return node.location.start_line if node.is_a?(Prism::DefNode)
|
|
47
|
+
end
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def find_mixin_calls(scope_node)
|
|
52
|
+
return [] unless scope_node.body
|
|
53
|
+
|
|
54
|
+
scope_node.body.body.select do |node|
|
|
55
|
+
node.is_a?(Prism::CallNode) &&
|
|
56
|
+
MIXIN_METHODS.include?(node.name) &&
|
|
57
|
+
node.receiver.nil?
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Visitor to find the ClassNode or ModuleNode enclosing a given line number.
|
|
62
|
+
class ScopeFinder < Prism::Visitor
|
|
63
|
+
attr_reader :result
|
|
64
|
+
|
|
65
|
+
def initialize(target_line)
|
|
66
|
+
@target_line = target_line
|
|
67
|
+
@result = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def visit_class_node(node)
|
|
71
|
+
@result = node if @target_line.between?(node.location.start_line, node.location.end_line)
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def visit_module_node(node)
|
|
76
|
+
@result = node if @target_line.between?(node.location.start_line, node.location.end_line)
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class NegationInsertion < Base
|
|
7
|
-
def visit_call_node(node)
|
|
8
|
-
if node.name.to_s.end_with?("?")
|
|
9
|
-
add_mutation(
|
|
10
|
-
offset: node.location.start_offset,
|
|
11
|
-
length: 0,
|
|
12
|
-
replacement: "!",
|
|
13
|
-
node: node
|
|
14
|
-
)
|
|
15
|
-
end
|
|
3
|
+
require_relative "../operator"
|
|
16
4
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::NegationInsertion < Evilution::Mutator::Base
|
|
6
|
+
def visit_call_node(node)
|
|
7
|
+
if node.name.to_s.end_with?("?")
|
|
8
|
+
add_mutation(
|
|
9
|
+
offset: node.location.start_offset,
|
|
10
|
+
length: 0,
|
|
11
|
+
replacement: "!",
|
|
12
|
+
node: node
|
|
13
|
+
)
|
|
20
14
|
end
|
|
15
|
+
|
|
16
|
+
super
|
|
21
17
|
end
|
|
22
18
|
end
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class NilReplacement < Base
|
|
7
|
-
REPLACEMENTS = %w[true false 0 ""].freeze
|
|
3
|
+
require_relative "../operator"
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
add_mutation(
|
|
12
|
-
offset: node.location.start_offset,
|
|
13
|
-
length: node.location.length,
|
|
14
|
-
replacement: replacement,
|
|
15
|
-
node: node
|
|
16
|
-
)
|
|
17
|
-
end
|
|
5
|
+
class Evilution::Mutator::Operator::NilReplacement < Evilution::Mutator::Base
|
|
6
|
+
REPLACEMENTS = %w[true false 0 ""].freeze
|
|
18
7
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
def visit_nil_node(node)
|
|
9
|
+
REPLACEMENTS.each do |replacement|
|
|
10
|
+
add_mutation(
|
|
11
|
+
offset: node.location.start_offset,
|
|
12
|
+
length: node.location.length,
|
|
13
|
+
replacement: replacement,
|
|
14
|
+
node: node
|
|
15
|
+
)
|
|
22
16
|
end
|
|
17
|
+
|
|
18
|
+
super
|
|
23
19
|
end
|
|
24
20
|
end
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class RangeReplacement < Base
|
|
7
|
-
def visit_range_node(node)
|
|
8
|
-
replacement = node.operator == ".." ? "..." : ".."
|
|
3
|
+
require_relative "../operator"
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
replacement: replacement,
|
|
14
|
-
node: node
|
|
15
|
-
)
|
|
5
|
+
class Evilution::Mutator::Operator::RangeReplacement < Evilution::Mutator::Base
|
|
6
|
+
def visit_range_node(node)
|
|
7
|
+
replacement = node.operator == ".." ? "..." : ".."
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
add_mutation(
|
|
10
|
+
offset: node.operator_loc.start_offset,
|
|
11
|
+
length: node.operator_loc.length,
|
|
12
|
+
replacement: replacement,
|
|
13
|
+
node: node
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
super
|
|
21
17
|
end
|
|
22
18
|
end
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class ReceiverReplacement < Base
|
|
7
|
-
def visit_call_node(node)
|
|
8
|
-
if node.receiver.is_a?(Prism::SelfNode)
|
|
9
|
-
call_without_self = @file_source.byteslice(
|
|
10
|
-
node.message_loc.start_offset,
|
|
11
|
-
node.location.start_offset + node.location.length - node.message_loc.start_offset
|
|
12
|
-
)
|
|
3
|
+
require_relative "../operator"
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::ReceiverReplacement < Evilution::Mutator::Base
|
|
6
|
+
def visit_call_node(node)
|
|
7
|
+
if node.receiver.is_a?(Prism::SelfNode)
|
|
8
|
+
call_without_self = @file_source.byteslice(
|
|
9
|
+
node.message_loc.start_offset,
|
|
10
|
+
node.location.start_offset + node.location.length - node.message_loc.start_offset
|
|
11
|
+
)
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
add_mutation(
|
|
14
|
+
offset: node.location.start_offset,
|
|
15
|
+
length: node.location.length,
|
|
16
|
+
replacement: call_without_self,
|
|
17
|
+
node: node
|
|
18
|
+
)
|
|
25
19
|
end
|
|
20
|
+
|
|
21
|
+
super
|
|
26
22
|
end
|
|
27
23
|
end
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class RegexpMutation < Base
|
|
7
|
-
NEVER_MATCH = 'a\A'
|
|
8
|
-
ALWAYS_MATCH = ".*"
|
|
3
|
+
require_relative "../operator"
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::RegexpMutation < Evilution::Mutator::Base
|
|
6
|
+
NEVER_MATCH = 'a\A'
|
|
7
|
+
ALWAYS_MATCH = ".*"
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
REPLACEMENTS.each do |replacement|
|
|
14
|
-
add_mutation(
|
|
15
|
-
offset: node.content_loc.start_offset,
|
|
16
|
-
length: node.content_loc.length,
|
|
17
|
-
replacement: replacement,
|
|
18
|
-
node: node
|
|
19
|
-
)
|
|
20
|
-
end
|
|
9
|
+
REPLACEMENTS = [NEVER_MATCH, ALWAYS_MATCH].freeze
|
|
21
10
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
def visit_regular_expression_node(node)
|
|
12
|
+
REPLACEMENTS.each do |replacement|
|
|
13
|
+
add_mutation(
|
|
14
|
+
offset: node.content_loc.start_offset,
|
|
15
|
+
length: node.content_loc.length,
|
|
16
|
+
replacement: replacement,
|
|
17
|
+
node: node
|
|
18
|
+
)
|
|
25
19
|
end
|
|
20
|
+
|
|
21
|
+
super
|
|
26
22
|
end
|
|
27
23
|
end
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class ReturnValueRemoval < Base
|
|
7
|
-
def visit_return_node(node)
|
|
8
|
-
if node.arguments
|
|
9
|
-
add_mutation(
|
|
10
|
-
offset: node.location.start_offset,
|
|
11
|
-
length: node.location.length,
|
|
12
|
-
replacement: "return",
|
|
13
|
-
node: node
|
|
14
|
-
)
|
|
15
|
-
end
|
|
3
|
+
require_relative "../operator"
|
|
16
4
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::ReturnValueRemoval < Evilution::Mutator::Base
|
|
6
|
+
def visit_return_node(node)
|
|
7
|
+
if node.arguments
|
|
8
|
+
add_mutation(
|
|
9
|
+
offset: node.location.start_offset,
|
|
10
|
+
length: node.location.length,
|
|
11
|
+
replacement: "return",
|
|
12
|
+
node: node
|
|
13
|
+
)
|
|
20
14
|
end
|
|
15
|
+
|
|
16
|
+
super
|
|
21
17
|
end
|
|
22
18
|
end
|
|
@@ -1,49 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class SendMutation < Base
|
|
7
|
-
REPLACEMENTS = {
|
|
8
|
-
flat_map: [:map],
|
|
9
|
-
map: [:flat_map],
|
|
10
|
-
collect: [:map],
|
|
11
|
-
public_send: [:send],
|
|
12
|
-
send: [:public_send],
|
|
13
|
-
gsub: [:sub],
|
|
14
|
-
sub: [:gsub],
|
|
15
|
-
detect: [:find],
|
|
16
|
-
find: [:detect],
|
|
17
|
-
each_with_object: [:inject],
|
|
18
|
-
inject: [:each_with_object],
|
|
19
|
-
reverse_each: [:each],
|
|
20
|
-
each: [:reverse_each],
|
|
21
|
-
length: [:size],
|
|
22
|
-
size: [:length],
|
|
23
|
-
values_at: [:fetch_values],
|
|
24
|
-
fetch_values: [:values_at]
|
|
25
|
-
}.freeze
|
|
3
|
+
require_relative "../operator"
|
|
26
4
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::SendMutation < Evilution::Mutator::Base
|
|
6
|
+
REPLACEMENTS = {
|
|
7
|
+
flat_map: [:map],
|
|
8
|
+
map: [:flat_map],
|
|
9
|
+
collect: [:map],
|
|
10
|
+
public_send: [:send],
|
|
11
|
+
send: [:public_send],
|
|
12
|
+
gsub: [:sub],
|
|
13
|
+
sub: [:gsub],
|
|
14
|
+
detect: [:find],
|
|
15
|
+
find: [:detect],
|
|
16
|
+
each_with_object: [:inject],
|
|
17
|
+
inject: [:each_with_object],
|
|
18
|
+
reverse_each: [:each],
|
|
19
|
+
each: [:reverse_each],
|
|
20
|
+
length: [:size],
|
|
21
|
+
size: [:length],
|
|
22
|
+
values_at: [:fetch_values],
|
|
23
|
+
fetch_values: [:values_at]
|
|
24
|
+
}.freeze
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
def visit_call_node(node)
|
|
27
|
+
replacements = REPLACEMENTS[node.name]
|
|
28
|
+
return super unless replacements
|
|
29
|
+
return super unless node.receiver
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
offset: loc.start_offset,
|
|
38
|
-
length: loc.length,
|
|
39
|
-
replacement: replacement.to_s,
|
|
40
|
-
node: node
|
|
41
|
-
)
|
|
42
|
-
end
|
|
31
|
+
loc = node.message_loc
|
|
32
|
+
return super unless loc
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
replacements.each do |replacement|
|
|
35
|
+
add_mutation(
|
|
36
|
+
offset: loc.start_offset,
|
|
37
|
+
length: loc.length,
|
|
38
|
+
replacement: replacement.to_s,
|
|
39
|
+
node: node
|
|
40
|
+
)
|
|
47
41
|
end
|
|
42
|
+
|
|
43
|
+
super
|
|
48
44
|
end
|
|
49
45
|
end
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class StatementDeletion < Base
|
|
7
|
-
def visit_statements_node(node)
|
|
8
|
-
if node.body.length > 1
|
|
9
|
-
node.body.each do |child|
|
|
10
|
-
add_mutation(
|
|
11
|
-
offset: child.location.start_offset,
|
|
12
|
-
length: child.location.length,
|
|
13
|
-
replacement: "",
|
|
14
|
-
node: child
|
|
15
|
-
)
|
|
16
|
-
end
|
|
17
|
-
end
|
|
3
|
+
require_relative "../operator"
|
|
18
4
|
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::StatementDeletion < Evilution::Mutator::Base
|
|
6
|
+
def visit_statements_node(node)
|
|
7
|
+
if node.body.length > 1
|
|
8
|
+
node.body.each do |child|
|
|
9
|
+
add_mutation(
|
|
10
|
+
offset: child.location.start_offset,
|
|
11
|
+
length: child.location.length,
|
|
12
|
+
replacement: "",
|
|
13
|
+
node: child
|
|
14
|
+
)
|
|
21
15
|
end
|
|
22
16
|
end
|
|
17
|
+
|
|
18
|
+
super
|
|
23
19
|
end
|
|
24
20
|
end
|
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class StringLiteral < Base
|
|
7
|
-
def visit_string_node(node)
|
|
8
|
-
replacement = node.content.empty? ? '"mutation"' : '""'
|
|
3
|
+
require_relative "../operator"
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
replacement: replacement,
|
|
14
|
-
node: node
|
|
15
|
-
)
|
|
5
|
+
class Evilution::Mutator::Operator::StringLiteral < Evilution::Mutator::Base
|
|
6
|
+
def visit_string_node(node)
|
|
7
|
+
replacement = node.content.empty? ? '"mutation"' : '""'
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
add_mutation(
|
|
10
|
+
offset: node.location.start_offset,
|
|
11
|
+
length: node.location.length,
|
|
12
|
+
replacement: replacement,
|
|
13
|
+
node: node
|
|
14
|
+
)
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
add_mutation(
|
|
17
|
+
offset: node.location.start_offset,
|
|
18
|
+
length: node.location.length,
|
|
19
|
+
replacement: "nil",
|
|
20
|
+
node: node
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
super
|
|
28
24
|
end
|
|
29
25
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../operator"
|
|
6
|
+
|
|
7
|
+
class Evilution::Mutator::Operator::SuperclassRemoval < Evilution::Mutator::Base
|
|
8
|
+
def call(subject)
|
|
9
|
+
@subject = subject
|
|
10
|
+
@file_source = File.read(subject.file_path)
|
|
11
|
+
@mutations = []
|
|
12
|
+
|
|
13
|
+
tree = self.class.parsed_tree_for(subject.file_path, @file_source)
|
|
14
|
+
enclosing = find_enclosing_class(tree, subject.line_number)
|
|
15
|
+
return @mutations unless enclosing
|
|
16
|
+
return @mutations unless enclosing.superclass
|
|
17
|
+
|
|
18
|
+
first_method_line = find_first_method_line(enclosing)
|
|
19
|
+
return @mutations unless first_method_line == subject.line_number
|
|
20
|
+
|
|
21
|
+
name_end = enclosing.constant_path.location.start_offset + enclosing.constant_path.location.length
|
|
22
|
+
superclass_end = enclosing.superclass.location.start_offset + enclosing.superclass.location.length
|
|
23
|
+
|
|
24
|
+
add_mutation(
|
|
25
|
+
offset: name_end,
|
|
26
|
+
length: superclass_end - name_end,
|
|
27
|
+
replacement: "",
|
|
28
|
+
node: enclosing
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@mutations
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def find_enclosing_class(tree, target_line)
|
|
37
|
+
finder = ClassFinder.new(target_line)
|
|
38
|
+
finder.visit(tree)
|
|
39
|
+
finder.result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find_first_method_line(class_node)
|
|
43
|
+
return nil unless class_node.body
|
|
44
|
+
|
|
45
|
+
class_node.body.body.each do |node|
|
|
46
|
+
return node.location.start_line if node.is_a?(Prism::DefNode)
|
|
47
|
+
end
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Visitor to find the ClassNode enclosing a given line number.
|
|
52
|
+
class ClassFinder < Prism::Visitor
|
|
53
|
+
attr_reader :result
|
|
54
|
+
|
|
55
|
+
def initialize(target_line)
|
|
56
|
+
@target_line = target_line
|
|
57
|
+
@result = nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def visit_class_node(node)
|
|
61
|
+
@result = node if @target_line.between?(node.location.start_line, node.location.end_line)
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
module Mutator
|
|
5
|
-
module Operator
|
|
6
|
-
class SymbolLiteral < Base
|
|
7
|
-
def visit_symbol_node(node)
|
|
8
|
-
add_mutation(
|
|
9
|
-
offset: node.location.start_offset,
|
|
10
|
-
length: node.location.length,
|
|
11
|
-
replacement: ":__evilution_mutated__",
|
|
12
|
-
node: node
|
|
13
|
-
)
|
|
3
|
+
require_relative "../operator"
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
class Evilution::Mutator::Operator::SymbolLiteral < Evilution::Mutator::Base
|
|
6
|
+
def visit_symbol_node(node)
|
|
7
|
+
add_mutation(
|
|
8
|
+
offset: node.location.start_offset,
|
|
9
|
+
length: node.location.length,
|
|
10
|
+
replacement: ":__evilution_mutated__",
|
|
11
|
+
node: node
|
|
12
|
+
)
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
add_mutation(
|
|
15
|
+
offset: node.location.start_offset,
|
|
16
|
+
length: node.location.length,
|
|
17
|
+
replacement: "nil",
|
|
18
|
+
node: node
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
super
|
|
26
22
|
end
|
|
27
23
|
end
|