reek 3.8.3 → 3.9.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +16 -6
  5. data/README.md +1 -0
  6. data/features/command_line_interface/smells_count.feature +1 -1
  7. data/features/command_line_interface/stdin.feature +1 -1
  8. data/features/configuration_loading.feature +2 -2
  9. data/features/programmatic_access.feature +2 -2
  10. data/features/rake_task/rake_task.feature +4 -4
  11. data/features/reports/json.feature +2 -2
  12. data/features/reports/reports.feature +6 -6
  13. data/features/reports/yaml.feature +2 -2
  14. data/features/samples.feature +19 -19
  15. data/features/step_definitions/sample_file_steps.rb +1 -1
  16. data/lib/reek/ast/node.rb +12 -1
  17. data/lib/reek/ast/sexp_extensions.rb +1 -0
  18. data/lib/reek/ast/sexp_extensions/constant.rb +9 -0
  19. data/lib/reek/ast/sexp_extensions/methods.rb +1 -20
  20. data/lib/reek/ast/sexp_extensions/module.rb +2 -2
  21. data/lib/reek/ast/sexp_extensions/self.rb +12 -0
  22. data/lib/reek/ast/sexp_extensions/send.rb +4 -9
  23. data/lib/reek/ast/sexp_extensions/super.rb +1 -1
  24. data/lib/reek/ast/sexp_extensions/variables.rb +5 -0
  25. data/lib/reek/context/attribute_context.rb +12 -0
  26. data/lib/reek/context/code_context.rb +28 -26
  27. data/lib/reek/context/ghost_context.rb +54 -0
  28. data/lib/reek/context/method_context.rb +28 -1
  29. data/lib/reek/context/module_context.rb +55 -1
  30. data/lib/reek/context/root_context.rb +8 -0
  31. data/lib/reek/context/singleton_attribute_context.rb +15 -0
  32. data/lib/reek/context/singleton_method_context.rb +20 -0
  33. data/lib/reek/context/visibility_tracker.rb +25 -16
  34. data/lib/reek/context_builder.rb +61 -31
  35. data/lib/reek/examiner.rb +0 -6
  36. data/lib/reek/smells/control_parameter.rb +1 -1
  37. data/lib/reek/smells/nested_iterators.rb +1 -1
  38. data/lib/reek/smells/nil_check.rb +2 -2
  39. data/lib/reek/smells/utility_function.rb +1 -2
  40. data/lib/reek/version.rb +1 -1
  41. data/reek.gemspec +1 -12
  42. data/spec/reek/ast/node_spec.rb +51 -3
  43. data/spec/reek/ast/sexp_extensions_spec.rb +16 -3
  44. data/spec/reek/context/code_context_spec.rb +12 -31
  45. data/spec/reek/context/ghost_context_spec.rb +60 -0
  46. data/spec/reek/context/module_context_spec.rb +22 -2
  47. data/spec/reek/context_builder_spec.rb +225 -2
  48. data/spec/reek/examiner_spec.rb +1 -1
  49. data/spec/reek/smells/attribute_spec.rb +35 -0
  50. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  51. data/spec/reek/tree_dresser_spec.rb +0 -1
  52. data/spec/samples/checkstyle.xml +2 -2
  53. metadata +8 -152
  54. data/lib/reek/ast/sexp_formatter.rb +0 -31
  55. data/spec/reek/ast/sexp_formatter_spec.rb +0 -35
@@ -0,0 +1,12 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Utility methods for :self nodes.
5
+ module SelfNode
6
+ def name
7
+ :self
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -3,14 +3,13 @@ module Reek
3
3
  module SexpExtensions
4
4
  # Utility methods for :send nodes.
5
5
  module SendNode
6
- VISIBILITY_MODIFIERS = [:private, :public, :protected, :module_function]
7
6
  ATTR_DEFN_METHODS = [:attr_writer, :attr_accessor]
8
7
 
9
8
  def receiver
10
9
  children.first
11
10
  end
12
11
 
13
- def method_name
12
+ def name
14
13
  children[1]
15
14
  end
16
15
 
@@ -35,22 +34,18 @@ module Reek
35
34
  end
36
35
 
37
36
  def object_creation_call?
38
- method_name == :new
39
- end
40
-
41
- def visibility_modifier?
42
- VISIBILITY_MODIFIERS.include?(method_name)
37
+ name == :new
43
38
  end
44
39
 
