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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -1
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +1 -1
  5. data/defaults.reek +3 -0
  6. data/docs/Code-Smells.md +1 -0
  7. data/docs/How-reek-works-internally.md +3 -2
  8. data/docs/Unused-Private-Method.md +47 -0
  9. data/features/samples.feature +22 -2
  10. data/features/step_definitions/sample_file_steps.rb +3 -0
  11. data/lib/reek/ast/node.rb +1 -2
  12. data/lib/reek/ast/object_refs.rb +30 -7
  13. data/lib/reek/code_comment.rb +25 -19
  14. data/lib/reek/context/class_context.rb +11 -0
  15. data/lib/reek/context/code_context.rb +58 -78
  16. data/lib/reek/context/module_context.rb +18 -0
  17. data/lib/reek/context/send_context.rb +17 -0
  18. data/lib/reek/context/singleton_method_context.rb +0 -3
  19. data/lib/reek/context/statement_counter.rb +32 -0
  20. data/lib/reek/context/visibility_tracker.rb +54 -0
  21. data/lib/reek/context_builder.rb +473 -0
  22. data/lib/reek/examiner.rb +14 -14
  23. data/lib/reek/smells/feature_envy.rb +3 -3
  24. data/lib/reek/smells/smell_detector.rb +1 -0
  25. data/lib/reek/smells/smell_repository.rb +11 -0
  26. data/lib/reek/smells/too_many_statements.rb +1 -1
  27. data/lib/reek/smells/unused_private_method.rb +82 -0
  28. data/lib/reek/smells/utility_function.rb +1 -1
  29. data/lib/reek/smells.rb +1 -0
  30. data/lib/reek/version.rb +1 -1
  31. data/spec/reek/ast/object_refs_spec.rb +20 -20
  32. data/spec/reek/cli/input_spec.rb +55 -0
  33. data/spec/reek/code_comment_spec.rb +10 -0
  34. data/spec/reek/context/code_context_spec.rb +8 -0
  35. data/spec/reek/context/module_context_spec.rb +10 -8
  36. data/spec/reek/context_builder_spec.rb +221 -0
  37. data/spec/reek/examiner_spec.rb +13 -0
  38. data/spec/reek/smells/boolean_parameter_spec.rb +2 -0
  39. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  40. data/spec/reek/smells/feature_envy_spec.rb +1 -1
  41. data/spec/reek/smells/too_many_statements_spec.rb +3 -3
  42. data/spec/reek/smells/unused_private_method_spec.rb +110 -0
  43. data/spec/spec_helper.rb +7 -0
  44. data/tasks/console.rake +5 -0
  45. metadata +13 -5
  46. data/lib/reek/tree_walker.rb +0 -237
  47. data/spec/reek/context/singleton_method_context_spec.rb +0 -16
  48. data/spec/reek/tree_walker_spec.rb +0 -237
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0360a27f0d7285b66c630aa23ff08c48273f0ff7
4
- data.tar.gz: cf166a5b8541a65c60738afa51aa903c0c673998
3
+ metadata.gz: f9790efcdbc4208c4183611dd613d3c9e5075583
4
+ data.tar.gz: 4584bf1720a0ae7b9db6088e2c7e44550ed98fd3
5
5
  SHA512:
6
- metadata.gz: d52ffb53c36fc67b58993411a6bdeb1f319cadd044736f5cb13e690789dc77c2e0022b7f25261e1ebe4838fe4c11036f47d005472d5af30d6f33f4885651ae37
7
- data.tar.gz: 443d185d3acc0d65d3f673e34f1e050e754a87637cc6fae8c63fa1059d9f1150c5936437c0cfe662d8fd9952ca903fa90647036ee35a35fcb07f200909d67f40
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/tree_walker.rb
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
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.8.0 (2015-12-27)
6
+
7
+ * (troessner) Report unused private instance methods.
8
+ * (troessner) Add Rake task for console.
9
+
5
10
  ## 3.7.1 (2015-11-29)
6
11
 
7
12
  * (troessner) Reverse merge default directives into directory directives.
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 bussiness logic.
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
@@ -106,6 +106,9 @@ UncommunicativeVariableName:
106
106
  UnusedParameters:
107
107
  enabled: true
108
108
  exclude: []
109
+ UnusedPrivateMethod:
110
+ enabled: true
111
+ exclude: []
109
112
  UtilityFunction:
110
113
  enabled: true
111
114
  exclude: []
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
- * runs all corresponding smell detectors via a Treewalker (core/tree_walker) for the SmellRepository above
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 TreeWalker then traverses this now adorned tree again and
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
+
@@ -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 -- 101 warnings:
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
- 265 total warnings
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
@@ -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
- # Manages and counts the references out of a method to other objects.
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
@@ -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
- CONFIG_REGEX = /:reek:(\w+)(:\s*\{.*?\})?/
14
-
15
- def initialize(text)
16
- @config = Hash.new { |hash, key| hash[key] = {} }
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
- text.split(/\s+/).length >= 2
33
+ sanitized_comment.split(/\s+/).length >= MINIMUM_CONTENT_LENGTH
27
34
  end
28
35
 
29
36
  private
30
37
 
31
- # :reek:UtilityFunction
32
- def add_to_config(smell, options)
33
- options ||= ': { enabled: false }'
34
- YAML.load(smell + options)
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
@@ -0,0 +1,11 @@
1
+ require_relative 'module_context'
2
+
3
+ module Reek
4
+ module Context
5
+ #
6
+ # A context wrapper for any class found in a syntax tree.
7
+ #
8
+ class ClassContext < ModuleContext
9
+ end
10
+ end
11
+ 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
- attr_reader :exp
16
- attr_reader :num_statements
17
- attr_reader :children
18
- attr_reader :visibility
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 {TreeWalker} object first instantiates a {RootContext}, which has no parent.
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, {TreeWalker} will instantiate a {MethodContext}. This time,
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 = context
56
- @exp = exp
57
- @visibility = :public
58
- @children = []
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
- @num_statements = 0
61
- @refs = AST::ObjectRefs.new
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.visibility = tracked_visibility
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.record_reference_to(receiver.name, line: line)
119
+ refs.record_reference(name: receiver.name, line: line)
90
120
  end
91
121
  when :self
92
- refs.record_reference_to(:self, line: line)
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.record_reference_to(:self)
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
- config[detector_class.smell_type] || {})
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
- if names.any?
142
- children.each do |child|
143
- child.visibility = visibility if names.include? child.name
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 type
151
- exp.type
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
- private_attr_writer :tracked_visibility
173
- private_attr_reader :context, :refs
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
@@ -6,9 +6,6 @@ module Reek
6
6
  # A context wrapper for any singleton method definition found in a syntax tree.
7
7
  #
8
8
  class SingletonMethodContext < MethodContext
9
- def envious_receivers
10
- {}
11
- end
12
9
  end
13
10
  end
14
11
  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