mutant 0.11.27 → 0.11.29

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant/ast.rb +8 -9
  3. data/lib/mutant/bootstrap.rb +39 -13
  4. data/lib/mutant/cli/command/environment/test.rb +38 -1
  5. data/lib/mutant/cli/command/environment.rb +15 -0
  6. data/lib/mutant/cli/command.rb +10 -6
  7. data/lib/mutant/context.rb +31 -61
  8. data/lib/mutant/env.rb +8 -1
  9. data/lib/mutant/expression/method.rb +4 -1
  10. data/lib/mutant/expression/methods.rb +4 -1
  11. data/lib/mutant/expression/namespace.rb +4 -4
  12. data/lib/mutant/hooks.rb +1 -0
  13. data/lib/mutant/integration/null.rb +1 -0
  14. data/lib/mutant/integration.rb +5 -1
  15. data/lib/mutant/matcher/descendants.rb +1 -1
  16. data/lib/mutant/matcher/method/instance.rb +5 -4
  17. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  18. data/lib/mutant/matcher/method/singleton.rb +1 -1
  19. data/lib/mutant/matcher/method.rb +30 -4
  20. data/lib/mutant/matcher/methods.rb +8 -7
  21. data/lib/mutant/matcher/namespace.rb +1 -1
  22. data/lib/mutant/meta/example.rb +12 -2
  23. data/lib/mutant/mutation/runner/sink.rb +7 -3
  24. data/lib/mutant/mutation/runner.rb +2 -1
  25. data/lib/mutant/mutator/node/literal/hash.rb +3 -1
  26. data/lib/mutant/parallel/connection.rb +178 -0
  27. data/lib/mutant/parallel/pipe.rb +39 -0
  28. data/lib/mutant/parallel/worker.rb +42 -14
  29. data/lib/mutant/parallel.rb +18 -7
  30. data/lib/mutant/reporter/cli/format.rb +19 -2
  31. data/lib/mutant/reporter/cli/printer/env_progress.rb +3 -4
  32. data/lib/mutant/reporter/cli/printer/status_progressive.rb +0 -1
  33. data/lib/mutant/reporter/cli/printer/test.rb +138 -0
  34. data/lib/mutant/reporter/cli.rb +33 -4
  35. data/lib/mutant/reporter.rb +22 -1
  36. data/lib/mutant/result.rb +53 -13
  37. data/lib/mutant/scope.rb +41 -1
  38. data/lib/mutant/subject/method/instance.rb +3 -2
  39. data/lib/mutant/subject/method/metaclass.rb +1 -1
  40. data/lib/mutant/subject/method/singleton.rb +2 -2
  41. data/lib/mutant/subject/method.rb +1 -1
  42. data/lib/mutant/test/runner/sink.rb +51 -0
  43. data/lib/mutant/test/runner.rb +62 -0
  44. data/lib/mutant/timer.rb +9 -0
  45. data/lib/mutant/version.rb +1 -1
  46. data/lib/mutant/world.rb +6 -0
  47. data/lib/mutant.rb +7 -1
  48. metadata +9 -19
  49. data/lib/mutant/pipe.rb +0 -96
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 594919332bf75ff59d19206e356cc568d4b59c859dd2d6eccc459805e3445cf3
4
- data.tar.gz: c8e919b4826b6596c51ce26c8ca2eda5ff19e50bc1e8ff785b48ab479e6ad42f
3
+ metadata.gz: 3e57360ec50ffdef54f132f4764c940caad83a2d3692ac812b95c236cd8ff52d
4
+ data.tar.gz: cc69f0948238d888f569caeee901dc9197b620a3693474323a0959212d73fc30
5
5
  SHA512:
