mutant 0.8.7 → 0.8.8

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +5 -0
  3. data/README.md +64 -3
  4. data/config/flay.yml +1 -1
  5. data/lib/mutant.rb +2 -0
  6. data/lib/mutant/cli.rb +1 -1
  7. data/lib/mutant/env/bootstrap.rb +36 -8
  8. data/lib/mutant/expression/method.rb +3 -5
  9. data/lib/mutant/expression/methods.rb +2 -4
  10. data/lib/mutant/expression/namespace.rb +4 -8
  11. data/lib/mutant/matcher.rb +3 -18
  12. data/lib/mutant/matcher/chain.rb +7 -13
  13. data/lib/mutant/matcher/compiler.rb +2 -13
  14. data/lib/mutant/matcher/filter.rb +6 -19
  15. data/lib/mutant/matcher/method.rb +124 -104
  16. data/lib/mutant/matcher/method/instance.rb +40 -34
  17. data/lib/mutant/matcher/method/singleton.rb +80 -61
  18. data/lib/mutant/matcher/methods.rb +19 -29
  19. data/lib/mutant/matcher/namespace.rb +22 -16
  20. data/lib/mutant/matcher/null.rb +4 -7
  21. data/lib/mutant/matcher/scope.rb +23 -13
  22. data/lib/mutant/matcher/static.rb +17 -0
  23. data/lib/mutant/mutation.rb +0 -5
  24. data/lib/mutant/reporter/cli/format.rb +2 -3
  25. data/lib/mutant/reporter/cli/printer/env_progress.rb +37 -11
  26. data/lib/mutant/reporter/cli/printer/status_progressive.rb +1 -1
  27. data/lib/mutant/scope.rb +6 -0
  28. data/lib/mutant/subject/method.rb +0 -7
  29. data/lib/mutant/subject/method/instance.rb +0 -10
  30. data/lib/mutant/subject/method/singleton.rb +0 -10
  31. data/lib/mutant/version.rb +1 -1
  32. data/lib/mutant/zombifier.rb +2 -1
  33. data/mutant-rspec.gemspec +1 -1
  34. data/spec/integration/mutant/rspec_spec.rb +1 -1
  35. data/spec/shared/method_matcher_behavior.rb +21 -14
  36. data/spec/spec_helper.rb +6 -0
  37. data/spec/unit/mutant/env/boostrap_spec.rb +88 -26
  38. data/spec/unit/mutant/env_spec.rb +0 -1
  39. data/spec/unit/mutant/expression/method_spec.rb +3 -3
  40. data/spec/unit/mutant/expression/methods_spec.rb +3 -4
  41. data/spec/unit/mutant/expression/namespace/flat_spec.rb +2 -3
  42. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +2 -4
  43. data/spec/unit/mutant/matcher/chain_spec.rb +21 -29
  44. data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +16 -13
  45. data/spec/unit/mutant/matcher/compiler_spec.rb +49 -60
  46. data/spec/unit/mutant/matcher/filter_spec.rb +15 -31
  47. data/spec/unit/mutant/matcher/method/instance_spec.rb +84 -128
  48. data/spec/unit/mutant/matcher/method/singleton_spec.rb +48 -52
  49. data/spec/unit/mutant/matcher/methods/instance_spec.rb +21 -24
  50. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +18 -21
  51. data/spec/unit/mutant/matcher/namespace_spec.rb +30 -38
  52. data/spec/unit/mutant/matcher/null_spec.rb +5 -20
  53. data/spec/unit/mutant/matcher/scope_spec.rb +33 -0
  54. data/spec/unit/mutant/matcher/static_spec.rb +11 -0
  55. data/spec/unit/mutant/mutation_spec.rb +30 -10
  56. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +6 -0
  57. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +2 -0
  58. data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +10 -0
  59. data/spec/unit/mutant/reporter/cli_spec.rb +4 -0
  60. data/spec/unit/mutant/subject/method/instance_spec.rb +0 -28
  61. data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -28
  62. data/test_app/Gemfile.rspec3.4 +7 -0
  63. data/test_app/lib/test_app.rb +16 -12
  64. data/test_app/lib/test_app/literal.rb +3 -0
  65. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f0d162e90a523f4f62c1f3541b1157fb78d6080