45
40
  def attribute_writer?
46
- ATTR_DEFN_METHODS.include?(method_name) ||
41
+ ATTR_DEFN_METHODS.include?(name) ||
47
42
  attr_with_writable_flag?
48
43
  end
49
44
 
50
45
  # Handles the case where we create an attribute writer via:
51
46
  # attr :foo, true
52
47
  def attr_with_writable_flag?
53
- method_name == :attr && args.any? && args.last.type == :true
48
+ name == :attr && args.any? && args.last.type == :true
54
49
  end
55
50
  end
56
51
 
@@ -3,7 +3,7 @@ module Reek
3
3
  module SexpExtensions
4
4
  # Utility methods for :super nodes.
5
5
  module SuperNode
6
- def method_name
6
+ def name
7
7
  :super
8
8
  end
9
9
  end
@@ -30,6 +30,11 @@ module Reek
30
30
  alias_method :var_name, :name
31
31
  end
32
32
 
33
+ # Utility methods for :gvar nodes.
34
+ module GvarNode
35
+ include VariableBase
36
+ end
37
+
33
38
  LvasgnNode = LvarNode
34
39
  CvasgnNode = CvarNode
35
40
  CvdeclNode = CvarNode
@@ -5,8 +5,12 @@ module Reek
5
5
  #
6
6
  # A context wrapper for attribute definitions found in a syntax tree.
7
7
  #
8
+ # :reek:Attribute
8
9
  class AttributeContext < CodeContext
10
+ attr_accessor :visibility
11
+
9
12
  def initialize(context, exp, send_expression)
13
+ @visibility = :public
10
14
  @send_expression = send_expression
11
15
  super context, exp
12
16
  end
@@ -15,6 +19,14 @@ module Reek
15
19
  send_expression.full_comment || ''
16
20
  end
17
21
 
22
+ def instance_method?
23
+ true
24
+ end
25
+
26
+ def apply_current_visibility(current_visibility)
27
+ self.visibility = current_visibility
28
+ end
29
+
18
30
  private_attr_reader :send_expression
19
31
  end
20
32
  end
@@ -1,6 +1,5 @@
1
1
  require_relative '../code_comment'
2
2
  require_relative '../ast/object_refs'
3
- require_relative 'visibility_tracker'
4
3
  require_relative 'statement_counter'
5
4
 
6
5
  require 'forwardable'
@@ -20,14 +19,14 @@ module Reek
20
19
  extend Forwardable
21
20
  delegate each_node: :exp
22
21
  delegate %i(name type) => :exp
23
- delegate %i(visibility visibility= non_public_visibility?) => :visibility_tracker
24
22
 
25
- attr_reader :children, :context, :exp, :statement_counter, :visibility_tracker
23
+ attr_reader :children, :parent, :exp, :statement_counter
24
+
26
25
  private_attr_reader :refs
27
26
 
28
27
  # Initializes a new CodeContext.
29
28
  #
30
- # @param context [CodeContext, nil] The parent context
29
+ # @param parent [CodeContext, nil] The parent context
31
30
  # @param exp [Reek::AST::Node] The code described by this context
32
31
  #
33
32
  # For example, given the following code:
@@ -40,7 +39,7 @@ module Reek
40
39
  #
41
40
  # The {ContextBuilder} object first instantiates a {RootContext}, which has no parent.
42
41
  #
43
- # Next, it instantiates a {ModuleContext}, with +context+ being the
42
+ # Next, it instantiates a {ModuleContext}, with +parent+ being the
44
43
  # {RootContext} just created, and +exp+ looking like this:
45
44
  #
46
45
  # (class
@@ -52,18 +51,16 @@ module Reek
52
51
  # (lvar :x))))
53
52
  #
54
53
  # Finally, {ContextBuilder} will instantiate a {MethodContext}. This time,
55
- # +context+ is the {ModuleContext} created above, and +exp+ is:
54
+ # +parent+ is the {ModuleContext} created above, and +exp+ is:
56
55
  #
57
56
  # (def :foo
58
57
  # (args
59
58
  # (arg :x))
60
59
  # (send nil :puts
61
60
  # (lvar :x)))
62
- def initialize(context, exp)
63
- @context = context
61
+ def initialize(_parent, exp)
64
62
  @exp = exp
65
63
  @children = []
66
- @visibility_tracker = VisibilityTracker.new
67
64
  @statement_counter = StatementCounter.new