6
- metadata.gz: e0e91da3e7ed933633c00ba849f502df74bff6407afaed8f2f7bfe167efb4d100bf89eac7ea4f7cf3872a2b257a0093d0c359956f01ea4365f870fd1ce731132
7
- data.tar.gz: ed1f39b700889360dd049edd5dc00391fc978b7be64cf3f0662e890d39a9b8b1b2001f6df3291b3760e9866f48efc32b344b84ded8d94921ad12a29e089bc748
6
+ metadata.gz: cf4a364cc90b650a6c9dff90b38fb22aaa54d62f98f3b85ae732894a112f89a72b7c6e81b25fdd051b4436e8b55c58203fa4645a1c471cd5745fd665c7f0418b
7
+ data.tar.gz: 27a2195e3cd277816e37970fbbf66ed3d4f5237d9be667345229a9739deebaa45366914c71ce41254b7e0a65ea3e40af6012ddf4f8d654164d5eab904345dcf0
data/lib/mutant/ast.rb CHANGED
@@ -8,12 +8,12 @@ module Mutant
8
8
  )
9
9
 
10
10
  class View
11
- include Adamantium, Anima.new(:node, :path)
11
+ include Adamantium, Anima.new(:node, :stack)
12
12
  end
13
13
 
14
14
  def on_line(line)
15
- line_map.fetch(line, EMPTY_HASH).map do |node, path|
16
- View.new(node: node, path: path)
15
+ line_map.fetch(line, EMPTY_HASH).map do |node, stack|
16
+ View.new(node: node, stack: stack)
17
17
  end
18
18
  end
19
19
 
@@ -22,21 +22,20 @@ module Mutant
22
22
  def line_map
23
23
  line_map = {}
24
24
 
25
- walk_path(node) do |node, path|
25
+ walk_path(node, []) do |node, stack|
26
26
  expression = node.location.expression || next
27
- (line_map[expression.line] ||= []) << [node, path]
27
+ (line_map[expression.line] ||= []) << [node, stack]
28
28
  end
29
29
 
30
30
  line_map
31
31
  end
32
32
  memoize :line_map
33
33
 
34
- def walk_path(node, stack = [node.type], &block)
35
- block.call(node, stack.dup)
34
+ def walk_path(node, stack, &block)
35
+ block.call(node, stack)
36
+ stack = [*stack, node]
36
37
  node.children.grep(::Parser::AST::Node) do |child|
37
- stack.push(child.type)
38
38
  walk_path(child, stack, &block)
39
- stack.pop
40
39
  end
41
40
  end
42
41
  end # AST
@@ -20,7 +20,7 @@ module Mutant
20
20
  '%<scope_class>s#name from: %<scope>s raised an error: %<exception>s'
21
21
 
22
22
  CLASS_NAME_TYPE_MISMATCH_FORMAT =
23
- '%<scope_class>s#name from: %<scope>s returned %<name>s'
23
+ '%<scope_class>s#name from: %<raw_scope>s returned %<name>s'
24
24
 
25
25
  private_constant(*constants(false))
26
26
 
@@ -47,6 +47,32 @@ module Mutant
47
47
  selected_subjects.flat_map(&:mutations)
48
48
  end
49
49
 
50
+ setup_integration(
51
+ env: env,
52
+ mutations: mutations,
53
+ selected_subjects: selected_subjects
54
+ )
55
+ end
56
+ end
57
+ # rubocop:enable Metrics/MethodLength
58
+
59
+ # Run test only bootstrap
60
+ #
61
+ # @param [Env] env
62
+ #
63
+ # @return [Either<String, Env>]
64
+ def self.call_test(env)
65
+ env.record(:bootstrap) do
66
+ setup_integration(
67
+ env: load_hooks(env),
68
+ mutations: [],
69
+ selected_subjects: []
70
+ )
71
+ end
72
+ end
73
+
74
+ def self.setup_integration(env:, mutations:, selected_subjects:)
75
+ env.record(__method__) do
50
76
  Integration.setup(env).fmap do |integration|
