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
@@ -47,13 +47,13 @@ module Reek
|
|
47
47
|
# @return [Array<SmellWarning>]
|
48
48
|
#
|
49
49
|
def examine_context(ctx)
|
50
|
-
|
51
|
-
|
50
|
+
reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
51
|
+
accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
52
52
|
name = ctx.name.to_s
|
53
|
-
return [] if
|
53
|
+
return [] if accept_names.include?(ctx.full_name)
|
54
54
|
var = name.gsub(/^[@\*\&]*/, '')
|
55
|
-
return [] if
|
56
|
-
return [] unless
|
55
|
+
return [] if accept_names.include?(var)
|
56
|
+
return [] unless reject_names.find { |patt| patt =~ var }
|
57
57
|
[SmellWarning.new(self,
|
58
58
|
context: ctx.full_name,
|
59
59
|
lines: [ctx.exp.line],
|
@@ -52,15 +52,15 @@ module Reek
|
|
52
52
|
#
|
53
53
|
# :reek:Duplication { allow_calls: [ to_s ] }
|
54
54
|
def examine_context(ctx)
|
55
|
-
|
56
|
-
|
55
|
+
reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
56
|
+
accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
57
57
|
exp = ctx.exp
|
58
58
|
full_name = ctx.full_name
|
59
59
|
name = exp.simple_name.to_s
|
60
|
-
return [] if
|
60
|
+
return [] if accept_names.include?(full_name)
|
61
61
|
var = name.gsub(/^[@\*\&]*/, '')
|
62
|
-
return [] if
|
63
|
-
return [] unless
|
62
|
+
return [] if accept_names.include?(var)
|
63
|
+
return [] unless reject_names.find { |patt| patt =~ var }
|
64
64
|
[SmellWarning.new(self,
|
65
65
|
context: full_name,
|
66
66
|
lines: [exp.line],
|
@@ -47,8 +47,8 @@ module Reek
|
|
47
47
|
# @return [Array<SmellWarning>]
|
48
48
|
#
|
49
49
|
def examine_context(ctx)
|
50
|
-
|
51
|
-
|
50
|
+
self.reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
51
|
+
self.accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
52
52
|
context_expression = ctx.exp
|
53
53
|
context_expression.parameter_names.select do |name|
|
54
54
|
bad_name?(name) && ctx.uses_param?(name)
|
@@ -63,9 +63,13 @@ module Reek
|
|
63
63
|
|
64
64
|
def bad_name?(name)
|
65
65
|
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
66
|
-
return false if var == '*' ||
|
67
|
-
|
66
|
+
return false if var == '*' || accept_names.include?(var)
|
67
|
+
reject_names.find { |patt| patt =~ var }
|
68
68
|
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
private_attr_accessor :accept_names, :reject_names
|
69
73
|
end
|
70
74
|
end
|
71
75
|
end
|
@@ -51,8 +51,8 @@ module Reek
|
|
51
51
|
# @return [Array<SmellWarning>]
|
52
52
|
#
|
53
53
|
def examine_context(ctx)
|
54
|
-
|
55
|
-
|
54
|
+
self.reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
55
|
+
self.accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
56
56
|
variable_names(ctx.exp).select do |name, _lines|
|
57
57
|
bad_name?(name, ctx)
|
58
58
|
end.map do |name, lines|
|
@@ -66,8 +66,8 @@ module Reek
|
|
66
66
|
|
67
67
|
def bad_name?(name, _ctx)
|
68
68
|
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
69
|
-
return false if
|
70
|
-
|
69
|
+
return false if accept_names.include?(var)
|
70
|
+
reject_names.find { |patt| patt =~ var }
|
71
71
|
end
|
72
72
|
|
73
73
|
def variable_names(exp)
|
@@ -121,6 +121,10 @@ module Reek
|
|
121
121
|
var = varname.to_sym
|
122
122
|
accumulator[var].push(exp.line)
|
123
123
|
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
private_attr_accessor :accept_names, :reject_names
|
124
128
|
end
|
125
129
|
end
|
126
130
|
end
|
@@ -20,7 +20,7 @@ module Reek
|
|
20
20
|
|
21
21
|
# Initializer.
|
22
22
|
#
|
23
|
-
# code -
|
23
|
+
# code - Ruby code as String
|
24
24
|
# description - 'STDIN', 'string' or a filepath as String
|
25
25
|
# parser - the parser to use for generating AST's out of the given source
|
26
26
|
def initialize(code, description, parser = Parser::Ruby22)
|
@@ -82,7 +82,7 @@ module Reek
|
|
82
82
|
@syntax_tree ||=
|
83
83
|
begin
|
84
84
|
begin
|
85
|
-
ast, comments =
|
85
|
+
ast, comments = parser.parse_with_comments(source, description)
|
86
86
|
rescue Racc::ParseError, Parser::SyntaxError => error
|
87
87
|
$stderr.puts "#{description}: #{error.class.name}: #{error}"
|
88
88
|
end
|
@@ -92,6 +92,10 @@ module Reek
|
|
92
92
|
TreeDresser.new.dress(ast, comment_map)
|
93
93
|
end
|
94
94
|
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
private_attr_reader :parser, :source
|
95
99
|
end
|
96
100
|
end
|
97
101
|
end
|
@@ -12,15 +12,15 @@ module Reek
|
|
12
12
|
#
|
13
13
|
# paths - a list of paths as Strings
|
14
14
|
def initialize(paths, configuration: Configuration::AppConfiguration.new)
|
15
|
-
|
15
|
+
@paths = paths.flat_map do |string|
|
16
16
|
path = Pathname.new(string)
|
17
17
|
current_directory?(path) ? path.entries : path
|
18
18
|
end
|
19
|
-
|
19
|
+
@configuration = configuration
|
20
20
|
end
|
21
21
|
|
22
22
|
# Traverses all paths we initialized the SourceLocator with, finds
|
23
|
-
# all relevant
|
23
|
+
# all relevant Ruby files and returns them as a list.
|
24
24
|
#
|
25
25
|
# @return [Array<Pathname>] - Ruby paths found
|
26
26
|
def sources
|
@@ -29,7 +29,7 @@ module Reek
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
|
32
|
+
private_attr_reader :configuration, :paths
|
33
33
|
|
34
34
|
def source_paths
|
35
35
|
paths.each_with_object([]) do |given_path, relevant_paths|
|
@@ -13,18 +13,23 @@ module Reek
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def matches?(actual)
|
16
|
-
|
17
|
-
|
16
|
+
self.examiner = Examiner.new(actual, configuration: configuration)
|
17
|
+
examiner.smelly?
|
18
18
|
end
|
19
19
|
|
20
20
|
def failure_message
|
21
|
-
"Expected #{
|
21
|
+
"Expected #{examiner.description} to reek, but it didn't"
|
22
22
|
end
|
23
23
|
|
24
24
|
def failure_message_when_negated
|
25
|
-
rpt = Report::Formatter.format_list(
|
25
|
+
rpt = Report::Formatter.format_list(examiner.smells)
|
26
26
|
"Expected no smells, but got:\n#{rpt}"
|
27
27
|
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
private_attr_reader :configuration
|
32
|
+
private_attr_accessor :examiner
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
@@ -17,21 +17,24 @@ module Reek
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def matches?(actual)
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
self.examiner = Examiner.new(actual, configuration: configuration)
|
21
|
+
self.all_smells = examiner.smells
|
22
|
+
all_smells.any? { |warning| warning.matches?(smell_category, smell_details) }
|
23
23
|
end
|
24
24
|
|
25
25
|
def failure_message
|
26
|
-
"Expected #{
|
26
|
+
"Expected #{examiner.description} to reek of #{smell_category}, but it didn't"
|
27
27
|
end
|
28
28
|
|
29
29
|
def failure_message_when_negated
|
30
|
-
"Expected #{
|
30
|
+
"Expected #{examiner.description} not to reek of #{smell_category}, but it did"
|
31
31
|
end
|
32
32
|
|
33
33
|
private
|
34
34
|
|
35
|
+
private_attr_reader :configuration, :smell_category, :smell_details
|
36
|
+
private_attr_accessor :all_smells, :examiner
|
37
|
+
|
35
38
|
def normalize(smell_category_or_type)
|
36
39
|
# In theory, users can give us many different types of input (see the documentation for
|
37
40
|
# reek_of below), however we're basically ignoring all of those subleties and just
|
@@ -10,24 +10,28 @@ module Reek
|
|
10
10
|
# @api private
|
11
11
|
class ShouldReekOnlyOf < ShouldReekOf
|
12
12
|
def matches?(actual)
|
13
|
-
matches_examiner?(Examiner.new(actual, configuration:
|
13
|
+
matches_examiner?(Examiner.new(actual, configuration: configuration))
|
14
14
|
end
|
15
15
|
|
16
16
|
def matches_examiner?(examiner)
|
17
|
-
|
18
|
-
|
19
|
-
return false if
|
20
|
-
|
17
|
+
self.examiner = examiner
|
18
|
+
self.warnings = examiner.smells
|
19
|
+
return false if warnings.empty?
|
20
|
+
warnings.all? { |warning| warning.matches?(smell_category) }
|
21
21
|
end
|
22
22
|
|
23
23
|
def failure_message
|
24
|
-
rpt = Report::Formatter.format_list(
|
25
|
-
"Expected #{
|
24
|
+
rpt = Report::Formatter.format_list(warnings)
|
25
|
+
"Expected #{examiner.description} to reek only of #{smell_category}, but got:\n#{rpt}"
|
26
26
|
end
|
27
27
|
|
28
28
|
def failure_message_when_negated
|
29
|
-
"Expected #{
|
29
|
+
"Expected #{examiner.description} not to reek only of #{smell_category}, but it did"
|
30
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
private_attr_accessor :warnings
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
data/lib/reek/tree_dresser.rb
CHANGED
@@ -25,8 +25,12 @@ module Reek
|
|
25
25
|
type = sexp.type
|
26
26
|
children = sexp.children.map { |child| dress(child, comment_map, sexp) }
|
27
27
|
comments = comment_map[sexp]
|
28
|
-
|
29
|
-
|
28
|
+
klass_map.klass_for(type).new(type, children,
|
29
|
+
location: sexp.loc, comments: comments, parent: parent)
|
30
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
private_attr_reader :klass_map
|
31
35
|
end
|
32
36
|
end
|
data/lib/reek/tree_walker.rb
CHANGED
@@ -24,14 +24,20 @@ module Reek
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def walk
|
27
|
-
|
28
|
-
|
29
|
-
@smell_repository.examine(element)
|
27
|
+
result.each do |element|
|
28
|
+
smell_repository.examine(element)
|
30
29
|
end
|
31
30
|
end
|
32
31
|
|
33
32
|
private
|
34
33
|
|
34
|
+
private_attr_accessor :element
|
35
|
+
private_attr_reader :exp, :smell_repository
|
36
|
+
|
37
|
+
def result
|
38
|
+
@result ||= process(exp)
|
39
|
+
end
|
40
|
+
|
35
41
|
def process(exp)
|
36
42
|
context_processor = "process_#{exp.type}"
|
37
43
|
if context_processor_exists?(context_processor)
|
@@ -39,7 +45,7 @@ module Reek
|
|
39
45
|
else
|
40
46
|
process_default exp
|
41
47
|
end
|
42
|
-
|
48
|
+
element
|
43
49
|
end
|
44
50
|
|
45
51
|
def process_module(exp)
|
@@ -86,28 +92,28 @@ module Reek
|
|
86
92
|
|
87
93
|
def process_send(exp)
|
88
94
|
if visibility_modifier? exp
|
89
|
-
|
95
|
+
element.track_visibility(exp.method_name, exp.arg_names)
|
90
96
|
end
|
91
|
-
|
97
|
+
element.record_call_to(exp)
|
92
98
|
process_default(exp)
|
93
99
|
end
|
94
100
|
|
95
101
|
def process_attrasgn(exp)
|
96
|
-
|
102
|
+
element.record_call_to(exp)
|
97
103
|
process_default(exp)
|
98
104
|
end
|
99
105
|
|
100
106
|
alias_method :process_op_asgn, :process_attrasgn
|
101
107
|
|
102
108
|
def process_ivar(exp)
|
103
|
-
|
109
|
+
element.record_use_of_self
|
104
110
|
process_default(exp)
|
105
111
|
end
|
106
112
|
|
107
113
|
alias_method :process_ivasgn, :process_ivar
|
108
114
|
|
109
115
|
def process_self(_)
|
110
|
-
|
116
|
+
element.record_use_of_self
|
111
117
|
end
|
112
118
|
|
113
119
|
alias_method :process_zsuper, :process_self
|
@@ -123,7 +129,7 @@ module Reek
|
|
123
129
|
|
124
130
|
def process_begin(exp)
|
125
131
|
count_statement_list(exp.children)
|
126
|
-
|
132
|
+
element.count_statements(-1)
|
127
133
|
process_default(exp)
|
128
134
|
end
|
129
135
|
|
@@ -132,13 +138,13 @@ module Reek
|
|
132
138
|
def process_if(exp)
|
133
139
|
count_clause(exp[2])
|
134
140
|
count_clause(exp[3])
|
135
|
-
|
141
|
+
element.count_statements(-1)
|
136
142
|
process_default(exp)
|
137
143
|
end
|
138
144
|
|
139
145
|
def process_while(exp)
|
140
146
|
count_clause(exp[2])
|
141
|
-
|
147
|
+
element.count_statements(-1)
|
142
148
|
process_default(exp)
|
143
149
|
end
|
144
150
|
|
@@ -146,13 +152,13 @@ module Reek
|
|
146
152
|
|
147
153
|
def process_for(exp)
|
148
154
|
count_clause(exp[3])
|
149
|
-
|
155
|
+
element.count_statements(-1)
|
150
156
|
process_default(exp)
|
151
157
|
end
|
152
158
|
|
153
159
|
def process_rescue(exp)
|
154
160
|
count_clause(exp[1])
|
155
|
-
|
161
|
+
element.count_statements(-1)
|
156
162
|
process_default(exp)
|
157
163
|
end
|
158
164
|
|
@@ -163,7 +169,7 @@ module Reek
|
|
163
169
|
|
164
170
|
def process_case(exp)
|
165
171
|
count_clause(exp.else_body)
|
166
|
-
|
172
|
+
element.count_statements(-1)
|
167
173
|
process_default(exp)
|
168
174
|
end
|
169
175
|
|
@@ -177,16 +183,16 @@ module Reek
|
|
177
183
|
end
|
178
184
|
|
179
185
|
def count_clause(sexp)
|
180
|
-
|
186
|
+
element.count_statements(1) if sexp
|
181
187
|
end
|
182
188
|
|
183
189
|
def count_statement_list(statement_list)
|
184
|
-
|
190
|
+
element.count_statements statement_list.length
|
185
191
|
end
|
186
192
|
|
187
193
|
def inside_new_context(klass, exp)
|
188
|
-
scope = klass.new(
|
189
|
-
|
194
|
+
scope = klass.new(element, exp)
|
195
|
+
element.append_child_context(scope)
|
190
196
|
push(scope) do
|
191
197
|
yield
|
192
198
|
end
|
@@ -194,10 +200,10 @@ module Reek
|
|
194
200
|
end
|
195
201
|
|
196
202
|
def push(scope)
|
197
|
-
orig =
|
198
|
-
|
203
|
+
orig = element
|
204
|
+
self.element = scope
|
199
205
|
yield
|
200
|
-
|
206
|
+
self.element = orig
|
201
207
|
end
|
202
208
|
|
203
209
|
# FIXME: Move to SendNode?
|
data/lib/reek/version.rb
CHANGED
data/reek.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.license = 'MIT'
|
16
16
|
s.email = ['timo.roessner@googlemail.com']
|
17
|
-
s.extra_rdoc_files = ['CHANGELOG', 'License.txt']
|
17
|
+
s.extra_rdoc_files = ['CHANGELOG.md', 'License.txt']
|
18
18
|
s.files = `git ls-files -z`.split("\0")
|
19
19
|
s.executables = s.files.grep(%r{^bin/}).map { |path| File.basename(path) }
|
20
20
|
s.homepage = 'https://github.com/troessner/reek/wiki'
|
@@ -22,12 +22,13 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.required_ruby_version = '>= 2.0.0'
|
23
23
|
s.summary = 'Code smell detector for Ruby'
|
24
24
|
|
25
|
-
s.add_runtime_dependency 'parser',
|
26
|
-
s.add_runtime_dependency '
|
27
|
-
s.add_runtime_dependency '
|
25
|
+
s.add_runtime_dependency 'parser', '~> 2.2.2.5'
|
26
|
+
s.add_runtime_dependency 'private_attr', '~> 1.1'
|
27
|
+
s.add_runtime_dependency 'rainbow', '~> 2.0'
|
28
|
+
s.add_runtime_dependency 'unparser', '~> 0.2.2'
|
28
29
|
|
29
30
|
s.add_development_dependency 'activesupport', '~> 4.2'
|
30
|
-
s.add_development_dependency 'aruba', '~> 0.
|
31
|
+
s.add_development_dependency 'aruba', '~> 0.8.0'
|
31
32
|
s.add_development_dependency 'ataru', '~> 0.2.0'
|
32
33
|
s.add_development_dependency 'bundler', '~> 1.1'
|
33
34
|
s.add_development_dependency 'cucumber', '~> 2.0'
|