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
@@ -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
  }