51
77
  env.with(
52
78
  integration: integration,
@@ -57,7 +83,7 @@ module Mutant
57
83
  end
58
84
  end
59
85
  end
60
- # rubocop:enable Metrics/MethodLength
86
+ private_class_method :setup_integration
61
87
 
62
88
  def self.load_hooks(env)
63
89
  env.record(__method__) do
@@ -113,9 +139,9 @@ module Mutant
113
139
  env.record(__method__) do
114
140
  config = env.config
115
141
 
116
- scopes = env.world.object_space.each_object(Module).with_object([]) do |scope, aggregate|
117
- expression = expression(config.reporter, config.expression_parser, scope) || next
118
- aggregate << Scope.new(raw: scope, expression: expression)
142
+ scopes = env.world.object_space.each_object(Module).with_object([]) do |raw_scope, aggregate|
143
+ expression = expression(config.reporter, config.expression_parser, raw_scope) || next
144
+ aggregate << Scope.new(raw: raw_scope, expression: expression)
119
145
  end
120
146
 
121
147
  scopes.sort_by { |scope| scope.expression.syntax }
@@ -123,31 +149,31 @@ module Mutant
123
149
  end
124
150
  private_class_method :matchable_scopes
125
151
 
126
- def self.scope_name(reporter, scope)
127
- scope.name
152
+ def self.scope_name(reporter, raw_scope)
153
+ raw_scope.name
128
154
  rescue => exception
129
155
  semantics_warning(
130
156
  reporter,
131
157
  CLASS_NAME_RAISED_EXCEPTION,
132
158
  exception: exception.inspect,
133
- scope: scope,
134
- scope_class: scope.class
159
+ scope: raw_scope,
160
+ scope_class: raw_scope.class
135
161
  )
136
162
  nil
137
163
  end
138
164
  private_class_method :scope_name
139
165
 
140
166
  # rubocop:disable Metrics/MethodLength
141
- def self.expression(reporter, expression_parser, scope)
142
- name = scope_name(reporter, scope) or return
167
+ def self.expression(reporter, expression_parser, raw_scope)
168
+ name = scope_name(reporter, raw_scope) or return
143
169
 
144
170
  unless name.instance_of?(String)
145
171
  semantics_warning(
146
172
  reporter,
147
173
  CLASS_NAME_TYPE_MISMATCH_FORMAT,
148
174
  name: name,
149
- scope_class: scope.class,
150
- scope: scope
175
+ scope_class: raw_scope.class,
176
+ raw_scope: raw_scope
151
177
  )
152
178
  return
153
179
  end
@@ -8,6 +8,21 @@ module Mutant
8
8
  NAME = 'test'
9
9
  SHORT_DESCRIPTION = 'test subcommands'
10
10
 
11
+ private
12
+
13
+ def parse_remaining_arguments(arguments)
14
+ arguments.each(&method(:add_integration_argument))
15
+ Either::Right.new(self)
16
+ end
17
+
18
+ def bootstrap
19
+ env = Env.empty(world, @config)
20
+
21
+ env
22
+ .record(:config) { Config.load(cli_config: @config, world: world) }
23
+ .bind { |config| Bootstrap.call_test(env.with(config: config)) }
24
+ end
25
+
11
26
  class List < self
12
27
  NAME = 'list'
13
28
  SHORT_DESCRIPTION = 'List tests detected in the environment'
@@ -28,7 +43,29 @@ module Mutant
28
43
  end
29
44
  end
30
45
 
31
- SUBCOMMANDS = [List].freeze
46
+ class Run < self
47
+ NAME = 'run'
48
+ SHORT_DESCRIPTION = 'Run tests'
49
+ SUBCOMMANDS = EMPTY_ARRAY
50
+
51
+ private
52
+
53
+ def action
54
+ bootstrap
55
+ .bind(&Mutant::Test::Runner.public_method(:call))
56
+ .bind(&method(:from_result))
57
+ end
58
+
59
+ def from_result(result)
60
+ if result.success?
61
+ Either::Right.new(nil)
62
+ else
63
+ Either::Left.new('Test failures, exiting nonzero!')
64
+ end
65
+ end
66
+ end
67
+
68
+ SUBCOMMANDS = [List, Run].freeze
32
69
  end # Test
33
70
  end # Environment
34
71
  end # Command
@@ -3,6 +3,7 @@
3
3
  module Mutant
4
4
  module CLI
5
5
  class Command
6
+ # rubocop:disable Metrics/ClassLength
6
7
  class Environment < self
7
8
  NAME = 'environment'
8
9
  SHORT_DESCRIPTION = 'Environment subcommands'
@@ -13,6 +14,7 @@ module Mutant
13
14
  add_runner_options
14
15
  add_integration_options
15
16
  add_matcher_options
17
+ add_reporter_options
16
18
  ].freeze
