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
@@ -19,32 +19,32 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- def sniff(ctx)
23
- method_expressions = ctx.node_instance_methods
24
-
25
- assumptions = (variables_from_context(method_expressions) -
26
- variables_from_initialize(method_expressions)).uniq
22
+ def sniff
23
+ assumptions = (variables_from_context - variables_from_initialize).uniq
27
24
 
28
25
  assumptions.map do |assumption|
29
- build_smell_warning(ctx, assumption)
26
+ build_smell_warning(assumption)
30
27
  end
31
28
  end
32
29
 
33
30
  private
34
31
 
35
- def build_smell_warning(ctx, assumption)
32
+ def method_expressions
33
+ @method_expressions ||= context.node_instance_methods
34
+ end
35
+
36
+ def build_smell_warning(assumption)
36
37
  message = "assumes too much for instance variable '#{assumption}'"
37
38
 
38
39
  smell_warning(
39
- context: ctx,
40
- lines: [ctx.exp.line],
40
+ context: context,
41
+ lines: [source_line],
41
42
  message: message,
42
43
  parameters: { assumption: assumption.to_s })
43
44
  end
44
45
 
45
- # :reek:UtilityFunction
46
- def variables_from_initialize(instance_methods)
47
- initialize_exp = instance_methods.detect do |method|
46
+ def variables_from_initialize
47
+ initialize_exp = method_expressions.detect do |method|
48
48
  method.name == :initialize
49
49
  end
50
50
 
@@ -53,8 +53,8 @@ module Reek
53
53
  initialize_exp.each_node(:ivasgn).map(&:name)
54
54
  end
55
55
 
56
- def variables_from_context(instance_methods)
57
- instance_methods.map do |method|
56
+ def variables_from_context
57
+ method_expressions.map do |method|
58
58
  method.find_nodes(assumption_nodes, ignored_nodes).map(&:name)
59
59
  end.flatten
60
60
  end
@@ -19,24 +19,18 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- def sniff(ctx)
23
- return [] if descriptive?(ctx) || ctx.namespace_module?
24
- expression = ctx.exp
22
+ def sniff
23
+ return [] if descriptive_context? || context.namespace_module?
25
24
  [smell_warning(
26
- context: ctx,
27
- lines: [expression.line],
25
+ context: context,
26
+ lines: [source_line],
28
27
  message: 'has no descriptive comment')]
29
28
  end
30
29
 
31
30
  private
32
31
 
33
- def descriptive
34
- @descriptive ||= {}
35
- end
36
-
37
- # :reek:FeatureEnvy
38
- def descriptive?(ctx)
39
- descriptive[ctx.full_name] ||= ctx.descriptively_commented?
32
+ def descriptive_context?
33
+ context.descriptively_commented?
40
34
  end
41
35
  end
42
36
  end
@@ -32,17 +32,21 @@ module Reek
32
32
  #
33
33
  # @return [Array<SmellWarning>]
34
34
  #
35
- def sniff(ctx)
36
- max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx)
37
- exp = ctx.exp
38
- count = exp.arg_names.length
35
+ def sniff
36
+ count = expression.arg_names.length
39
37
  return [] if count <= max_allowed_params
40
38
  [smell_warning(
41
- context: ctx,
42
- lines: [exp.line],
39
+ context: context,
40
+ lines: [source_line],
43
41
  message: "has #{count} parameters",
44
42
  parameters: { count: count })]
45
43
  end
44
+
45
+ private
46
+
47
+ def max_allowed_params
48
+ value(MAX_ALLOWED_PARAMS_KEY, context)
49
+ end
46
50
  end
47
51
  end
48
52
  end
@@ -24,21 +24,25 @@ module Reek
24
24
  #
25
25
  # @return [Array<SmellWarning>]
26
26
  #
27
- # :reek:FeatureEnvy
28
27
  # :reek:DuplicateMethodCall: { max_calls: 2 }
29
- def sniff(ctx)
30
- max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx)
31
- ctx.local_nodes(:yield).select do |yield_node|
28
+ def sniff
29
+ context.local_nodes(:yield).select do |yield_node|
32
30
  yield_node.args.length > max_allowed_params
33
31
  end.map do |yield_node|