4
- data.tar.gz: fa8dcc8abc2f6960ebd74a53711bf8e87ec0f32c
3
+ metadata.gz: d83617d43f73b49c391d1b7e8f3317a666297c83
4
+ data.tar.gz: 186b8ce58089718ba3cafa1c08f609b89941541e
5
5
  SHA512:
6
- metadata.gz: 4c672d952620acf28cff4a26127a03facbc32c13bece89934b487433dc1fd2a918a76188fa0efe86588c7a480e8bf54383baa66eed869eeaf27752dce5ab313e
7
- data.tar.gz: f3e533b36d8f52544523fbd81cda480a0653861106cf841420e11043a953c41a043e7a3b88fd8838629272d3bd960e76d22494bce9b7f014b10c67601d7a2c6e
6
+ metadata.gz: 3d4ee1e94a96ba909cf327bde6afdca8ff5e50f80603ca8835115fdba2abbfa05ad89897366908f8b64bca2e9302605e7accddf1167260c187cf4e40417d0df5
7
+ data.tar.gz: 7b599e22fa7ddd03e9f9ac91af9ce0e730b155c8df2b5ab1b5a67fc8cd62f8bf8972ca8df64b45fb5114a556e7416f3c86baf666ea62f5de5201e8b068406afc
data/Changelog.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v0.8.8 2015-11-15
2
+
3
+ * Add support for rspec-3.3.4
4
+ * Add mutations/s performance metric to report
5
+
1
6
  # v0.8.7 2015-10-30
2
7
 
3
8
  * Fix blackliting regexp to correctly match the String `(eval)` absolutely.
data/README.md CHANGED
@@ -73,6 +73,54 @@ To mutation test Rails models with rspec comment out ```require 'rspec/autorun'`
73
73
  RAILS_ENV=test bundle exec mutant -r ./config/environment --use rspec User
