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.
- checksums.yaml +4 -4
- data/.rubocop.yml +18 -4
- data/.travis.yml +0 -5
- data/CHANGELOG.md +8 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +4 -4
- data/docs/How-To-Write-New-Detectors.md +6 -5
- data/docs/Unused-Private-Method.md +1 -1
- data/features/configuration_via_source_comments/erroneous_source_comments.feature +1 -1
- data/features/locales.feature +32 -0
- data/features/rake_task/rake_task.feature +46 -6
- data/features/rspec_matcher.feature +32 -0
- data/features/step_definitions/reek_steps.rb +0 -4
- data/features/support/env.rb +0 -9
- data/lib/reek/ast/builder.rb +1 -1
- data/lib/reek/ast/sexp_extensions/send.rb +0 -4
- data/lib/reek/cli/options.rb +2 -2
- data/lib/reek/configuration/app_configuration.rb +3 -2
- data/lib/reek/configuration/configuration_file_finder.rb +3 -3
- data/lib/reek/context/ghost_context.rb +0 -2
- data/lib/reek/context/module_context.rb +0 -3
- data/lib/reek/context_builder.rb +2 -4
- data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +2 -2
- data/lib/reek/errors/bad_detector_in_comment_error.rb +2 -2
- data/lib/reek/errors/encoding_error.rb +38 -0
- data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +3 -3
- data/lib/reek/errors/incomprehensible_source_error.rb +4 -4
- data/lib/reek/examiner.rb +15 -11
- data/lib/reek/rake/task.rb +5 -1
- data/lib/reek/smell_detectors/attribute.rb +6 -11
- data/lib/reek/smell_detectors/base_detector.rb +9 -1
- data/lib/reek/smell_detectors/boolean_parameter.rb +4 -5
- data/lib/reek/smell_detectors/class_variable.rb +5 -6
- data/lib/reek/smell_detectors/control_parameter.rb +3 -3
- data/lib/reek/smell_detectors/data_clump.rb +13 -6
- data/lib/reek/smell_detectors/duplicate_method_call.rb +18 -11
- data/lib/reek/smell_detectors/feature_envy.rb +9 -7
- data/lib/reek/smell_detectors/instance_variable_assumption.rb +14 -14
- data/lib/reek/smell_detectors/irresponsible_module.rb +6 -12
- data/lib/reek/smell_detectors/long_parameter_list.rb +10 -6
- data/lib/reek/smell_detectors/long_yield_list.rb +9 -5
- data/lib/reek/smell_detectors/manual_dispatch.rb +3 -4
- data/lib/reek/smell_detectors/module_initialize.rb +4 -5
- data/lib/reek/smell_detectors/nested_iterators.rb +11 -19
- data/lib/reek/smell_detectors/nil_check.rb +9 -15
- data/lib/reek/smell_detectors/prima_donna_method.rb +17 -16
- data/lib/reek/smell_detectors/repeated_conditional.rb +11 -8
- data/lib/reek/smell_detectors/subclassed_from_core_class.rb +8 -8
- data/lib/reek/smell_detectors/too_many_constants.rb +10 -8
- data/lib/reek/smell_detectors/too_many_instance_variables.rb +10 -5
- data/lib/reek/smell_detectors/too_many_methods.rb +11 -6
- data/lib/reek/smell_detectors/too_many_statements.rb +10 -5
- data/lib/reek/smell_detectors/uncommunicative_method_name.rb +8 -8
- data/lib/reek/smell_detectors/uncommunicative_module_name.rb +12 -15
- data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +10 -13
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +23 -23
- data/lib/reek/smell_detectors/unused_parameters.rb +5 -6
- data/lib/reek/smell_detectors/unused_private_method.rb +11 -18
- data/lib/reek/smell_detectors/utility_function.rb +12 -15
- data/lib/reek/source/source_code.rb +27 -6
- data/lib/reek/source/source_locator.rb +1 -1
- data/lib/reek/spec.rb +1 -1
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +0 -2
- data/spec/reek/ast/sexp_extensions_spec.rb +12 -32
- data/spec/reek/cli/application_spec.rb +4 -6
- data/spec/reek/configuration/configuration_file_finder_spec.rb +0 -2
- data/spec/reek/examiner_spec.rb +38 -1
- data/spec/reek/rake/task_spec.rb +25 -2
- data/spec/reek/smell_detectors/base_detector_spec.rb +4 -5
- data/spec/reek/smell_detectors/prima_donna_method_spec.rb +3 -3
- data/spec/reek/source/source_code_spec.rb +28 -1
- data/spec/reek/spec/should_reek_of_spec.rb +18 -18
- data/spec/reek/spec/should_reek_spec.rb +5 -5
- data/spec/reek/spec/smell_matcher_spec.rb +20 -20
- data/spec/spec_helper.rb +1 -1
- metadata +6 -3
@@ -19,32 +19,32 @@ module Reek
|
|
19
19
|
#
|
20
20
|
# @return [Array<SmellWarning>]
|
21
21
|
#
|
22
|
-
def sniff
|
23
|
-
|
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(
|
26
|
+
build_smell_warning(assumption)
|
30
27
|
end
|
31
28
|
end
|
32
29
|
|
33
30
|
private
|
34
31
|
|
35
|
-
def
|
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:
|
40
|
-
lines: [
|
40
|
+
context: context,
|
41
|
+
lines: [source_line],
|
41
42
|
message: message,
|
42
43
|
parameters: { assumption: assumption.to_s })
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
-
|
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
|
57
|
-
|
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
|
23
|
-
return [] if
|
24
|
-
expression = ctx.exp
|
22
|
+
def sniff
|
23
|
+
return [] if descriptive_context? || context.namespace_module?
|
25
24
|
[smell_warning(
|
26
|
-
context:
|
27
|
-
lines: [
|
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
|
34
|
-
|
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
|
36
|
-
|
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:
|
42
|
-
lines: [
|
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
|
30
|
-
|
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:
|
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
|
-
|
23
|
-
|
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:
|
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
|
-
|
24
|
-
|
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:
|
29
|
-
lines: [
|
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
|
45
|
-
|
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:
|
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
|
73
|
-
|
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
|
84
|
-
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
|
125
|
-
|
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
|
133
|
-
value(MAX_ALLOWED_NESTING_KEY,
|
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
|
13
|
-
lines =
|
12
|
+
def sniff
|
13
|
+
lines = detect_nodes.map(&:line)
|
14
14
|
if lines.any?
|
15
15
|
[smell_warning(
|
16
|
-
context:
|
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
|
-
|
25
|
-
class NodeDetector
|
26
|
-
attr_reader :ctx
|
27
|
-
def initialize(ctx)
|
28
|
-
@ctx = ctx
|
29
|
-
end
|
24
|
+
private
|
30
25
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
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
|
51
|
-
|
52
|
-
prima_donna_method?(method_sexp
|
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:
|
57
|
-
lines: [
|
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
|
64
|
+
def prima_donna_method?(method_sexp)
|
66
65
|
return false unless method_sexp.ends_with_bang?
|
67
|
-
return false if ignore_method?
|
68
|
-
return false if version_without_bang_exists?
|
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
|
-
|
73
|
-
|
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
|
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
|
52
|
-
|
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:
|
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
|
-
|
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|
|
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
|
30
|
-
superclass =
|
29
|
+
def sniff
|
30
|
+
superclass = expression.superclass
|
31
31
|
|
32
32
|
return [] unless superclass
|
33
33
|
|
34
|
-
sniff_superclass
|
34
|
+
sniff_superclass superclass.name
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
-
def sniff_superclass(
|
39
|
+
def sniff_superclass(superclass_name)
|
40
40
|
return [] unless CORE_CLASSES.include?(superclass_name)
|
41
41
|
|
42
|
-
[build_smell_warning(
|
42
|
+
[build_smell_warning(superclass_name)]
|
43
43
|
end
|
44
44
|
|
45
|
-
def build_smell_warning(
|
45
|
+
def build_smell_warning(ancestor_name)
|
46
46
|
smell_attributes = {
|
47
|
-
context:
|
48
|
-
lines: [
|
47
|
+
context: context,
|
48
|
+
lines: [source_line],
|
49
49
|
message: "inherits from core class '#{ancestor_name}'",
|
50
50
|
parameters: { ancestor: ancestor_name }
|
51
51
|
}
|