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
data/lib/reek/rake/task.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'private_attr/everywhere'
|
3
4
|
require 'rake'
|
4
5
|
require 'rake/tasklib'
|
5
6
|
require 'pathname'
|
@@ -38,17 +39,17 @@ module Reek
|
|
38
39
|
|
39
40
|
# Path to reek's config file.
|
40
41
|
# Setting the REEK_CFG environment variable overrides this.
|
41
|
-
|
42
|
+
attr_accessor :config_file
|
42
43
|
|
43
44
|
# Glob pattern to match source files.
|
44
45
|
# Setting the REEK_SRC environment variable overrides this.
|
45
46
|
# Defaults to 'lib/**/*.rb'.
|
46
|
-
|
47
|
+
attr_accessor :source_files
|
47
48
|
|
48
49
|
# String containing commandline options to be passed to Reek.
|
49
50
|
# Setting the REEK_OPTS environment variable overrides this value.
|
50
51
|
# Defaults to ''.
|
51
|
-
|
52
|
+
attr_accessor :reek_opts
|
52
53
|
|
53
54
|
# Whether or not to fail Rake when an error occurs (typically when smells are found).
|
54
55
|
# Defaults to true.
|
@@ -59,10 +60,11 @@ module Reek
|
|
59
60
|
attr_writer :verbose
|
60
61
|
|
61
62
|
def initialize(name = :reek)
|
63
|
+
@config_file = ENV['REEK_CFG']
|
62
64
|
@name = name
|
63
|
-
@reek_opts = ''
|
65
|
+
@reek_opts = ENV['REEK_OPTS'] || ''
|
64
66
|
@fail_on_error = true
|
65
|
-
@source_files = 'lib/**/*.rb'
|
67
|
+
@source_files = ENV['REEK_SRC'] || 'lib/**/*.rb'
|
66
68
|
@verbose = false
|
67
69
|
|
68
70
|
yield self if block_given?
|
@@ -71,15 +73,17 @@ module Reek
|
|
71
73
|
|
72
74
|
private
|
73
75
|
|
76
|
+
private_attr_reader :fail_on_error, :name, :verbose
|
77
|
+
|
74
78
|
def define_task
|
75
79
|
desc 'Check for code smells'
|
76
|
-
task(
|
80
|
+
task(name) { run_task }
|
77
81
|
end
|
78
82
|
|
79
83
|
def run_task
|
80
|
-
puts "\n\n!!! Running 'reek' rake command: #{command}\n\n" if
|
84
|
+
puts "\n\n!!! Running 'reek' rake command: #{command}\n\n" if verbose
|
81
85
|
system(*command)
|
82
|
-
abort("\n\n!!! `reek` has found smells - exiting!") if sys_call_failed? &&
|
86
|
+
abort("\n\n!!! `reek` has found smells - exiting!") if sys_call_failed? && fail_on_error
|
83
87
|
end
|
84
88
|
|
85
89
|
def command
|
@@ -88,25 +92,12 @@ module Reek
|
|
88
92
|
reject(&:empty?)
|
89
93
|
end
|
90
94
|
|
91
|
-
def source_files
|
92
|
-
FileList[ENV['REEK_SRC'] || @source_files]
|
93
|
-
end
|
94
|
-
|
95
|
-
def reek_opts
|
96
|
-
ENV['REEK_OPTS'] || @reek_opts
|
97
|
-
end
|
98
|
-
|
99
|
-
def config_file
|
100
|
-
ENV['REEK_CFG'] || @config_file
|
101
|
-
end
|
102
|
-
|
103
95
|
def sys_call_failed?
|
104
96
|
!$CHILD_STATUS.success?
|
105
97
|
end
|
106
98
|
|
107
99
|
def config_file_as_argument
|
108
|
-
|
109
|
-
['-c', @config_file]
|
100
|
+
config_file ? ['-c', config_file] : []
|
110
101
|
end
|
111
102
|
|
112
103
|
def reek_opts_as_arguments
|
@@ -35,7 +35,7 @@ module Reek
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def format(warning)
|
38
|
-
"#{
|
38
|
+
"#{location_formatter.format(warning)}#{base_format(warning)}"
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
@@ -43,6 +43,10 @@ module Reek
|
|
43
43
|
def base_format(warning)
|
44
44
|
"#{warning.context} #{warning.message} (#{warning.smell_type})"
|
45
45
|
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
private_attr_reader :location_formatter
|
46
50
|
end
|
47
51
|
|
48
52
|
#
|
data/lib/reek/report/report.rb
CHANGED
@@ -30,8 +30,8 @@ module Reek
|
|
30
30
|
#
|
31
31
|
# @param [Reek::Examiner] examiner object to report on
|
32
32
|
def add_examiner(examiner)
|
33
|
-
|
34
|
-
|
33
|
+
self.total_smell_count += examiner.smells_count
|
34
|
+
examiners << examiner
|
35
35
|
self
|
36
36
|
end
|
37
37
|
|
@@ -42,13 +42,22 @@ module Reek
|
|
42
42
|
|
43
43
|
# @api private
|
44
44
|
def smells?
|
45
|
-
|
45
|
+
total_smell_count > 0
|
46
46
|
end
|
47
47
|
|
48
48
|
# @api private
|
49
49
|
def smells
|
50
|
-
|
50
|
+
examiners.map(&:smells).flatten
|
51
51
|
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
attr_accessor :total_smell_count
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
private_attr_reader :examiners, :options, :report_formatter,
|
60
|
+
:sort_by_issue_count, :warning_formatter
|
52
61
|
end
|
53
62
|
|
54
63
|
#
|
@@ -64,7 +73,7 @@ module Reek
|
|
64
73
|
private
|
65
74
|
|
66
75
|
def smell_summaries
|
67
|
-
|
76
|
+
examiners.map { |ex| summarize_single_examiner(ex) }.reject(&:empty?)
|
68
77
|
end
|
69
78
|
|
70
79
|
def display_summary
|
@@ -72,33 +81,33 @@ module Reek
|
|
72
81
|
end
|
73
82
|
|
74
83
|
def display_total_smell_count
|
75
|
-
return unless
|
84
|
+
return unless examiners.size > 1
|
76
85
|
print total_smell_count_message
|
77
86
|
end
|
78
87
|
|
79
88
|
def summarize_single_examiner(examiner)
|
80
89
|
result = heading_formatter.header(examiner)
|
81
90
|
if examiner.smelly?
|
82
|
-
formatted_list =
|
83
|
-
|
91
|
+
formatted_list = report_formatter.format_list(examiner.smells,
|
92
|
+
warning_formatter)
|
84
93
|
result += ":\n#{formatted_list}"
|
85
94
|
end
|
86
95
|
result
|
87
96
|
end
|
88
97
|
|
89
98
|
def sort_examiners
|
90
|
-
|
99
|
+
examiners.sort_by!(&:smells_count).reverse! if sort_by_issue_count
|
91
100
|
end
|
92
101
|
|
93
102
|
def total_smell_count_message
|
94
103
|
colour = smells? ? WARNINGS_COLOR : NO_WARNINGS_COLOR
|
95
|
-
s =
|
96
|
-
Rainbow("#{
|
104
|
+
s = total_smell_count == 1 ? '' : 's'
|
105
|
+
Rainbow("#{total_smell_count} total warning#{s}\n").color(colour)
|
97
106
|
end
|
98
107
|
|
99
108
|
def heading_formatter
|
100
109
|
@heading_formatter ||=
|
101
|
-
|
110
|
+
options.fetch(:heading_formatter, HeadingFormatter::Quiet).new(report_formatter)
|
102
111
|
end
|
103
112
|
end
|
104
113
|
|
@@ -118,7 +127,7 @@ module Reek
|
|
118
127
|
def show
|
119
128
|
print ::JSON.generate(
|
120
129
|
smells.map do |smell|
|
121
|
-
smell.yaml_hash(
|
130
|
+
smell.yaml_hash(warning_formatter)
|
122
131
|
end
|
123
132
|
)
|
124
133
|
end
|
@@ -144,53 +153,46 @@ module Reek
|
|
144
153
|
require 'rexml/document'
|
145
154
|
|
146
155
|
def show
|
147
|
-
|
148
|
-
|
149
|
-
smells.group_by(&:source).each do |file, file_smells|
|
150
|
-
file_to_xml(file, file_smells, checkstyle)
|
151
|
-
end
|
152
|
-
|
153
|
-
print_xml(checkstyle.parent)
|
156
|
+
document.write output: $stdout, indent: 2
|
157
|
+
$stdout.puts
|
154
158
|
end
|
155
159
|
|
156
160
|
private
|
157
161
|
|
158
162
|
def document
|
159
|
-
REXML::Document.new.tap do |
|
160
|
-
|
163
|
+
REXML::Document.new.tap do |document|
|
164
|
+
document << REXML::XMLDecl.new << checkstyle
|
161
165
|
end
|
162
166
|
end
|
163
167
|
|
164
|
-
def
|
165
|
-
REXML::Element.new('
|
166
|
-
|
167
|
-
|
168
|
+
def checkstyle
|
169
|
+
REXML::Element.new('checkstyle').tap do |checkstyle|
|
170
|
+
smells.group_by(&:source).each do |source, source_smells|
|
171
|
+
checkstyle << file(source, source_smells)
|
172
|
+
end
|
168
173
|
end
|
169
174
|
end
|
170
175
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
176
|
+
def file(name, smells)
|
177
|
+
REXML::Element.new('file').tap do |file|
|
178
|
+
file.add_attribute 'name', File.realpath(name)
|
179
|
+
smells.each do |smell|
|
180
|
+
smell.lines.each do |line|
|
181
|
+
file << error(smell, line)
|
182
|
+
end
|
183
|
+
end
|
174
184
|
end
|
175
185
|
end
|
176
186
|
|
177
|
-
def
|
178
|
-
REXML::Element.new('error'
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
['source', smell.smell_type]
|
185
|
-
]
|
186
|
-
element.add_attributes(attributes)
|
187
|
+
def error(smell, line)
|
188
|
+
REXML::Element.new('error').tap do |error|
|
189
|
+
error.add_attributes 'column' => 0,
|
190
|
+
'line' => line,
|
191
|
+
'message' => smell.message,
|
192
|
+
'severity' => 'warning',
|
193
|
+
'source' => smell.smell_type
|
187
194
|
end
|
188
195
|
end
|
189
|
-
|
190
|
-
def print_xml(document)
|
191
|
-
formatter = REXML::Formatters::Default.new
|
192
|
-
puts formatter.write(document, '')
|
193
|
-
end
|
194
196
|
end
|
195
197
|
end
|
196
198
|
end
|
@@ -9,22 +9,18 @@ module Reek
|
|
9
9
|
# invites client classes to become too intimate with its inner workings,
|
10
10
|
# and in particular with its representation of state.
|
11
11
|
#
|
12
|
-
#
|
13
|
-
# +
|
14
|
-
# that are private.
|
12
|
+
# This detector raises a warning for every public +attr_writer+,
|
13
|
+
# +attr_accessor+, and +attr+ with the writable flag set to +true+.
|
15
14
|
#
|
16
15
|
# See {file:docs/Attribute.md} for details.
|
17
16
|
# @api private
|
18
17
|
#
|
19
18
|
# TODO: Catch attributes declared "by hand"
|
20
19
|
class Attribute < SmellDetector
|
21
|
-
ATTR_DEFN_METHODS = [:
|
20
|
+
ATTR_DEFN_METHODS = [:attr_writer, :attr_accessor]
|
22
21
|
VISIBILITY_MODIFIERS = [:private, :public, :protected]
|
23
22
|
|
24
23
|
def initialize(*args)
|
25
|
-
@visiblity_tracker = {}
|
26
|
-
@visiblity_mode = :public
|
27
|
-
@result = Set.new
|
28
24
|
super
|
29
25
|
end
|
30
26
|
|
@@ -32,39 +28,61 @@ module Reek
|
|
32
28
|
[:class, :module]
|
33
29
|
end
|
34
30
|
|
35
|
-
def self.default_config
|
36
|
-
super.merge(SmellConfiguration::ENABLED_KEY => false)
|
37
|
-
end
|
38
|
-
|
39
31
|
#
|
40
32
|
# Checks whether the given class declares any attributes.
|
41
33
|
#
|
42
34
|
# @return [Array<SmellWarning>]
|
43
35
|
#
|
44
36
|
def examine_context(ctx)
|
37
|
+
self.visiblity_tracker = {}
|
38
|
+
self.visiblity_mode = :public
|
45
39
|
attributes_in(ctx).map do |attribute, line|
|
46
40
|
SmellWarning.new self,
|
47
41
|
context: ctx.full_name,
|
48
42
|
lines: [line],
|
49
|
-
message: "declares the attribute #{attribute}",
|
43
|
+
message: "declares the writable attribute #{attribute}",
|
50
44
|
parameters: { name: attribute.to_s }
|
51
45
|
end
|
52
46
|
end
|
53
47
|
|
54
48
|
private
|
55
49
|
|
50
|
+
private_attr_accessor :visiblity_mode, :visiblity_tracker
|
51
|
+
private_attr_reader :result
|
52
|
+
|
56
53
|
def attributes_in(module_ctx)
|
54
|
+
attributes = Set.new
|
57
55
|
module_ctx.local_nodes(:send) do |call_node|
|
58
|
-
|
59
|
-
track_visibility(call_node)
|
60
|
-
elsif ATTR_DEFN_METHODS.include?(call_node.method_name)
|
61
|
-
call_node.arg_names.each do |arg|
|
62
|
-
@visiblity_tracker[arg] = @visiblity_mode
|
63
|
-
@result << [arg, call_node.line]
|
64
|
-
end
|
65
|
-
end
|
56
|
+
attributes += track_attributes(call_node)
|
66
57
|
end
|
67
|
-
|
58
|
+
attributes.select { |name, _line| recorded_public_methods.include?(name) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def track_attributes(call_node)
|
62
|
+
if attribute_writer? call_node
|
63
|
+
return track_arguments call_node.args, call_node.line
|
64
|
+
end
|
65
|
+
track_visibility call_node if visibility_modifier? call_node
|
66
|
+
[]
|
67
|
+
end
|
68
|
+
|
69
|
+
def attribute_writer?(call_node)
|
70
|
+
ATTR_DEFN_METHODS.include?(call_node.method_name) ||
|
71
|
+
attr_with_writable_flag?(call_node)
|
72
|
+
end
|
73
|
+
|
74
|
+
def attr_with_writable_flag?(call_node)
|
75
|
+
call_node.method_name == :attr && call_node.args.last.type == :true
|
76
|
+
end
|
77
|
+
|
78
|
+
def track_arguments(args, line)
|
79
|
+
args.select { |arg| arg.type == :sym }.map { |arg| track_argument(arg, line) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def track_argument(arg, line)
|
83
|
+
arg_name = arg.children.first
|
84
|
+
visiblity_tracker[arg_name] = visiblity_mode
|
85
|
+
[arg_name, line]
|
68
86
|
end
|
69
87
|
|
70
88
|
def visibility_modifier?(call_node)
|
@@ -73,14 +91,14 @@ module Reek
|
|
73
91
|
|
74
92
|
def track_visibility(call_node)
|
75
93
|
if call_node.arg_names.any?
|
76
|
-
call_node.arg_names.each { |arg|
|
94
|
+
call_node.arg_names.each { |arg| visiblity_tracker[arg] = call_node.method_name }
|
77
95
|
else
|
78
|
-
|
96
|
+
self.visiblity_mode = call_node.method_name
|
79
97
|
end
|
80
98
|
end
|
81
99
|
|
82
100
|
def recorded_public_methods
|
83
|
-
|
101
|
+
visiblity_tracker.select { |_, visbility| visbility == :public }
|
84
102
|
end
|
85
103
|
end
|
86
104
|
end
|
@@ -73,16 +73,20 @@ module Reek
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def smells?
|
76
|
-
|
76
|
+
occurences.any?
|
77
77
|
end
|
78
78
|
|
79
79
|
def lines
|
80
|
-
|
80
|
+
occurences.map(&:line)
|
81
81
|
end
|
82
82
|
|
83
83
|
def name
|
84
|
-
|
84
|
+
param.to_s
|
85
85
|
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
private_attr_reader :occurences, :param
|
86
90
|
end
|
87
91
|
|
88
92
|
# Finds cases of ControlParameter in a particular node for a particular parameter
|
@@ -108,13 +112,15 @@ module Reek
|
|
108
112
|
|
109
113
|
private
|
110
114
|
|
115
|
+
private_attr_reader :node, :param
|
116
|
+
|
111
117
|
def conditional_nodes
|
112
|
-
|
118
|
+
node.body_nodes(CONDITIONAL_NODE_TYPES)
|
113
119
|
end
|
114
120
|
|
115
121
|
def nested_finders
|
116
122
|
@nested_finders ||= conditional_nodes.flat_map do |node|
|
117
|
-
self.class.new(node,
|
123
|
+
self.class.new(node, param)
|
118
124
|
end
|
119
125
|
end
|
120
126
|
|
@@ -129,12 +135,12 @@ module Reek
|
|
129
135
|
|
130
136
|
def uses_of_param_in_condition
|
131
137
|
return [] unless condition
|
132
|
-
condition.each_node(:lvar).select { |inner| inner.var_name ==
|
138
|
+
condition.each_node(:lvar).select { |inner| inner.var_name == param }
|
133
139
|
end
|
134
140
|
|
135
141
|
def condition
|
136
|
-
return nil unless CONDITIONAL_NODE_TYPES.include?
|
137
|
-
|
142
|
+
return nil unless CONDITIONAL_NODE_TYPES.include? node.type
|
143
|
+
node.condition
|
138
144
|
end
|
139
145
|
|
140
146
|
def regular_call_involving_param?(call_node)
|
@@ -150,12 +156,12 @@ module Reek
|
|
150
156
|
end
|
151
157
|
|
152
158
|
def call_involving_param?(call_node)
|
153
|
-
call_node.each_node(:lvar).any? { |it| it.var_name ==
|
159
|
+
call_node.each_node(:lvar).any? { |it| it.var_name == param }
|
154
160
|
end
|
155
161
|
|
156
162
|
def uses_param_in_body?
|
157
|
-
nodes =
|
158
|
-
nodes.any? { |lvar_node| lvar_node.var_name ==
|
163
|
+
nodes = node.body_nodes([:lvar], [:if, :case, :and, :or])
|
164
|
+
nodes.any? { |lvar_node| lvar_node.var_name == param }
|
159
165
|
end
|
160
166
|
end
|
161
167
|
|
@@ -175,12 +181,14 @@ module Reek
|
|
175
181
|
|
176
182
|
private
|
177
183
|
|
184
|
+
private_attr_reader :context
|
185
|
+
|
178
186
|
def potential_parameters
|
179
|
-
|
187
|
+
context.exp.parameter_names
|
180
188
|
end
|
181
189
|
|
182
190
|
def find_matches(param)
|
183
|
-
ControlParameterFinder.new(
|
191
|
+
ControlParameterFinder.new(context.exp, param).find_matches
|
184
192
|
end
|
185
193
|
end
|
186
194
|
end
|