17
19
 
18
20
  private
@@ -56,6 +58,10 @@ module Mutant
56
58
  set(matcher: @config.matcher.add(attribute, value))
57
59
  end
58
60
 
61
+ def effective_options
62
+ instance_of?(Environment) ? EMPTY_ARRAY : super()
63
+ end
64
+
59
65
  # rubocop:disable Metrics/MethodLength
60
66
  def add_environment_options(parser)
61
67
  parser.separator('Environment:')
@@ -122,7 +128,16 @@ module Mutant
122
128
  set(mutation: @config.mutation.with(timeout: Float(number)))
123
129
  end
124
130
  end
131
+
132
+ def add_reporter_options(parser)
133
+ parser.separator('Reporting:')
134
+
135
+ parser.on('--print-warnings', 'Print warnings') do
136
+ set(reporter: @config.reporter.with(print_warnings: true))
137
+ end
138
+ end
125
139
  end # Run
140
+ # rubocop:enable Metrics/ClassLength
126
141
  end # Command
127
142
  end # CLI
128
143
  end # Mutant
@@ -6,7 +6,7 @@ module Mutant
6
6
  class Command
7
7
  include AbstractType, Anima.new(
8
8
  :main,
9
- :parent,
9
+ :parent_names,
10
10
  :print_profile,
11
11
  :world,
12
12
  :zombie
@@ -27,10 +27,10 @@ module Mutant
27
27
  # @return [Command]
28
28
  #
29
29
  # rubocop:disable Metrics/ParameterLists
30
- def self.parse(arguments:, parent: nil, print_profile: false, world:, zombie: false)
30
+ def self.parse(arguments:, parent_names: nil, print_profile: false, world:, zombie: false)
31
31
  new(
32
32
  main: nil,
33
- parent: parent,
33
+ parent_names: parent_names,
34
34
  print_profile: print_profile,
35
35
  world: world,
36
36
  zombie: zombie
@@ -63,7 +63,7 @@ module Mutant
63
63
  #
64
64
  # @return [String]
65
65
  def full_name
66
- [*parent&.full_name, self.class.command_name].join(' ')
66
+ [*parent_names, self.class.command_name].join(' ')
67
67
  end
68
68
 
69
69
  alias_method :print_profile?, :print_profile
@@ -97,13 +97,17 @@ module Mutant
97
97
  add_global_options(parser)
98
98
  add_subcommands(parser)
99
99
 
100
- self.class::OPTIONS.each do |method_name|
100
+ effective_options.each do |method_name|
101
101
  2.times { parser.separator(nil) }
102
102
  __send__(method_name, parser)
103
103
  end
104
104
  end
105
105
  end
106
106
 
107
+ def effective_options
108
+ self.class::OPTIONS
109
+ end
110
+
107
111
  def capture_main(&block)
108
112
  @main = block
109
113
  end
@@ -189,7 +193,7 @@ module Mutant
189
193
  find_command(command_name).bind do |command|
190
194
  command.parse(
191
195
  arguments: arguments,
192
- parent: self,
196
+ parent_names: [*parent_names, self.class::NAME],
193
197
  print_profile: print_profile,
194
198
  world: world,
195
199
  zombie: zombie
@@ -3,83 +3,53 @@
3
3
  module Mutant
4
4
  # An abstract context where mutations can be applied to.
5
5
  class Context
6
- include Adamantium, Anima.new(:scope, :source_path)
7
- extend AST::Sexp
6
+ include Adamantium, Anima.new(:constant_scope, :scope, :source_path)
8
7
 
9
- NAMESPACE_DELIMITER = '::'
8
+ class ConstantScope
9
+ include AST::Sexp
10
10
 
11
- # Return root node for mutation
12
- #
13
- # @return [Parser::AST::Node]
14
- def root(node)
15
- nesting.reverse.reduce(node) do |current, scope|
16
- self.class.wrap(scope, current)
11
+ class Class < self
12
+ include Anima.new(:const, :descendant)
13
+
14
+ def call(node)
15
+ s(:class, const, nil, descendant.call(node))
16
+ end
17
17
  end
18
- end
19
18
 
20
- # Identification string
21
- #
22
- # @return [String]
23
- def identification
24
- scope.name
25
- end
19
+ class Module < self
20
+ include Anima.new(:const, :descendant)
26
21
 
27
- # Wrap node into ast node
28
- #
29
- # @param [Class, Module] scope
30
- # @param [Parser::AST::Node] node
31
- #
32
- # @return [Parser::AST::Class]
33
- # if scope is of kind Class
34
- #
35
- # @return [Parser::AST::Module]
36
- # if scope is of kind module
37
- def self.wrap(scope, node)
38
- name = s(:const, nil, scope.name.split(NAMESPACE_DELIMITER).last.to_sym)
39
- case scope
40
- when Class
41
- s(:class, name, nil, node)
42
- when Module
43
- s(:module, name, node)
22
+ def call(node)
23
+ s(:module, const, descendant.call(node))
24
+ end
44
25
  end
45
- end
46
26
 
47
- # Nesting of scope
48
- #
49
- # @return [Enumerable<Class,Module>]
50
- def nesting
51
- const = Object
52
- name_nesting.map do |name|
53
- const = const.const_get(name)
27
+ class None < self
28
+ include Equalizer.new
29
+
30
+ def call(node)
31
+ node
32
+ end
54
33
  end
55
34
  end
56
- memoize :nesting
57
35
 
58
- # Unqualified name of scope
59
- #
60
- # @return [String]
61
- def unqualified_name
62
- name_nesting.last
36
+ def match_expressions
37
+ scope.match_expressions
63
38
  end
64
39
 
65
- # Match expressions for scope
40
+ # Return root node for mutation
66
41
  #
67
- # @return [Enumerable<Expression>]
68
- def match_expressions
69
- name_nesting.each_index.reverse_each.map do |index|
70
- Expression::Namespace::Recursive.new(
71
- scope_name: name_nesting.take(index.succ).join(NAMESPACE_DELIMITER)
72
- )
73
- end
42
+ # @return [Parser::AST::Node]
43
+ def root(node)
44
+ constant_scope.call(node)
74
45
  end
75
- memoize :match_expressions
76
46
 
77
- private
78
-
79
- def name_nesting
80
- scope.name.split(NAMESPACE_DELIMITER)
47
+ # Identification string
48
+ #
49
+ # @return [String]
50
+ def identification
51
+ scope.raw.name
81
52
  end
82
- memoize :name_nesting
83
53
 
84
54
  end # Context
85
55
  end # Mutant
data/lib/mutant/env.rb CHANGED
@@ -66,10 +66,18 @@ module Mutant
66
66
  )
67
67
  end
68
68
 
69
+ def run_test_index(test_index)
70
+ integration.call([integration.all_tests.fetch(test_index)])
71
+ end
72
+
69
73
  def emit_mutation_worker_process_start(index:)
70
74
  hooks.run(:mutation_worker_process_start, index: index)
71
75
  end
72
76
 
77
+ def emit_test_worker_process_start(index:)
78
+ hooks.run(:test_worker_process_start, index: index)
79
+ end
80
+
73
81
  # The test selections
74
82
  #
75
83
  # @return Hash{Mutation => Enumerable<Test>}
@@ -175,7 +183,6 @@ module Mutant
175
183
  def timer
176
184
  world.timer
177
185
  end
178
-
179
186
  end # Env
180
187
  # rubocop:enable Metrics/ClassLength
181
188
  end # Mutant
@@ -78,7 +78,10 @@ module Mutant
78
78
  private
79
79
 
80
80
  def scope
81
- Object.const_get(scope_name)
81
+ Scope.new(
82
+ raw: Object.const_get(scope_name),
83
+ expression: Namespace::Exact.new(scope_name: scope_name)
84
+ )
82
85
  end
83
86
 
84
87
  end # Method
@@ -57,7 +57,10 @@ module Mutant
57
57
  private
58
58
 
59
59
  def scope
60
- Object.const_get(scope_name)
60
+ Scope.new(
61
+ expression: Namespace::Exact.new(scope_name: scope_name),
62
+ raw: Object.const_get(scope_name)
63
+ )
61
64
  end
62
65
 
63
66
  end # Methods
@@ -66,10 +66,10 @@ module Mutant
66
66
  #
67
67
  # @return [Matcher]
68
68
  def matcher
69
- scope = find_scope
69
+ raw_scope = find_raw_scope
70
70
 
71
- if scope
72
- Matcher::Scope.new(scope: scope)
71
+ if raw_scope
72
+ Matcher::Scope.new(scope: Scope.new(expression: self, raw: raw_scope))
73
73
  else
74
74
  Matcher::Null.new
75
75
  end
@@ -83,7 +83,7 @@ module Mutant
83
83
 
84
84
  private
85
85
 
86
- def find_scope
86
+ def find_raw_scope
87
87
  Object.const_get(scope_name)
88
88
  rescue NameError # rubocop:disable Lint/SuppressedException
89
89
  end
data/lib/mutant/hooks.rb CHANGED
@@ -10,6 +10,7 @@ module Mutant
10
10
  mutation_insert_post
11
11
  mutation_insert_pre
12
12
  mutation_worker_process_start
13
+ test_worker_process_start
13
14
  ].product([EMPTY_ARRAY]).to_h.transform_values(&:freeze).freeze
14
15
 
15
16
  MESSAGE = 'Unknown hook %s'
@@ -18,6 +18,7 @@ module Mutant
18
18
  # @return [Result::Test]
19
19
  def call(_tests)
20
20
  Result::Test.new(
21
+ output: '',
21
22
  passed: true,
22
23
  runtime: 0.0
23
24
  )
@@ -4,7 +4,11 @@ module Mutant
4
4
 
5
5
  # Abstract base class mutant test framework integrations
6
6
  class Integration
7
- include AbstractType, Adamantium, Anima.new(:arguments, :expression_parser, :world)
7
+ include AbstractType, Adamantium, Anima.new(
8
+ :arguments,
9
+ :expression_parser,
10
+ :world
11
+ )
8
12
 
9
13
  LOAD_MESSAGE = <<~'MESSAGE'
10
14
  Unable to load integration mutant-%<integration_name>s:
@@ -10,7 +10,7 @@ module Mutant
10
10
  const = env.world.try_const_get(const_name) or return EMPTY_ARRAY
11
11
 
12
12
  Chain.new(
13
- matchers: matched_scopes(env, const).map { |scope| Scope.new(scope: scope.raw) }
13
+ matchers: matched_scopes(env, const).map { |scope| Scope.new(scope: scope) }
14
14
  ).call(env)
15
15
  end
16
16
 
@@ -8,7 +8,7 @@ module Mutant
8
8
 
9
9
  # Dispatching builder, detects memoizable case
10
10
  #
11
- # @param [Class, Module] scope
11
+ # @param [Scope] scope
12
12
  # @param [UnboundMethod] method
13
13
  #
14
14
  # @return [Matcher::Method::Instance]
@@ -31,7 +31,7 @@ module Mutant
31
31
  # rubocop:enable Metrics/MethodLength
32
32
 
33
33
  def self.memoized_method?(scope, method_name)
34
- scope < Adamantium && scope.memoized?(method_name)
34
+ scope.raw < Adamantium && scope.raw.memoized?(method_name)
35
35
  end
36
36
  private_class_method :memoized_method?
37
37
 
@@ -48,9 +48,9 @@ module Mutant
48
48
  end
49
49
 
50
50
  def visibility
51
- if scope.private_instance_methods.include?(method_name)
51
+ if scope.raw.private_instance_methods.include?(method_name)
52
52
  :private
53
- elsif scope.protected_instance_methods.include?(method_name)
53
+ elsif scope.raw.protected_instance_methods.include?(method_name)
54
54
  :protected
55
55
  else
56
56
  :public
@@ -65,6 +65,7 @@ module Mutant
65
65
 
66
66
  def source_location
67
67
  scope
68
+ .raw
68
69
  .unmemoized_instance_method(method_name)
69
70
  .source_location
70
71
  end
@@ -62,7 +62,7 @@ module Mutant
62
62
 
63
63
  def sclass_const_name?(node)
64
64
  name = node.children.fetch(CONST_NAME_INDEX)
65
- name.to_s.eql?(context.unqualified_name)
65
+ name.to_s.eql?(scope.unqualified_name)
66
66
  end
67
67
 
68
68
  end # Evaluator
@@ -46,7 +46,7 @@ module Mutant
46
46
 
47
47
  def receiver_name?(node)
48
48
  name = node.children.fetch(NAME_INDEX)
49
- name.to_s.eql?(context.unqualified_name)
49
+ name.to_s.eql?(scope.unqualified_name)
50
50
  end
51
51
 
52
52
  end # Evaluator
@@ -14,6 +14,11 @@ module Mutant
14
14
  CLOSURE_WARNING_FORMAT =
15
15
  '%s is dynamically defined in a closure, unable to emit subject'
16
16
 
17
+ CONSTANT_SCOPES = {
18
+ class: Context::ConstantScope::Class,
19
+ module: Context::ConstantScope::Module
20
+ }.freeze
21
+
17
22
  # Matched subjects
18
23
  #
19
24
  # @param [Env] env
@@ -28,6 +33,8 @@ module Mutant
28
33
  # Present to avoid passing the env argument around in case the
29
34
  # logic would be implemented directly on the Matcher::Method
30
35
  # instance
36
+ #
37
+ # rubocop:disable Metrics/ClassLength
31
38
  class Evaluator
32
39
  include(
33
40
  AbstractType,
@@ -57,7 +64,7 @@ module Mutant
57
64
  def match_view
58
65
  return EMPTY_ARRAY if matched_view.nil?
59
66
 
60
- if matched_view.path.any?(&:block.public_method(:equal?))
67
+ if matched_view.stack.any? { |node| node.type.equal?(:block) }
61
68
  env.warn(CLOSURE_WARNING_FORMAT % target_method)
62
69
 
63
70
  return EMPTY_ARRAY
@@ -80,7 +87,26 @@ module Mutant
80
87
  end
81
88
 
82
89
  def context
83
- Context.new(scope: scope, source_path: source_path)
90
+ Context.new(constant_scope: constant_scope, scope: scope, source_path: source_path)
91
+ end
92
+
93
+ # rubocop:disable Metrics/MethodLength
94
+ def constant_scope
95
+ matched_view
96
+ .stack
97
+ .reverse
98
+ .reduce(Context::ConstantScope::None.new) do |descendant, node|
99
+ klass = CONSTANT_SCOPES[node.type]
100
+
101
+ if klass
102
+ klass.new(
103
+ const: node.children.fetch(0),
104
+ descendant: descendant
105
+ )
106
+ else
107
+ descendant
108
+ end
109
+ end
84
110
  end
85
111
 
86
112
  def ast
@@ -151,9 +177,9 @@ module Mutant
151
177
  # end
152
178
  #
153
179
  # Change to this once 3.0 is EOL.
154
- if scope.private_methods.include?(method_name)
180
+ if scope.raw.private_methods.include?(method_name)
155
181
  :private
156
- elsif scope.protected_methods.include?(method_name)
182
+ elsif scope.raw.protected_methods.include?(method_name)
157
183
  :protected
158
184
  else
159
185
  :public