34
32
  count = yield_node.args.length
35
33
  smell_warning(
36
- context: ctx,
34
+ context: context,
37
35
  lines: [yield_node.line],
38
36
  message: "yields #{count} parameters",
39
37
  parameters: { count: count })
40
38
  end
41
39
  end
40
+
41
+ private
42
+
43
+ def max_allowed_params
44
+ value(MAX_ALLOWED_PARAMS_KEY, context)
45
+ end
42
46
  end
43
47
  end
44
48
  end
@@ -19,12 +19,11 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- # :reek:FeatureEnvy
23
- def sniff(ctx)
24
- smelly_nodes = ctx.each_node(:send).select { |node| node.name == :respond_to? }
22
+ def sniff
23
+ smelly_nodes = context.each_node(:send).select { |node| node.name == :respond_to? }
25
24
  return [] if smelly_nodes.empty?
26
25
  lines = smelly_nodes.map(&:line)
27
- [smell_warning(context: ctx, lines: lines, message: MESSAGE)]
26
+ [smell_warning(context: context, lines: lines, message: MESSAGE)]
28
27
  end
29
28
  end
30
29
  end
@@ -20,13 +20,12 @@ module Reek
20
20
  #
21
21
  # @return [Array<SmellWarning>]
22
22
  #
23
- # :reek:FeatureEnvy
24
- def sniff(ctx)
25
- ctx.local_nodes(:def) do |node|
23
+ def sniff
24
+ context.local_nodes(:def) do |node|
26
25
  if node.name == :initialize
27
26
  return smell_warning(
28
- context: ctx,
29
- lines: [ctx.exp.line],
27
+ context: context,
28
+ lines: [source_line],
30
29
  message: 'has initialize method')
31
30
  end
32
31
  end
@@ -41,14 +41,11 @@ module Reek
41
41
  #
42
42
  # @return [Array<SmellWarning>]
43
43
  #
44
- def sniff(ctx)
45
- configure_ignore_iterators ctx
46
- violations = find_violations ctx
47
-
48
- violations.group_by(&:depth).map do |depth, group|
44
+ def sniff
45
+ find_violations.group_by(&:depth).map do |depth, group|
49
46
  lines = group.map(&:line)
50
47
  smell_warning(
51
- context: ctx,
48
+ context: context,
52
49
  lines: lines,
53
50
  message: "contains iterators nested #{depth} deep",
54
51
  parameters: { depth: depth })
@@ -57,8 +54,6 @@ module Reek
57
54
 
58
55
  private
59
56
 
60
- attr_accessor :ignore_iterators
61
-
62
57
  # Finds the set of independent most deeply nested iterators that are
63
58
  # nested more deeply than allowed.
64
59
  #
@@ -69,10 +64,8 @@ module Reek
69
64
  #
70
65
  # @return [Array<Iterator>]
71
66
  #
72
- def find_violations(ctx)
73
- candidates = find_candidates ctx
74
- max_allowed_nesting = max_nesting(ctx)
75
- candidates.select { |it| it.depth > max_allowed_nesting }
67
+ def find_violations
68
+ find_candidates.select { |it| it.depth > max_allowed_nesting }
76
69
  end
77
70
 
78
71
  # Finds the set of independent most deeply nested iterators regardless of
@@ -80,9 +73,8 @@ module Reek
80
73
  #
81
74
  # @return [Array<Iterator>]
82
75
  #
83
- def find_candidates(ctx)
84
- exp = ctx.exp
85
- scout(exp: exp, depth: 0)
76
+ def find_candidates
77
+ scout(exp: expression, depth: 0)
86
78
  end
87
79
 
88
80
  # A little digression into parser's sexp is necessary here:
@@ -121,16 +113,16 @@ module Reek
121
113
  end
122
114
  end
123
115
 
124
- def configure_ignore_iterators(ctx)
125
- self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx)
116
+ def ignore_iterators
117
+ @ignore_iterators ||= value(IGNORE_ITERATORS_KEY, context)
126
118
  end
127
119
 
128
120
  def increment_depth(iterator, depth)
129
121
  ignored_iterator?(iterator) ? depth : depth + 1
130
122
  end
131
123
 
