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
data/lib/reek/rake/task.rb
CHANGED
@@ -4,14 +4,12 @@ require 'rake'
|
|
4
4
|
require 'rake/tasklib'
|
5
5
|
|
6
6
|
module Reek
|
7
|
-
|
8
7
|
#
|
9
8
|
# Defines a task library for running reek.
|
10
9
|
# (Classes here will be configured via the Rakefile, and therefore will
|
11
10
|
# possess a :reek:attribute or two.)
|
12
11
|
#
|
13
12
|
module Rake
|
14
|
-
|
15
13
|
# A Rake task that runs reek on a set of source files.
|
16
14
|
#
|
17
15
|
# Example:
|
@@ -33,7 +31,6 @@ module Reek
|
|
33
31
|
# rake reek REEK_OPTS=-s # sorts the report by smell
|
34
32
|
#
|
35
33
|
class Task < ::Rake::TaskLib
|
36
|
-
|
37
34
|
# Name of reek task.
|
38
35
|
# Defaults to :reek.
|
39
36
|
attr_accessor :name
|
@@ -85,7 +82,7 @@ module Reek
|
|
85
82
|
define
|
86
83
|
end
|
87
84
|
|
88
|
-
|
85
|
+
private
|
89
86
|
|
90
87
|
def define # :nodoc:
|
91
88
|
desc 'Check for code smells' unless ::Rake.application.last_comment
|
@@ -97,7 +94,7 @@ module Reek
|
|
97
94
|
return if source_file_list.empty?
|
98
95
|
cmd = cmd_words.join(' ')
|
99
96
|
puts cmd if @verbose
|
100
|
-
raise('Smells found!') if !system(cmd)
|
97
|
+
raise('Smells found!') if !system(cmd) && fail_on_error
|
101
98
|
end
|
102
99
|
|
103
100
|
def self.reek_script
|
@@ -111,16 +108,16 @@ module Reek
|
|
111
108
|
def cmd_words
|
112
109
|
[Task.ruby_exe] +
|
113
110
|
ruby_options +
|
114
|
-
[
|
111
|
+
[%("#{Task.reek_script}")] +
|
115
112
|
[sort_option] +
|
116
|
-
config_file_list.
|
117
|
-
source_file_list.
|
113
|
+
config_file_list.map { |fn| ['-c', %("#{fn}")] }.flatten +
|
114
|
+
source_file_list.map { |fn| %("#{fn}") }
|
118
115
|
end
|
119
116
|
|
120
117
|
def config_file_list
|
121
118
|
files = ENV['REEK_CFG'] || @config_files
|
122
119
|
return [] unless files
|
123
|
-
|
120
|
+
FileList[files]
|
124
121
|
end
|
125
122
|
|
126
123
|
def ruby_options
|
@@ -143,7 +140,7 @@ module Reek
|
|
143
140
|
def source_file_list # :nodoc:
|
144
141
|
files = ENV['REEK_SRC'] || @source_files
|
145
142
|
return [] unless files
|
146
|
-
|
143
|
+
FileList[files]
|
147
144
|
end
|
148
145
|
end
|
149
146
|
end
|
data/lib/reek/smell_warning.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
module Reek
|
2
|
-
|
3
2
|
#
|
4
3
|
# Reports a warning that a smell has been found.
|
5
4
|
# This object is essentially a DTO, and therefore contains a :reek:attribute or two.
|
6
5
|
#
|
7
6
|
class SmellWarning
|
8
|
-
|
9
7
|
include Comparable
|
10
8
|
|
11
9
|
MESSAGE_KEY = 'message'
|
@@ -23,7 +21,7 @@ module Reek
|
|
23
21
|
@smell = {
|
24
22
|
CLASS_KEY => class_name,
|
25
23
|
SUBCLASS_KEY => subclass_name,
|
26
|
-
MESSAGE_KEY => message
|
24
|
+
MESSAGE_KEY => message
|
27
25
|
}
|
28
26
|
@smell.merge!(parameters)
|
29
27
|
@status = {
|
@@ -69,8 +67,6 @@ module Reek
|
|
69
67
|
#
|
70
68
|
attr_reader :status
|
71
69
|
|
72
|
-
def is_active() @status[ACTIVE_KEY] end
|
73
|
-
|
74
70
|
def hash
|
75
71
|
sort_key.hash
|
76
72
|
end
|
@@ -85,18 +81,18 @@ module Reek
|
|
85
81
|
|
86
82
|
def contains_all?(patterns)
|
87
83
|
rpt = sort_key.to_s
|
88
|
-
|
84
|
+
patterns.all? { |pattern| pattern =~ rpt }
|
89
85
|
end
|
90
86
|
|
91
87
|
def matches?(klass, patterns)
|
92
|
-
@smell.values.include?(klass.to_s)
|
88
|
+
@smell.values.include?(klass.to_s) && contains_all?(patterns)
|
93
89
|
end
|
94
90
|
|
95
91
|
def report_on(listener)
|
96
92
|
listener.found_smell(self)
|
97
93
|
end
|
98
94
|
|
99
|
-
|
95
|
+
protected
|
100
96
|
|
101
97
|
def sort_key
|
102
98
|
[@location[CONTEXT_KEY], @smell[MESSAGE_KEY], @smell[CLASS_KEY]]
|
data/lib/reek/smells.rb
CHANGED
@@ -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 class that publishes a getter or setter for an instance variable
|
10
9
|
# invites client classes to become too intimate with its inner workings,
|
@@ -14,13 +13,11 @@ module Reek
|
|
14
13
|
# +attr_reader+, +attr_writer+ and +attr_accessor+ -- including those
|
15
14
|
# that are private.
|
16
15
|
#
|
17
|
-
# TODO:
|
18
|
-
#
|
19
|
-
# * catch attributes declared "by hand"
|
16
|
+
# TODO: Eliminate private attributes
|
17
|
+
# TODO: Catch attributes declared "by hand"
|
20
18
|
#
|
21
19
|
class Attribute < SmellDetector
|
22
|
-
|
23
|
-
SMELL_CLASS = self.name.split(/::/)[-1]
|
20
|
+
SMELL_CLASS = name.split(/::/)[-1]
|
24
21
|
SMELL_SUBCLASS = SMELL_CLASS
|
25
22
|
|
26
23
|
ATTRIBUTE_KEY = 'attribute'
|
@@ -41,21 +38,21 @@ module Reek
|
|
41
38
|
def examine_context(ctx)
|
42
39
|
attributes_in(ctx).map do |attr, line|
|
43
40
|
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [line],
|
44
|
-
|
45
|
-
|
46
|
-
|
41
|
+
"declares the attribute #{attr}",
|
42
|
+
@source, SMELL_SUBCLASS,
|
43
|
+
ATTRIBUTE_KEY => attr.to_s)
|
47
44
|
smell
|
48
45
|
end
|
49
46
|
end
|
50
47
|
|
51
|
-
|
48
|
+
private
|
52
49
|
|
53
50
|
def attributes_in(module_ctx)
|
54
51
|
result = Set.new
|
55
52
|
attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
|
56
53
|
module_ctx.local_nodes(:call) do |call_node|
|
57
54
|
if attr_defn_methods.include?(call_node.method_name)
|
58
|
-
call_node.arg_names.each {|arg| result << [arg, call_node.line] }
|
55
|
+
call_node.arg_names.each { |arg| result << [arg, call_node.line] }
|
59
56
|
end
|
60
57
|
end
|
61
58
|
result
|
@@ -3,19 +3,17 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
#
|
8
7
|
# A Boolean parameter effectively permits a method's caller
|
9
8
|
# to decide which execution path to take. The
|
10
9
|
# offending parameter is a kind of Control Couple.
|
11
|
-
#
|
10
|
+
#
|
12
11
|
# Currently Reek can only detect a Boolean parameter when it has a
|
13
12
|
# default initializer.
|
14
13
|
#
|
15
14
|
class BooleanParameter < SmellDetector
|
16
|
-
|
17
15
|
SMELL_CLASS = 'ControlCouple'
|
18
|
-
SMELL_SUBCLASS =
|
16
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
19
17
|
|
20
18
|
PARAMETER_KEY = 'parameter'
|
21
19
|
|
@@ -25,13 +23,13 @@ module Reek
|
|
25
23
|
# @return [Array<SmellWarning>]
|
26
24
|
#
|
27
25
|
def examine_context(method_ctx)
|
28
|
-
method_ctx.parameters.default_assignments.select do |
|
26
|
+
method_ctx.parameters.default_assignments.select do |_param, value|
|
29
27
|
[:true, :false].include?(value[0])
|
30
|
-
end.map do |param,
|
28
|
+
end.map do |param, _value|
|
31
29
|
param_name = param.to_s
|
32
30
|
SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
33
31
|
"has boolean parameter '#{param_name}'",
|
34
|
-
@source, SMELL_SUBCLASS,
|
32
|
+
@source, SMELL_SUBCLASS, PARAMETER_KEY => param_name)
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
@@ -4,7 +4,6 @@ require 'reek/smell_warning'
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Smells
|
7
|
-
|
8
7
|
#
|
9
8
|
# Class variables form part of the global runtime state, and as such make
|
10
9
|
# it easy for one part of the system to accidentally or inadvertently
|
@@ -14,8 +13,7 @@ module Reek
|
|
14
13
|
# the context of the test includes all global state).
|
15
14
|
#
|
16
15
|
class ClassVariable < SmellDetector
|
17
|
-
|
18
|
-
SMELL_CLASS = self.name.split(/::/)[-1]
|
16
|
+
SMELL_CLASS = name.split(/::/)[-1]
|
19
17
|
SMELL_SUBCLASS = SMELL_CLASS
|
20
18
|
|
21
19
|
VARIABLE_KEY = 'variable'
|
@@ -31,10 +29,11 @@ module Reek
|
|
31
29
|
#
|
32
30
|
def examine_context(ctx)
|
33
31
|
class_variables_in(ctx.exp).map do |attr_name, lines|
|
32
|
+
attr_name = attr_name.to_s
|
34
33
|
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
"declares the class variable #{attr_name}",
|
35
|
+
@source, SMELL_SUBCLASS,
|
36
|
+
VARIABLE_KEY => attr_name)
|
38
37
|
smell
|
39
38
|
end
|
40
39
|
end
|
@@ -44,7 +43,7 @@ module Reek
|
|
44
43
|
# in the given module.
|
45
44
|
#
|
46
45
|
def class_variables_in(ast)
|
47
|
-
result = Hash.new {|hash,key| hash[key] = []}
|
46
|
+
result = Hash.new { |hash, key| hash[key] = [] }
|
48
47
|
collector = proc do |cvar_node|
|
49
48
|
result[cvar_node.name].push(cvar_node.line)
|
50
49
|
end
|
@@ -3,7 +3,6 @@ require 'reek/smell_warning'
|
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Smells
|
6
|
-
|
7
6
|
#
|
8
7
|
# Control Coupling occurs when a method or block checks the value of
|
9
8
|
# a parameter in order to decide which execution path to take. The
|
@@ -42,11 +41,9 @@ module Reek
|
|
42
41
|
# the source code.
|
43
42
|
#
|
44
43
|
class ControlParameter < SmellDetector
|
45
|
-
|
46
44
|
SMELL_CLASS = 'ControlCouple'
|
47
|
-
SMELL_SUBCLASS =
|
45
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
48
46
|
PARAMETER_KEY = 'parameter'
|
49
|
-
VALUE_POSITION = 1
|
50
47
|
|
51
48
|
#
|
52
49
|
# Checks whether the given method chooses its execution path
|
@@ -59,7 +56,7 @@ module Reek
|
|
59
56
|
SmellWarning.new(SMELL_CLASS, ctx.full_name, control_parameter.lines,
|
60
57
|
control_parameter.smell_message,
|
61
58
|
@source, SMELL_SUBCLASS,
|
62
|
-
|
59
|
+
PARAMETER_KEY => control_parameter.name)
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
@@ -67,13 +64,13 @@ module Reek
|
|
67
64
|
# Collects information about a single control parameter.
|
68
65
|
#
|
69
66
|
class FoundControlParameter
|
70
|
-
def initialize(param)
|
67
|
+
def initialize(param, occurences)
|
71
68
|
@param = param
|
72
|
-
@occurences =
|
69
|
+
@occurences = occurences
|
73
70
|
end
|
74
71
|
|
75
|
-
def
|
76
|
-
@occurences.
|
72
|
+
def smells?
|
73
|
+
@occurences.any?
|
77
74
|
end
|
78
75
|
|
79
76
|
def smell_message
|
@@ -89,6 +86,73 @@ module Reek
|
|
89
86
|
end
|
90
87
|
end
|
91
88
|
|
89
|
+
# Finds cases of ControlParameter in a particular node for a particular parameter
|
90
|
+
class ControlParameterFinder
|
91
|
+
def initialize(node, param)
|
92
|
+
@node = node
|
93
|
+
@param = param
|
94
|
+
end
|
95
|
+
|
96
|
+
def find_matches
|
97
|
+
return [] if legitimite_uses?
|
98
|
+
nested_finders.flat_map(&:find_matches) + uses_of_param_in_condition
|
99
|
+
end
|
100
|
+
|
101
|
+
def legitimite_uses?
|
102
|
+
return true if uses_param_in_body?
|
103
|
+
return true if uses_param_in_call_in_condition?
|
104
|
+
return true if nested_finders.any?(&:legitimite_uses?)
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def conditional_nodes
|
111
|
+
@node.body.unnested_nodes([:if, :case, :and, :or])
|
112
|
+
end
|
113
|
+
|
114
|
+
def nested_finders
|
115
|
+
@nested_finders ||= conditional_nodes.flat_map do |node|
|
116
|
+
self.class.new(node, @param)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def uses_param_in_call_in_condition?
|
121
|
+
return false unless (condition = @node.condition)
|
122
|
+
condition.each_node(:call) do |inner|
|
123
|
+
next unless regular_call_involving_param? inner
|
124
|
+
return true
|
125
|
+
end
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def uses_of_param_in_condition
|
130
|
+
return [] unless (condition = @node.condition)
|
131
|
+
condition.each_node(:lvar).select { |inner| inner.var_name == @param }
|
132
|
+
end
|
133
|
+
|
134
|
+
def regular_call_involving_param?(call_node)
|
135
|
+
call_involving_param?(call_node) && !comparison_call?(call_node)
|
136
|
+
end
|
137
|
+
|
138
|
+
def comparison_call?(call_node)
|
139
|
+
comparison_method_names.include? call_node.method_name
|
140
|
+
end
|
141
|
+
|
142
|
+
def comparison_method_names
|
143
|
+
[:==, :!=]
|
144
|
+
end
|
145
|
+
|
146
|
+
def call_involving_param?(call_node)
|
147
|
+
call_node.participants.any? { |it| it.var_name == @param }
|
148
|
+
end
|
149
|
+
|
150
|
+
def uses_param_in_body?
|
151
|
+
nodes = @node.body.each_node(:lvar, [:if, :case, :and, :or])
|
152
|
+
nodes.any? { |lvar_node| lvar_node.var_name == @param }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
92
156
|
#
|
93
157
|
# Collects all control parameters in a given context.
|
94
158
|
#
|
@@ -98,50 +162,19 @@ module Reek
|
|
98
162
|
end
|
99
163
|
|
100
164
|
def control_parameters
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
result[param].record(matches) if matches.any?
|
105
|
-
end
|
106
|
-
result.values
|
165
|
+
potential_parameters.
|
166
|
+
map { |param| FoundControlParameter.new(param, find_matches(param)) }.
|
167
|
+
select(&:smells?)
|
107
168
|
end
|
108
169
|
|
109
170
|
private
|
110
171
|
|
111
|
-
# Returns parameters that aren't used outside of a conditional statements and that
|
112
|
-
# could be good candidates for being a control parameter.
|
113
172
|
def potential_parameters
|
114
|
-
@context.exp.parameter_names
|
115
|
-
end
|
116
|
-
|
117
|
-
# Returns wether the parameter is used outside of the conditional statement.
|
118
|
-
def used_outside_conditional?(param)
|
119
|
-
nodes = @context.exp.each_node(:lvar, [:if, :case, :and, :or, :args])
|
120
|
-
nodes.any? {|node| node.value == param}
|
173
|
+
@context.exp.parameter_names
|
121
174
|
end
|
122
175
|
|
123
|
-
# Find the use of the param that match the definition of a control parameter.
|
124
176
|
def find_matches(param)
|
125
|
-
|
126
|
-
[:if, :case, :and, :or].each do |keyword|
|
127
|
-
@context.local_nodes(keyword).each do |node|
|
128
|
-
return [] if used_besides_in_condition?(node, param)
|
129
|
-
node.each_node(:lvar, []) {|inner| matches.push(inner) if inner.value == param}
|
130
|
-
end
|
131
|
-
end
|
132
|
-
matches
|
133
|
-
end
|
134
|
-
|
135
|
-
# Returns wether the parameter is used somewhere besides in the condition of the
|
136
|
-
# conditional statement.
|
137
|
-
def used_besides_in_condition?(node, param)
|
138
|
-
times_in_conditional, times_total = 0, 0
|
139
|
-
node.each_node(:lvar, [:if, :case]) {|lvar| times_total +=1 if lvar.value == param}
|
140
|
-
if node.condition
|
141
|
-
times_in_conditional += 1 if node.condition[VALUE_POSITION] == param
|
142
|
-
times_in_conditional += node.condition.count {|inner| inner.class == Sexp && inner[VALUE_POSITION] == param}
|
143
|
-
end
|
144
|
-
return times_total > times_in_conditional
|
177
|
+
ControlParameterFinder.new(@context.exp, param).find_matches
|
145
178
|
end
|
146
179
|
end
|
147
180
|
end
|
@@ -4,7 +4,6 @@ require 'reek/source'
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Smells
|
7
|
-
|
8
7
|
#
|
9
8
|
# A Data Clump occurs when the same two or three items frequently
|
10
9
|
# appear together in classes and parameter lists, or when a group
|
@@ -18,9 +17,8 @@ module Reek
|
|
18
17
|
# the same names that are expected by three or more methods of a class.
|
19
18
|
#
|
20
19
|
class DataClump < SmellDetector
|
21
|
-
|
22
|
-
|
23
|
-
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
20
|
+
SMELL_CLASS = name.split(/::/)[-1]
|
21
|
+
SMELL_SUBCLASS = name.split(/::/)[-1]
|
24
22
|
|
25
23
|
METHODS_KEY = 'methods'
|
26
24
|
OCCURRENCES_KEY = 'occurrences'
|
@@ -65,19 +63,18 @@ module Reek
|
|
65
63
|
@min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
|
66
64
|
MethodGroup.new(ctx, @min_clump_size, @max_copies).clumps.map do |clump, methods|
|
67
65
|
SmellWarning.new(SMELL_CLASS, ctx.full_name,
|
68
|
-
methods.map
|
66
|
+
methods.map(&:line),
|
69
67
|
"takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods",
|
70
|
-
@source, SMELL_SUBCLASS,
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
})
|
68
|
+
@source, SMELL_SUBCLASS,
|
69
|
+
PARAMETERS_KEY => clump.map(&:to_s),
|
70
|
+
OCCURRENCES_KEY => methods.length,
|
71
|
+
METHODS_KEY => methods.map(&:name))
|
75
72
|
end
|
76
73
|
end
|
77
74
|
|
78
75
|
# @private
|
79
76
|
def self.print_clump(clump)
|
80
|
-
"[#{clump.map
|
77
|
+
"[#{clump.map(&:to_s).join(', ')}]"
|
81
78
|
end
|
82
79
|
end
|
83
80
|
end
|
@@ -85,12 +82,12 @@ module Reek
|
|
85
82
|
# Represents a group of methods
|
86
83
|
# @private
|
87
84
|
class MethodGroup
|
88
|
-
|
89
85
|
def initialize(ctx, min_clump_size, max_copies)
|
90
86
|
@min_clump_size = min_clump_size
|
91
87
|
@max_copies = max_copies
|
92
|
-
@candidate_methods = ctx.local_nodes(:defn).map
|
93
|
-
CandidateMethod.new(defn_node)
|
88
|
+
@candidate_methods = ctx.local_nodes(:defn).map do |defn_node|
|
89
|
+
CandidateMethod.new(defn_node)
|
90
|
+
end
|
94
91
|
end
|
95
92
|
|
96
93
|
def candidate_clumps
|
@@ -102,7 +99,7 @@ module Reek
|
|
102
99
|
end
|
103
100
|
|
104
101
|
def common_argument_names_for(methods)
|
105
|
-
methods.
|
102
|
+
methods.map(&:arg_names).inject(:&)
|
106
103
|
end
|
107
104
|
|
108
105
|
def methods_containing_clump(clump)
|
@@ -121,7 +118,7 @@ module Reek
|
|
121
118
|
class CandidateMethod
|
122
119
|
def initialize(defn_node)
|
123
120
|
@defn = defn_node
|
124
|
-
@params = defn_node.arg_names.clone.sort {|first, second| first.to_s <=> second.to_s}
|
121
|
+
@params = defn_node.arg_names.clone.sort { |first, second| first.to_s <=> second.to_s }
|
125
122
|
end
|
126
123
|
|
127
124
|
def arg_names
|