reek 4.7.2 → 4.7.3

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 (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