132
- def max_nesting(ctx)
133
- value(MAX_ALLOWED_NESTING_KEY, ctx)
124
+ def max_allowed_nesting
125
+ @max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context)
134
126
  end
135
127
 
136
128
  # :reek:FeatureEnvy
@@ -9,11 +9,11 @@ module Reek
9
9
  #
10
10
  # See {file:docs/Nil-Check.md} for details.
11
11
  class NilCheck < BaseDetector
12
- def sniff(ctx)
13
- lines = NodeDetector.new(ctx).detect.map(&:line)
12
+ def sniff
13
+ lines = detect_nodes.map(&:line)
14
14
  if lines.any?
15
15
  [smell_warning(
16
- context: ctx,
16
+ context: context,
17
17
  lines: lines,
18
18
  message: 'performs a nil-check')]
19
19
  else
@@ -21,19 +21,13 @@ module Reek
21
21
  end
22
22
  end
23
23
 
24
- # Detect all nodes that smell of NilCheck
25
- class NodeDetector
26
- attr_reader :ctx
27
- def initialize(ctx)
28
- @ctx = ctx
29
- end
24
+ private
30
25
 
31
- def detect
32
- finders = [NodeFinder.new(ctx, :send, NilCallNodeDetector),
33
- NodeFinder.new(ctx, :when, NilWhenNodeDetector),
34
- NodeFinder.new(ctx, :csend, SafeNavigationNodeDetector)]
35
- finders.flat_map(&:smelly_nodes)
36
- end
26
+ def detect_nodes
27
+ finders = [NodeFinder.new(context, :send, NilCallNodeDetector),
28
+ NodeFinder.new(context, :when, NilWhenNodeDetector),
29
+ NodeFinder.new(context, :csend, SafeNavigationNodeDetector)]
30
+ finders.flat_map(&:smelly_nodes)
37
31
  end
38
32
 
39
33
  #
@@ -30,7 +30,6 @@ module Reek
30
30
  end
31
31
 
32
32
  #
33
- # @param ctx [Context::ModuleContext]
34
33
  # @return [Array<SmellWarning>]
35
34
  #
36
35
  # Given this code:
@@ -40,21 +39,21 @@ module Reek
40
39
  # end
41
40
  # end
42
41
  #
43
- # An example `ctx` could look like this:
42
+ # An example context could look like this:
44
43
  #
45
44
  # s(:class,
46
45
  # s(:const, nil, :Alfa), nil,
47
46
  # s(:def, :bravo!,
48
47
  # s(:args), nil))
49
48
  #
50
- def sniff(ctx)
51
- ctx.node_instance_methods.select do |method_sexp|
52
- prima_donna_method?(method_sexp, ctx)
49
+ def sniff
50
+ context.node_instance_methods.select do |method_sexp|
51
+ prima_donna_method?(method_sexp)
53
52
  end.map do |method_sexp|
54
53
  name = method_sexp.name
55
54
  smell_warning(
56
- context: ctx,
57
- lines: [ctx.exp.line],
55
+ context: context,
56
+ lines: [method_sexp.line],
58
57
  message: "has prima donna method '#{name}'",
59
58
  parameters: { name: name.to_s })
60
59
  end
@@ -62,16 +61,15 @@ module Reek
62
61
 
63
62
  private
64
63
 
65
- def prima_donna_method?(method_sexp, ctx)
64
+ def prima_donna_method?(method_sexp)
66
65
  return false unless method_sexp.ends_with_bang?
67
- return false if ignore_method?(method_sexp, ctx)
68
- return false if version_without_bang_exists?(method_sexp, ctx)
66
+ return false if ignore_method? method_sexp
67
+ return false if version_without_bang_exists? method_sexp
69
68
  true
70
69
  end
71
70
 
72
- # :reek:UtilityFunction
73
- def version_without_bang_exists?(method_sexp, ctx)
74
- ctx.node_instance_methods.find do |sexp_item|
71
+ def version_without_bang_exists?(method_sexp)
72
+ context.node_instance_methods.find do |sexp_item|
75
73
  sexp_item.name.to_s == method_sexp.name_without_bang
76
74
  end
77
75
  end
