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
@@ -34,22 +34,24 @@ module Reek
34
34
  #
35
35
  # @return [Array<SmellWarning>]
36
36
  #
37
- def sniff(ctx)
38
- max_allowed_constants = value(MAX_ALLOWED_CONSTANTS_KEY, ctx)
39
-
40
- count = ctx.each_node(:casgn, IGNORED_NODES).delete_if(&:defines_module?).length
37
+ def sniff
38
+ count = context.each_node(:casgn, IGNORED_NODES).delete_if(&:defines_module?).length
41
39
 
42
40
  return [] if count <= max_allowed_constants
43
41
 
44
- build_smell_warning(ctx, count)
42
+ build_smell_warning(count)
45
43
  end
46
44
 
47
45
  private
48
46
 
49
- def build_smell_warning(ctx, count)
47
+ def max_allowed_constants
48
+ value(MAX_ALLOWED_CONSTANTS_KEY, context)
49
+ end
50
+
51
+ def build_smell_warning(count)
50
52
  [smell_warning(
51
- context: ctx,
52
- lines: [ctx.exp.line],
53
+ context: context,
54
+ lines: [source_line],
53
55
  message: "has #{count} constants",
54
56
  parameters: { count: count })]
55
57
  end
@@ -33,17 +33,22 @@ module Reek
33
33
  #
34
34
  # @return [Array<SmellWarning>]
35
35
  #
36
- def sniff(ctx)
37
- max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx)
38
- variables = ctx.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
36
+ def sniff
37
+ variables = context.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
39
38
  count = variables.uniq.size
40
39
  return [] if count <= max_allowed_ivars
41
40
  [smell_warning(
42
- context: ctx,
43
- lines: [ctx.exp.line],
41
+ context: context,
42
+ lines: [source_line],
44
43
  message: "has at least #{count} instance variables",
45
44
  parameters: { count: count })]
46
45
  end
46
+
47
+ private
48
+
49
+ def max_allowed_ivars
50
+ value(MAX_ALLOWED_IVARS_KEY, context)
51
+ end
47
52
  end
48
53
  end
49
54
  end
@@ -31,21 +31,26 @@ module Reek
31
31
  end
32
32
 
33
33
  #
34
- # Checks +ctx+ for too many methods
34
+ # Checks context for too many methods
35
35
  #
36
36
  # @return [Array<SmellWarning>]
37
37
  #
38
- def sniff(ctx)
39
- max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx)
38
+ def sniff
40
39
  # TODO: Only checks instance methods!
41
- actual = ctx.node_instance_methods.length
40
+ actual = context.node_instance_methods.length
42
41
  return [] if actual <= max_allowed_methods
43
42
  [smell_warning(
44
- context: ctx,
45
- lines: [ctx.exp.line],
43
+ context: context,
44
+ lines: [source_line],
46
45
  message: "has at least #{actual} methods",
47
46
  parameters: { count: actual })]
48
47
  end
48
+
49
+ private
50
+
51
+ def max_allowed_methods
52
+ value(MAX_ALLOWED_METHODS_KEY, context)
53
+ end
49
54
  end
50
55
  end
51
56
  end
@@ -27,16 +27,21 @@ module Reek
27
27
  #
28
28
  # @return [Array<SmellWarning>]
29
29
  #
30
- def sniff(ctx)
31
- max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY, ctx)
32
- count = ctx.number_of_statements
30
+ def sniff
31
+ count = context.number_of_statements
33
32
  return [] if count <= max_allowed_statements
34
33
  [smell_warning(
35
- context: ctx,
36
- lines: [ctx.exp.line],
34
+ context: context,
35
+ lines: [source_line],
37
36
  message: "has approx #{count} statements",
38
37
  parameters: { count: count })]
39
38
  end
39
+
40
+ private
41
+
42
+ def max_allowed_statements
43
+ value(MAX_ALLOWED_STATEMENTS_KEY, context)
44
+ end
40
45
  end
41
46
  end
42
47
  end
@@ -36,29 +36,29 @@ module Reek
36
36
  #
37
37
  # @return [Array<SmellWarning>]
38
38
  #
39
- def sniff(context)
39
+ def sniff
40
40
  name = context.name.to_s
41
- return [] if acceptable_name?(name: name, context: context)
41
+ return [] if acceptable_name?(name)
42
42
 
43
43
  [smell_warning(
44
44
  context: context,
45
- lines: [context.exp.line],
45
+ lines: [source_line],
46
46
  message: "has the name '#{name}'",
47
47
  parameters: { name: name })]
48
48
  end
49
49
 
50
50
  private
51
51
 
