reek 3.7.1 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
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