reek 4.7.2 → 4.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +18 -4
  3. data/.travis.yml +0 -5
  4. data/CHANGELOG.md +8 -0
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +4 -4
  7. data/docs/How-To-Write-New-Detectors.md +6 -5
  8. data/docs/Unused-Private-Method.md +1 -1
  9. data/features/configuration_via_source_comments/erroneous_source_comments.feature +1 -1
  10. data/features/locales.feature +32 -0
  11. data/features/rake_task/rake_task.feature +46 -6
  12. data/features/rspec_matcher.feature +32 -0
  13. data/features/step_definitions/reek_steps.rb +0 -4
  14. data/features/support/env.rb +0 -9
  15. data/lib/reek/ast/builder.rb +1 -1
  16. data/lib/reek/ast/sexp_extensions/send.rb +0 -4
  17. data/lib/reek/cli/options.rb +2 -2
  18. data/lib/reek/configuration/app_configuration.rb +3 -2
  19. data/lib/reek/configuration/configuration_file_finder.rb +3 -3
  20. data/lib/reek/context/ghost_context.rb +0 -2
  21. data/lib/reek/context/module_context.rb +0 -3
  22. data/lib/reek/context_builder.rb +2 -4
  23. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +2 -2
  24. data/lib/reek/errors/bad_detector_in_comment_error.rb +2 -2
  25. data/lib/reek/errors/encoding_error.rb +38 -0
  26. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +3 -3
  27. data/lib/reek/errors/incomprehensible_source_error.rb +4 -4
  28. data/lib/reek/examiner.rb +15 -11
  29. data/lib/reek/rake/task.rb +5 -1
  30. data/lib/reek/smell_detectors/attribute.rb +6 -11
  31. data/lib/reek/smell_detectors/base_detector.rb +9 -1
  32. data/lib/reek/smell_detectors/boolean_parameter.rb +4 -5
  33. data/lib/reek/smell_detectors/class_variable.rb +5 -6
  34. data/lib/reek/smell_detectors/control_parameter.rb +3 -3
  35. data/lib/reek/smell_detectors/data_clump.rb +13 -6
  36. data/lib/reek/smell_detectors/duplicate_method_call.rb +18 -11
  37. data/lib/reek/smell_detectors/feature_envy.rb +9 -7
  38. data/lib/reek/smell_detectors/instance_variable_assumption.rb +14 -14
  39. data/lib/reek/smell_detectors/irresponsible_module.rb +6 -12
  40. data/lib/reek/smell_detectors/long_parameter_list.rb +10 -6
  41. data/lib/reek/smell_detectors/long_yield_list.rb +9 -5
  42. data/lib/reek/smell_detectors/manual_dispatch.rb +3 -4
  43. data/lib/reek/smell_detectors/module_initialize.rb +4 -5
  44. data/lib/reek/smell_detectors/nested_iterators.rb +11 -19
  45. data/lib/reek/smell_detectors/nil_check.rb +9 -15
  46. data/lib/reek/smell_detectors/prima_donna_method.rb +17 -16
  47. data/lib/reek/smell_detectors/repeated_conditional.rb +11 -8
  48. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +8 -8
  49. data/lib/reek/smell_detectors/too_many_constants.rb +10 -8
  50. data/lib/reek/smell_detectors/too_many_instance_variables.rb +10 -5
  51. data/lib/reek/smell_detectors/too_many_methods.rb +11 -6
  52. data/lib/reek/smell_detectors/too_many_statements.rb +10 -5
  53. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +8 -8
  54. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +12 -15
  55. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +10 -13
  56. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +23 -23
  57. data/lib/reek/smell_detectors/unused_parameters.rb +5 -6
  58. data/lib/reek/smell_detectors/unused_private_method.rb +11 -18
  59. data/lib/reek/smell_detectors/utility_function.rb +12 -15
  60. data/lib/reek/source/source_code.rb +27 -6
  61. data/lib/reek/source/source_locator.rb +1 -1
  62. data/lib/reek/spec.rb +1 -1
  63. data/lib/reek/version.rb +1 -1
  64. data/reek.gemspec +0 -2
  65. data/spec/reek/ast/sexp_extensions_spec.rb +12 -32
  66. data/spec/reek/cli/application_spec.rb +4 -6
  67. data/spec/reek/configuration/configuration_file_finder_spec.rb +0 -2
  68. data/spec/reek/examiner_spec.rb +38 -1
  69. data/spec/reek/rake/task_spec.rb +25 -2
  70. data/spec/reek/smell_detectors/base_detector_spec.rb +4 -5
  71. data/spec/reek/smell_detectors/prima_donna_method_spec.rb +3 -3
  72. data/spec/reek/source/source_code_spec.rb +28 -1
  73. data/spec/reek/spec/should_reek_of_spec.rb +18 -18
  74. data/spec/reek/spec/should_reek_spec.rb +5 -5
  75. data/spec/reek/spec/smell_matcher_spec.rb +20 -20
  76. data/spec/spec_helper.rb +1 -1
  77. metadata +6 -3
