mutant 0.8.7 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +5 -0
- data/README.md +64 -3
- data/config/flay.yml +1 -1
- data/lib/mutant.rb +2 -0
- data/lib/mutant/cli.rb +1 -1
- data/lib/mutant/env/bootstrap.rb +36 -8
- data/lib/mutant/expression/method.rb +3 -5
- data/lib/mutant/expression/methods.rb +2 -4
- data/lib/mutant/expression/namespace.rb +4 -8
- data/lib/mutant/matcher.rb +3 -18
- data/lib/mutant/matcher/chain.rb +7 -13
- data/lib/mutant/matcher/compiler.rb +2 -13
- data/lib/mutant/matcher/filter.rb +6 -19
- data/lib/mutant/matcher/method.rb +124 -104
- data/lib/mutant/matcher/method/instance.rb +40 -34
- data/lib/mutant/matcher/method/singleton.rb +80 -61
- data/lib/mutant/matcher/methods.rb +19 -29
- data/lib/mutant/matcher/namespace.rb +22 -16
- data/lib/mutant/matcher/null.rb +4 -7
- data/lib/mutant/matcher/scope.rb +23 -13
- data/lib/mutant/matcher/static.rb +17 -0
- data/lib/mutant/mutation.rb +0 -5
- data/lib/mutant/reporter/cli/format.rb +2 -3
- data/lib/mutant/reporter/cli/printer/env_progress.rb +37 -11
- data/lib/mutant/reporter/cli/printer/status_progressive.rb +1 -1
- data/lib/mutant/scope.rb +6 -0
- data/lib/mutant/subject/method.rb +0 -7
- data/lib/mutant/subject/method/instance.rb +0 -10
- data/lib/mutant/subject/method/singleton.rb +0 -10
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/zombifier.rb +2 -1
- data/mutant-rspec.gemspec +1 -1
- data/spec/integration/mutant/rspec_spec.rb +1 -1
- data/spec/shared/method_matcher_behavior.rb +21 -14
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/mutant/env/boostrap_spec.rb +88 -26
- data/spec/unit/mutant/env_spec.rb +0 -1
- data/spec/unit/mutant/expression/method_spec.rb +3 -3
- data/spec/unit/mutant/expression/methods_spec.rb +3 -4
- data/spec/unit/mutant/expression/namespace/flat_spec.rb +2 -3
- data/spec/unit/mutant/expression/namespace/recursive_spec.rb +2 -4
- data/spec/unit/mutant/matcher/chain_spec.rb +21 -29
- data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +16 -13
- data/spec/unit/mutant/matcher/compiler_spec.rb +49 -60
- data/spec/unit/mutant/matcher/filter_spec.rb +15 -31
- data/spec/unit/mutant/matcher/method/instance_spec.rb +84 -128
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +48 -52
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +21 -24
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +18 -21
- data/spec/unit/mutant/matcher/namespace_spec.rb +30 -38
- data/spec/unit/mutant/matcher/null_spec.rb +5 -20
- data/spec/unit/mutant/matcher/scope_spec.rb +33 -0
- data/spec/unit/mutant/matcher/static_spec.rb +11 -0
- data/spec/unit/mutant/mutation_spec.rb +30 -10
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +6 -0
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +2 -0
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +10 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +4 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +0 -28
- data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -28
- data/test_app/Gemfile.rspec3.4 +7 -0
- data/test_app/lib/test_app.rb +16 -12
- data/test_app/lib/test_app/literal.rb +3 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d83617d43f73b49c391d1b7e8f3317a666297c83
|
4
|
+
data.tar.gz: 186b8ce58089718ba3cafa1c08f609b89941541e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d4ee1e94a96ba909cf327bde6afdca8ff5e50f80603ca8835115fdba2abbfa05ad89897366908f8b64bca2e9302605e7accddf1167260c187cf4e40417d0df5
|
7
|
+
data.tar.gz: 7b599e22fa7ddd03e9f9ac91af9ce0e730b155c8df2b5ab1b5a67fc8cd62f8bf8972ca8df64b45fb5114a556e7416f3c86baf666ea62f5de5201e8b068406afc
|
data/Changelog.md
CHANGED
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
|
233
|
+
Blog posts
|
186
234
|
----------
|
187
235
|
|
188
|
-
|
189
|
-
|
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
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
data/lib/mutant/env/bootstrap.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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(
|
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 <<
|
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
|
-
|
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
|
41
|
-
methods_matcher = MATCHERS.fetch(scope_symbol).new(
|
38
|
+
def matcher
|
39
|
+
methods_matcher = MATCHERS.fetch(scope_symbol).new(scope)
|
42
40
|
|
43
|
-
Matcher::Filter.
|
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
|
35
|
-
MATCHERS.fetch(scope_symbol).new(
|
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
|
44
|
-
Matcher::Namespace.new(
|
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
|
80
|
-
Matcher::Scope.new(
|
75
|
+
def matcher
|
76
|
+
Matcher::Scope.new(Object.const_get(scope_name))
|
81
77
|
end
|
82
78
|
|
83
79
|
# Syntax for expression
|
data/lib/mutant/matcher.rb
CHANGED
@@ -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,
|
4
|
+
include Adamantium::Flat, AbstractType
|
5
5
|
|
6
|
-
#
|
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
|
-
|
27
|
-
abstract_method :each
|
12
|
+
abstract_method :call
|
28
13
|
|
29
14
|
end # Matcher
|
30
15
|
end # Mutant
|
data/lib/mutant/matcher/chain.rb
CHANGED
@@ -1,26 +1,20 @@
|
|
1
1
|
module Mutant
|
2
2
|
class Matcher
|
3
|
-
#
|
3
|
+
# Matcher chaining results of other matchers together
|
4
4
|
class Chain < self
|
5
5
|
include Concord.new(:matchers)
|
6
6
|
|
7
|
-
#
|
7
|
+
# Call matcher
|
8
8
|
#
|
9
|
-
# @
|
10
|
-
# if no block given
|
9
|
+
# @param [Env] env
|
11
10
|
#
|
12
|
-
# @return [
|
13
|
-
# otherwise
|
11
|
+
# @return [Enumerable<Subject>]
|
14
12
|
#
|
15
13
|
# @api private
|
16
|
-
def
|
17
|
-
|
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(:
|
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.
|
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
|
-
# @
|
21
|
-
# if block given
|
9
|
+
# @param [Env] env
|
22
10
|
#
|
23
|
-
# @return [
|
24
|
-
# otherwise
|
11
|
+
# @return [Enumerable<Subject>]
|
25
12
|
#
|
26
13
|
# @api private
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
#
|
3
|
+
# Abstract base class for method matchers
|
4
4
|
class Method < self
|
5
|
-
include
|
6
|
-
|
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
|
-
|
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
|
-
# @
|
15
|
-
# if no block given
|
21
|
+
# @param [Env] env
|
16
22
|
#
|
17
|
-
# @return [
|
18
|
-
# otherwise
|
23
|
+
# @return [Enumerable<Subject>]
|
19
24
|
#
|
20
25
|
# @api private
|
21
|
-
def
|
22
|
-
|
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
|
-
|
25
|
-
yield subject
|
50
|
+
[subject].compact
|
26
51
|
end
|
27
52
|
|
28
|
-
|
29
|
-
end
|
53
|
+
private
|
30
54
|
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
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
|