68
65
  @refs = AST::ObjectRefs.new
69
66
  end
@@ -93,18 +90,17 @@ module Reek
93
90
  end
94
91
  end
95
92
 
96
- alias_method :parent, :context
93
+ def register_with_parent(parent)
94
+ @parent = parent.append_child_context(self) if parent
95
+ end
97
96
 
98
- # Register a child context. The child's parent context should be equal to
99
- # the current context.
100
- #
101
- # This makes the current context responsible for setting the child's
102
- # visibility.
97
+ # Register a context as a child context of this context. This is
98
+ # generally used by a child context to register itself with its parent.
103
99
  #
104
100
  # @param child [CodeContext] the child context to register
105
101
  def append_child_context(child)
106
- visibility_tracker.set_child_visibility(child)
107
102
  children << child
103
+ self
108
104
  end
109
105
 
110
106
  # :reek:TooManyStatements: { max_statements: 6 }
@@ -136,24 +132,30 @@ module Reek
136
132
  end
137
133
 
138
134
  def full_name
139
- exp.full_name(context ? context.full_name : '')
135
+ exp.full_name(parent ? parent.full_name : '')
140
136
  end
141
137
 
142
138
  def config_for(detector_class)
143
- context_config_for(detector_class).merge(
139
+ parent_config_for(detector_class).merge(
144
140
  configuration_via_code_commment[detector_class.smell_type] || {})
145
141
  end
146
142
 
147
- def track_visibility(visibility, names)
148
- visibility_tracker.track_visibility children: children,
149
- visibility: visibility,
150
- names: names
151
- end
152
-
153
143
  def number_of_statements
154
144
  statement_counter.value
155
145
  end
156
146
 
147
+ def singleton_method?
148
+ false
149
+ end
150
+
151
+ def instance_method?
152
+ false
153
+ end
154
+
155
+ def apply_current_visibility(_current_visibility)
156
+ # Nothing to do by default
157
+ end
158
+
157
159
  private
158
160
 
159
161
  def configuration_via_code_commment
@@ -164,8 +166,8 @@ module Reek
164
166
  exp.full_comment || ''
165
167
  end
166
168
 
167
- def context_config_for(detector_class)
168
- context ? context.config_for(detector_class) : {}
169
+ def parent_config_for(detector_class)
170
+ parent ? parent.config_for(detector_class) : {}
169
171
  end
170
172
  end
171
173
  end
@@ -0,0 +1,54 @@
1
+ require_relative 'code_context'
2
+ require_relative 'singleton_method_context'
3
+
4
+ module Reek
5
+ module Context
6
+ # Semi-transparent context to represent a metaclass while building the
7
+ # context tree. This context will not be part of the resulting tree, but
8
+ # will track context and visibility separately while building is in
9
+ # progress.
10
+ class GhostContext < ModuleContext
11
+ attr_reader :children
12
+
13
+ def register_with_parent(parent)
14
+ @parent = parent
15
+ end
16
+
17
+ def append_child_context(child)
18
+ real_parent = parent.append_child_context(child)
19
+ super
20
+ real_parent
21
+ end
22
+
23
+ # Return the correct class for child method contexts (representing nodes
24
+ # of type `:def`). For GhostContext, this is the class that represents
25
+ # singleton methods.
26
+ def method_context_class
27
+ SingletonMethodContext
28
+ end
29
+
30
+ # Return the correct class for child attribute contexts. For
31
+ # GhostContext, this is the class that represents singleton attributes.
32
+ def attribute_context_class
33
+ SingletonAttributeContext
34
+ end
35
+
36
+ def track_visibility(visibility, names)
37
+ visibility_tracker.track_visibility children: children,
38
+ visibility: visibility,
39
+ names: names
40
+ end
41
+
42
+ def track_singleton_visibility(_visibility, _names)
43
+ end
44
+
45
+ def record_use_of_self
46
+ parent.record_use_of_self
47
+ end
48
+
49
+ def statement_counter
50
+ parent.statement_counter
51
+ end
52
+ end
53
+ end
54
+ end
@@ -5,9 +5,16 @@ module Reek
5
5
  #
6
6
  # A context wrapper for any method definition found in a syntax tree.
7
7
  #
8
+ # :reek:Attribute
8
9
  class MethodContext < CodeContext
10
+ attr_accessor :visibility
9
11
  attr_reader :refs
10
12
 
13
+ def initialize(context, exp)
14
+ @visibility = :public
15
+ super
16
+ end
17
+
11
18
  def references_self?
12
19
  exp.depends_on_instance?
13
20
  end
@@ -34,8 +41,28 @@ module Reek
34
41
  exp.parameters.select(&:optional_argument?).map(&:children)
35
42
  end
36
43
 
44
+ def method_context_class
45
+ self.class
46
+ end
47
+
37
48
  def singleton_method?
38
- exp.singleton_method? || visibility == :module_function
49
+ false
50
+ end
51
+
52
+ def instance_method?
53
+ true
54
+ end
55
+
56
+ def apply_current_visibility(current_visibility)
57
+ self.visibility = current_visibility
58
+ end
59
+
60
+ def module_function?
61
+ visibility == :module_function
62
+ end
63
+
64
+ def non_public_visibility?
65
+ visibility != :public
39
66
  end
40
67
  end
41
68
  end
@@ -1,6 +1,7 @@
1
1
  require_relative 'code_context'
2
+ require_relative 'attribute_context'
2
3
  require_relative 'method_context'
3
- require_relative '../ast/sexp_formatter'
4
+ require_relative 'visibility_tracker'
4
5
 
5
6
  module Reek
6
7
  module Context
@@ -9,6 +10,39 @@ module Reek
9
10
  #
10
11
  # :reek:FeatureEnvy
11
12
  class ModuleContext < CodeContext
13
+ attr_reader :visibility_tracker
14
+
15
+ def initialize(context, exp)
16
+ super
17
+
18
+ @visibility_tracker = VisibilityTracker.new
19
+ end
20
+
21
+ # Register a child context. The child's parent context should be equal to
22
+ # the current context.
23
+ #
24
+ # This makes the current context responsible for setting the child's
25
+ # visibility.
26
+ #
27
+ # @param child [CodeContext] the child context to register
28
+ def append_child_context(child)
29
+ visibility_tracker.set_child_visibility(child)
30
+ super
31
+ end
32
+
33
+ # Return the correct class for child method contexts (representing nodes
34
+ # of type `:def`). For ModuleContext, this is the class that represents
35
+ # instance methods.
36
+ def method_context_class
37
+ MethodContext
38
+ end
39
+
40
+ # Return the correct class for child attribute contexts. For
41
+ # ModuleContext, this is the class that represents instance attributes.
42
+ def attribute_context_class
43
+ AttributeContext
44
+ end
45
+
12
46
  def defined_instance_methods(visibility: :public)
13
47
  each.select do |context|
14
48
  context.is_a?(Context::MethodContext) &&
@@ -44,6 +78,26 @@ module Reek
44
78
  contents = exp.children.last
45
79
  contents && contents.find_nodes([:def, :defs], [:casgn, :class, :module]).empty?
46
80
  end
81
+
82
+ def track_visibility(visibility, names)
83
+ visibility_tracker.track_visibility children: instance_method_children,
84
+ visibility: visibility,
85
+ names: names
86
+ end
87
+
88
+ def track_singleton_visibility(visibility, names)
89
+ visibility_tracker.track_singleton_visibility children: singleton_method_children,
90
+ visibility: visibility,
91
+ names: names
92
+ end
93
+
94
+ def instance_method_children
95
+ children.select(&:instance_method?)
96
+ end
97
+
98
+ def singleton_method_children
99
+ children.select(&:singleton_method?)
100
+ end
47
101
  end
48
102
  end
49
103
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'code_context'
2
+ require_relative 'method_context'
2
3
 
3
4
  module Reek
4
5
  module Context
@@ -17,6 +18,13 @@ module Reek
17
18
  def full_name
18
19
  ''
19
20
  end
21
+
22
+ # Return the correct class for child method contexts (representing nodes
23
+ # of type `:def`). For RootContext, this is the class that represents
24
+ # instance methods.
25
+ def method_context_class
26
+ MethodContext
27
+ end
20
28
  end
21
29
  end
22
30
  end
@@ -0,0 +1,15 @@
1
+ require_relative 'attribute_context'
2
+
3
+ module Reek
4
+ module Context
5
+ #
6
+ # A context wrapper for any singleton attribute definition found in a
7
+ # syntax tree.
8
+ #
9
+ class SingletonAttributeContext < AttributeContext
10
+ def instance_method?
11
+ false
12
+ end
13
+ end
14
+ end
15
+ end