reek 3.7.1 → 3.8.0
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/.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
|