74
74
  ```
75
75
 
76
+ Limitations
77
+ -----------
78
+
79
+ Mutant cannot emit mutations for...
80
+
81
+ * methods defined within a closure. For example, methods defined using `module_eval`, `class_eval`,
82
+ `define_method`, or `define_singleton_method`:
83
+
84
+ ```ruby
85
+ class Example
86
+ class_eval do
87
+ def example1
88
+ end
89
+ end
90
+
91
+ module_eval do
92
+ def example2
93
+ end
94
+ end
95
+
96
+ define_method(:example3) do
97
+ end
98
+
99
+ define_singleton_method(:example4) do
100
+ end
101
+ end
102
+ ```
103
+
104
+ * singleton methods not defined on a constant or `self`
105
+
106
+ ```ruby
107
+ class Foo
108
+ def self.bar; end # ok
109
+ def Foo.baz; end # ok
110
+
111
+ myself = self
112
+ def myself.qux; end # cannot mutate
113
+ end
114
+ ```
115
+
116
+ * methods defined with eval:
117
+
118
+ ```ruby
119
+ class Foo
120
+ class_eval('def bar; end') # cannot mutate
121
+ end
122
+ ```
123
+
76
124
  Mutation-Operators:
77
125
  -------------------
78
126
 
@@ -182,11 +230,24 @@ There are some presentations about mutant in the wild:
182
230
  * [eurucamp 2013](http://2013.eurucamp.org/) / FrOSCon-2013 http://slid.es/markusschirp/mutation-testing
183
231
  * [Cologne.rb](http://www.colognerb.de/topics/mutation-testing-mit-mutant) / https://github.com/DonSchado/colognerb-on-mutant/blob/master/mutation_testing_slides.pdf
184
232
 
185
- Blog-Posts
233
+ Blog posts
186
234
  ----------
187
235
 
188
- * http://www.sitepoint.com/mutation-testing-mutant/
189
- * http://solnic.eu/2013/01/23/mutation-testing-with-mutant.html
236
+ Sorted by recency:
237
+
238
+ * [How to write better code using mutation testing (November 2015)][blockscore]
239
+ * [How good are your Ruby tests? Testing your tests with mutant (June 2015)][arkency1]
240
+ * [Mutation testing and continuous integration (May 2015)][arkency2]
241
+ * [Why I want to introduce mutation testing to the `rails_event_store` gem (April 2015)][arkency3]
242
+ * [Mutation testing with mutant (April 2014)][sitepoint]
243
+ * [Mutation testing with mutant (January 2013)][solnic]
244
+
245
+ [blockscore]: https://blog.blockscore.com/how-to-write-better-code-using-mutation-testing/
246
+ [sitepoint]: http://www.sitepoint.com/mutation-testing-mutant/
247
+ [arkency1]: http://blog.arkency.com/2015/06/how-good-are-your-ruby-tests-testing-your-tests-with-mutant/
248
+ [arkency2]: http://blog.arkency.com/2015/05/mutation-testing-and-continuous-integration/
249
+ [arkency3]: http://blog.arkency.com/2015/04/why-i-want-to-introduce-mutation-testing-to-the-rails-event-store-gem/
250
+ [solnic]: http://solnic.eu/2013/01/23/mutation-testing-with-mutant.html
190
251
 
191
252
  The Crash / Stuck Problem (MRI)
192
253
  -------------------------------
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 18
3
- total_score: 1253
3
+ total_score: 1232
data/lib/mutant.rb CHANGED
@@ -132,6 +132,7 @@ require 'mutant/mutator/node/match_current_line'
132
132
  require 'mutant/loader'
133
133
  require 'mutant/context'
134
134
  require 'mutant/context/scope'
135
+ require 'mutant/scope'
135
136
  require 'mutant/subject'
136
137
  require 'mutant/subject/method'
137
138
  require 'mutant/subject/method/instance'
@@ -148,6 +149,7 @@ require 'mutant/matcher/namespace'
148
149
  require 'mutant/matcher/scope'
149
150
  require 'mutant/matcher/filter'
150
151
  require 'mutant/matcher/null'
152
+ require 'mutant/matcher/static'
151
153
  require 'mutant/expression'
152
154
  require 'mutant/expression/parser'
153
155
  require 'mutant/expression/method'
data/lib/mutant/cli.rb CHANGED
@@ -192,7 +192,7 @@ module Mutant
192
192
  #
193
193
  # @api private
194
194
  def with(attributes)
195
- @config = @config.with(attributes)
195
+ @config = config.with(attributes)
196
196
  end
197
197
 
198
198
  # Add configuration
@@ -4,10 +4,18 @@ module Mutant
4
4
  class Bootstrap
5
5
  include Adamantium::Flat, Concord::Public.new(:config, :cache), Procto.call(:env)
6
6
 
7
- SEMANTICS_MESSAGE =
8
- "Fix your lib to follow normal ruby semantics!\n" \
7
+ SEMANTICS_MESSAGE_FORMAT =
8
+ "%<message>s. Fix your lib to follow normal ruby semantics!\n" \
9
9
  '{Module,Class}#name should return resolvable constant name as String or nil'.freeze
10
10
 
11
+ CLASS_NAME_RAISED_EXCEPTION =
12
+ '%<scope_class>s#name from: %<scope>s raised an error: %<exception>s'.freeze
13
+
14
+ CLASS_NAME_TYPE_MISMATCH_FORMAT =
15
+ '%<scope_class>s#name from: %<scope>s returned %<name>s'.freeze
16
+
17
+ private_constant(*constants(false))
18
+
11
19
  # Scopes that are eligible for matching
12
20
  #
13
21
  # @return [Enumerable<Matcher::Scope>]
@@ -84,7 +92,12 @@ module Mutant
84
92
  def scope_name(scope)
85
93
  scope.name
86
94
  rescue => exception
87
- warn("#{scope.class}#name from: #{scope.inspect} raised an error: #{exception.inspect}. #{SEMANTICS_MESSAGE}")
95
+ semantics_warning(
96
+ CLASS_NAME_RAISED_EXCEPTION,
97
+ scope_class: scope.class,
98
+ scope: scope,
99
+ exception: exception.inspect
100
+ )
88
101
  nil
89
102
  end
90
103
 
@@ -95,7 +108,7 @@ module Mutant
95
108
  # @api private
96
109
  def infect
97
110
  config.includes.each(&$LOAD_PATH.method(:<<))
98
- config.requires.each(&method(:require))
111
+ config.requires.each(&Kernel.method(:require))
99
112
  @integration = config.integration.new(config).setup
100
113
  end
101
114
 
@@ -105,7 +118,7 @@ module Mutant
105
118
  #
106
119
  # @api private
107
120
  def matched_subjects
108
- Matcher::Compiler.call(self, config.matcher).to_a
121
+ Matcher::Compiler.call(config.matcher).call(self)
109
122
  end
110
123
 
111
124
  # Initialize matchable scopes
@@ -115,8 +128,8 @@ module Mutant
115
128
  # @api private
116
129
  def initialize_matchable_scopes
117
130
  scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
118
- expression = expression(scope)
119
- aggregate << Matcher::Scope.new(self, scope, expression) if expression
131
+ expression = expression(scope) || next
132
+ aggregate << Scope.new(scope, expression)
120
133
  end
121
134
 
122
135
  @matchable_scopes = scopes.sort_by { |scope| scope.expression.syntax }
@@ -137,12 +150,27 @@ module Mutant
137
150
  name = scope_name(scope) or return
138
151
 
139
152
  unless name.instance_of?(String)
140
- warn("#{scope.class}#name from: #{scope.inspect} returned #{name.inspect}. #{SEMANTICS_MESSAGE}")
153
+ semantics_warning(
154
+ CLASS_NAME_TYPE_MISMATCH_FORMAT,
155
+ scope_class: scope.class,
156
+ scope: scope,
157
+ name: name
158
+ )
141
159
  return
142
160
  end
143
161
 
144
162
  config.expression_parser.try_parse(name)
145
163
  end
164
+
165
+ # Write a semantics warning
166
+ #
167
+ # @return [undefined]
168
+ #
169
+ # @api private
170
+ def semantics_warning(format, options)
171
+ message = format % options
172
+ warn(SEMANTICS_MESSAGE_FORMAT % { message: message })
173
+ end
146
174
  end # Boostrap
147
175
  end # Env
148
176
  end # Mutant
@@ -32,15 +32,13 @@ module Mutant
32
32
 
33
33
  # Matcher for expression
34
34
  #
35
- # @param [Env] env
36
- #
37
35
  # @return [Matcher]
38
36
  #
39
37
  # @api private
40
- def matcher(env)
41
- methods_matcher = MATCHERS.fetch(scope_symbol).new(env, scope)
38
+ def matcher
39
+ methods_matcher = MATCHERS.fetch(scope_symbol).new(scope)
42
40
 
43
- Matcher::Filter.build(methods_matcher) { |subject| subject.expression.eql?(self) }
41
+ Matcher::Filter.new(methods_matcher, ->(subject) { subject.expression.eql?(self) })
44
42
  end
45
43
 
46
44
  private
@@ -26,13 +26,11 @@ module Mutant
26
26
 
27
27
  # Matcher on expression
28
28
  #
29
- # @param [Env] env
30
- #
31
29
  # @return [Matcher::Method]
32
30
  #
33
31
  # @api private
34
- def matcher(env)
35
- MATCHERS.fetch(scope_symbol).new(env, scope)
32
+ def matcher
33
+ MATCHERS.fetch(scope_symbol).new(scope)
36
34
  end
37
35
 
38
36
  # Length of match with other expression
@@ -35,13 +35,11 @@ module Mutant
35
35
 
36
36
  # Matcher for expression
37
37
  #
38
- # @param [Env::Bootstrap] env
39
- #
40
38
  # @return [Matcher]
41
39
  #
42
40
  # @api private
43
- def matcher(env)
44
- Matcher::Namespace.new(env, self)
41
+ def matcher
42
+ Matcher::Namespace.new(self)
45
43
  end
46
44
 
47
45
  # Length of match with other expression
@@ -71,13 +69,11 @@ module Mutant
71
69
 
72
70
  # Matcher matcher on expression
73
71
  #
74
- # @param [Env::Bootstrap] env
75
- #
76
72
  # @return [Matcher]
77
73
  #
78
74
  # @api private
79
- def matcher(env)
80
- Matcher::Scope.new(env, Object.const_get(scope_name), self)
75
+ def matcher
76
+ Matcher::Scope.new(Object.const_get(scope_name))
81
77
  end
82
78
 
83
79
  # Syntax for expression
@@ -1,30 +1,15 @@
1
1
  module Mutant
2
2
  # Abstract matcher to find subjects to mutate
3
3
  class Matcher
4
- include Adamantium::Flat, Enumerable, AbstractType
4
+ include Adamantium::Flat, AbstractType
5
5
 
6
- # Default matcher build implementation
6
+ # Call matcher
7
7
  #
8
8
  # @param [Env] env
9
- # @param [Object] input
10
- #
11
- # @return [undefined]
12
- #
13
- # @api private
14
- def self.build(*arguments)
15
- new(*arguments)
16
- end
17
-
18
- # Enumerate subjects
19
- #
20
- # @return [self]
21
- # if block given
22
9
  #
23
10
  # @return [Enumerable<Subject>]
24
- # otherwise
25
11
  #
26
- # @api private
27
- abstract_method :each
12
+ abstract_method :call
28
13
 
29
14
  end # Matcher
30
15
  end # Mutant
@@ -1,26 +1,20 @@
1
1
  module Mutant
2
2
  class Matcher
3
- # A chain of matchers
3
+ # Matcher chaining results of other matchers together
4
4
  class Chain < self
5
5
  include Concord.new(:matchers)
6
6
 
7
- # Enumerate subjects
7
+ # Call matcher
8
8
  #
9
- # @return [Enumerator<Subject]
10
- # if no block given
9
+ # @param [Env] env
11
10
  #
12
- # @return [self]
13
- # otherwise
11
+ # @return [Enumerable<Subject>]
14
12
  #
15
13
  # @api private
16
- def each(&block)
17
- return to_enum unless block_given?
18
-
19
- matchers.each do |matcher|
20
- matcher.each(&block)
14
+ def call(env)
15
+ matchers.flat_map do |matcher|
16
+ matcher.call(env)
21
17
  end
22
-
23
- self
24
18
  end
25
19
 
26
20
  end # Chain
@@ -3,7 +3,7 @@ module Mutant
3
3
 
4
4
  # Compiler for complex matchers
5
5
  class Compiler
6
- include Concord.new(:env, :config), AST::Sexp, Procto.call(:result)
6
+ include Concord.new(:config), AST::Sexp, Procto.call(:result)
7
7
 
8
8
  # Generated matcher
9
9
  #
@@ -12,7 +12,7 @@ module Mutant
12
12
  # @api private
13
13
  def result
14
14
  Filter.new(
15
- Chain.build(config.match_expressions.map(&method(:matcher))),
15
+ Chain.new(config.match_expressions.map(&:matcher)),
16
16
  Morpher::Evaluator::Predicate::Boolean::And.new(
17
17
  [
18
18
  ignored_subjects,
@@ -61,17 +61,6 @@ module Mutant
61
61
  Morpher::Evaluator::Predicate::Boolean::And.new(config.subject_filters)
62
62
  end
63
63
 
64
- # Matcher for expression on env
65
- #
66
- # @param [Mutant::Expression] expression
67
- #
68
- # @return [Matcher]
69
- #
70
- # @api private
71
- def matcher(expression)
72
- expression.matcher(env)
73
- end
74
-
75
64
  end # Compiler
76
65
  end # Matcher
77
66
  end # Mutant
@@ -4,30 +4,17 @@ module Mutant
4
4
  class Filter < self
5
5
  include Concord.new(:matcher, :predicate)
6
6
 
7
- # New matcher
8
- #
9
- # @param [Matcher] matcher
10
- #
11
- # @return [Matcher]
12
- #
13
- # @api private
14
- def self.build(matcher, &predicate)
15
- new(matcher, predicate)
16
- end
17
-
18
7
  # Enumerate matches
19
8
  #
20
- # @return [self]
21
- # if block given
9
+ # @param [Env] env
22
10
  #
23
- # @return [Enumerator<Subject>]
24
- # otherwise
11
+ # @return [Enumerable<Subject>]
25
12
  #
26
13
  # @api private
27
- def each(&block)
28
- return to_enum unless block_given?
29
- matcher.select(&predicate.method(:call)).each(&block)
30
- self
14
+ def call(env)
15
+ matcher
16
+ .call(env)
17
+ .select(&predicate.method(:call))
31
18
  end
32
19
 
33
20
  end # Filter
@@ -1,133 +1,153 @@
1
1
  module Mutant
2
2
  class Matcher
3
- # Matcher for subjects that are a specific method
3
+ # Abstract base class for method matchers
4
4
  class Method < self
5
- include Adamantium::Flat, Concord::Public.new(:env, :scope, :target_method)
6
- include AST::NodePredicates
5
+ include AbstractType,
6
+ Adamantium::Flat,
7
+ Concord::Public.new(:scope, :target_method, :evaluator)
7
8
 
8
9
  # Methods within rbx kernel directory are precompiled and their source
9
10
  # cannot be accessed via reading source location. Same for methods created by eval.
10
11
  BLACKLIST = %r{\A(kernel/|\(eval\)\z)}.freeze
11
12
 
12
- # Enumerate matches
13
+ SOURCE_LOCATION_WARNING_FORMAT =
14
+ '%s does not have a valid source location, unable to emit subject'.freeze
15
+
16
+ CLOSURE_WARNING_FORMAT =
17
+ '%s is dynamically defined in a closure, unable to emit subject'.freeze
18
+
19
+ # Matched subjects
13
20
  #
14
- # @return [Enumerable<Subject>]
15
- # if no block given
21
+ # @param [Env] env
16
22
  #
17
- # @return [self]
18
- # otherwise
23
+ # @return [Enumerable<Subject>]
19
24
  #
20
25
  # @api private
21
- def each
22
- return to_enum unless block_given?
26
+ def call(env)
27
+ evaluator.call(scope, target_method, env)
28
+ end
29
+
30
+ # Abstract method match evaluator
31
+ #
32
+ # Present to avoid passing the env argument around in case the
33
+ # logic would be implemnented directly on the Matcher::Method
34
+ # instance
35
+ class Evaluator
36
+ include AbstractType,
37
+ Adamantium,
38
+ Concord.new(:scope, :target_method, :env),
39
+ Procto.call,
40
+ AST::NodePredicates
41
+
42
+ # Matched subjects
43
+ #
44
+ # @return [Enumerable<Subject>]
45
+ #
46
+ # @api private
47
+ def call
48
+ return EMPTY_ARRAY if skip?
23
49
 
24
- if !skip? && subject
25
- yield subject
50
+ [subject].compact
26
51
  end
27
52
 
28
- self
29
- end
53
+ private
30
54
 
31
- private
55
+ # Test if method should be skipped
56
+ #
57
+ # @return [Truthy]
58
+ #
59
+ # @api private
60
+ def skip?
61
+ location = source_location
62
+ if location.nil? || BLACKLIST.match(location.first)
63
+ env.warn(SOURCE_LOCATION_WARNING_FORMAT % target_method)
64
+ elsif matched_node_path.any?(&method(:n_block?))
65
+ env.warn(CLOSURE_WARNING_FORMAT % target_method)
66
+ end
67
+ end
32
68
 
33
- # Test if method should be skipped
34
- #
35
- # @return [Boolean]
36
- #
37
- # @api private
38
- def skip?
39
- location = source_location
40
- if location.nil? || BLACKLIST.match(location.first)
41
- env.warn(format('%s does not have valid source location unable to emit subject', target_method.inspect))
42
- true
43
- elsif matched_node_path.any?(&method(:n_block?))
44
- env.warn(format('%s is defined from a 3rd party lib unable to emit subject', target_method.inspect))
45
- true
46
- else
47
- false
69
+ # Target method name
70
+ #
71
+ # @return [String]
72
+ #
73
+ # @api private
74
+ def method_name
75
+ target_method.name
48
76
  end
49
- end
50
77
 
51
- # Target method name
52
- #
53
- # @return [String]
54
- #
55
- # @api private
56
- def method_name
57
- target_method.name
58
- end
78
+ # Target context
79
+ #
80
+ # @return [Context::Scope]
81
+ #
82
+ # @api private
83
+ def context
84
+ Context::Scope.new(scope, source_path)
85
+ end
59
86
 
60
- # Target context
61
- #
62
- # @return [Context::Scope]
63
- #
64
- # @api private
65
- def context
66
- Context::Scope.new(scope, source_path)
67
- end
87
+ # Root source node
88
+ #
89
+ # @return [Parser::AST::Node]
90
+ #
91
+ # @api private
92
+ def ast
93
+ env.cache.parse(source_path)
94
+ end
68
95
 
69
- # Root source node
70
- #
71
- # @return [Parser::AST::Node]
72
- #
73
- # @api private
74
- def ast
75
- env.cache.parse(source_path)
76
- end
96
+ # Path to source
97
+ #
98
+ # @return [Pathname]
99
+ #
100
+ # @api private
101
+ def source_path
102
+ Pathname.new(source_location.first)
103
+ end
104
+ memoize :source_path
77
105
 
78
- # Path to source
79
- #
80
- # @return [Pathname]
81
- #
82
- # @api private
83
- def source_path
84
- Pathname.new(source_location.first)
85
- end
86
- memoize :source_path
106
+ # Source file line
107
+ #
108
+ # @return [Fixnum]
109
+ #
110
+ # @api private
111
+ def source_line
112
+ source_location.last
113
+ end
87
114
 
88
- # Source file line
89
- #
90
- # @return [Fixnum]
91
- #
92
- # @api private
93
- def source_line
94
- source_location.last
95
- end
115
+ # Full source location
116
+ #
117
+ # @return [Array{String,Fixnum}]
118
+ #
119
+ # @api private
120
+ def source_location
121
+ target_method.source_location
122
+ end
96
123
 
97
- # Full source location
98
- #
99
- # @return [Array{String,Fixnum}]
100
- #
101
- # @api private
102
- def source_location
103
- target_method.source_location
104
- end
124
+ # Matched subject
125
+ #
126
+ # @return [Subject]
127
+ # if there is a matched node
128
+ #
129
+ # @return [nil]
130
+ # otherwise
131
+ #
132
+ # @api private
133
+ def subject
134
+ node = matched_node_path.last || return
135
+ self.class::SUBJECT_CLASS.new(context, node)
136
+ end
137
+ memoize :subject
105
138
 
106
- # Matched subject
107
- #
108
- # @return [Subject]
109
- # if there is a matched node
110
- #
111
- # @return [nil]
112
- # otherwise
113
- #
114
- # @api private
115
- def subject
116
- node = matched_node_path.last
117
- return unless node
118
- self.class::SUBJECT_CLASS.new(context, node)
119
- end
120
- memoize :subject
139
+ # Matched node path
140
+ #
141
+ # @return [Array<Parser::AST::Node>]
142
+ #
143
+ # @api private
144
+ def matched_node_path
145
+ AST.find_last_path(ast, &method(:match?))
146
+ end
147
+ memoize :matched_node_path
148
+ end # Evaluator
121
149
 
122
- # Matched node path
123
- #
124
- # @return [Array<Parser::AST::Node>]
125
- #
126
- # @api private
127
- def matched_node_path
128
- AST.find_last_path(ast, &method(:match?))
129
- end
130
- memoize :matched_node_path
150
+ private_constant(*constants(false))
131
151
 
132
152
  end # Method
133
153
  end # Matcher