@@ -41,8 +41,6 @@ module Reek
41
41
  names: names
42
42
  end
43
43
 
44
- def track_singleton_visibility(_visibility, _names); end
45
-
46
44
  def record_use_of_self
47
45
  parent.record_use_of_self
48
46
  end
@@ -84,9 +84,6 @@ module Reek
84
84
  visibility_tracker.track_visibility children: instance_method_children,
85
85
  visibility: visibility,
86
86
  names: names
87
- end
88
-
89
- def track_singleton_visibility(visibility, names)
90
87
  visibility_tracker.track_singleton_visibility children: singleton_method_children,
91
88
  visibility: visibility,
92
89
  names: names
@@ -509,10 +509,8 @@ module Reek
509
509
  end
510
510
 
511
511
  def handle_send_for_modules(exp)
512
- method_name = exp.name
513
- arg_names = exp.arg_names
514
- current_context.track_visibility(method_name, arg_names)
515
- current_context.track_singleton_visibility(method_name, arg_names)
512
+ arg_names = exp.args.map { |arg| arg.children.first }
513
+ current_context.track_visibility(exp.name, arg_names)
516
514
  register_attributes(exp)
517
515
  end
518
516
 
@@ -7,7 +7,7 @@ module Reek
7
7
  # Gets raised when trying to configure a detector with an option
8
8
  # which is unknown to it.
9
9
  class BadDetectorConfigurationKeyInCommentError < BaseError
10
- UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
10
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE.freeze
11
11
 
12
12
  Error: You are trying to configure the smell detector '%s'
13
13
  in one of your source code comments with the unknown option %s.
@@ -22,7 +22,7 @@ module Reek
22
22
  * what custom options are available by checking the detector specific documentation in /docs
23
23
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
24
24
 
25
- EOS
25
+ MESSAGE
26
26
 
27
27
  def initialize(detector_name:, offensive_keys:, source:, line:, original_comment:)
28
28
  message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
@@ -8,7 +8,7 @@ module Reek
8
8
  # This might happen for multiple reasons. The users might have a typo in
9
9
  # his comment or he might use a detector that does not exist anymore.
10
10
  class BadDetectorInCommentError < BaseError
11
- UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
11
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE.freeze
12
12
 
13
13
  Error: You are trying to configure an unknown smell detector '%s' in one
14
14
  of your source code comments.
@@ -22,7 +22,7 @@ module Reek
22
22
  * what smell detectors are available: https://github.com/troessner/reek/blob/master/docs/Code-Smells.md
23
23
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
24
24
 
25
- EOS
25
+ MESSAGE
26
26
 
27
27
  def initialize(detector_name:, source:, line:, original_comment:)
28
28
  message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_error'
4
+
5
+ module Reek
6
+ module Errors
7
+ # Gets raised when Reek is unable to process the source due to an EncodingError
8
+ class EncodingError < BaseError
9
+ ENCODING_ERROR_TEMPLATE = <<-MESSAGE.freeze
10
+ !!!
11
+ Source '%s' cannot be processed by Reek due to an encoding error in the source file.
12
+
13
+ This is a problem that is outside of Reek's scope and should be fixed by you, the
14
+ user, in order for Reek being able to continue.
15
+ Check out this article for an idea on how to get started:
16
+ https://www.justinweiss.com/articles/3-steps-to-fix-encoding-problems-in-ruby/
17
+
18
+ Exception message:
19
+
20
+ %s
21
+
22
+ Original exception:
23
+
24
+ %s
25
+
26
+ !!!
27
+ MESSAGE
28
+
29
+ def initialize(origin:, original_exception:)
30
+ message = format(ENCODING_ERROR_TEMPLATE,
31
+ origin,
32
+ original_exception.message,
33
+ original_exception.backtrace.join("\n\t"))
34
+ super message
35
+ end
36
+ end
37
+ end
38
+ end
@@ -7,10 +7,10 @@ module Reek
7
7
  # Gets raised when trying to use a configuration for a detector
