reek 1.3.8 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +11 -0
- data/README.md +22 -14
- data/Rakefile +2 -15
- data/assets/html_output.html.erb +103 -0
- data/features/command_line_interface/options.feature +3 -0
- data/features/command_line_interface/smell_selection.feature +19 -0
- data/features/rake_task/rake_task.feature +1 -1
- data/features/reports/reports.feature +16 -0
- data/features/reports/yaml.feature +26 -23
- data/features/samples.feature +2 -1
- data/features/step_definitions/reek_steps.rb +15 -15
- data/features/support/env.rb +7 -9
- data/lib/reek/cli/application.rb +2 -4
- data/lib/reek/cli/command.rb +12 -0
- data/lib/reek/cli/help_command.rb +3 -6
- data/lib/reek/cli/options.rb +147 -0
- data/lib/reek/cli/reek_command.rb +18 -14
- data/lib/reek/cli/report/formatter.rb +56 -0
- data/lib/reek/cli/report/report.rb +106 -0
- data/lib/reek/cli/report/strategy.rb +63 -0
- data/lib/reek/cli/version_command.rb +3 -6
- data/lib/reek/config_file_exception.rb +0 -1
- data/lib/reek/core/code_context.rb +1 -3
- data/lib/reek/core/code_parser.rb +13 -12
- data/lib/reek/core/method_context.rb +13 -2
- data/lib/reek/core/module_context.rb +0 -4
- data/lib/reek/core/object_refs.rb +2 -3
- data/lib/reek/core/singleton_method_context.rb +0 -2
- data/lib/reek/core/smell_configuration.rb +3 -5
- data/lib/reek/core/smell_repository.rb +7 -8
- data/lib/reek/core/sniffer.rb +4 -10
- data/lib/reek/core/stop_context.rb +2 -4
- data/lib/reek/core/warning_collector.rb +0 -1
- data/lib/reek/examiner.rb +19 -17
- data/lib/reek/rake/task.rb +7 -10
- data/lib/reek/smell_warning.rb +4 -8
- data/lib/reek/smells.rb +0 -1
- data/lib/reek/smells/attribute.rb +8 -11
- data/lib/reek/smells/boolean_parameter.rb +5 -7
- data/lib/reek/smells/class_variable.rb +6 -7
- data/lib/reek/smells/control_parameter.rb +78 -45
- data/lib/reek/smells/data_clump.rb +13 -16
- data/lib/reek/smells/duplicate_method_call.rb +13 -11
- data/lib/reek/smells/feature_envy.rb +6 -7
- data/lib/reek/smells/irresponsible_module.rb +4 -6
- data/lib/reek/smells/long_parameter_list.rb +5 -7
- data/lib/reek/smells/long_yield_list.rb +2 -4
- data/lib/reek/smells/nested_iterators.rb +12 -22
- data/lib/reek/smells/nil_check.rb +35 -46
- data/lib/reek/smells/prima_donna_method.rb +24 -16
- data/lib/reek/smells/repeated_conditional.rb +8 -10
- data/lib/reek/smells/smell_detector.rb +9 -7
- data/lib/reek/smells/too_many_instance_variables.rb +7 -9
- data/lib/reek/smells/too_many_methods.rb +6 -8
- data/lib/reek/smells/too_many_statements.rb +4 -6
- data/lib/reek/smells/uncommunicative_method_name.rb +5 -7
- data/lib/reek/smells/uncommunicative_module_name.rb +5 -7
- data/lib/reek/smells/uncommunicative_parameter_name.rb +7 -9
- data/lib/reek/smells/uncommunicative_variable_name.rb +15 -18
- data/lib/reek/smells/unused_parameters.rb +5 -45
- data/lib/reek/smells/utility_function.rb +9 -10
- data/lib/reek/source.rb +0 -1
- data/lib/reek/source/code_comment.rb +7 -8
- data/lib/reek/source/config_file.rb +2 -4
- data/lib/reek/source/core_extras.rb +1 -1
- data/lib/reek/source/reference_collector.rb +1 -2
- data/lib/reek/source/sexp_extensions.rb +93 -10
- data/lib/reek/source/sexp_formatter.rb +2 -3
- data/lib/reek/source/sexp_node.rb +19 -15
- data/lib/reek/source/source_code.rb +4 -14
- data/lib/reek/source/source_file.rb +3 -5
- data/lib/reek/source/source_locator.rb +5 -6
- data/lib/reek/source/source_repository.rb +3 -3
- data/lib/reek/source/tree_dresser.rb +2 -2
- data/lib/reek/spec.rb +1 -2
- data/lib/reek/spec/should_reek.rb +8 -5
- data/lib/reek/spec/should_reek_of.rb +6 -4
- data/lib/reek/spec/should_reek_only_of.rb +10 -6
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +34 -30
- data/spec/gem/updates_spec.rb +3 -4
- data/spec/gem/yard_spec.rb +1 -2
- data/spec/matchers/smell_of_matcher.rb +12 -14
- data/spec/quality/reek_source_spec.rb +42 -0
- data/spec/reek/cli/help_command_spec.rb +7 -5
- data/spec/reek/cli/report_spec.rb +89 -22
- data/spec/reek/cli/version_command_spec.rb +8 -6
- data/spec/reek/core/code_context_spec.rb +25 -26
- data/spec/reek/core/code_parser_spec.rb +6 -6
- data/spec/reek/core/method_context_spec.rb +18 -18
- data/spec/reek/core/module_context_spec.rb +5 -5
- data/spec/reek/core/object_refs_spec.rb +21 -22
- data/spec/reek/core/smell_configuration_spec.rb +22 -21
- data/spec/reek/core/stop_context_spec.rb +2 -2
- data/spec/reek/core/warning_collector_spec.rb +3 -3
- data/spec/reek/examiner_spec.rb +9 -9
- data/spec/reek/smell_warning_spec.rb +29 -29
- data/spec/reek/smells/attribute_spec.rb +6 -6
- data/spec/reek/smells/behaves_like_variable_detector.rb +6 -6
- data/spec/reek/smells/boolean_parameter_spec.rb +17 -17
- data/spec/reek/smells/class_variable_spec.rb +9 -9
- data/spec/reek/smells/control_parameter_spec.rb +161 -137
- data/spec/reek/smells/data_clump_spec.rb +22 -19
- data/spec/reek/smells/duplicate_method_call_spec.rb +71 -27
- data/spec/reek/smells/feature_envy_spec.rb +32 -32
- data/spec/reek/smells/irresponsible_module_spec.rb +21 -21
- data/spec/reek/smells/long_parameter_list_spec.rb +14 -14
- data/spec/reek/smells/long_yield_list_spec.rb +6 -6
- data/spec/reek/smells/nested_iterators_spec.rb +21 -21
- data/spec/reek/smells/nil_check_spec.rb +23 -15
- data/spec/reek/smells/prima_donna_method_spec.rb +5 -5
- data/spec/reek/smells/repeated_conditional_spec.rb +14 -14
- data/spec/reek/smells/smell_detector_shared.rb +9 -9
- data/spec/reek/smells/too_many_instance_variables_spec.rb +12 -12
- data/spec/reek/smells/too_many_methods_spec.rb +10 -10
- data/spec/reek/smells/too_many_statements_spec.rb +41 -41
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +4 -4
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +12 -12
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +21 -21
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +49 -49
- data/spec/reek/smells/unused_parameters_spec.rb +26 -16
- data/spec/reek/smells/utility_function_spec.rb +20 -20
- data/spec/reek/source/code_comment_spec.rb +37 -37
- data/spec/reek/source/object_source_spec.rb +5 -5
- data/spec/reek/source/reference_collector_spec.rb +9 -9
- data/spec/reek/source/sexp_extensions_spec.rb +73 -52
- data/spec/reek/source/sexp_formatter_spec.rb +3 -4
- data/spec/reek/source/sexp_node_spec.rb +3 -3
- data/spec/reek/source/source_code_spec.rb +16 -15
- data/spec/reek/source/tree_dresser_spec.rb +2 -2
- data/spec/reek/spec/should_reek_of_spec.rb +11 -11
- data/spec/reek/spec/should_reek_only_of_spec.rb +11 -11
- data/spec/reek/spec/should_reek_spec.rb +11 -11
- data/spec/samples/one_smelly_file/dirty.rb +3 -0
- data/spec/spec_helper.rb +0 -6
- data/tasks/develop.rake +8 -16
- data/tasks/reek.rake +5 -13
- data/tasks/test.rake +5 -22
- metadata +56 -34
- data/lib/reek/cli/command_line.rb +0 -126
- data/lib/reek/cli/report.rb +0 -138
@@ -3,23 +3,22 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
#
|
8
7
|
# Duplication occurs when two fragments of code look nearly identical,
|
9
8
|
# or when two fragments of code have nearly identical effects
|
10
9
|
# at some conceptual level.
|
11
|
-
#
|
10
|
+
#
|
12
11
|
# +DuplicateMethodCall+ checks for repeated identical method calls
|
13
12
|
# within any one method definition. For example, the following method
|
14
13
|
# will report a warning:
|
15
|
-
#
|
14
|
+
#
|
16
15
|
# def double_thing()
|
17
16
|
# @other.thing + @other.thing
|
18
17
|
# end
|
19
18
|
#
|
20
19
|
class DuplicateMethodCall < SmellDetector
|
21
20
|
SMELL_CLASS = 'Duplication'
|
22
|
-
SMELL_SUBCLASS =
|
21
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
23
22
|
|
24
23
|
CALL_KEY = 'call'
|
25
24
|
OCCURRENCES_KEY = 'occurrences'
|
@@ -57,7 +56,7 @@ module Reek
|
|
57
56
|
SmellWarning.new(SMELL_CLASS, ctx.full_name, found_call.lines,
|
58
57
|
found_call.smell_message,
|
59
58
|
@source, SMELL_SUBCLASS,
|
60
|
-
|
59
|
+
CALL_KEY => found_call.call, OCCURRENCES_KEY => found_call.occurs)
|
61
60
|
end
|
62
61
|
end
|
63
62
|
|
@@ -86,7 +85,7 @@ module Reek
|
|
86
85
|
end
|
87
86
|
|
88
87
|
def lines
|
89
|
-
@occurences.map
|
88
|
+
@occurences.map(&:line)
|
90
89
|
end
|
91
90
|
end
|
92
91
|
|
@@ -101,14 +100,14 @@ module Reek
|
|
101
100
|
end
|
102
101
|
|
103
102
|
def calls
|
104
|
-
result = Hash.new {|hash,key| hash[key] = FoundCall.new(key)}
|
103
|
+
result = Hash.new { |hash, key| hash[key] = FoundCall.new(key) }
|
105
104
|
collect_calls(result)
|
106
105
|
collect_assignments(result)
|
107
|
-
result.values.sort_by
|
106
|
+
result.values.sort_by(&:call)
|
108
107
|
end
|
109
108
|
|
110
109
|
def smelly_calls
|
111
|
-
calls.select {|found_call| smelly_call? found_call }
|
110
|
+
calls.select { |found_call| smelly_call? found_call }
|
112
111
|
end
|
113
112
|
|
114
113
|
private
|
@@ -125,14 +124,17 @@ module Reek
|
|
125
124
|
next if !call_node.receiver && call_node.args.empty?
|
126
125
|
result[call_node].record(call_node)
|
127
126
|
end
|
127
|
+
context.local_nodes(:iter) do |call_node|
|
128
|
+
result[call_node].record(call_node)
|
129
|
+
end
|
128
130
|
end
|
129
131
|
|
130
132
|
def smelly_call?(found_call)
|
131
|
-
found_call.occurs > @max_allowed_calls
|
133
|
+
found_call.occurs > @max_allowed_calls && !allow_calls?(found_call.call)
|
132
134
|
end
|
133
135
|
|
134
136
|
def allow_calls?(method)
|
135
|
-
@allow_calls.any? { |allow| /#{allow}/
|
137
|
+
@allow_calls.any? { |allow| /#{allow}/ =~ method }
|
136
138
|
end
|
137
139
|
end
|
138
140
|
end
|
@@ -3,15 +3,14 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
#
|
8
7
|
# Feature Envy occurs when a code fragment references another object
|
9
8
|
# more often than it references itself, or when several clients do
|
10
9
|
# the same series of manipulations on a particular type of object.
|
11
|
-
#
|
10
|
+
#
|
12
11
|
# A simple example would be the following method, which "belongs"
|
13
12
|
# on the Item class and not on the Cart class:
|
14
|
-
#
|
13
|
+
#
|
15
14
|
# class Cart
|
16
15
|
# def price
|
17
16
|
# @item.price + @item.tax
|
@@ -22,12 +21,12 @@ module Reek
|
|
22
21
|
# code that "belongs" on one class but which is located in another
|
23
22
|
# can be hard to find, and may upset the "System of Names"
|
24
23
|
# in the host class.
|
25
|
-
#
|
24
|
+
#
|
26
25
|
# Feature Envy also affects the design's flexibility: A code fragment
|
27
26
|
# that is in the wrong class creates couplings that may not be natural
|
28
27
|
# within the application's domain, and creates a loss of cohesion
|
29
28
|
# in the unwilling host class.
|
30
|
-
#
|
29
|
+
#
|
31
30
|
# Currently +FeatureEnvy+ reports any method that refers to self less
|
32
31
|
# often than it refers to (ie. send messages to) some other object.
|
33
32
|
#
|
@@ -35,7 +34,7 @@ module Reek
|
|
35
34
|
include ExcludeInitialize
|
36
35
|
|
37
36
|
SMELL_CLASS = 'LowCohesion'
|
38
|
-
SMELL_SUBCLASS =
|
37
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
39
38
|
|
40
39
|
RECEIVER_KEY = 'receiver'
|
41
40
|
REFERENCES_KEY = 'references'
|
@@ -51,7 +50,7 @@ module Reek
|
|
51
50
|
target = ref.format_ruby
|
52
51
|
SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
53
52
|
"refers to #{target} more than self",
|
54
|
-
@source, SMELL_SUBCLASS,
|
53
|
+
@source, SMELL_SUBCLASS, RECEIVER_KEY => target, REFERENCES_KEY => occurs)
|
55
54
|
end
|
56
55
|
end
|
57
56
|
end
|
@@ -4,14 +4,12 @@ require 'reek/source/code_comment'
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Smells
|
7
|
-
|
8
7
|
#
|
9
8
|
# It is considered good practice to annotate every class and module
|
10
9
|
# with a brief comment outlining its responsibilities.
|
11
10
|
#
|
12
11
|
class IrresponsibleModule < SmellDetector
|
13
|
-
|
14
|
-
SMELL_CLASS = self.name.split(/::/)[-1]
|
12
|
+
SMELL_CLASS = name.split(/::/)[-1]
|
15
13
|
SMELL_SUBCLASS = SMELL_CLASS
|
16
14
|
|
17
15
|
MODULE_NAME_KEY = 'module_name'
|
@@ -31,10 +29,10 @@ module Reek
|
|
31
29
|
#
|
32
30
|
def examine_context(ctx)
|
33
31
|
comment = Source::CodeComment.new(ctx.exp.comments)
|
34
|
-
return [] if self.class.descriptive[ctx.full_name] ||= comment.
|
32
|
+
return [] if self.class.descriptive[ctx.full_name] ||= comment.descriptive?
|
35
33
|
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
36
|
-
|
37
|
-
|
34
|
+
'has no descriptive comment',
|
35
|
+
@source, SMELL_SUBCLASS, MODULE_NAME_KEY => ctx.exp.text_name)
|
38
36
|
[smell]
|
39
37
|
end
|
40
38
|
end
|
@@ -4,7 +4,6 @@ require 'reek/core/smell_configuration'
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Smells
|
7
|
-
|
8
7
|
#
|
9
8
|
# A Long Parameter List occurs when a method has more than one
|
10
9
|
# or two parameters, or when a method yields more than one or
|
@@ -14,9 +13,8 @@ module Reek
|
|
14
13
|
# many parameters.
|
15
14
|
#
|
16
15
|
class LongParameterList < SmellDetector
|
17
|
-
|
18
16
|
SMELL_CLASS = 'LongParameterList'
|
19
|
-
SMELL_SUBCLASS =
|
17
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
20
18
|
|
21
19
|
PARAMETER_COUNT_KEY = 'parameter_count'
|
22
20
|
|
@@ -32,7 +30,7 @@ module Reek
|
|
32
30
|
super.merge(
|
33
31
|
MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
|
34
32
|
Core::SmellConfiguration::OVERRIDES_KEY => {
|
35
|
-
|
33
|
+
'initialize' => { MAX_ALLOWED_PARAMS_KEY => 5 }
|
36
34
|
}
|
37
35
|
)
|
38
36
|
end
|
@@ -47,9 +45,9 @@ module Reek
|
|
47
45
|
num_params = ctx.exp.arg_names.length
|
48
46
|
return [] if num_params <= @max_allowed_params
|
49
47
|
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
"has #{num_params} parameters",
|
49
|
+
@source, SMELL_SUBCLASS,
|
50
|
+
PARAMETER_COUNT_KEY => num_params)
|
53
51
|
[smell]
|
54
52
|
end
|
55
53
|
end
|
@@ -3,15 +3,13 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
#
|
8
7
|
# A variant on LongParameterList that checks the number of items
|
9
8
|
# passed to a block by a +yield+ call.
|
10
9
|
#
|
11
10
|
class LongYieldList < SmellDetector
|
12
|
-
|
13
11
|
SMELL_CLASS = 'LongParameterList'
|
14
|
-
SMELL_SUBCLASS =
|
12
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
15
13
|
|
16
14
|
# The name of the config field that sets the maximum number of
|
17
15
|
# parameters permitted in any method or block.
|
@@ -42,7 +40,7 @@ module Reek
|
|
42
40
|
num_params = yield_node.args.length
|
43
41
|
SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [yield_node.line],
|
44
42
|
"yields #{num_params} parameters",
|
45
|
-
@source, SMELL_SUBCLASS,
|
43
|
+
@source, SMELL_SUBCLASS, PARAMETER_COUNT_KEY => num_params)
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
@@ -3,15 +3,13 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
#
|
8
7
|
# A Nested Iterator occurs when a block contains another block.
|
9
8
|
#
|
10
9
|
# +NestedIterators+ reports failing methods only once.
|
11
10
|
#
|
12
11
|
class NestedIterators < SmellDetector
|
13
|
-
|
14
|
-
SMELL_CLASS = self.name.split(/::/)[-1]
|
12
|
+
SMELL_CLASS = name.split(/::/)[-1]
|
15
13
|
SMELL_SUBCLASS = SMELL_CLASS
|
16
14
|
# SMELL: should be a subclass of UnnecessaryComplexity
|
17
15
|
NESTING_DEPTH_KEY = 'depth'
|
@@ -45,9 +43,9 @@ module Reek
|
|
45
43
|
|
46
44
|
if depth && depth > value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
|
47
45
|
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [exp.line],
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
"contains iterators nested #{depth} deep",
|
47
|
+
@source, SMELL_SUBCLASS,
|
48
|
+
NESTING_DEPTH_KEY => depth)
|
51
49
|
[smell]
|
52
50
|
else
|
53
51
|
[]
|
@@ -55,39 +53,31 @@ module Reek
|
|
55
53
|
# BUG: no longer reports nesting outside methods (eg. in Optparse)
|
56
54
|
end
|
57
55
|
|
58
|
-
|
56
|
+
private
|
59
57
|
|
60
58
|
def find_deepest_iterator(ctx)
|
61
59
|
@ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
|
62
60
|
|
63
|
-
find_iters(ctx.exp, 1).sort_by {|item| item[1]}.last
|
61
|
+
find_iters(ctx.exp, 1).sort_by { |item| item[1] }.last
|
64
62
|
end
|
65
63
|
|
66
64
|
def find_iters(exp, depth)
|
67
|
-
exp.
|
68
|
-
|
69
|
-
|
70
|
-
when :iter
|
71
|
-
find_iters_for_iter_node(elem, depth)
|
72
|
-
when :class, :defn, :defs, :module
|
73
|
-
next
|
74
|
-
else
|
75
|
-
find_iters(elem, depth)
|
76
|
-
end
|
77
|
-
end.flatten(1).compact
|
65
|
+
exp.unnested_nodes([:iter]).flat_map do |elem|
|
66
|
+
find_iters_for_iter_node(elem, depth)
|
67
|
+
end
|
78
68
|
end
|
79
69
|
|
80
70
|
def find_iters_for_iter_node(exp, depth)
|
81
71
|
ignored = ignored_iterator? exp
|
82
|
-
result = find_iters(
|
83
|
-
find_iters(
|
72
|
+
result = find_iters(exp.call, depth) +
|
73
|
+
find_iters(exp.block, depth + (ignored ? 0 : 1))
|
84
74
|
result << [exp, depth] unless ignored
|
85
75
|
result
|
86
76
|
end
|
87
77
|
|
88
78
|
def ignored_iterator?(exp)
|
89
79
|
name = exp.call.method_name.to_s
|
90
|
-
@ignore_iterators.any? { |pattern| /#{pattern}/
|
80
|
+
@ignore_iterators.any? { |pattern| /#{pattern}/ =~ name }
|
91
81
|
end
|
92
82
|
end
|
93
83
|
end
|
@@ -3,22 +3,20 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
# Checking for nil is a special kind of type check, and therefore a case of
|
8
7
|
# SimulatedPolymorphism.
|
9
8
|
class NilCheck < SmellDetector
|
10
|
-
|
11
9
|
SMELL_CLASS = 'SimulatedPolymorphism'
|
12
|
-
SMELL_SUBCLASS =
|
10
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
13
11
|
|
14
12
|
def examine_context(ctx)
|
15
|
-
|
16
|
-
|
17
|
-
smelly_nodes =
|
13
|
+
call_node_finder = NodeFinder.new(ctx, :call, NilCallNodeDetector)
|
14
|
+
case_node_finder = NodeFinder.new(ctx, :when, NilWhenNodeDetector)
|
15
|
+
smelly_nodes = call_node_finder.smelly_nodes + case_node_finder.smelly_nodes
|
18
16
|
|
19
17
|
smelly_nodes.map do |node|
|
20
18
|
SmellWarning.new(SMELL_CLASS, ctx.full_name, Array(node.line),
|
21
|
-
|
19
|
+
'performs a nil-check.',
|
22
20
|
@source, SMELL_SUBCLASS)
|
23
21
|
end
|
24
22
|
end
|
@@ -27,64 +25,55 @@ module Reek
|
|
27
25
|
# A base class that allows to work on all nodes of a certain type.
|
28
26
|
#
|
29
27
|
class NodeFinder
|
30
|
-
|
31
|
-
|
32
|
-
@
|
28
|
+
def initialize(ctx, type, detector = nil)
|
29
|
+
@nodes = ctx.local_nodes(type)
|
30
|
+
@detector = detector
|
33
31
|
end
|
34
|
-
end
|
35
32
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def initialize(ctx)
|
41
|
-
super(ctx, :call)
|
33
|
+
def smelly_nodes
|
34
|
+
@nodes.select do |when_node|
|
35
|
+
@detector.detect(when_node)
|
36
|
+
end
|
42
37
|
end
|
38
|
+
end
|
43
39
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
}
|
48
|
-
end
|
40
|
+
# Detect 'call' nodes which perform a nil check.
|
41
|
+
module NilCallNodeDetector
|
42
|
+
module_function
|
49
43
|
|
50
|
-
def
|
51
|
-
|
44
|
+
def detect(node)
|
45
|
+
nil_query?(node) || nil_comparison?(node)
|
52
46
|
end
|
53
47
|
|
54
|
-
def
|
55
|
-
call.
|
48
|
+
def nil_query?(call)
|
49
|
+
call.method_name == :nil?
|
56
50
|
end
|
57
51
|
|
58
|
-
def
|
59
|
-
|
52
|
+
def nil_comparison?(call)
|
53
|
+
is_comparison_call?(call) && involves_nil?(call)
|
60
54
|
end
|
61
55
|
|
62
|
-
def
|
63
|
-
|
56
|
+
def is_comparison_call?(call)
|
57
|
+
comparison_methods.include? call.method_name
|
64
58
|
end
|
65
|
-
end
|
66
|
-
|
67
|
-
#
|
68
|
-
# Finds when statements that perform a nil check.
|
69
|
-
#
|
70
|
-
class CaseNodeFinder < NodeFinder
|
71
|
-
CASE_NIL_NODE = Sexp.new(:array, SEXP_NIL)
|
72
59
|
|
73
|
-
def
|
74
|
-
|
60
|
+
def involves_nil?(call)
|
61
|
+
call.receiver.nil_node? || call.args.any?(&:nil_node?)
|
75
62
|
end
|
76
63
|
|
77
|
-
def
|
78
|
-
|
79
|
-
nil_chk?(when_node)
|
80
|
-
}
|
64
|
+
def comparison_methods
|
65
|
+
[:==, :===]
|
81
66
|
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Detect 'when' statements that perform a nil check.
|
70
|
+
module NilWhenNodeDetector
|
71
|
+
module_function
|
82
72
|
|
83
|
-
def
|
84
|
-
|
73
|
+
def detect(node)
|
74
|
+
node.condition_list.any?(&:nil_node?)
|
85
75
|
end
|
86
76
|
end
|
87
|
-
|
88
77
|
end
|
89
78
|
end
|
90
79
|
end
|
@@ -6,19 +6,22 @@ module Reek
|
|
6
6
|
# Excerpt from:
|
7
7
|
# http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist
|
8
8
|
# since this sums it up really well:
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# or, more precisely, this method is the
|
12
|
-
#
|
13
|
-
# the ! doesn
|
14
|
-
# corresponds to a similar but bang-less method name.
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
9
|
+
#
|
10
|
+
# The ! in method names that end with ! means, "This method is dangerous"
|
11
|
+
# -- or, more precisely, this method is the "dangerous" version of an
|
12
|
+
# equivalent method, with the same name minus the !. "Danger" is
|
13
|
+
# relative; the ! doesn't mean anything at all unless the method name
|
14
|
+
# it's in corresponds to a similar but bang-less method name.
|
15
|
+
#
|
16
|
+
# Don't add ! to your destructive (receiver-changing) methods' names,
|
17
|
+
# unless you consider the changing to be "dangerous" and you have a
|
18
|
+
# "non-dangerous" equivalent method without the !. If some arbitrary
|
19
|
+
# subset of destructive methods end with !, then the whole point of !
|
20
|
+
# gets distorted and diluted, and ! ceases to convey any information
|
21
|
+
# whatsoever.
|
22
|
+
#
|
21
23
|
# Such a method is called PrimaDonnaMethod and is reported as a smell.
|
24
|
+
#
|
22
25
|
class PrimaDonnaMethod < SmellDetector
|
23
26
|
SMELL_CLASS = smell_class_name
|
24
27
|
SMELL_SUBCLASS = smell_class_name
|
@@ -29,11 +32,16 @@ module Reek
|
|
29
32
|
|
30
33
|
def examine_context(ctx)
|
31
34
|
ctx.node_instance_methods.map do |method_sexp|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
next unless method_sexp.ends_with_bang?
|
36
|
+
|
37
|
+
version_without_bang = ctx.node_instance_methods.find do |sexp_item|
|
38
|
+
sexp_item.name.to_s == method_sexp.name_without_bang
|
36
39
|
end
|
40
|
+
next if version_without_bang
|
41
|
+
|
42
|
+
SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
43
|
+
"has prima donna method `#{method_sexp.name}`",
|
44
|
+
@source, SMELL_SUBCLASS)
|
37
45
|
end.compact
|
38
46
|
end
|
39
47
|
end
|