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