8
8
  # that can't be parsed into a hash.
9
9
  class GarbageDetectorConfigurationInCommentError < BaseError
10
- BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-EOS.freeze
10
+ BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-MESSAGE.freeze
11
11
 
12
12
  Error: You are trying to configure the smell detector '%s'.
13
- Unfortunately we can not parse the configuration you have given.
13
+ Unfortunately we cannot parse the configuration you have given.
14
14
  The source is '%s' and the comment belongs to the expression starting in line %d.
15
15
  Here's the original comment:
16
16
 
@@ -21,7 +21,7 @@ module Reek
21
21
  * what smell detectors are available: https://github.com/troessner/reek/blob/master/docs/Code-Smells.md
22
22
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
23
23
 
24
- EOS
24
+ MESSAGE
25
25
 
26
26
  def initialize(detector_name:, source:, line:, original_comment:)
27
27
  message = format(BAD_DETECTOR_CONFIGURATION_MESSAGE,
@@ -6,11 +6,11 @@ module Reek
6
6
  module Errors
7
7
  # Gets raised when Reek is unable to process the source
8
8
  class IncomprehensibleSourceError < BaseError
9
- INCOMPREHENSIBLE_SOURCE_TEMPLATE = <<-EOS.freeze
9
+ INCOMPREHENSIBLE_SOURCE_TEMPLATE = <<-MESSAGE.freeze
10
10
  !!!
11
- Source %s can not be processed by Reek.
11
+ Source %s cannot be processed by Reek.
12
12
 
13
- This is most likely either a bug in your Reek configuration (config file or
13
+ This is most likely either a problem in your Reek configuration (config file or
14
14
  source code comments) or a Reek bug.
15
15
 
16
16
  Please double check your Reek configuration taking the original exception
@@ -34,7 +34,7 @@ module Reek
34
34
  %s
35
35
 
36
36
  !!!
37
- EOS
37
+ MESSAGE
38
38
 
39
39
  def initialize(origin:, original_exception:)
40
40
  message = format(INCOMPREHENSIBLE_SOURCE_TEMPLATE,
@@ -95,18 +95,26 @@ module Reek
95
95
  #
96
96
  # @return [Array<SmellWarning>] the smells found in the source
97
97
  #
98
- # :reek:TooManyStatements { max_statements: 8 }
98
+ # :reek:TooManyStatements { max_statements: 6 }
99
99
  def run
100
100
  if source.valid_syntax? && syntax_tree
101
- begin
102
- examine_tree
103
- rescue Errors::BaseError => exception
104
- raise unless @error_handler.handle exception
105
- []
106
- end
101
+ examine_tree
107
102
  else
108
103
  SmellDetectors::Syntax.smells_from_source(source)
109
104
  end
105
+ rescue StandardError => exception
106
+ wrapper = wrap_exception exception
107
+ raise wrapper unless @error_handler.handle wrapper
108
+ []
109
+ end
110
+
111
+ def wrap_exception(exception)
112
+ case exception
113
+ when Errors::BaseError
114
+ exception
115
+ else
116
+ Errors::IncomprehensibleSourceError.new(origin: origin, original_exception: exception)
117
+ end
110
118
  end
111
119
 
112
120
  def syntax_tree
@@ -117,10 +125,6 @@ module Reek
117
125
  ContextBuilder.new(syntax_tree).context_tree.flat_map do |element|
118
126
  detector_repository.examine(element)
119
127
  end
120
- rescue Errors::BaseError
121
- raise
122
- rescue StandardError => exception
123
- raise Errors::IncomprehensibleSourceError, origin: origin, original_exception: exception
124
128
  end
125
129
  end
126
130
  end
@@ -73,10 +73,14 @@ module Reek
73
73
  @name = name
74
74
  @reek_opts = ENV['REEK_OPTS'] || ''
75
75
  @fail_on_error = true
76
- @source_files = FileList[ENV['REEK_SRC'] || 'lib/**/*.rb']
77
76
  @verbose = false
78
77
 
79
78
  yield self if block_given?
79
+
80
+ if (reek_src = ENV['REEK_SRC'])
81
+ @source_files = FileList[reek_src]
82
+ end
83
+ @source_files ||= FileList['lib/**/*.rb']
80
84
  define_task
81
85
  end
82
86
 
@@ -16,10 +16,6 @@ module Reek
16
16
  #
17
17
  # TODO: Catch attributes declared "by hand"
18
18
  class Attribute < BaseDetector
19
- def initialize(*args)
20
- super
21
- end
22
-
23
19
  def self.contexts # :nodoc:
24
20
  [:sym]
25
21
  end
@@ -29,10 +25,10 @@ module Reek
29
25
  #
30
26
  # @return [Array<SmellWarning>]
31
27
  #
32
- def sniff(ctx)
33
- attributes_in(ctx).map do |_attribute, line|
28
+ def sniff
29
+ attributes_in_context.map do |_attribute, line|
34
30
  smell_warning(
35
- context: ctx,
31
+ context: context,
36
32
  lines: [line],
37
33
  message: 'is a writable attribute')
38
34
  end
@@ -40,10 +36,9 @@ module Reek
40
36
 
41
37
  private
42
38
 
43
- # :reek:UtilityFunction
44
- def attributes_in(module_ctx)
45
- if module_ctx.visibility == :public
46
- call_node = module_ctx.exp
39
+ def attributes_in_context
40
+ if context.visibility == :public
41
+ call_node = expression
47
42
  [[call_node.name, call_node.line]]
48
43
  else
49
44
  []
@@ -41,7 +41,7 @@ module Reek
41
41
  return [] unless enabled?
42
42
  return [] if exception?
43
43
 
44
- sniff(context)
44
+ sniff
45
45
  end
46
46
 
47
47
  def self.todo_configuration_for(smells)
@@ -54,6 +54,14 @@ module Reek
54
54
 
55
55
  attr_reader :context
56
56
 
57
+ def expression
58
+ @expression ||= context.exp
59
+ end
60
+
61
+ def source_line
62
+ @line ||= expression.line
63
+ end
64
+
57
65
  def exception?
58
66
  context.matches?(value(EXCLUDE_KEY, context))
59
67
  end
@@ -19,14 +19,13 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- # :reek:FeatureEnvy
23
- def sniff(ctx)
24
- ctx.default_assignments.select do |_parameter, value|
22
+ def sniff
23
+ context.default_assignments.select do |_parameter, value|
25
24
  [:true, :false].include?(value.type)
26
25
  end.map do |parameter, _value|
27
26
  smell_warning(
28
- context: ctx,
29
- lines: [ctx.exp.line],
27
+ context: context,
28
+ lines: [source_line],
30
29
  message: "has boolean parameter '#{parameter}'",
31
30
  parameters: { parameter: parameter.to_s })
32
31
  end
@@ -24,10 +24,10 @@ module Reek
24
24
  #
25
25
  # @return [Array<SmellWarning>]
26
26
  #
27
- def sniff(ctx)
28
- class_variables_in(ctx.exp).map do |variable, lines|
27
+ def sniff
28
+ class_variables_in_context.map do |variable, lines|
29
29
  smell_warning(
30
- context: ctx,
30
+ context: context,
31
31
  lines: lines,
32
32
  message: "declares the class variable '#{variable}'",
33
33
  parameters: { name: variable.to_s })
@@ -39,14 +39,13 @@ module Reek
39
39
  # in the given module.
40
40
  #
41
41
  # :reek:TooManyStatements: { max_statements: 7 }
42
- # :reek:FeatureEnvy
43
- def class_variables_in(exp)
42
+ def class_variables_in_context
44
43
  result = Hash.new { |hash, key| hash[key] = [] }
45
44
  collector = proc do |cvar_node|
46
45
  result[cvar_node.name].push(cvar_node.line)
47
46
  end
48
47
  [:cvar, :cvasgn, :cvdecl].each do |stmt_type|
49
- exp.each_node(stmt_type, [:class, :module], &collector)
48
+ expression.each_node(stmt_type, [:class, :module], &collector)
50
49
  end
51
50
  result
52
51
  end
@@ -50,11 +50,11 @@ module Reek
50
50
  # @return [Array<SmellWarning>]
51
51
  #
52
52
  # :reek:FeatureEnvy
53
- def sniff(ctx)
54
- ControlParameterCollector.new(ctx).control_parameters.map do |control_parameter|
53
+ def sniff
54
+ ControlParameterCollector.new(context).control_parameters.map do |control_parameter|
55
55
  argument = control_parameter.name.to_s
56
56
  smell_warning(
57
- context: ctx,
57
+ context: context,
58
58
  lines: control_parameter.lines,
59
59
  message: "is controlled by argument '#{argument}'",
60
60
  parameters: { argument: argument })
@@ -50,14 +50,11 @@ module Reek
50
50
  #
51
51
  # @return [Array<SmellWarning>]
52
52
  #
53
- # :reek:FeatureEnvy
54
- def sniff(ctx)
55
- max_copies = value(MAX_COPIES_KEY, ctx)
56
- min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx)
57
- MethodGroup.new(ctx, min_clump_size, max_copies).clumps.map do |clump, methods|
53
+ def sniff
54
+ MethodGroup.new(context, min_clump_size, max_copies).clumps.map do |clump, methods|
58
55
  methods_length = methods.length
59
56
  smell_warning(
60
- context: ctx,
57
+ context: context,
61
58
  lines: methods.map(&:line),
62
59
  message: "takes parameters #{DataClump.print_clump(clump)} " \
63
60
  "to #{methods_length} methods",
@@ -72,6 +69,16 @@ module Reek
72
69
  def self.print_clump(clump)
73
70
  "[#{clump.map { |parameter| "'#{parameter}'" }.join(', ')}]"
74
71
  end
72
+
73
+ private
74
+
75
+ def max_copies
76
+ value(MAX_COPIES_KEY, context)
77
+ end
78
+
79
+ def min_clump_size
80
+ value(MIN_CLUMP_SIZE_KEY, context)
81
+ end
75
82
  end
76
83
  end
77
84
 
@@ -37,26 +37,33 @@ module Reek
37
37
  end
38
38
 
39
39
  #
40
- # Looks for duplicate calls within the body of the method +ctx+.
40
+ # Looks for duplicate calls within the body of the method context.
41
41
  #
42
42
  # @return [Array<SmellWarning>]
43
43
  #
44
- # :reek:FeatureEnvy
45
- # :reek:DuplicateMethodCall: { max_calls: 2 }
46
- def sniff(ctx)
47
- max_allowed_calls = value(MAX_ALLOWED_CALLS_KEY, ctx)
48
- allow_calls = value(ALLOW_CALLS_KEY, ctx)
49
-
50
- collector = CallCollector.new(ctx, max_allowed_calls, allow_calls)
44
+ def sniff
45
+ collector = CallCollector.new(context, max_allowed_calls, allow_calls)
51
46
  collector.smelly_calls.map do |found_call|
47
+ call = found_call.call
48
+ occurs = found_call.occurs
52
49
  smell_warning(
53
- context: ctx,
50
+ context: context,
54
51
  lines: found_call.lines,
55
- message: "calls '#{found_call.call}' #{found_call.occurs} times",
56
- parameters: { name: found_call.call, count: found_call.occurs })
52
+ message: "calls '#{call}' #{occurs} times",
53
+ parameters: { name: call, count: occurs })
57
54
  end
58
55
  end
59
56
 
57
+ private
58
+
59
+ def max_allowed_calls
60
+ value(MAX_ALLOWED_CALLS_KEY, context)
61
+ end
62
+
63
+ def allow_calls
64
+ value(ALLOW_CALLS_KEY, context)
65
+ end
66
+
60
67
  # Collects information about a single found call
61
68
  class FoundCall
62
69
  def initialize(call_node)
@@ -42,11 +42,11 @@ module Reek
42
42
  #
43
43
  # @return [Array<SmellWarning>]
44
44
  #
45
- def sniff(ctx)
46
- return [] unless ctx.references_self?
47
- envious_receivers(ctx).map do |name, lines|
45
+ def sniff
46
+ return [] unless context.references_self?
47
+ envious_receivers.map do |name, lines|
48
48
  smell_warning(
49
- context: ctx,
49
+ context: context,
50
50
  lines: lines,
51
51
  message: "refers to '#{name}' more than self (maybe move it to another class?)",
52
52
  parameters: { name: name.to_s })
@@ -55,9 +55,11 @@ module Reek
55
55
 
56
56
  private
57
57
 
58
- # :reek:UtilityFunction
59
- def envious_receivers(ctx)
60
- refs = ctx.refs
58
+ def refs
59
+ @refs ||= context.refs
60
+ end
61
+
62
+ def envious_receivers
61
63
  return {} if refs.self_is_max?
62
64
  refs.most_popular
63
65
  end