reek 3.1 → 3.2
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/.travis.yml +1 -2
- data/{CHANGELOG → CHANGELOG.md} +150 -123
- data/README.md +61 -21
- data/Rakefile +2 -1
- data/bin/reek +1 -0
- data/config/defaults.reek +2 -2
- data/docs/Attribute.md +9 -13
- data/docs/Basic-Smell-Options.md +2 -2
- data/docs/Command-Line-Options.md +2 -2
- data/docs/Too-Many-Instance-Variables.md +1 -1
- data/features/samples.feature +22 -31
- data/features/step_definitions/sample_file_steps.rb +2 -2
- data/features/support/env.rb +1 -0
- data/lib/reek.rb +1 -0
- data/lib/reek/ast/ast_node_class_map.rb +5 -1
- data/lib/reek/ast/node.rb +4 -2
- data/lib/reek/ast/object_refs.rb +9 -5
- data/lib/reek/ast/reference_collector.rb +4 -2
- data/lib/reek/cli/application.rb +12 -9
- data/lib/reek/cli/command.rb +4 -0
- data/lib/reek/cli/input.rb +4 -4
- data/lib/reek/cli/option_interpreter.rb +11 -7
- data/lib/reek/cli/options.rb +42 -40
- data/lib/reek/cli/reek_command.rb +3 -3
- data/lib/reek/cli/warning_collector.rb +7 -3
- data/lib/reek/code_comment.rb +5 -1
- data/lib/reek/configuration/app_configuration.rb +4 -4
- data/lib/reek/context/code_context.rb +19 -17
- data/lib/reek/examiner.rb +8 -6
- data/lib/reek/rake/task.rb +13 -22
- data/lib/reek/report/formatter.rb +5 -1
- data/lib/reek/report/report.rb +46 -44
- data/lib/reek/smells/attribute.rb +42 -24
- data/lib/reek/smells/control_parameter.rb +21 -13
- data/lib/reek/smells/data_clump.rb +17 -9
- data/lib/reek/smells/duplicate_method_call.rb +12 -6
- data/lib/reek/smells/long_parameter_list.rb +2 -2
- data/lib/reek/smells/long_yield_list.rb +4 -4
- data/lib/reek/smells/nested_iterators.rb +4 -2
- data/lib/reek/smells/nil_check.rb +6 -2
- data/lib/reek/smells/repeated_conditional.rb +2 -2
- data/lib/reek/smells/smell_configuration.rb +15 -7
- data/lib/reek/smells/smell_detector.rb +23 -10
- data/lib/reek/smells/smell_repository.rb +9 -16
- data/lib/reek/smells/smell_warning.rb +6 -6
- data/lib/reek/smells/too_many_instance_variables.rb +4 -4
- data/lib/reek/smells/too_many_methods.rb +2 -2
- data/lib/reek/smells/too_many_statements.rb +4 -4
- data/lib/reek/smells/uncommunicative_method_name.rb +5 -5
- data/lib/reek/smells/uncommunicative_module_name.rb +5 -5
- data/lib/reek/smells/uncommunicative_parameter_name.rb +8 -4
- data/lib/reek/smells/uncommunicative_variable_name.rb +8 -4
- data/lib/reek/source/source_code.rb +6 -2
- data/lib/reek/source/source_locator.rb +4 -4
- data/lib/reek/spec/should_reek.rb +9 -4
- data/lib/reek/spec/should_reek_of.rb +8 -5
- data/lib/reek/spec/should_reek_only_of.rb +12 -8
- data/lib/reek/tree_dresser.rb +6 -2
- data/lib/reek/tree_walker.rb +28 -22
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +6 -5
- data/spec/gem/yard_spec.rb +6 -9
- data/spec/reek/code_comment_spec.rb +1 -1
- data/spec/reek/report/xml_report_spec.rb +11 -21
- data/spec/reek/smells/attribute_spec.rb +73 -57
- data/spec/reek/smells/too_many_instance_variables_spec.rb +26 -12
- data/spec/reek/source/source_locator_spec.rb +2 -2
- data/spec/samples/checkstyle.xml +12 -1
- data/spec/spec_helper.rb +1 -0
- metadata +20 -7
- data/spec/samples/unusual_syntax.rb +0 -21
@@ -52,9 +52,9 @@ module Reek
|
|
52
52
|
# @return [Array<SmellWarning>]
|
53
53
|
#
|
54
54
|
def examine_context(ctx)
|
55
|
-
|
56
|
-
|
57
|
-
MethodGroup.new(ctx,
|
55
|
+
max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
|
56
|
+
min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
|
57
|
+
MethodGroup.new(ctx, min_clump_size, max_copies).clumps.map do |clump, methods|
|
58
58
|
print_clump = DataClump.print_clump(clump)
|
59
59
|
SmellWarning.new self,
|
60
60
|
context: ctx.full_name,
|
@@ -88,10 +88,10 @@ module Reek
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def candidate_clumps
|
91
|
-
|
91
|
+
candidate_methods.each_cons(max_copies + 1).map do |methods|
|
92
92
|
common_argument_names_for(methods)
|
93
93
|
end.select do |clump|
|
94
|
-
clump.length >=
|
94
|
+
clump.length >= min_clump_size
|
95
95
|
end.uniq
|
96
96
|
end
|
97
97
|
|
@@ -100,7 +100,7 @@ module Reek
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def methods_containing_clump(clump)
|
103
|
-
|
103
|
+
candidate_methods.select { |method| clump & method.arg_names == clump }
|
104
104
|
end
|
105
105
|
|
106
106
|
def clumps
|
@@ -108,6 +108,10 @@ module Reek
|
|
108
108
|
[clump, methods_containing_clump(clump)]
|
109
109
|
end
|
110
110
|
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
private_attr_reader :candidate_methods, :max_copies, :min_clump_size
|
111
115
|
end
|
112
116
|
|
113
117
|
# A method definition and a copy of its parameters
|
@@ -118,15 +122,19 @@ module Reek
|
|
118
122
|
end
|
119
123
|
|
120
124
|
def arg_names
|
121
|
-
@arg_names ||=
|
125
|
+
@arg_names ||= defn.arg_names.compact.sort
|
122
126
|
end
|
123
127
|
|
124
128
|
def line
|
125
|
-
|
129
|
+
defn.line
|
126
130
|
end
|
127
131
|
|
128
132
|
def name
|
129
|
-
|
133
|
+
defn.name.to_s # BUG: should report the symbols!
|
130
134
|
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
private_attr_reader :defn
|
131
139
|
end
|
132
140
|
end
|
@@ -68,20 +68,24 @@ module Reek
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def record(occurence)
|
71
|
-
|
71
|
+
occurences.push occurence
|
72
72
|
end
|
73
73
|
|
74
74
|
def call
|
75
|
-
@call ||=
|
75
|
+
@call ||= call_node.format_to_ruby
|
76
76
|
end
|
77
77
|
|
78
78
|
def occurs
|
79
|
-
|
79
|
+
occurences.length
|
80
80
|
end
|
81
81
|
|
82
82
|
def lines
|
83
|
-
|
83
|
+
occurences.map(&:line)
|
84
84
|
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
private_attr_reader :call_node, :occurences
|
85
89
|
end
|
86
90
|
|
87
91
|
# Collects all calls in a given context
|
@@ -106,6 +110,8 @@ module Reek
|
|
106
110
|
|
107
111
|
private
|
108
112
|
|
113
|
+
private_attr_reader :allow_calls, :max_allowed_calls
|
114
|
+
|
109
115
|
def collect_calls(result)
|
110
116
|
context.each_node(:send, [:mlhs]) do |call_node|
|
111
117
|
next if call_node.object_creation_call?
|
@@ -118,7 +124,7 @@ module Reek
|
|
118
124
|
end
|
119
125
|
|
120
126
|
def smelly_call?(found_call)
|
121
|
-
found_call.occurs >
|
127
|
+
found_call.occurs > max_allowed_calls && !allow_calls?(found_call.call)
|
122
128
|
end
|
123
129
|
|
124
130
|
def simple_method_call?(call_node)
|
@@ -126,7 +132,7 @@ module Reek
|
|
126
132
|
end
|
127
133
|
|
128
134
|
def allow_calls?(method)
|
129
|
-
|
135
|
+
allow_calls.any? { |allow| /#{allow}/ =~ method }
|
130
136
|
end
|
131
137
|
end
|
132
138
|
end
|
@@ -35,9 +35,9 @@ module Reek
|
|
35
35
|
# @return [Array<SmellWarning>]
|
36
36
|
#
|
37
37
|
def examine_context(ctx)
|
38
|
-
|
38
|
+
max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx, DEFAULT_MAX_ALLOWED_PARAMS)
|
39
39
|
count = ctx.exp.arg_names.length
|
40
|
-
return [] if count <=
|
40
|
+
return [] if count <= max_allowed_params
|
41
41
|
[SmellWarning.new(self,
|
42
42
|
context: ctx.full_name,
|
43
43
|
lines: [ctx.exp.line],
|
@@ -29,11 +29,11 @@ module Reek
|
|
29
29
|
# @return [Array<SmellWarning>]
|
30
30
|
#
|
31
31
|
def examine_context(method_ctx)
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY,
|
33
|
+
method_ctx,
|
34
|
+
DEFAULT_MAX_ALLOWED_PARAMS)
|
35
35
|
method_ctx.local_nodes(:yield).select do |yield_node|
|
36
|
-
yield_node.args.length >
|
36
|
+
yield_node.args.length > max_allowed_params
|
37
37
|
end.map do |yield_node|
|
38
38
|
count = yield_node.args.length
|
39
39
|
SmellWarning.new self,
|
@@ -50,8 +50,10 @@ module Reek
|
|
50
50
|
|
51
51
|
private
|
52
52
|
|
53
|
+
private_attr_accessor :ignore_iterators
|
54
|
+
|
53
55
|
def find_deepest_iterator(ctx)
|
54
|
-
|
56
|
+
self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
|
55
57
|
|
56
58
|
find_iters(ctx.exp, 1).sort_by { |item| item[1] }.last
|
57
59
|
end
|
@@ -73,7 +75,7 @@ module Reek
|
|
73
75
|
|
74
76
|
def ignored_iterator?(exp)
|
75
77
|
name = exp.call.method_name.to_s
|
76
|
-
|
78
|
+
ignore_iterators.any? { |pattern| /#{pattern}/ =~ name }
|
77
79
|
end
|
78
80
|
end
|
79
81
|
end
|
@@ -36,10 +36,14 @@ module Reek
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def smelly_nodes
|
39
|
-
|
40
|
-
|
39
|
+
nodes.select do |when_node|
|
40
|
+
detector.detect(when_node)
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
private_attr_reader :detector, :nodes
|
43
47
|
end
|
44
48
|
|
45
49
|
# Detect 'call' nodes which perform a nil check.
|
@@ -51,9 +51,9 @@ module Reek
|
|
51
51
|
# @return [Array<SmellWarning>]
|
52
52
|
#
|
53
53
|
def examine_context(ctx)
|
54
|
-
|
54
|
+
max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx, DEFAULT_MAX_IFS)
|
55
55
|
conditional_counts(ctx).select do |_key, lines|
|
56
|
-
lines.length >
|
56
|
+
lines.length > max_identical_ifs
|
57
57
|
end.map do |key, lines|
|
58
58
|
occurs = lines.length
|
59
59
|
expression = key.format_to_ruby
|
@@ -17,8 +17,8 @@ module Reek
|
|
17
17
|
@options = hash
|
18
18
|
end
|
19
19
|
|
20
|
-
def merge!(
|
21
|
-
|
20
|
+
def merge!(new_options)
|
21
|
+
options.merge!(new_options)
|
22
22
|
end
|
23
23
|
|
24
24
|
#
|
@@ -26,11 +26,11 @@ module Reek
|
|
26
26
|
#--
|
27
27
|
# SMELL: Getter
|
28
28
|
def enabled?
|
29
|
-
|
29
|
+
options[ENABLED_KEY]
|
30
30
|
end
|
31
31
|
|
32
32
|
def overrides_for(context)
|
33
|
-
Overrides.new(
|
33
|
+
Overrides.new(options.fetch(OVERRIDES_KEY, {})).for_context(context)
|
34
34
|
end
|
35
35
|
|
36
36
|
# Retrieves the value, if any, for the given +key+.
|
@@ -39,8 +39,12 @@ module Reek
|
|
39
39
|
#
|
40
40
|
def value(key, context, fall_back)
|
41
41
|
overrides_for(context).each { |conf| return conf[key] if conf.key?(key) }
|
42
|
-
|
42
|
+
options.fetch(key, fall_back)
|
43
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
private_attr_reader :options
|
44
48
|
end
|
45
49
|
|
46
50
|
#
|
@@ -54,9 +58,13 @@ module Reek
|
|
54
58
|
|
55
59
|
# Find any overrides that match the supplied context
|
56
60
|
def for_context(context)
|
57
|
-
contexts =
|
58
|
-
contexts.map { |exc|
|
61
|
+
contexts = hash.keys.select { |ckey| context.matches?([ckey]) }
|
62
|
+
contexts.map { |exc| hash[exc] }
|
59
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
private_attr_reader :hash
|
60
68
|
end
|
61
69
|
end
|
62
70
|
end
|
@@ -38,12 +38,17 @@ module Reek
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def inherited(subclass)
|
41
|
-
|
42
|
-
@subclasses << subclass
|
41
|
+
subclasses << subclass
|
43
42
|
end
|
44
43
|
|
45
44
|
def descendants
|
46
|
-
|
45
|
+
subclasses
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def subclasses
|
51
|
+
@subclasses ||= []
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
@@ -78,17 +83,17 @@ module Reek
|
|
78
83
|
end
|
79
84
|
|
80
85
|
def register(hooks)
|
81
|
-
return unless
|
86
|
+
return unless config.enabled?
|
82
87
|
self.class.contexts.each { |ctx| hooks[ctx] << self }
|
83
88
|
end
|
84
89
|
|
85
90
|
# SMELL: Getter (only used in 1 test)
|
86
91
|
def enabled?
|
87
|
-
|
92
|
+
config.enabled?
|
88
93
|
end
|
89
94
|
|
90
|
-
def configure_with(
|
91
|
-
|
95
|
+
def configure_with(new_config)
|
96
|
+
config.merge!(new_config)
|
92
97
|
end
|
93
98
|
|
94
99
|
def examine(context)
|
@@ -96,7 +101,7 @@ module Reek
|
|
96
101
|
return if exception?(context)
|
97
102
|
|
98
103
|
sm = examine_context(context)
|
99
|
-
|
104
|
+
self.smells_found += sm
|
100
105
|
end
|
101
106
|
|
102
107
|
def enabled_for?(context)
|
@@ -108,16 +113,24 @@ module Reek
|
|
108
113
|
end
|
109
114
|
|
110
115
|
def report_on(report)
|
111
|
-
|
116
|
+
smells_found.each { |smell| smell.report_on(report) }
|
112
117
|
end
|
113
118
|
|
114
119
|
def value(key, ctx, fall_back)
|
115
|
-
config_for(ctx)[key] ||
|
120
|
+
config_for(ctx)[key] || config.value(key, ctx, fall_back)
|
116
121
|
end
|
117
122
|
|
118
123
|
def config_for(ctx)
|
119
124
|
ctx.config_for(self.class)
|
120
125
|
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
attr_writer :smells_found
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
private_attr_reader :config
|
121
134
|
end
|
122
135
|
end
|
123
136
|
end
|
@@ -16,10 +16,9 @@ module Reek
|
|
16
16
|
def initialize(source_description: nil,
|
17
17
|
smell_types: self.class.smell_types,
|
18
18
|
configuration: Configuration::AppConfiguration.new)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
self.smell_types = smell_types
|
19
|
+
@source_via = source_description
|
20
|
+
@configuration = configuration
|
21
|
+
@smell_types = smell_types
|
23
22
|
|
24
23
|
configuration.directive_for(source_via).each do |klass, config|
|
25
24
|
configure klass, config
|
@@ -43,25 +42,19 @@ module Reek
|
|
43
42
|
end
|
44
43
|
|
45
44
|
def detectors
|
46
|
-
@initialized_detectors ||=
|
47
|
-
|
48
|
-
|
49
|
-
@detectors[klass] = klass.new(source_via)
|
50
|
-
end
|
51
|
-
@detectors
|
52
|
-
end
|
45
|
+
@initialized_detectors ||= smell_types.map do |klass|
|
46
|
+
{ klass => klass.new(source_via) }
|
47
|
+
end.reduce({}, :merge)
|
53
48
|
end
|
54
49
|
|
55
50
|
private
|
56
51
|
|
57
|
-
|
52
|
+
private_attr_reader :configuration, :source_via, :smell_types
|
58
53
|
|
59
54
|
def smell_listeners
|
60
|
-
|
61
|
-
|
62
|
-
detectors.each_value { |detector| detector.register(typed_detectors) }
|
55
|
+
@smell_listeners ||= Hash.new { |hash, key| hash[key] = [] }.tap do |listeners|
|
56
|
+
detectors.each_value { |detector| detector.register(listeners) }
|
63
57
|
end
|
64
|
-
typed_detectors
|
65
58
|
end
|
66
59
|
end
|
67
60
|
end
|
@@ -8,15 +8,15 @@ module Reek
|
|
8
8
|
class SmellWarning
|
9
9
|
include Comparable
|
10
10
|
extend Forwardable
|
11
|
-
|
11
|
+
attr_reader :context, :lines, :message, :parameters, :smell_detector
|
12
12
|
def_delegators :smell_detector, :smell_category, :smell_type, :source
|
13
13
|
|
14
14
|
def initialize(smell_detector, options = {})
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
@smell_detector = smell_detector
|
16
|
+
@context = options.fetch(:context, '').to_s
|
17
|
+
@lines = options.fetch(:lines)
|
18
|
+
@message = options.fetch(:message)
|
19
|
+
@parameters = options.fetch(:parameters, {})
|
20
20
|
end
|
21
21
|
|
22
22
|
def hash
|
@@ -16,13 +16,13 @@ module Reek
|
|
16
16
|
# The name of the config field that sets the maximum number of instance
|
17
17
|
# variables permitted in a class.
|
18
18
|
MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
|
19
|
-
DEFAULT_MAX_IVARS =
|
19
|
+
DEFAULT_MAX_IVARS = 4
|
20
20
|
|
21
21
|
def self.smell_category
|
22
22
|
'LargeClass'
|
23
23
|
end
|
24
24
|
|
25
|
-
def self.contexts
|
25
|
+
def self.contexts
|
26
26
|
[:class]
|
27
27
|
end
|
28
28
|
|
@@ -39,9 +39,9 @@ module Reek
|
|
39
39
|
# @return [Array<SmellWarning>]
|
40
40
|
#
|
41
41
|
def examine_context(ctx)
|
42
|
-
|
42
|
+
max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx, DEFAULT_MAX_IVARS)
|
43
43
|
count = ctx.local_nodes(:ivasgn).map { |ivasgn| ivasgn[1] }.uniq.length
|
44
|
-
return [] if count <=
|
44
|
+
return [] if count <= max_allowed_ivars
|
45
45
|
[SmellWarning.new(self,
|
46
46
|
context: ctx.full_name,
|
47
47
|
lines: [ctx.exp.line],
|
@@ -41,9 +41,9 @@ module Reek
|
|
41
41
|
# @return [Array<SmellWarning>]
|
42
42
|
#
|
43
43
|
def examine_context(ctx)
|
44
|
-
|
44
|
+
max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx, DEFAULT_MAX_METHODS)
|
45
45
|
actual = ctx.node_instance_methods.length
|
46
|
-
return [] if actual <=
|
46
|
+
return [] if actual <= max_allowed_methods
|
47
47
|
[SmellWarning.new(self,
|
48
48
|
context: ctx.full_name,
|
49
49
|
lines: [ctx.exp.line],
|
@@ -33,11 +33,11 @@ module Reek
|
|
33
33
|
# @return [Array<SmellWarning>]
|
34
34
|
#
|
35
35
|
def examine_context(ctx)
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY,
|
37
|
+
ctx,
|
38
|
+
DEFAULT_MAX_STATEMENTS)
|
39
39
|
count = ctx.num_statements
|
40
|
-
return [] if count <=
|
40
|
+
return [] if count <= max_allowed_statements
|
41
41
|
[SmellWarning.new(self,
|
42
42
|
context: ctx.full_name,
|
43
43
|
lines: [ctx.exp.line],
|