mutant 0.11.16 → 0.11.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/mutant +55 -46
- data/lib/mutant/bootstrap.rb +66 -35
- data/lib/mutant/cli/command/environment/run.rb +1 -1
- data/lib/mutant/cli/command/environment.rb +2 -2
- data/lib/mutant/cli/command.rb +24 -11
- data/lib/mutant/env.rb +9 -0
- data/lib/mutant/mutator/node/literal/regex.rb +8 -8
- data/lib/mutant/mutator/node/send.rb +27 -17
- data/lib/mutant/mutator/regexp.rb +211 -0
- data/lib/mutant/parallel/driver.rb +1 -0
- data/lib/mutant/parallel/worker.rb +5 -1
- data/lib/mutant/runner.rb +8 -6
- data/lib/mutant/segment/recorder.rb +124 -0
- data/lib/mutant/segment.rb +25 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +5 -0
- data/lib/mutant.rb +288 -228
- metadata +12 -33
- data/lib/mutant/ast/regexp/transformer/direct.rb +0 -145
- data/lib/mutant/ast/regexp/transformer/named_group.rb +0 -50
- data/lib/mutant/ast/regexp/transformer/options_group.rb +0 -68
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +0 -92
- data/lib/mutant/ast/regexp/transformer/recursive.rb +0 -56
- data/lib/mutant/ast/regexp/transformer/root.rb +0 -28
- data/lib/mutant/ast/regexp/transformer/text.rb +0 -58
- data/lib/mutant/ast/regexp/transformer.rb +0 -152
- data/lib/mutant/ast/regexp.rb +0 -54
- data/lib/mutant/mutator/node/regexp/alternation_meta.rb +0 -20
- data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +0 -20
- data/lib/mutant/mutator/node/regexp/capture_group.rb +0 -23
- data/lib/mutant/mutator/node/regexp/character_type.rb +0 -31
- data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +0 -20
- data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +0 -20
- data/lib/mutant/mutator/node/regexp/named_group.rb +0 -39
- data/lib/mutant/mutator/node/regexp/zero_or_more.rb +0 -34
- data/lib/mutant/mutator/node/regexp.rb +0 -20
@@ -1,152 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class AST
|
5
|
-
module Regexp
|
6
|
-
# Regexp bijective mapper
|
7
|
-
#
|
8
|
-
# Transforms parsed regular expression representation from
|
9
|
-
# `Regexp::Expression` instances (provided by `regexp_parser`) into
|
10
|
-
# equivalent representations using `Parser::AST::Node`
|
11
|
-
class Transformer
|
12
|
-
include AbstractType
|
13
|
-
|
14
|
-
REGISTRY = Registry.new(
|
15
|
-
->(type) { fail "No regexp transformer registered for: #{type}" }
|
16
|
-
)
|
17
|
-
|
18
|
-
# Lookup transformer class for regular expression node type
|
19
|
-
#
|
20
|
-
# @param type [Symbol]
|
21
|
-
#
|
22
|
-
# @return [Class<Transformer>]
|
23
|
-
def self.lookup(type)
|
24
|
-
REGISTRY.lookup(type)
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.register(type)
|
28
|
-
REGISTRY.register(type, self)
|
29
|
-
end
|
30
|
-
private_class_method :register
|
31
|
-
|
32
|
-
# Transform expression
|
33
|
-
#
|
34
|
-
# @param expression [Regexp::Expression]
|
35
|
-
#
|
36
|
-
# @return [Parser::AST::Node]
|
37
|
-
def self.to_ast(expression)
|
38
|
-
self::ExpressionToAST.call(expression)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Transform node
|
42
|
-
#
|
43
|
-
# @param node [Parser::AST::Node]
|
44
|
-
#
|
45
|
-
# @return [Regexp::Expression]
|
46
|
-
def self.to_expression(node)
|
47
|
-
self::ASTToExpression.call(node)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Abstract expression transformer
|
51
|
-
class ExpressionToAST
|
52
|
-
PREFIX = :regexp
|
53
|
-
|
54
|
-
include Concord.new(:expression), Procto, AST::Sexp, AbstractType, Adamantium
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def ast(*children)
|
59
|
-
s(type, *children)
|
60
|
-
end
|
61
|
-
|
62
|
-
def quantify(node)
|
63
|
-
return node unless expression.quantified?
|
64
|
-
|
65
|
-
Quantifier.to_ast(expression.quantifier).append(node)
|
66
|
-
end
|
67
|
-
|
68
|
-
def children
|
69
|
-
expression.map(&Regexp.public_method(:to_ast))
|
70
|
-
end
|
71
|
-
|
72
|
-
def type
|
73
|
-
:"#{PREFIX}_#{expression.token}_#{expression.type}"
|
74
|
-
end
|
75
|
-
end # ExpressionToAST
|
76
|
-
|
77
|
-
# Abstract node transformer
|
78
|
-
class ASTToExpression
|
79
|
-
include Concord.new(:node), Procto, AbstractType, Adamantium
|
80
|
-
|
81
|
-
# Call generic transform method and freeze result
|
82
|
-
#
|
83
|
-
# @return [Regexp::Expression]
|
84
|
-
def call
|
85
|
-
transform.freeze
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
abstract_method :transform
|
91
|
-
|
92
|
-
def subexpressions
|
93
|
-
node.children.map(&Regexp.public_method(:to_expression))
|
94
|
-
end
|
95
|
-
end # ASTToExpression
|
96
|
-
|
97
|
-
# Mixin for node transformers
|
98
|
-
#
|
99
|
-
# Helps construct a mapping from Parser::AST::Node domain to
|
100
|
-
# Regexp::Expression domain
|
101
|
-
module LookupTable
|
102
|
-
Mapping = Class.new.include(Concord::Public.new(:token, :regexp_class))
|
103
|
-
|
104
|
-
# Table mapping ast types to object information for regexp domain
|
105
|
-
class Table
|
106
|
-
|
107
|
-
# Coerce array of mapping information into structured table
|
108
|
-
#
|
109
|
-
# @param [Array(Symbol, Array, Class<Regexp::Expression>)]
|
110
|
-
#
|
111
|
-
# @return [Table]
|
112
|
-
def self.create(*rows)
|
113
|
-
table = rows.to_h do |ast_type, token, klass|
|
114
|
-
[ast_type, Mapping.new(::Regexp::Token.new(*token), klass)]
|
115
|
-
end
|
116
|
-
|
117
|
-
new(table)
|
118
|
-
end
|
119
|
-
|
120
|
-
include Concord.new(:table), Adamantium
|
121
|
-
|
122
|
-
# Types defined by the table
|
123
|
-
#
|
124
|
-
# @return [Array<Symbol>]
|
125
|
-
def types
|
126
|
-
table.keys
|
127
|
-
end
|
128
|
-
|
129
|
-
# Lookup mapping information given an ast node type
|
130
|
-
#
|
131
|
-
# @param type [Symbol]
|
132
|
-
#
|
133
|
-
# @return [Mapping]
|
134
|
-
def lookup(type)
|
135
|
-
table.fetch(type)
|
136
|
-
end
|
137
|
-
end # Table
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
def expression_token
|
142
|
-
self.class::TABLE.lookup(node.type).token
|
143
|
-
end
|
144
|
-
|
145
|
-
def expression_class
|
146
|
-
self.class::TABLE.lookup(node.type).regexp_class
|
147
|
-
end
|
148
|
-
end # LookupTable
|
149
|
-
end # Transformer
|
150
|
-
end # Regexp
|
151
|
-
end # AST
|
152
|
-
end # Mutant
|
data/lib/mutant/ast/regexp.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class AST
|
5
|
-
# Regexp source mapper
|
6
|
-
module Regexp
|
7
|
-
# Parse regex string into expression
|
8
|
-
#
|
9
|
-
# @param regexp [String]
|
10
|
-
#
|
11
|
-
# @return [Regexp::Expression, nil]
|
12
|
-
def self.parse(regexp)
|
13
|
-
::Regexp::Parser.parse(regexp)
|
14
|
-
end
|
15
|
-
|
16
|
-
# Convert expression into ast node
|
17
|
-
#
|
18
|
-
# @param expression [Regexp::Expression]
|
19
|
-
#
|
20
|
-
# @return [Parser::AST::Node]
|
21
|
-
def self.to_ast(expression)
|
22
|
-
ast_type = :"regexp_#{expression.token}_#{expression.type}"
|
23
|
-
|
24
|
-
Transformer.lookup(ast_type).to_ast(expression)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Convert node into expression
|
28
|
-
#
|
29
|
-
# @param node [Parser::AST::Node]
|
30
|
-
#
|
31
|
-
# @return [Regexp::Expression]
|
32
|
-
def self.to_expression(node)
|
33
|
-
Transformer.lookup(node.type).to_expression(node)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Convert's a `parser` `regexp` node into more fine-grained AST nodes.
|
37
|
-
#
|
38
|
-
# @param node [Parser::AST::Node]
|
39
|
-
#
|
40
|
-
# @return [Parser::AST::Node]
|
41
|
-
def self.expand_regexp_ast(node)
|
42
|
-
*body, _opts = node.children
|
43
|
-
|
44
|
-
# NOTE: We only mutate parts of regexp body if the body is composed of
|
45
|
-
# only strings. Regular expressions with interpolation are skipped
|
46
|
-
return unless body.all? { |child| child.type.equal?(:str) }
|
47
|
-
|
48
|
-
body_expression = parse(body.map(&:children).join)
|
49
|
-
|
50
|
-
to_ast(body_expression)
|
51
|
-
end
|
52
|
-
end # Regexp
|
53
|
-
end # AST
|
54
|
-
end # Mutant
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for pipe in `/foo|bar/` regexp
|
8
|
-
class AlternationMeta < Node
|
9
|
-
handle(:regexp_alternation_meta)
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def dispatch
|
14
|
-
children.each_index(&method(:delete_child))
|
15
|
-
end
|
16
|
-
end # AlternationMeta
|
17
|
-
end # Regexp
|
18
|
-
end # Node
|
19
|
-
end # Mutator
|
20
|
-
end # Mutant
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for beginning of line anchor `^`
|
8
|
-
class BeginningOfLineAnchor < Node
|
9
|
-
handle(:regexp_bol_anchor)
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def dispatch
|
14
|
-
emit(s(:regexp_bos_anchor))
|
15
|
-
end
|
16
|
-
end # BeginningOfLineAnchor
|
17
|
-
end # Regexp
|
18
|
-
end # Node
|
19
|
-
end # Mutator
|
20
|
-
end # Mutant
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for regexp capture groups, such as `/(foo)/`
|
8
|
-
class CaptureGroup < Node
|
9
|
-
handle(:regexp_capture_group)
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def dispatch
|
14
|
-
return if children.empty?
|
15
|
-
|
16
|
-
emit(s(:regexp_passive_group, *children))
|
17
|
-
children.each_index(&method(:mutate_child))
|
18
|
-
end
|
19
|
-
end # EndOfLineAnchor
|
20
|
-
end # Regexp
|
21
|
-
end # Node
|
22
|
-
end # Mutator
|
23
|
-
end # Mutant
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Character type mutator
|
8
|
-
class CharacterType < Node
|
9
|
-
map = {
|
10
|
-
regexp_digit_type: :regexp_nondigit_type,
|
11
|
-
regexp_hex_type: :regexp_nonhex_type,
|
12
|
-
regexp_space_type: :regexp_nonspace_type,
|
13
|
-
regexp_word_boundary_anchor: :regexp_nonword_boundary_anchor,
|
14
|
-
regexp_word_type: :regexp_nonword_type,
|
15
|
-
regexp_xgrapheme_type: :regexp_linebreak_type
|
16
|
-
}
|
17
|
-
|
18
|
-
MAP = map.merge(map.invert)
|
19
|
-
|
20
|
-
handle(*MAP.keys)
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def dispatch
|
25
|
-
emit(s(MAP.fetch(node.type)))
|
26
|
-
end
|
27
|
-
end # CharacterType
|
28
|
-
end # Regexp
|
29
|
-
end # Node
|
30
|
-
end # Mutator
|
31
|
-
end # Mutant
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for end of line anchor `$`
|
8
|
-
class EndOfLineAnchor < Node
|
9
|
-
handle(:regexp_eol_anchor)
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def dispatch
|
14
|
-
emit(s(:regexp_eos_anchor))
|
15
|
-
end
|
16
|
-
end # EndOfLineAnchor
|
17
|
-
end # Regexp
|
18
|
-
end # Node
|
19
|
-
end # Mutator
|
20
|
-
end # Mutant
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for end of line or before end of string anchor `\Z`
|
8
|
-
class EndOfStringOrBeforeEndOfLineAnchor < Node
|
9
|
-
handle(:regexp_eos_ob_eol_anchor)
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def dispatch
|
14
|
-
emit(s(:regexp_eos_anchor))
|
15
|
-
end
|
16
|
-
end # EndOfStringOrBeforeEndOfLineAnchor
|
17
|
-
end # Regexp
|
18
|
-
end # Node
|
19
|
-
end # Mutator
|
20
|
-
end # Mutant
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for regexp named capture groups, such as `/(?<foo>bar)/`
|
8
|
-
class NamedGroup < Node
|
9
|
-
handle(:regexp_named_group)
|
10
|
-
|
11
|
-
children :name
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def dispatch
|
16
|
-
return if remaining_children.empty?
|
17
|
-
|
18
|
-
remaining_children_indices.each(&method(:mutate_child))
|
19
|
-
|
20
|
-
# Allows unused captures to be kept and named if they are explicitly prefixed with an
|
21
|
-
# underscore, like we allow with unused local variables.
|
22
|
-
return if name_underscored?
|
23
|
-
|
24
|
-
emit(s(:regexp_passive_group, *remaining_children))
|
25
|
-
emit_name_underscore_mutation
|
26
|
-
end
|
27
|
-
|
28
|
-
def emit_name_underscore_mutation
|
29
|
-
emit_type("_#{name}", *remaining_children)
|
30
|
-
end
|
31
|
-
|
32
|
-
def name_underscored?
|
33
|
-
name.start_with?('_')
|
34
|
-
end
|
35
|
-
end # EndOfLineAnchor
|
36
|
-
end # Regexp
|
37
|
-
end # Node
|
38
|
-
end # Mutator
|
39
|
-
end # Mutant
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for zero-or-more quantifier, `*`
|
8
|
-
class ZeroOrMore < Node
|
9
|
-
MAP = {
|
10
|
-
regexp_greedy_zero_or_more: :regexp_greedy_one_or_more,
|
11
|
-
regexp_reluctant_zero_or_more: :regexp_reluctant_one_or_more,
|
12
|
-
regexp_possessive_zero_or_more: :regexp_possessive_one_or_more
|
13
|
-
}.freeze
|
14
|
-
|
15
|
-
handle(*MAP.keys)
|
16
|
-
|
17
|
-
children :min, :max, :subject
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
# Replace:
|
22
|
-
# * `/a*/` with `/a+/`
|
23
|
-
# * `/a*?/` with `/a+?/`
|
24
|
-
# * `/a*+/` with `/a++/`
|
25
|
-
def dispatch
|
26
|
-
emit(s(MAP.fetch(node.type), *children))
|
27
|
-
emit_subject_mutations
|
28
|
-
emit(subject)
|
29
|
-
end
|
30
|
-
end # ZeroOrMore
|
31
|
-
end # Regexp
|
32
|
-
end # Node
|
33
|
-
end # Mutator
|
34
|
-
end # Mutant
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Mutant
|
4
|
-
class Mutator
|
5
|
-
class Node
|
6
|
-
module Regexp
|
7
|
-
# Mutator for root expression regexp wrapper
|
8
|
-
class RootExpression < Node
|
9
|
-
handle(:regexp_root_expression)
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def dispatch
|
14
|
-
children.each_index(&method(:mutate_child))
|
15
|
-
end
|
16
|
-
end # RootExpression
|
17
|
-
end # Regexp
|
18
|
-
end # Node
|
19
|
-
end # Mutator
|
20
|
-
end # Mutant
|