reek 3.8.3 → 3.9.0

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