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.
- 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
|