mutant 0.10.23 → 0.10.24

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +20 -4
  3. data/lib/mutant/ast/regexp.rb +37 -0
  4. data/lib/mutant/ast/regexp/transformer.rb +150 -0
  5. data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
  6. data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
  7. data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
  8. data/lib/mutant/ast/regexp/transformer/quantifier.rb +90 -0
  9. data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
  10. data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
  11. data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
  12. data/lib/mutant/ast/types.rb +115 -2
  13. data/lib/mutant/cli/command/environment.rb +9 -3
  14. data/lib/mutant/config.rb +8 -54
  15. data/lib/mutant/config/coverage_criteria.rb +61 -0
  16. data/lib/mutant/expression.rb +0 -12
  17. data/lib/mutant/matcher.rb +2 -2
  18. data/lib/mutant/matcher/config.rb +25 -6
  19. data/lib/mutant/matcher/method.rb +2 -3
  20. data/lib/mutant/meta/example/dsl.rb +6 -1
  21. data/lib/mutant/mutator/node/kwargs.rb +2 -2
  22. data/lib/mutant/mutator/node/literal/regex.rb +26 -0
  23. data/lib/mutant/mutator/node/regexp.rb +31 -0
  24. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  25. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  26. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  27. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  28. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  29. data/lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb +24 -0
  30. data/lib/mutant/subject.rb +1 -3
  31. data/lib/mutant/subject/method/instance.rb +1 -3
  32. data/lib/mutant/version.rb +1 -1
  33. data/lib/mutant/world.rb +1 -2
  34. metadata +40 -4
  35. 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,7 +3,7 @@
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
9
  # Set of op-assign types
@@ -54,11 +54,124 @@ module Mutant
54
54
  #
55
55
  BLACKLIST = Set.new(%i[not]).freeze
56
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
+
57
170
  # Nodes that are NOT generated by parser but used by mutant / unparser.
58
171
  GENERATED = Set.new(%i[empty]).freeze
59
172
 
60
173
  ALL = Set.new(
61
- (Parser::Meta::NODE_TYPES + GENERATED) - BLACKLIST
174
+ (Parser::Meta::NODE_TYPES + GENERATED + REGEXP) - BLACKLIST
62
175
  ).freeze
63
176
  end # Types
64
177
  end # AST
@@ -29,13 +29,19 @@ module Mutant
29
29
  end
30
30
 
31
31
  def expand(file_config)
32
+ if @config.matcher.subjects.any?
33
+ file_config = file_config.with(
34
+ matcher: file_config.matcher.with(subjects: [])
35
+ )
36
+ end
37
+
32
38
  @config = Config.env.merge(file_config).merge(@config).expand_defaults
33
39
  end
34
40
 
35
41
  def parse_remaining_arguments(arguments)
36
42
  Mutant.traverse(@config.expression_parser, arguments)
37
- .fmap do |match_expressions|
38
- matcher(match_expressions: match_expressions)
43
+ .fmap do |expressions|
44
+ matcher(subjects: expressions)
39
45
  self
40
46
  end
41
47
  end
@@ -82,7 +88,7 @@ module Mutant
82
88
  parser.separator('Matcher:')
83
89
 
84
90
  parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
85
- add_matcher(:ignore_expressions, @config.expression_parser.call(pattern).from_right)
91
+ add_matcher(:ignore, @config.expression_parser.call(pattern).from_right)
86
92
  end
87
93
  parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
88
94
  add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)