mutant 0.10.20 → 0.10.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +23 -4
  3. data/lib/mutant/ast/meta/send.rb +0 -1
  4. data/lib/mutant/ast/regexp.rb +37 -0
  5. data/lib/mutant/ast/regexp/transformer.rb +150 -0
  6. data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
  7. data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
  8. data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
  9. data/lib/mutant/ast/regexp/transformer/quantifier.rb +90 -0
  10. data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
  11. data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
  12. data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
  13. data/lib/mutant/ast/types.rb +115 -11
  14. data/lib/mutant/cli/command/environment.rb +9 -3
  15. data/lib/mutant/config.rb +8 -54
  16. data/lib/mutant/config/coverage_criteria.rb +61 -0
  17. data/lib/mutant/env.rb +8 -6
  18. data/lib/mutant/expression.rb +0 -12
  19. data/lib/mutant/expression/namespace.rb +1 -1
  20. data/lib/mutant/isolation/fork.rb +1 -1
  21. data/lib/mutant/loader.rb +1 -1
  22. data/lib/mutant/matcher.rb +2 -2
  23. data/lib/mutant/matcher/config.rb +27 -6
  24. data/lib/mutant/matcher/method.rb +2 -3
  25. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  26. data/lib/mutant/meta/example/dsl.rb +6 -1
  27. data/lib/mutant/mutator/node/arguments.rb +0 -2
  28. data/lib/mutant/mutator/node/block.rb +5 -1
  29. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  30. data/lib/mutant/mutator/node/kwargs.rb +44 -0
  31. data/lib/mutant/mutator/node/literal/regex.rb +26 -0
  32. data/lib/mutant/mutator/node/literal/symbol.rb +0 -2
  33. data/lib/mutant/mutator/node/regexp.rb +20 -0
  34. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  35. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  36. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  37. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  38. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  39. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  40. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  41. data/lib/mutant/mutator/node/sclass.rb +1 -1
  42. data/lib/mutant/mutator/node/send.rb +36 -19
  43. data/lib/mutant/parallel.rb +43 -28
  44. data/lib/mutant/parallel/driver.rb +9 -3
  45. data/lib/mutant/parallel/worker.rb +60 -2
  46. data/lib/mutant/pipe.rb +94 -0
  47. data/lib/mutant/reporter/cli/printer/env.rb +1 -1
  48. data/lib/mutant/reporter/cli/printer/isolation_result.rb +1 -6
  49. data/lib/mutant/result.rb +8 -0
  50. data/lib/mutant/runner.rb +7 -10
  51. data/lib/mutant/runner/sink.rb +12 -2
  52. data/lib/mutant/subject.rb +1 -3
  53. data/lib/mutant/subject/method/instance.rb +2 -4
  54. data/lib/mutant/timer.rb +2 -4
  55. data/lib/mutant/transform.rb +25 -2
  56. data/lib/mutant/version.rb +1 -1
  57. data/lib/mutant/world.rb +1 -2
  58. metadata +48 -9
  59. data/lib/mutant/warnings.rb +0 -106
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ module Regexp
6
+ class Transformer
7
+ # Transformer for option groups
8
+ class OptionsGroup < self
9
+ register :regexp_options_group
10
+ register :regexp_options_switch_group
11
+
12
+ # Mapper from `Regexp::Expression` to `Parser::AST::Node`
13
+ class ExpressionToAST < Transformer::ExpressionToAST
14
+
15
+ # Transform options group into node
16
+ #
17
+ # @return [Parser::AST::Node]
18
+ def call
19
+ quantify(ast(expression.option_changes, *children))
20
+ end
21
+ end # ExpressionToAST
22
+
23
+ # Mapper from `Parser::AST::Node` to `Regexp::Expression`
24
+ class ASTToExpression < Transformer::ASTToExpression
25
+ include NamedChildren
26
+
27
+ children :option_changes
28
+
29
+ private
30
+
31
+ def transform
32
+ options_group.tap do |expression|
33
+ expression.expressions = subexpressions
34
+ end
35
+ end
36
+
37
+ def subexpressions
38
+ remaining_children.map(&Regexp.public_method(:to_expression))
39
+ end
40
+
41
+ def options_group
42
+ ::Regexp::Expression::Group::Options.new(
43
+ ::Regexp::Token.new(:group, type, text)
44
+ )
45
+ end
46
+
47
+ def type
48
+ {
49
+ regexp_options_group: :options,
50
+ regexp_options_switch_group: :options_switch
51
+ }.fetch(node.type)
52
+ end
53
+
54
+ def text
55
+ pos, neg = option_changes.partition { |_opt, val| val }.map do |arr|
56
+ arr.map(&:first).join
57
+ end
58
+ neg_opt_sep = '-' unless neg.empty?
59
+ content_sep = ':' unless type.equal?(:options_switch)
60
+
61
+ "(?#{pos}#{neg_opt_sep}#{neg}#{content_sep}"
62
+ end
63
+ end # ASTToExpression
64
+ end # OptionsGroup
65
+ end # Transformer
66
+ end # Regexp
67
+ end # AST
68
+ end # Mutant
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ module Regexp
6
+ class Transformer
7
+ # Transformer for regexp quantifiers
8
+ class Quantifier < self
9
+ # Mapper from `Regexp::Expression` to `Parser::AST::Node`
10
+ class ExpressionToAST < Transformer::ExpressionToAST
11
+ # Transform quantifier into node
12
+ #
13
+ # @return [Parser::AST::Node]
14
+ def call
15
+ ast(expression.min, expression.max)
16
+ end
17
+
18
+ private
19
+
20
+ def type
21
+ :"regexp_#{expression.mode}_#{expression.token}"
22
+ end
23
+ end # ExpressionToAST
24
+
25
+ # Mapper from `Parser::AST::Node` to `Regexp::Expression`
26
+ class ASTToExpression < Transformer::ASTToExpression
27
+ include NamedChildren
28
+
29
+ children :min, :max, :subject
30
+
31
+ Quantifier = Class.new.include(Concord::Public.new(:type, :suffix, :mode))
32
+
33
+ QUANTIFIER_MAP = IceNine.deep_freeze({
34
+ regexp_greedy_zero_or_more: [:zero_or_more, '*', :greedy],
35
+ regexp_greedy_one_or_more: [:one_or_more, '+', :greedy],
36
+ regexp_greedy_zero_or_one: [:zero_or_one, '?', :greedy],
37
+ regexp_possessive_zero_or_one: [:zero_or_one, '?+', :possessive],
38
+ regexp_reluctant_zero_or_more: [:zero_or_more, '*?', :reluctant],
39
+ regexp_reluctant_one_or_more: [:one_or_more, '+?', :reluctant],
40
+ regexp_possessive_zero_or_more: [:zero_or_more, '*+', :possessive],
41
+ regexp_possessive_one_or_more: [:one_or_more, '++', :possessive],
42
+ regexp_greedy_interval: [:interval, '', :greedy],
43
+ regexp_reluctant_interval: [:interval, '?', :reluctant],
44
+ regexp_possessive_interval: [:interval, '+', :possessive]
45
+ }.transform_values { |arguments| Quantifier.new(*arguments) }.to_h)
46
+
47
+ private
48
+
49
+ def transform
50
+ Regexp.to_expression(subject).dup.tap do |expression|
51
+ expression.quantify(type, text, min, max, mode)
52
+ end
53
+ end
54
+
55
+ def text
56
+ if type.equal?(:interval)
57
+ interval_text + suffix
58
+ else
59
+ suffix
60
+ end
61
+ end
62
+
63
+ def type
64
+ quantifier.type
65
+ end
66
+
67
+ def suffix
68
+ quantifier.suffix
69
+ end
70
+
71
+ def mode
72
+ quantifier.mode
73
+ end
74
+
75
+ def quantifier
76
+ QUANTIFIER_MAP.fetch(node.type)
77
+ end
78
+
79
+ def interval_text
80
+ interval = [min, max].map { |num| num if num.positive? }.uniq
81
+ "{#{interval.join(',')}}"
82
+ end
83
+ end # ASTToExpression
84
+
85
+ ASTToExpression::QUANTIFIER_MAP.keys.each(&method(:register))
86
+ end # Quantifier
87
+ end # Transformer
88
+ end # Regexp
89
+ end # AST
90
+ end # Mutant
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ module Regexp
6
+ class Transformer
7
+ # Transformer for nodes with children
8
+ class Recursive < self
9
+ # Mapper from `Regexp::Expression` to `Parser::AST::Node`
10
+ class ExpressionToAST < Transformer::ExpressionToAST
11
+ # Transform expression and children into nodes
12
+ #
13
+ # @return [Parser::AST::Node]
14
+ def call
15
+ quantify(ast(*children))
16
+ end
17
+ end # ExpressionToAST
18
+
19
+ # Mapper from `Parser::AST::Node` to `Regexp::Expression`
20
+ class ASTToExpression < Transformer::ASTToExpression
21
+ include LookupTable
22
+
23
+ # Expression::Sequence represents conditional branches, alternation branches, and intersection branches
24
+ # rubocop:disable Layout/LineLength
25
+ TABLE = Table.create(
26
+ [:regexp_alternation_meta, [:meta, :alternation, '|'], ::Regexp::Expression::Alternation],
27
+ [:regexp_atomic_group, [:group, :atomic, '(?>'], ::Regexp::Expression::Group::Atomic],
28
+ [:regexp_capture_group, [:group, :capture, '('], ::Regexp::Expression::Group::Capture],
29
+ [:regexp_character_set, [:set, :character, '['], ::Regexp::Expression::CharacterSet],
30
+ [:regexp_intersection_set, [:set, :intersection, '&&'], ::Regexp::Expression::CharacterSet::Intersection],
31
+ [:regexp_lookahead_assertion, [:assertion, :lookahead, '(?='], ::Regexp::Expression::Assertion::Lookahead],
32
+ [:regexp_lookbehind_assertion, [:assertion, :lookbehind, '(?<='], ::Regexp::Expression::Assertion::Lookbehind],
33
+ [:regexp_nlookahead_assertion, [:assertion, :nlookahead, '(?!'], ::Regexp::Expression::Assertion::NegativeLookahead],
34
+ [:regexp_nlookbehind_assertion, [:assertion, :nlookbehind, '(?<!'], ::Regexp::Expression::Assertion::NegativeLookbehind],
35
+ [:regexp_open_conditional, [:conditional, :open, '(?'], ::Regexp::Expression::Conditional::Expression],
36
+ [:regexp_passive_group, [:group, :passive, '(?:'], ::Regexp::Expression::Group::Passive],
37
+ [:regexp_range_set, [:set, :range, '-'], ::Regexp::Expression::CharacterSet::Range],
38
+ [:regexp_sequence_expression, [:expression, :sequence, ''], ::Regexp::Expression::Sequence]
39
+ )
40
+ # rubocop:enable Layout/LineLength
41
+
42
+ private
43
+
44
+ def transform
45
+ expression_class.new(expression_token).tap do |expression|
46
+ expression.expressions = subexpressions
47
+ end
48
+ end
49
+ end # ASTToExpression
50
+
51
+ ASTToExpression::TABLE.types.each(&method(:register))
52
+ end # Recursive
53
+ end # Transformer
54
+ end # Regexp
55
+ end # AST
56
+ end # Mutant
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ module Regexp
6
+ class Transformer
7
+ # Transformer for root nodes
8
+ class Root < self
9
+ register :regexp_root_expression
10
+
11
+ ExpressionToAST = Class.new(Recursive::ExpressionToAST)
12
+
13
+ # Mapper from `Parser::AST::Node` to `Regexp::Expression`
14
+ class ASTToExpression < Transformer::ASTToExpression
15
+
16
+ private
17
+
18
+ def transform
19
+ ::Regexp::Expression::Root.build.tap do |root|
20
+ root.expressions = subexpressions
21
+ end
22
+ end
23
+ end # ASTToExpression
24
+ end # Root
25
+ end # Transformer
26
+ end # Regexp
27
+ end # AST
28
+ end # Mutant
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module AST
5
+ module Regexp
6
+ class Transformer
7
+ # Regexp AST transformer for nodes that encode a text value
8
+ class Text < self
9
+ # Mapper from `Regexp::Expression` to `Parser::AST::Node`
10
+ class ExpressionToAST < Transformer::ExpressionToAST
11
+ # Transform expression into node preserving text value
12
+ #
13
+ # @return [Parser::AST::Node]
14
+ def call
15
+ quantify(ast(expression.text))
16
+ end
17
+ end # ExpressionToAST
18
+
19
+ # Mapper from `Parser::AST::Node` to `Regexp::Expression`
20
+ class ASTToExpression < Transformer::ASTToExpression
21
+ include LookupTable
22
+
23
+ # rubocop:disable Layout/LineLength
24
+ TABLE = Table.create(
25
+ [:regexp_literal_literal, %i[literal literal], ::Regexp::Expression::Literal],
26
+ [:regexp_comment_group, %i[group comment], ::Regexp::Expression::Group::Comment],
27
+ [:regexp_number_backref, %i[backref number], ::Regexp::Expression::Backreference::Number],
28
+ [:regexp_name_call_backref, %i[backref name_call], ::Regexp::Expression::Backreference::NameCall],
29
+ [:regexp_whitespace_free_space, %i[free_space whitespace], ::Regexp::Expression::WhiteSpace],
30
+ [:regexp_comment_free_space, %i[free_space comment], ::Regexp::Expression::WhiteSpace],
31
+ [:regexp_hex_escape, %i[escape hex], ::Regexp::Expression::EscapeSequence::Hex],
32
+ [:regexp_octal_escape, %i[escape octal], ::Regexp::Expression::EscapeSequence::Octal],
33
+ [:regexp_literal_escape, %i[escape literal], ::Regexp::Expression::EscapeSequence::Literal],
34
+ [:regexp_backslash_escape, %i[escape backslash], ::Regexp::Expression::EscapeSequence::Literal],
35
+ [:regexp_tab_escape, %i[escape tab], ::Regexp::Expression::EscapeSequence::Literal],
36
+ [:regexp_codepoint_list_escape, %i[escape codepoint_list], ::Regexp::Expression::EscapeSequence::CodepointList],
37
+ [:regexp_codepoint_escape, %i[escape codepoint], ::Regexp::Expression::EscapeSequence::Codepoint],
38
+ [:regexp_control_escape, %i[escape control], ::Regexp::Expression::EscapeSequence::Control],
39
+ [:regexp_meta_sequence_escape, %i[escape meta_sequence], ::Regexp::Expression::EscapeSequence::Control],
40
+ [:regexp_condition_conditional, %i[conditional condition], ::Regexp::Expression::Conditional::Condition]
41
+ )
42
+ # rubocop:enable Layout/LineLength
43
+
44
+ private
45
+
46
+ def transform
47
+ token = expression_token.dup
48
+ token.text = Util.one(node.children)
49
+ expression_class.new(token)
50
+ end
51
+ end # ASTToExpression
52
+
53
+ ASTToExpression::TABLE.types.each(&method(:register))
54
+ end # Text
55
+ end # Transformer
56
+ end # Regexp
57
+ end # AST
58
+ end # Mutant
@@ -3,14 +3,9 @@
3
3
  module Mutant