52
- def acceptable_name?(name:, context:)
53
- accept_patterns(context).any? { |accept_pattern| name.match accept_pattern } ||
54
- reject_patterns(context).none? { |reject_pattern| name.match reject_pattern }
52
+ def acceptable_name?(name)
53
+ accept_patterns.any? { |accept_pattern| name.match accept_pattern } ||
54
+ reject_patterns.none? { |reject_pattern| name.match reject_pattern }
55
55
  end
56
56
 
57
- def reject_patterns(context)
57
+ def reject_patterns
58
58
  Array value(REJECT_KEY, context)
59
59
  end
60
60
 
61
- def accept_patterns(context)
61
+ def accept_patterns
62
62
  Array value(ACCEPT_KEY, context)
63
63
  end
64
64
  end
@@ -41,40 +41,37 @@ module Reek
41
41
  end
42
42
 
43
43
  #
44
- # Checks the given +context+ for uncommunicative names.
44
+ # Checks the detector's context for uncommunicative names.
45
45
  #
46
46
  # @return [Array<SmellWarning>]
47
47
  #
48
- def sniff(context)
48
+ def sniff
49
49
  fully_qualified_name = context.full_name
50
- exp = context.exp
51
- module_name = exp.simple_name
50
+ module_name = expression.simple_name
52
51
 
