reek 3.7.1 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -1
- data/CHANGELOG.md +5 -0
- data/README.md +1 -1
- data/defaults.reek +3 -0
- data/docs/Code-Smells.md +1 -0
- data/docs/How-reek-works-internally.md +3 -2
- data/docs/Unused-Private-Method.md +47 -0
- data/features/samples.feature +22 -2
- data/features/step_definitions/sample_file_steps.rb +3 -0
- data/lib/reek/ast/node.rb +1 -2
- data/lib/reek/ast/object_refs.rb +30 -7
- data/lib/reek/code_comment.rb +25 -19
- data/lib/reek/context/class_context.rb +11 -0
- data/lib/reek/context/code_context.rb +58 -78
- data/lib/reek/context/module_context.rb +18 -0
- data/lib/reek/context/send_context.rb +17 -0
- data/lib/reek/context/singleton_method_context.rb +0 -3
- data/lib/reek/context/statement_counter.rb +32 -0
- data/lib/reek/context/visibility_tracker.rb +54 -0
- data/lib/reek/context_builder.rb +473 -0
- data/lib/reek/examiner.rb +14 -14
- data/lib/reek/smells/feature_envy.rb +3 -3
- data/lib/reek/smells/smell_detector.rb +1 -0
- data/lib/reek/smells/smell_repository.rb +11 -0
- data/lib/reek/smells/too_many_statements.rb +1 -1
- data/lib/reek/smells/unused_private_method.rb +82 -0
- data/lib/reek/smells/utility_function.rb +1 -1
- data/lib/reek/smells.rb +1 -0
- data/lib/reek/version.rb +1 -1
- data/spec/reek/ast/object_refs_spec.rb +20 -20
- data/spec/reek/cli/input_spec.rb +55 -0
- data/spec/reek/code_comment_spec.rb +10 -0
- data/spec/reek/context/code_context_spec.rb +8 -0
- data/spec/reek/context/module_context_spec.rb +10 -8
- data/spec/reek/context_builder_spec.rb +221 -0
- data/spec/reek/examiner_spec.rb +13 -0
- data/spec/reek/smells/boolean_parameter_spec.rb +2 -0
- data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
- data/spec/reek/smells/feature_envy_spec.rb +1 -1
- data/spec/reek/smells/too_many_statements_spec.rb +3 -3
- data/spec/reek/smells/unused_private_method_spec.rb +110 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/console.rake +5 -0
- metadata +13 -5
- data/lib/reek/tree_walker.rb +0 -237
- data/spec/reek/context/singleton_method_context_spec.rb +0 -16
- data/spec/reek/tree_walker_spec.rb +0 -237
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9790efcdbc4208c4183611dd613d3c9e5075583
|
4
|
+
data.tar.gz: 4584bf1720a0ae7b9db6088e2c7e44550ed98fd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1822f9ebf7c84992bcd366da12b336f5e48f5082d35e44c2c9ac639501237609a0be36b45907f639e5594ea2ba5afaf8dc7940cae8125609ba5e32ba6bc46e5d
|
7
|
+
data.tar.gz: 9dfe34d1d98f9816fb96504ca843f51e2daedc0912f4732fa5b7a07f36e1fde3a83796341ac157cb1866cbd91f26e9f0dd5dab4d40ac7c5c55d96d1a3c3acdcf
|
data/.rubocop.yml
CHANGED
@@ -7,7 +7,7 @@ AllCops:
|
|
7
7
|
# FIXME: Make the class shorter
|
8
8
|
Metrics/ClassLength:
|
9
9
|
Exclude:
|
10
|
-
- lib/reek/
|
10
|
+
- lib/reek/context_builder.rb
|
11
11
|
- lib/reek/cli/options.rb
|
12
12
|
|
13
13
|
# FIXME: Lower the method length by fixing the biggest offenders
|
@@ -59,3 +59,10 @@ Style/Documentation:
|
|
59
59
|
- 'lib/reek/ast/sexp_extensions/send.rb'
|
60
60
|
- 'lib/reek/ast/sexp_extensions/super.rb'
|
61
61
|
- 'lib/reek/ast/sexp_extensions/variables.rb'
|
62
|
+
|
63
|
+
Style/AccessorMethodName:
|
64
|
+
Exclude:
|
65
|
+
- 'lib/reek/context/visibility_tracker.rb'
|
66
|
+
|
67
|
+
Style/ParallelAssignment:
|
68
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -65,7 +65,7 @@ demo.rb -- 8 warnings:
|
|
65
65
|
|
66
66
|
Reek focuses on high-level code smells, so we can't tell you how to fix warnings in
|
67
67
|
a generic fashion; this is and will always be completely dependent on your domain
|
68
|
-
language and
|
68
|
+
language and business logic.
|
69
69
|
|
70
70
|
That said, an example might help you get going. Have a look at this sample of a
|
71
71
|
Ruby on Rails model (be aware that this is truncated, not working code):
|
data/defaults.reek
CHANGED
data/docs/Code-Smells.md
CHANGED
@@ -32,3 +32,4 @@ Reek currently includes checks for the following smells:
|
|
32
32
|
* [Uncommunicative Parameter Name](Uncommunicative-Parameter-Name.md)
|
33
33
|
* [Uncommunicative Variable Name](Uncommunicative-Variable-Name.md)
|
34
34
|
* [Unused Parameters](Unused-Parameters.md)
|
35
|
+
* [Unused Private Method](Unused-Private-Method.md)
|
@@ -42,7 +42,8 @@
|
|
42
42
|
* adorns the generated AST via a TreeDresser (core/tree_dresser)
|
43
43
|
* initializes a SmellRepository with all relevant smells (smells/smell_repository)
|
44
44
|
* initializes a WarningCollector (cli/warning_collector)
|
45
|
-
*
|
45
|
+
* builds a tree of Contexts using ContextBuilder
|
46
|
+
* runs the smell detectors from the SmellRepository above on each of the contexts
|
46
47
|
/ | \
|
47
48
|
/ | \
|
48
49
|
/ | \
|
@@ -110,5 +111,5 @@ The overall workflow is like this:
|
|
110
111
|
|
|
111
112
|
|
|
112
113
|
|
|
113
|
-
A
|
114
|
+
A ContextBuilder then traverses this now adorned tree again and
|
114
115
|
runs all SmellDetectors from the SmellRepository above
|
@@ -0,0 +1,47 @@
|
|
1
|
+
## Introduction
|
2
|
+
|
3
|
+
Classes should use their private methods. Otherwise this is dead
|
4
|
+
code which is confusing and bad for maintenance.
|
5
|
+
|
6
|
+
The `Unused Private Method` detector reports unused private instance
|
7
|
+
methods and instance methods only - class methods are ignored.
|
8
|
+
|
9
|
+
## Example
|
10
|
+
|
11
|
+
Given:
|
12
|
+
|
13
|
+
```Ruby
|
14
|
+
class Car
|
15
|
+
private
|
16
|
+
def drive; end
|
17
|
+
def start; end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
`Reek` would emit the following warning:
|
22
|
+
|
23
|
+
```
|
24
|
+
2 warnings:
|
25
|
+
[3]:Car has the unused private instance method `drive` (UnusedPrivateMethod)
|
26
|
+
[4]:Car has the unused private instance method `start` (UnusedPrivateMethod)
|
27
|
+
```
|
28
|
+
|
29
|
+
## Configuration
|
30
|
+
|
31
|
+
`Unused Private Method` offers the [Basic Smell Options](Basic-Smell-Options.md).
|
32
|
+
|
33
|
+
Private methods that are called via dynamic dispatch
|
34
|
+
will trigger a false alarm since detecting something like this is far out of
|
35
|
+
scope for `Reek`. In this case you can disable this detector via the `exclude`
|
36
|
+
configuration option (which is part of the [Basic Smell Options](Basic-Smell-Options.md))
|
37
|
+
for instance like this (an example from `Reek's` own codebase):
|
38
|
+
|
39
|
+
```Ruby
|
40
|
+
# :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /process_/ ] }
|
41
|
+
class ContextBuilder
|
42
|
+
def process_begin
|
43
|
+
# ....
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
data/features/samples.feature
CHANGED
@@ -177,7 +177,7 @@ Feature: Basic smell detection
|
|
177
177
|
UnusedParameters: OptionParser::Completion#convert has unused parameter 'opt' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
178
178
|
UnusedParameters: OptionParser::Switch::NoArgument#parse has unused parameter 'argv' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
179
179
|
UnusedParameters: OptionParser::Switch::OptionalArgument#parse has unused parameter 'argv' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
180
|
-
redcloth.rb --
|
180
|
+
redcloth.rb -- 121 warnings:
|
181
181
|
Attribute: RedCloth#filter_html is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
182
182
|
Attribute: RedCloth#filter_styles is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
183
183
|
Attribute: RedCloth#hard_breaks is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
|
@@ -269,6 +269,26 @@ Feature: Basic smell detection
|
|
269
269
|
UnusedParameters: RedCloth#textile_fn_ has unused parameter 'cite' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
270
270
|
UnusedParameters: RedCloth#textile_fn_ has unused parameter 'tag' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
271
271
|
UnusedParameters: RedCloth#textile_p has unused parameter 'cite' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
|
272
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_markdown_atx` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
273
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_markdown_bq` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
274
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_markdown_lists` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
275
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_markdown_rule` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
276
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_markdown_setext` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
277
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_textile_lists` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
278
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_textile_prefix` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
279
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `block_textile_table` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
280
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `inline_markdown_link` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
281
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `inline_markdown_reflink` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
282
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `inline_textile_code` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
283
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `inline_textile_image` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
284
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `inline_textile_link` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
285
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `inline_textile_span` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
286
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `refs_markdown` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
287
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `refs_textile` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
288
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `textile_bq` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
289
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `textile_fn_` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
290
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `textile_p` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
291
|
+
UnusedPrivateMethod: RedCloth has the unused private instance method `textile_popup_help` [https://github.com/troessner/reek/blob/master/docs/Unused-Private-Method.md]
|
272
292
|
UtilityFunction: RedCloth#block_markdown_rule doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
273
293
|
UtilityFunction: RedCloth#clean_html doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
274
294
|
UtilityFunction: RedCloth#flush_left doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
@@ -279,5 +299,5 @@ Feature: Basic smell detection
|
|
279
299
|
UtilityFunction: RedCloth#lT doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
280
300
|
UtilityFunction: RedCloth#no_textile doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
281
301
|
UtilityFunction: RedCloth#v_align doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
|
282
|
-
|
302
|
+
285 total warnings
|
283
303
|
"""
|
@@ -145,6 +145,9 @@ Given(/^a configuration file disabling UtilityFunction for non-public methods ca
|
|
145
145
|
---
|
146
146
|
UtilityFunction:
|
147
147
|
public_methods_only: true
|
148
|
+
# Not necessary for the feature per se but for removing distracting output.
|
149
|
+
UnusedPrivateMethod:
|
150
|
+
enabled: false
|
148
151
|
EOS
|
149
152
|
end
|
150
153
|
|
data/lib/reek/ast/node.rb
CHANGED
@@ -12,6 +12,7 @@ module Reek
|
|
12
12
|
#
|
13
13
|
class Node < ::Parser::AST::Node
|
14
14
|
attr_reader :parent
|
15
|
+
private_attr_reader :comments
|
15
16
|
|
16
17
|
def initialize(type, children = [], options = {})
|
17
18
|
@comments = options.fetch(:comments, [])
|
@@ -114,8 +115,6 @@ module Reek
|
|
114
115
|
|
115
116
|
private
|
116
117
|
|
117
|
-
private_attr_reader :comments
|
118
|
-
|
119
118
|
def each_sexp
|
120
119
|
children.each { |elem| yield elem if elem.is_a? ::Parser::AST::Node }
|
121
120
|
end
|
data/lib/reek/ast/object_refs.rb
CHANGED
@@ -3,25 +3,48 @@ require 'private_attr/everywhere'
|
|
3
3
|
module Reek
|
4
4
|
# Represents functionality related to an Abstract Syntax Tree.
|
5
5
|
module AST
|
6
|
-
# Responsible for holding one specific object reference.
|
7
|
-
ObjectRef = Struct.new(:name, :line)
|
8
6
|
#
|
9
|
-
#
|
7
|
+
# ObjectRefs is used in CodeContexts.
|
8
|
+
# It manages and counts the references out of a method to other objects and to `self`.
|
9
|
+
#
|
10
|
+
# E.g. this code:
|
11
|
+
# def foo(thing)
|
12
|
+
# bar.call_me
|
13
|
+
# bar.maybe(thing.wat)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# would make "@refs" below look like this after the TreeWalker has done his job:
|
17
|
+
# {
|
18
|
+
# :self=>[2, 3], # `bar.call_me` and `bar.maybe` count as refs to `self` in line 2 and 3
|
19
|
+
# :thing=>[3] # `thing.wat` in `bar.maybe()` counts as one reference to `thing`
|
20
|
+
# }
|
10
21
|
#
|
11
22
|
class ObjectRefs
|
12
23
|
def initialize
|
13
24
|
@refs = Hash.new { |refs, name| refs[name] = [] }
|
14
25
|
end
|
15
26
|
|
27
|
+
# Records the references a given method in a CodeContext has including
|
28
|
+
# `self` (see the example at the beginning of this file).
|
29
|
+
#
|
30
|
+
# @param name [Symbol] The name of the object that the method references or `self`.
|
31
|
+
# @param line [Int] The line number where this reference occurs.
|
32
|
+
#
|
33
|
+
# @return [Int|nil] The line number that was added (which might be nil).
|
34
|
+
def record_reference(name: raise, line: nil)
|
35
|
+
refs[name] << line
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Hash] The most popular references.
|
39
|
+
# E.g. for
|
40
|
+
# { foo: [2], self: [2,3], bar: [3,4] }
|
41
|
+
# this would return
|
42
|
+
# { self: [2,3], bar: [3,4] }
|
16
43
|
def most_popular
|
17
44
|
max = refs.values.map(&:size).max
|
18
45
|
refs.select { |_name, refs| refs.size == max }
|
19
46
|
end
|
20
47
|
|
21
|
-
def record_reference_to(name, line: nil)
|
22
|
-
refs[name] << ObjectRef.new(name, line)
|
23
|
-
end
|
24
|
-
|
25
48
|
def references_to(name)
|
26
49
|
refs[name]
|
27
50
|
end
|
data/lib/reek/code_comment.rb
CHANGED
@@ -1,39 +1,45 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'private_attr/everywhere'
|
3
3
|
|
4
|
-
# NOTE: Work-around for https://github.com/tenderlove/psych/issues/223
|
5
|
-
require 'psych.rb' if Object.const_defined?(:Psych)
|
6
|
-
|
7
4
|
module Reek
|
8
5
|
#
|
9
6
|
# A comment header from an abstract syntax tree; found directly above
|
10
7
|
# module, class and method definitions.
|
11
8
|
#
|
12
9
|
class CodeComment
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@text = text.gsub(CONFIG_REGEX) do
|
18
|
-
@config.merge! add_to_config($1, $2)
|
19
|
-
''
|
20
|
-
end.gsub(/#/, '').gsub(/\n/, '').strip
|
21
|
-
end
|
10
|
+
CONFIGURATION_REGEX = /:reek:(\w+)(:\s*\{.*?\})?/
|
11
|
+
SANITIZE_REGEX = /(#|\n|\s)+/ # Matches '#', newlines and > 1 whitespaces.
|
12
|
+
DISABLE_DETECTOR_CONFIGURATION = ': { enabled: false }'
|
13
|
+
MINIMUM_CONTENT_LENGTH = 2
|
22
14
|
|
23
15
|
attr_reader :config
|
16
|
+
private_attr_reader :original_comment
|
17
|
+
|
18
|
+
#
|
19
|
+
# @param comment [String] - the original comment as found in the source code
|
20
|
+
# E.g.:
|
21
|
+
# "\n # :reek:Duplication: { enabled: false }\n "
|
22
|
+
#
|
23
|
+
def initialize(comment)
|
24
|
+
@original_comment = comment
|
25
|
+
@config = Hash.new { |hash, key| hash[key] = {} }
|
26
|
+
|
27
|
+
@original_comment.scan(CONFIGURATION_REGEX) do |smell_type, options|
|
28
|
+
@config.merge! YAML.load(smell_type + (options || DISABLE_DETECTOR_CONFIGURATION))
|
29
|
+
end
|
30
|
+
end
|
24
31
|
|
25
32
|
def descriptive?
|
26
|
-
|
33
|
+
sanitized_comment.split(/\s+/).length >= MINIMUM_CONTENT_LENGTH
|
27
34
|
end
|
28
35
|
|
29
36
|
private
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
def sanitized_comment
|
39
|
+
@sanitized_comment ||= original_comment.
|
40
|
+
gsub(CONFIGURATION_REGEX, '').
|
41
|
+
gsub(SANITIZE_REGEX, ' ').
|
42
|
+
strip
|
35
43
|
end
|
36
|
-
|
37
|
-
private_attr_reader :text
|
38
44
|
end
|
39
45
|
end
|
@@ -1,5 +1,10 @@
|
|
1
1
|
require_relative '../code_comment'
|
2
2
|
require_relative '../ast/object_refs'
|
3
|
+
require_relative 'visibility_tracker'
|
4
|
+
require_relative 'statement_counter'
|
5
|
+
|
6
|
+
require 'forwardable'
|
7
|
+
require 'private_attr/everywhere'
|
3
8
|
|
4
9
|
module Reek
|
5
10
|
module Context
|
@@ -12,10 +17,13 @@ module Reek
|
|
12
17
|
# :reek:TooManyMethods: { max_methods: 19 }
|
13
18
|
# :reek:TooManyInstanceVariables: { max_instance_variables: 8 }
|
14
19
|
class CodeContext
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
extend Forwardable
|
21
|
+
delegate each_node: :exp
|
22
|
+
delegate %i(name type) => :exp
|
23
|
+
delegate %i(visibility visibility= non_public_visibility?) => :visibility_tracker
|
24
|
+
|
25
|
+
attr_reader :children, :context, :exp, :statement_counter, :visibility_tracker
|
26
|
+
private_attr_reader :refs
|
19
27
|
|
20
28
|
# Initializes a new CodeContext.
|
21
29
|
#
|
@@ -30,7 +38,7 @@ module Reek
|
|
30
38
|
# end
|
31
39
|
# end
|
32
40
|
#
|
33
|
-
# The {
|
41
|
+
# The {ContextBuilder} object first instantiates a {RootContext}, which has no parent.
|
34
42
|
#
|
35
43
|
# Next, it instantiates a {ModuleContext}, with +context+ being the
|
36
44
|
# {RootContext} just created, and +exp+ looking like this:
|
@@ -43,7 +51,7 @@ module Reek
|
|
43
51
|
# (send nil :puts
|
44
52
|
# (lvar :x))))
|
45
53
|
#
|
46
|
-
# Finally, {
|
54
|
+
# Finally, {ContextBuilder} will instantiate a {MethodContext}. This time,
|
47
55
|
# +context+ is the {ModuleContext} created above, and +exp+ is:
|
48
56
|
#
|
49
57
|
# (def :foo
|
@@ -52,15 +60,41 @@ module Reek
|
|
52
60
|
# (send nil :puts
|
53
61
|
# (lvar :x)))
|
54
62
|
def initialize(context, exp)
|
55
|
-
@context
|
56
|
-
@exp
|
57
|
-
@
|
58
|
-
@
|
63
|
+
@context = context
|
64
|
+
@exp = exp
|
65
|
+
@children = []
|
66
|
+
@visibility_tracker = VisibilityTracker.new
|
67
|
+
@statement_counter = StatementCounter.new
|
68
|
+
@refs = AST::ObjectRefs.new
|
69
|
+
end
|
70
|
+
|
71
|
+
# Iterate over each AST node (see `Reek::AST::Node`) of a given type for the current expression.
|
72
|
+
#
|
73
|
+
# @param type [Symbol] the type of the nodes we are looking for, e.g. :defs.
|
74
|
+
# @yield block that is executed for every node.
|
75
|
+
#
|
76
|
+
def local_nodes(type, &blk)
|
77
|
+
each_node(type, [:casgn, :class, :module], &blk)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Iterate over `self` and child contexts.
|
81
|
+
# The main difference (among others) to `each_node` is that we are traversing
|
82
|
+
# `CodeContexts` here, not AST nodes (see `Reek::AST::Node`).
|
83
|
+
#
|
84
|
+
# @yield block that is executed for every node.
|
85
|
+
# @return [Enumerator]
|
86
|
+
#
|
87
|
+
def each(&block)
|
88
|
+
return enum_for(:each) unless block_given?
|
59
89
|
|
60
|
-
|
61
|
-
|
90
|
+
yield self
|
91
|
+
children.each do |child|
|
92
|
+
child.each(&block)
|
93
|
+
end
|
62
94
|
end
|
63
95
|
|
96
|
+
alias_method :parent, :context
|
97
|
+
|
64
98
|
# Register a child context. The child's parent context should be equal to
|
65
99
|
# the current context.
|
66
100
|
#
|
@@ -69,14 +103,10 @@ module Reek
|
|
69
103
|
#
|
70
104
|
# @param child [CodeContext] the child context to register
|
71
105
|
def append_child_context(child)
|
72
|
-
child
|
106
|
+
visibility_tracker.set_child_visibility(child)
|
73
107
|
children << child
|
74
108
|
end
|
75
109
|
|
76
|
-
def count_statements(num)
|
77
|
-
self.num_statements += num
|
78
|
-
end
|
79
|
-
|
80
110
|
# :reek:TooManyStatements: { max_statements: 6 }
|
81
111
|
# :reek:FeatureEnvy
|
82
112
|
def record_call_to(exp)
|
@@ -86,28 +116,15 @@ module Reek
|
|
86
116
|
case type
|
87
117
|
when :lvar, :lvasgn
|
88
118
|
unless exp.object_creation_call?
|
89
|
-
refs.
|
119
|
+
refs.record_reference(name: receiver.name, line: line)
|
90
120
|
end
|
91
121
|
when :self
|
92
|
-
refs.
|
122
|
+
refs.record_reference(name: :self, line: line)
|
93
123
|
end
|
94
124
|
end
|
95
125
|
|
96
126
|
def record_use_of_self
|
97
|
-
refs.
|
98
|
-
end
|
99
|
-
|
100
|
-
def name
|
101
|
-
exp.name
|
102
|
-
end
|
103
|
-
|
104
|
-
def local_nodes(type, &blk)
|
105
|
-
each_node(type, [:casgn, :class, :module], &blk)
|
106
|
-
end
|
107
|
-
|
108
|
-
# See Reek::AST::Node for details.
|
109
|
-
def each_node(type, ignoring, &blk)
|
110
|
-
exp.each_node(type, ignoring, &blk)
|
127
|
+
refs.record_reference(name: :self)
|
111
128
|
end
|
112
129
|
|
113
130
|
def matches?(candidates)
|
@@ -124,60 +141,23 @@ module Reek
|
|
124
141
|
|
125
142
|
def config_for(detector_class)
|
126
143
|
context_config_for(detector_class).merge(
|
127
|
-
|
144
|
+
configuration_via_code_commment[detector_class.smell_type] || {})
|
128
145
|
end
|
129
146
|
|
130
|
-
# Handle the effects of a visibility modifier.
|
131
|
-
#
|
132
|
-
# @example Setting the current visibility
|
133
|
-
# track_visibility :public
|
134
|
-
#
|
135
|
-
# @example Modifying the visibility of existing children
|
136
|
-
# track_visibility :private, [:hide_me, :implementation_detail]
|
137
|
-
#
|
138
|
-
# @param visibility [Symbol]
|
139
|
-
# @param names [Array<Symbol>]
|
140
147
|
def track_visibility(visibility, names)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
else
|
146
|
-
self.tracked_visibility = visibility
|
147
|
-
end
|
148
|
+
visibility_tracker.track_visibility children: children,
|
149
|
+
visibility: visibility,
|
150
|
+
names: names
|
148
151
|
end
|
149
152
|
|
150
|
-
def
|
151
|
-
|
153
|
+
def number_of_statements
|
154
|
+
statement_counter.value
|
152
155
|
end
|
153
156
|
|
154
|
-
# Iterate over +self+ and child contexts.
|
155
|
-
def each(&block)
|
156
|
-
yield self
|
157
|
-
children.each do |child|
|
158
|
-
child.each(&block)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def non_public_visibility?
|
163
|
-
visibility != :public
|
164
|
-
end
|
165
|
-
|
166
|
-
protected
|
167
|
-
|
168
|
-
attr_writer :num_statements, :visibility
|
169
|
-
|
170
157
|
private
|
171
158
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
def tracked_visibility
|
176
|
-
@tracked_visibility ||= :public
|
177
|
-
end
|
178
|
-
|
179
|
-
def config
|
180
|
-
@config ||= CodeComment.new(full_comment).config
|
159
|
+
def configuration_via_code_commment
|
160
|
+
@configuration_via_code_commment ||= CodeComment.new(full_comment).config
|
181
161
|
end
|
182
162
|
|
183
163
|
def full_comment
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'code_context'
|
2
|
+
require_relative 'method_context'
|
2
3
|
require_relative '../ast/sexp_formatter'
|
3
4
|
|
4
5
|
module Reek
|
@@ -6,7 +7,24 @@ module Reek
|
|
6
7
|
#
|
7
8
|
# A context wrapper for any module found in a syntax tree.
|
8
9
|
#
|
10
|
+
# :reek:FeatureEnvy
|
9
11
|
class ModuleContext < CodeContext
|
12
|
+
def defined_instance_methods(visibility: :public)
|
13
|
+
each.select do |context|
|
14
|
+
context.is_a?(Context::MethodContext) &&
|
15
|
+
context.visibility == visibility
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def instance_method_calls
|
20
|
+
each.
|
21
|
+
grep(SendContext).
|
22
|
+
select { |context| context.parent.class == MethodContext }
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# @deprecated use `defined_instance_methods` instead
|
27
|
+
#
|
10
28
|
def node_instance_methods
|
11
29
|
local_nodes(:def)
|
12
30
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'code_context'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Context
|
5
|
+
#
|
6
|
+
# A context wrapper for method calls found in a syntax tree.
|
7
|
+
#
|
8
|
+
class SendContext < CodeContext
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(context, exp, name)
|
12
|
+
@name = name
|
13
|
+
super context, exp
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative '../ast/node'
|
2
|
+
require 'private_attr/everywhere'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Context
|
6
|
+
# Responsible for counting the statements in a `CodeContext`.
|
7
|
+
class StatementCounter
|
8
|
+
attr_reader :value
|
9
|
+
private_attr_writer :value
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@value = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def increase_by(sexp)
|
16
|
+
return unless sexp
|
17
|
+
case sexp
|
18
|
+
when Reek::AST::Node
|
19
|
+
self.value = value + 1
|
20
|
+
when Array
|
21
|
+
self.value = value + sexp.length
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Invalid type #{sexp} given"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def decrease_by(number)
|
28
|
+
self.value = value - number
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|