4
4
  module AST
5
5
  # Groups of node types
6
- module Types
6
+ module Types # rubocop:disable Metrics/ModuleLength
7
7
  ASSIGNABLE_VARIABLES = Set.new(%i[ivasgn lvasgn cvasgn gvasgn]).freeze
8
8
 
9
- INDEX_ASSIGN_OPERATOR = :[]=
10
-
11
- # Set of nodes that cannot be on the LHS of an assignment
12
- NOT_ASSIGNABLE = Set.new(%i[int float str dstr class module self nil]).freeze
13
-
14
9
  # Set of op-assign types
15
10
  OP_ASSIGN = Set.new(%i[or_asgn and_asgn op_asgn]).freeze
16
11
  # Set of node types that are not valid when emitted standalone
@@ -53,21 +48,130 @@ module Mutant
53
48
  METHOD_OPERATORS - (INDEX_OPERATORS + UNARY_METHOD_OPERATORS)
54
49
  )
55
50
 
56
- OPERATOR_METHODS = Set.new(
57
- METHOD_OPERATORS + INDEX_OPERATORS + UNARY_METHOD_OPERATORS
58
- ).freeze
59
-
60
51
  # Nodes that are NOT handled by mutant.
61
52
  #
62
53
  # not - 1.8 only, mutant does not support 1.8