@@ -79,13 +77,16 @@ module Reek
79
77
  #
80
78
  # @param method_node [Reek::AST::Node],
81
79
  # e.g. s(:def, :bravo!, s(:args), nil)
82
- # @param ctx [Context::ModuleContext]
83
80
  # @return [Boolean]
84
81
  #
85
- def ignore_method?(method_node, ctx)
86
- ignore_method_names = value(EXCLUDE_KEY, ctx) # e.g. ["bravo!"]
82
+ def ignore_method?(method_node)
87
83
  ignore_method_names.include? method_node.name.to_s # method_node.name is e.g.: :bravo!
88
84
  end
85
+
86
+ # e.g. ["bravo!"]
87
+ def ignore_method_names
88
+ @ignore_method_names ||= value(EXCLUDE_KEY, context)
89
+ end
89
90
  end
90
91
  end
91
92
  end
@@ -46,38 +46,41 @@ module Reek
46
46
  #
47
47
  # @return [Array<SmellWarning>]
48
48
  #
49
- # :reek:TooManyStatements: { max_statements: 6 }
50
49
  # :reek:DuplicateMethodCall: { max_calls: 2 }
51
- def sniff(ctx)
52
- max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx)
53
- conditional_counts(ctx).select do |_key, lines|
50
+ def sniff
51
+ conditional_counts.select do |_key, lines|
54
52
  lines.length > max_identical_ifs
55
53
  end.map do |key, lines|
56
54
  occurs = lines.length
57
55
  expression = key.format_to_ruby
58
56
  smell_warning(
59
- context: ctx,
57
+ context: context,
60
58
  lines: lines,
61
59
  message: "tests '#{expression}' at least #{occurs} times",
62
60
  parameters: { name: expression, count: occurs })
63
61
  end
64
62
  end
65
63
 
64
+ private
65
+
66
+ def max_identical_ifs
67
+ @max_identical_ifs ||= value(MAX_IDENTICAL_IFS_KEY, context)
68
+ end
69
+
66
70
  #
67
71
  # Returns a Hash listing all of the conditional expressions in
68
72
  # the given syntax tree together with the number of times each
69
73
  # occurs. Ignores nested classes and modules.
70
74
  #
71
75
  # :reek:TooManyStatements: { max_statements: 9 }
72
- # :reek:FeatureEnvy
73
- def conditional_counts(sexp)
76
+ def conditional_counts
74
77
  result = Hash.new { |hash, key| hash[key] = [] }
75
78
  collector = proc do |node|
76
79
  next unless (condition = node.condition)
77
80
  next if condition == BLOCK_GIVEN_CONDITION
78
81
  result[condition].push(condition.line)
79
82
  end
80
- [:if, :case].each { |stmt| sexp.local_nodes(stmt, &collector) }
83
+ [:if, :case].each { |stmt| context.local_nodes(stmt, &collector) }
81
84
  result
82
85
  end
83
86
  end
@@ -26,26 +26,26 @@ module Reek
26
26
  # class Foo < Bar; end;
27
27
  #
28
28
  # @return [Array<SmellWarning>]
29
- def sniff(ctx)
30
- superclass = ctx.exp.superclass
29
+ def sniff
30
+ superclass = expression.superclass
31
31
 
32
32
  return [] unless superclass
33
33
 
34
- sniff_superclass(ctx, superclass.name)
34
+ sniff_superclass superclass.name
35
35
  end
36
36
 
37
37
  private
38
38
 
39
- def sniff_superclass(ctx, superclass_name)
39
+ def sniff_superclass(superclass_name)
40
40
  return [] unless CORE_CLASSES.include?(superclass_name)
41
41
 
42
- [build_smell_warning(ctx, superclass_name)]
42
+ [build_smell_warning(superclass_name)]
43
43
  end
44
44
 
45
- def build_smell_warning(ctx, ancestor_name)
45
+ def build_smell_warning(ancestor_name)
46
46
  smell_attributes = {
47
- context: ctx,
48
- lines: [ctx.exp.line],
47
+ context: context,
48
+ lines: [source_line],
49
49
  message: "inherits from core class '#{ancestor_name}'",
50
50
  parameters: { ancestor: ancestor_name }
51
51
  }