53
- return [] if acceptable_name?(context: context,
54
- module_name: module_name,
52
+ return [] if acceptable_name?(module_name: module_name,
55
53
  fully_qualified_name: fully_qualified_name)
56
54
 
57
55
  [smell_warning(
58
56
  context: context,
59
- lines: [exp.line],
57
+ lines: [source_line],
60
58
  message: "has the name '#{module_name}'",
61
59
  parameters: { name: module_name })]
62
60
  end
63
61
 
64
62
  private
65
63
 
66
- # :reek:ControlParameter
67
- def acceptable_name?(context:, module_name:, fully_qualified_name:)
68
- accept_patterns(context).any? { |accept_pattern| fully_qualified_name.match accept_pattern } ||
69
- reject_patterns(context).none? { |reject_pattern| module_name.match reject_pattern }
64
+ def acceptable_name?(module_name:, fully_qualified_name:)
65
+ accept_patterns.any? { |accept_pattern| fully_qualified_name.match accept_pattern } ||
66
+ reject_patterns.none? { |reject_pattern| module_name.match reject_pattern }
70
67
  end
71
68
 
72
- def reject_patterns(context)
73
- Array value(REJECT_KEY, context)
69
+ def reject_patterns
70
+ @reject_patterns ||= Array value(REJECT_KEY, context)
74
71
  end
75
72
 
76
- def accept_patterns(context)
77
- Array value(ACCEPT_KEY, context)
73
+ def accept_patterns
74
+ @accept_patterns ||= Array value(ACCEPT_KEY, context)
78
75
  end
79
76
  end
80
77
  end
@@ -38,17 +38,15 @@ module Reek
38
38
  #
39
39
  # @return [Array<SmellWarning>]
40
40
  #
41
- def sniff(context)
42
- expression = context.exp
43
-
41
+ def sniff
44
42
  params = expression.parameters.select do |param|
45
- uncommunicative_parameter?(parameter: param, context: context)
43
+ uncommunicative_parameter?(param)
46
44
  end
47
45
 
48
46
  params.map(&:name).map do |name|
49
47
  smell_warning(
50
48
  context: context,
51
- lines: [expression.line],
49
+ lines: [source_line],
52
50
  message: "has the parameter name '#{name}'",
53
51
  parameters: { name: name.to_s })
54
52
  end
@@ -56,22 +54,21 @@ module Reek
56
54
 
57
55
  private
58
56
 
59
- def uncommunicative_parameter?(parameter:, context:)
60
- !acceptable_name?(parameter: parameter, context: context) &&
57
+ def uncommunicative_parameter?(parameter)
58
+ !acceptable_name?(parameter.plain_name) &&
61
59
  (context.uses_param?(parameter) || !parameter.marked_unused?)
62
60
  end
63
61
 
64
- def acceptable_name?(parameter:, context:)
65
- name = parameter.plain_name
66
- accept_patterns(context).any? { |accept_pattern| name.match accept_pattern } ||
67
- reject_patterns(context).none? { |reject_pattern| name.match reject_pattern }
62
+ def acceptable_name?(name)
63
+ accept_patterns.any? { |accept_pattern| name.match accept_pattern } ||
64
+ reject_patterns.none? { |reject_pattern| name.match reject_pattern }
68
65
  end
69
66
 
70
- def reject_patterns(context)
67
+ def reject_patterns
71
68
  Array value(REJECT_KEY, context)
72
69
  end
73
70
 
74
- def accept_patterns(context)
71
+ def accept_patterns
75
72
  Array value(ACCEPT_KEY, context)
76
73
  end
77
74
  end
@@ -21,7 +21,6 @@ module Reek
21
21
  #
22
22
  # See {file:docs/Uncommunicative-Variable-Name.md} for details.
23
23
  #
24
- # :reek:DataClump: { max_copies: 4 }
25
24
  class UncommunicativeVariableName < BaseDetector
26
25
  # The name of the config field that lists the regexps of
27
26
  # smelly names to be reported.
@@ -53,14 +52,12 @@ module Reek
53
52
  #
54
53
  # @return [Array<SmellWarning>]
55
54
  #
56
- def sniff(ctx)
57
- self.reject_names = value(REJECT_KEY, ctx)
58
- self.accept_names = value(ACCEPT_KEY, ctx)
59
- variable_names(ctx.exp).select do |name, _lines|
55
+ def sniff
56
+ variable_names.select do |name, _lines|
60
57
  uncommunicative_variable_name?(name)
61
58
  end.map do |name, lines|
62
59
  smell_warning(
63
- context: ctx,
60
+ context: context,
64
61
  lines: lines,
65
62
  message: "has the variable name '#{name}'",
66
63
  parameters: { name: name.to_s })
@@ -69,6 +66,14 @@ module Reek
69
66
 
70
67
  private
71
68
 
69
+ def reject_names
70
+ @reject_names ||= value(REJECT_KEY, context)
71
+ end
72
+
73
+ def accept_names
74
+ @accept_names ||= value(ACCEPT_KEY, context)
75
+ end
76
+
72
77
  def uncommunicative_variable_name?(name)
73
78
  sanitized_name = name.to_s.gsub(/^[@\*\&]*/, '')
74
79
  !acceptable_name?(sanitized_name)
@@ -79,34 +84,31 @@ module Reek
79
84
  Array(reject_names).none? { |reject_pattern| name.match reject_pattern }
80
85
  end
81
86
 
82
- # :reek:TooManyStatements: { max_statements: 6 }
83
- def variable_names(exp)
87
+ def variable_names
84
88
  result = Hash.new { |hash, key| hash[key] = [] }
85
- find_assignment_variable_names(exp, result)
86
- find_block_argument_variable_names(exp, result)
87
- result.to_a.sort_by { |name, _| name.to_s }
89
+ find_assignment_variable_names(result)
90
+ find_block_argument_variable_names(result)
91
+ result
88
92
  end
89
93
 
90
- # :reek:UtilityFunction
91
- def find_assignment_variable_names(exp, accumulator)
92
- assignment_nodes = exp.each_node(:lvasgn, [:class, :module, :defs, :def])
94
+ def find_assignment_variable_names(accumulator)
95
+ assignment_nodes = expression.each_node(:lvasgn, [:class, :module, :defs, :def])
93
96
 
94
- case exp.type
97
+ case expression.type
95
98
  when :class, :module
96
- assignment_nodes += exp.each_node(:ivasgn, [:class, :module])
99
+ assignment_nodes += expression.each_node(:ivasgn, [:class, :module])
97
100
  end
98
101
 
99
102
  assignment_nodes.each { |asgn| accumulator[asgn.children.first].push(asgn.line) }
100
103
  end
101
104
 
102
- # :reek:FeatureEnvy
103
105
  # :reek:TooManyStatements: { max_statements: 6 }
104
- def find_block_argument_variable_names(exp, accumulator)
105
- arg_search_exp = case exp.type
106
+ def find_block_argument_variable_names(accumulator)
107
+ arg_search_exp = case expression.type
106
108
  when :class, :module
107
- exp
109
+ expression
108
110
  when :defs, :def
109
- exp.body
111
+ expression.body
110
112
  end
111
113
 
112
114
  return unless arg_search_exp
@@ -135,8 +137,6 @@ module Reek
135
137
  var = varname.to_sym
136
138
  accumulator[var].push(exp.line)
137
139
  end
138
-
139
- attr_accessor :accept_names, :reject_names
140
140
  end
141
141
  end
142
142
  end
@@ -14,14 +14,13 @@ module Reek
14
14
  #
15
15
  # @return [Array<SmellWarning>]
16
16
  #
17
- # :reek:FeatureEnvy
18
- def sniff(ctx)
19
- return [] if ctx.uses_super_with_implicit_arguments?
20
- ctx.unused_params.map do |param|
17
+ def sniff
18
+ return [] if context.uses_super_with_implicit_arguments?
19
+ context.unused_params.map do |param|
21
20
  name = param.name.to_s
22
21
  smell_warning(
23
- context: ctx,
24
- lines: [ctx.exp.line],
22
+ context: context,
23
+ lines: [source_line],
25
24
  message: "has unused parameter '#{name}'",
26
25
  parameters: { name: name })
27
26
  end
@@ -32,15 +32,13 @@ module Reek
32
32
  end
33
33
 
34
34
  #
35
- # @param ctx [Context::ClassContext]
36
35
  # @return [Array<SmellWarning>]
37
36
  #
38
- # :reek:FeatureEnvy
39
- def sniff(ctx)
40
- hits(ctx).map do |hit|
37
+ def sniff
38
+ hits.map do |hit|
41
39
  name = hit.name
42
40
  smell_warning(
43
- context: ctx,
41
+ context: context,
44
42
  lines: [hit.line],
45
43
  message: "has the unused private instance method '#{name}'",
46
44
  parameters: { name: name.to_s })
@@ -50,23 +48,20 @@ module Reek
50
48
  private
51
49
 
52
50
  #
53
- # @param ctx [Context::ClassContext]
54
51
  # @return [Array<Hit>]
55
52
  #
56
- def hits(ctx)
57
- unused_private_methods(ctx).map do |defined_method|
58
- Hit.new(defined_method) unless ignore_method?(ctx, defined_method)
53
+ def hits
54
+ unused_private_methods.map do |defined_method|
55
+ Hit.new(defined_method) unless ignore_method?(defined_method)
59
56
  end.compact
60
57
  end
61
58
 
62
59
  #
63
- # @param ctx [Context::ClassContext]
64
60
  # @return [Array<Context::MethodContext]
65
61
  #
66
- # :reek:UtilityFunction
67
- def unused_private_methods(ctx)
68
- defined_private_methods = ctx.defined_instance_methods(visibility: :private)
69
- called_method_names = ctx.instance_method_calls.map(&:name)
62
+ def unused_private_methods
63
+ defined_private_methods = context.defined_instance_methods(visibility: :private)
64
+ called_method_names = context.instance_method_calls.map(&:name)
70
65
 
71
66
  defined_private_methods.reject do |defined_method|
72
67
  called_method_names.include?(defined_method.name)
@@ -74,14 +69,12 @@ module Reek
74
69
  end
75
70
 
76
71
  #
77
- # @param ctx [Context::ClassContext]
78
72
  # @param method [Context::MethodContext]
79
73
  # @return [Boolean]
80
74
  #
81
- # :reek:FeatureEnvy
82
- def ignore_method?(ctx, method)
75
+ def ignore_method?(method)
83
76
  # ignore_contexts will be e.g. ["Foo::Smelly#my_method", "..."]
84
- ignore_contexts = value(EXCLUDE_KEY, ctx)
77
+ ignore_contexts = value(EXCLUDE_KEY, context)
85
78
  ignore_contexts.any? do |ignore_context|
86
79
  full_name = "#{method.parent.full_name}##{method.name}"
87
80
  full_name[ignore_context]
@@ -56,30 +56,27 @@ module Reek
56
56
  #
57
57
  # @return [Array<SmellWarning>]
58
58
  #
59
- # :reek:FeatureEnvy
60
- # :reek:TooManyStatements: { max_statements: 6 }
61
- def sniff(ctx)
62
- return [] if ctx.singleton_method? || ctx.module_function?
63
- return [] if ctx.references_self?
64
- return [] if num_helper_methods(ctx).zero?
65
- return [] if ignore_method?(ctx)
59
+ def sniff
60
+ return [] if context.singleton_method? || context.module_function?
61
+ return [] if context.references_self?
62
+ return [] if num_helper_methods.zero?
63
+ return [] if ignore_method?
66
64
 
67
65
  [smell_warning(
68
- context: ctx,
69
- lines: [ctx.exp.line],
66
+ context: context,
67
+ lines: [source_line],
70
68
  message: "doesn't depend on instance state (maybe move it to another class?)")]
71
69
  end
72
70
 
73
71
  private
74
72
 
75
- # :reek:UtilityFunction
76
- def num_helper_methods(method_ctx)
77
- method_ctx.local_nodes(:send).length
73
+ def num_helper_methods
74
+ context.local_nodes(:send).length
78
75
  end
79
76
 
80
- def ignore_method?(method_ctx)
81
- method_ctx.non_public_visibility? &&
82
- value(PUBLIC_METHODS_ONLY_KEY, method_ctx)
77
+ def ignore_method?
78
+ context.non_public_visibility? &&
79
+ value(PUBLIC_METHODS_ONLY_KEY, context)
83
80
  end
84
81
  end
85
82
  end