63
54
  #
64
55
  BLACKLIST = Set.new(%i[not]).freeze
65
56
 
57
+ # Nodes generated by regular expression body parsing
58
+ REGEXP = Set.new(%i[
59
+ regexp_alnum_posixclass
60
+ regexp_alpha_posixclass
61
+ regexp_alpha_property
62
+ regexp_alternation_escape
63
+ regexp_alternation_meta
64
+ regexp_arabic_property
65
+ regexp_ascii_posixclass
66
+ regexp_atomic_group
67
+ regexp_backslash_escape
68
+ regexp_backspace_escape
69
+ regexp_bell_escape
70
+ regexp_blank_posixclass
71
+ regexp_bol_anchor
72
+ regexp_bol_escape
73
+ regexp_bos_anchor
74
+ regexp_capture_group
75
+ regexp_carriage_escape
76
+ regexp_character_set
77
+ regexp_cntrl_posixclass
78
+ regexp_codepoint_escape
79
+ regexp_codepoint_list_escape
80
+ regexp_comment_free_space
81
+ regexp_comment_group
82
+ regexp_condition_conditional
83
+ regexp_control_escape
84
+ regexp_digit_posixclass
85
+ regexp_digit_type
86
+ regexp_dot_escape
87
+ regexp_dot_meta
88
+ regexp_eol_anchor
89
+ regexp_eol_escape
90
+ regexp_eos_anchor
91
+ regexp_eos_ob_eol_anchor
92
+ regexp_escape_escape
93
+ regexp_form_feed_escape
94
+ regexp_graph_posixclass
95
+ regexp_greedy_interval
96
+ regexp_greedy_one_or_more
97
+ regexp_greedy_zero_or_more
98
+ regexp_greedy_zero_or_one
99
+ regexp_group_close_escape
100
+ regexp_group_open_escape
101
+ regexp_han_property
102
+ regexp_hangul_property
103
+ regexp_hex_escape
104
+ regexp_hex_type
105
+ regexp_hiragana_property
106
+ regexp_intersection_set
107
+ regexp_interval_close_escape
108
+ regexp_interval_open_escape
109
+ regexp_katakana_property
110
+ regexp_letter_property
111
+ regexp_linebreak_type
112
+ regexp_literal_escape
113
+ regexp_literal_literal
114
+ regexp_lookahead_assertion
115
+ regexp_lookbehind_assertion
116
+ regexp_lower_posixclass
117
+ regexp_mark_keep
118
+ regexp_match_start_anchor
119
+ regexp_meta_sequence_escape
120
+ regexp_name_call_backref
121
+ regexp_named_group
122
+ regexp_newline_escape
123
+ regexp_nlookahead_assertion
124
+ regexp_nlookbehind_assertion
125
+ regexp_nondigit_type
126
+ regexp_nonhex_type
127
+ regexp_nonspace_type
128
+ regexp_nonword_boundary_anchor
129
+ regexp_nonword_type
130
+ regexp_number_backref
131
+ regexp_octal_escape
132
+ regexp_one_or_more_escape
133
+ regexp_open_conditional
134
+ regexp_options_group
135
+ regexp_options_switch_group
136
+ regexp_passive_group
137
+ regexp_possessive_interval
138
+ regexp_possessive_one_or_more
139
+ regexp_possessive_zero_or_more
140
+ regexp_possessive_zero_or_one
141
+ regexp_print_nonposixclass
142
+ regexp_print_nonproperty
143
+ regexp_print_posixclass
144
+ regexp_print_posixclass
145
+ regexp_print_property
146
+ regexp_punct_posixclass
147
+ regexp_range_set
148
+ regexp_reluctant_interval
149
+ regexp_reluctant_one_or_more
150
+ regexp_reluctant_zero_or_more
151
+ regexp_root_expression
152
+ regexp_sequence_expression
153
+ regexp_set_close_escape
154
+ regexp_set_open_escape
155
+ regexp_space_posixclass
156
+ regexp_space_type
157
+ regexp_tab_escape
158
+ regexp_upper_posixclass
159
+ regexp_vertical_tab_escape
160
+ regexp_whitespace_free_space
161
+ regexp_word_boundary_anchor
162
+ regexp_word_posixclass
163
+ regexp_word_type
164
+ regexp_xdigit_posixclass
165
+ regexp_xgrapheme_type
166
+ regexp_zero_or_more_escape
167
+ regexp_zero_or_one_escape
168
+ ]).freeze
169
+
66
170
  # Nodes that are NOT generated by parser but used by mutant / unparser.
67
171
  GENERATED = Set.new(%i[empty]).freeze
68
172
 
69
173
  ALL = Set.new(
70
- (Parser::Meta::NODE_TYPES + GENERATED) - BLACKLIST
174
+ (Parser::Meta::NODE_TYPES + GENERATED + REGEXP) - BLACKLIST
71
175
  ).freeze
72
176
  end # Types
73
177
  end # AST