reek 1.2.3 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -1
- data/features/options.feature +1 -9
- data/features/samples.feature +7 -2
- data/features/step_definitions/reek_steps.rb +3 -3
- data/lib/reek.rb +1 -1
- data/lib/reek/adapters/application.rb +22 -27
- data/lib/reek/adapters/command_line.rb +41 -42
- data/lib/reek/adapters/report.rb +30 -23
- data/lib/reek/adapters/spec.rb +1 -1
- data/lib/reek/code_context.rb +6 -2
- data/lib/reek/code_parser.rb +3 -7
- data/lib/reek/detector_stack.rb +2 -4
- data/lib/reek/help_command.rb +14 -0
- data/lib/reek/masking_collection.rb +33 -0
- data/lib/reek/method_context.rb +18 -6
- data/lib/reek/module_context.rb +0 -13
- data/lib/reek/reek_command.rb +28 -0
- data/lib/reek/singleton_method_context.rb +1 -1
- data/lib/reek/smell_warning.rb +5 -3
- data/lib/reek/smells/attribute.rb +17 -1
- data/lib/reek/smells/class_variable.rb +1 -1
- data/lib/reek/smells/control_couple.rb +13 -10
- data/lib/reek/smells/large_class.rb +1 -1
- data/lib/reek/smells/long_method.rb +0 -2
- data/lib/reek/smells/simulated_polymorphism.rb +2 -2
- data/lib/reek/sniffer.rb +1 -3
- data/lib/reek/tree_dresser.rb +35 -23
- data/lib/reek/version_command.rb +14 -0
- data/reek.gemspec +3 -3
- data/spec/reek/adapters/report_spec.rb +8 -8
- data/spec/reek/adapters/should_reek_of_spec.rb +1 -1
- data/spec/reek/adapters/should_reek_only_of_spec.rb +2 -2
- data/spec/reek/adapters/should_reek_spec.rb +3 -3
- data/spec/reek/code_context_spec.rb +11 -11
- data/spec/reek/code_parser_spec.rb +0 -88
- data/spec/reek/help_command_spec.rb +24 -0
- data/spec/reek/masking_collection_spec.rb +236 -0
- data/spec/reek/method_context_spec.rb +43 -1
- data/spec/reek/reek_command_spec.rb +45 -0
- data/spec/reek/smell_warning_spec.rb +12 -4
- data/spec/reek/smells/attribute_spec.rb +79 -7
- data/spec/reek/smells/control_couple_spec.rb +40 -11
- data/spec/reek/smells/long_parameter_list_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_spec.rb +0 -17
- data/spec/reek/tree_dresser_spec.rb +20 -0
- data/spec/reek/version_command_spec.rb +29 -0
- metadata +11 -2
data/History.txt
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
== 1.2.
|
1
|
+
== 1.2.4 (2009-11-17)
|
2
|
+
|
3
|
+
=== Major Changes
|
4
|
+
* The -f, -c and -s options for formatting smell warnings have been removed
|
5
|
+
|
6
|
+
=== Minor Changes
|
7
|
+
* ControlCouple now warns about parameters defaulted to true/false
|
8
|
+
|
9
|
+
== 1.2.3 (2009-11-2)
|
2
10
|
|
3
11
|
=== Minor Changes
|
4
12
|
* New smell: Attribute (disabled by default)
|
data/features/options.feature
CHANGED
@@ -9,11 +9,6 @@ Feature: Reek can be controlled using command-line options
|
|
9
9
|
Then the exit status indicates an error
|
10
10
|
And it reports the error "Error: invalid option: --no-such-option"
|
11
11
|
|
12
|
-
Scenario: return non-zero status on missing argument
|
13
|
-
When I run reek -f
|
14
|
-
Then the exit status indicates an error
|
15
|
-
And it reports the error "Error: missing argument: -f"
|
16
|
-
|
17
12
|
Scenario: display the current version number
|
18
13
|
When I run reek --version
|
19
14
|
Then it succeeds
|
@@ -41,9 +36,6 @@ Feature: Reek can be controlled using command-line options
|
|
41
36
|
|
42
37
|
Report formatting:
|
43
38
|
-a, --[no-]show-all Show all smells, including those masked by config settings
|
44
|
-
-q, --quiet
|
45
|
-
-f, --format FORMAT Specify the format of smell warnings
|
46
|
-
-c, --context-first Sort by context; sets the format string to "%m%c %w (%s)"
|
47
|
-
-s, --smell-first Sort by smell; sets the format string to "%m[%s] %c %w"
|
39
|
+
-q, --[no-]quiet Suppress headings for smell-free source files
|
48
40
|
|
49
41
|
"""
|
data/features/samples.feature
CHANGED
@@ -4,6 +4,7 @@ Feature: Basic smell detection
|
|
4
4
|
As a developer
|
5
5
|
I want to detect the smels in my Ruby code
|
6
6
|
|
7
|
+
@inline
|
7
8
|
Scenario: Correct smells from inline.rb
|
8
9
|
When I run reek spec/samples/inline.rb
|
9
10
|
Then the exit status indicates smells
|
@@ -57,7 +58,7 @@ Feature: Basic smell detection
|
|
57
58
|
Then the exit status indicates smells
|
58
59
|
And it reports:
|
59
60
|
"""
|
60
|
-
spec/samples/optparse.rb --
|
61
|
+
spec/samples/optparse.rb -- 124 warnings:
|
61
62
|
OptionParser has at least 42 methods (Large Class)
|
62
63
|
OptionParser tests ((argv.size == 1) and Array.===(argv[0])) at least 3 times (Simulated Polymorphism)
|
63
64
|
OptionParser tests a at least 7 times (Simulated Polymorphism)
|
@@ -70,6 +71,7 @@ Feature: Basic smell detection
|
|
70
71
|
OptionParser#Completion::complete has approx 22 statements (Long Method)
|
71
72
|
OptionParser#Completion::complete has the variable name 'k' (Uncommunicative Name)
|
72
73
|
OptionParser#Completion::complete has the variable name 'v' (Uncommunicative Name)
|
74
|
+
OptionParser#Completion::complete is controlled by argument icase (Control Couple)
|
73
75
|
OptionParser#Completion::complete refers to candidates more than self (Feature Envy)
|
74
76
|
OptionParser#Completion::complete/block has the variable name 'k' (Uncommunicative Name)
|
75
77
|
OptionParser#Completion::complete/block has the variable name 'v' (Uncommunicative Name)
|
@@ -78,6 +80,7 @@ Feature: Basic smell detection
|
|
78
80
|
OptionParser#List#accept refers to pat more than self (Feature Envy)
|
79
81
|
OptionParser#List#add_banner refers to opt more than self (Feature Envy)
|
80
82
|
OptionParser#List#complete has 4 parameters (Long Parameter List)
|
83
|
+
OptionParser#List#complete is controlled by argument icase (Control Couple)
|
81
84
|
OptionParser#List#reject has the variable name 't' (Uncommunicative Name)
|
82
85
|
OptionParser#List#summarize refers to opt more than self (Feature Envy)
|
83
86
|
OptionParser#List#update has 5 parameters (Long Parameter List)
|
@@ -127,6 +130,7 @@ Feature: Basic smell detection
|
|
127
130
|
OptionParser#block/block is controlled by argument pkg (Control Couple)
|
128
131
|
OptionParser#block/block is nested (Nested Iterators)
|
129
132
|
OptionParser#complete has 4 parameters (Long Parameter List)
|
133
|
+
OptionParser#complete is controlled by argument icase (Control Couple)
|
130
134
|
OptionParser#complete/block/block is nested (Nested Iterators)
|
131
135
|
OptionParser#getopts calls result[opt] = false twice (Duplication)
|
132
136
|
OptionParser#getopts has approx 17 statements (Long Method)
|
@@ -187,7 +191,7 @@ Feature: Basic smell detection
|
|
187
191
|
Then the exit status indicates smells
|
188
192
|
And it reports:
|
189
193
|
"""
|
190
|
-
spec/samples/redcloth.rb --
|
194
|
+
spec/samples/redcloth.rb -- 96 warnings:
|
191
195
|
RedCloth has at least 44 methods (Large Class)
|
192
196
|
RedCloth takes parameters [atts, cite, content, tag] to 3 methods (Data Clump)
|
193
197
|
RedCloth tests atts at least 6 times (Simulated Polymorphism)
|
@@ -217,6 +221,7 @@ Feature: Basic smell detection
|
|
217
221
|
RedCloth#block_textile_table/block/block is nested (Nested Iterators)
|
218
222
|
RedCloth#block_textile_table/block/block/block is nested (Nested Iterators)
|
219
223
|
RedCloth#blocks has approx 18 statements (Long Method)
|
224
|
+
RedCloth#blocks is controlled by argument deep_code (Control Couple)
|
220
225
|
RedCloth#blocks/block is controlled by argument deep_code (Control Couple)
|
221
226
|
RedCloth#blocks/block/block is nested (Nested Iterators)
|
222
227
|
RedCloth#check_refs is controlled by argument text (Control Couple)
|
@@ -15,15 +15,15 @@ Then /^stdout equals "([^\"]*)"$/ do |report|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
Then /^it succeeds$/ do
|
18
|
-
@last_exit_status.should == Reek::
|
18
|
+
@last_exit_status.should == Reek::Application::STATUS_SUCCESS
|
19
19
|
end
|
20
20
|
|
21
21
|
Then /^the exit status indicates an error$/ do
|
22
|
-
@last_exit_status.should == Reek::
|
22
|
+
@last_exit_status.should == Reek::Application::STATUS_ERROR
|
23
23
|
end
|
24
24
|
|
25
25
|
Then /^the exit status indicates smells$/ do
|
26
|
-
@last_exit_status.should == Reek::
|
26
|
+
@last_exit_status.should == Reek::Application::STATUS_SMELLS
|
27
27
|
end
|
28
28
|
|
29
29
|
Then /^it reports:$/ do |report|
|
data/lib/reek.rb
CHANGED
@@ -4,48 +4,43 @@ require 'reek/adapters/core_extras'
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
|
7
|
-
EXIT_STATUS = {
|
8
|
-
:success => 0,
|
9
|
-
:error => 1,
|
10
|
-
:smells => 2
|
11
|
-
}
|
12
|
-
|
13
7
|
#
|
14
8
|
# Represents an instance of a Reek application.
|
15
9
|
# This is the entry point for all invocations of Reek from the
|
16
10
|
# command line.
|
17
11
|
#
|
18
12
|
class Application
|
19
|
-
def initialize(argv)
|
20
|
-
@options = Options.new(argv)
|
21
|
-
end
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
args = @options.parse
|
27
|
-
if args.length > 0
|
28
|
-
@sniffer = args.sniff
|
29
|
-
else
|
30
|
-
@sniffer = Reek::Sniffer.new($stdin.to_reek_source('$stdin'))
|
31
|
-
end
|
32
|
-
end
|
14
|
+
STATUS_SUCCESS = 0
|
15
|
+
STATUS_ERROR = 1
|
16
|
+
STATUS_SMELLS = 2
|
33
17
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
return EXIT_STATUS[@sniffer.smelly? ? :smells : :success]
|
18
|
+
def initialize(argv)
|
19
|
+
@options = Options.new(argv)
|
20
|
+
@status = STATUS_SUCCESS
|
38
21
|
end
|
39
22
|
|
40
23
|
def execute
|
41
24
|
begin
|
42
|
-
|
43
|
-
|
44
|
-
return ex.status
|
25
|
+
cmd = @options.parse
|
26
|
+
cmd.execute(self)
|
45
27
|
rescue Exception => error
|
46
28
|
$stderr.puts "Error: #{error}"
|
47
|
-
|
29
|
+
@status = STATUS_ERROR
|
48
30
|
end
|
31
|
+
return @status
|
32
|
+
end
|
33
|
+
|
34
|
+
def output(text)
|
35
|
+
puts text
|
36
|
+
end
|
37
|
+
|
38
|
+
def report_success
|
39
|
+
@status = STATUS_SUCCESS
|
40
|
+
end
|
41
|
+
|
42
|
+
def report_smells
|
43
|
+
@status = STATUS_SMELLS
|
49
44
|
end
|
50
45
|
end
|
51
46
|
end
|
@@ -1,78 +1,77 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'reek'
|
3
3
|
require 'reek/adapters/report'
|
4
|
+
require 'reek/help_command'
|
5
|
+
require 'reek/reek_command'
|
6
|
+
require 'reek/version_command'
|
4
7
|
|
5
8
|
module Reek
|
6
|
-
|
7
|
-
# SMELL: Greedy Module
|
8
|
-
# This creates the command-line parser AND invokes it. And for the
|
9
|
-
# -v and -h options it also executes them. And it holds the config
|
10
|
-
# options for the rest of the application.
|
11
|
-
class Options
|
12
9
|
|
13
|
-
|
14
|
-
SMELL_SORT = '%m[%s] %c %w'
|
10
|
+
class Options
|
15
11
|
|
16
12
|
def initialize(argv)
|
17
13
|
@argv = argv
|
18
14
|
@parser = OptionParser.new
|
19
|
-
@
|
15
|
+
@report_class = VerboseReport
|
20
16
|
@show_all = false
|
21
|
-
@
|
17
|
+
@command = nil
|
22
18
|
set_options
|
23
19
|
end
|
24
20
|
|
25
|
-
def
|
26
|
-
@parser.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
def banner
|
22
|
+
progname = @parser.program_name
|
23
|
+
# SMELL:
|
24
|
+
# The following banner isn't really correct. Help, Version and Reek
|
25
|
+
# are really sub-commands (in the git/svn sense) and so the usage
|
26
|
+
# banner should show three different command-lines. The other
|
27
|
+
# options are all flags for the Reek sub-command.
|
28
|
+
#
|
29
|
+
# reek -h|--help Display a help message
|
30
|
+
#
|
31
|
+
# reek -v|--version Output the tool's version number
|
32
|
+
#
|
33
|
+
# reek [options] files List the smells in the given files
|
34
|
+
# -a|--[no-]show-all Report masked smells
|
35
|
+
# -q|-[no-]quiet Only list files that have smells
|
36
|
+
# files Names of files or dirs to be checked
|
37
|
+
#
|
38
|
+
return <<EOB
|
39
|
+
Usage: #{progname} [options] [files]
|
33
40
|
|
34
41
|
Examples:
|
35
42
|
|
36
|
-
#{
|
37
|
-
#{
|
38
|
-
cat my_class.rb | #{
|
43
|
+
#{progname} lib/*.rb
|
44
|
+
#{progname} -q -a lib
|
45
|
+
cat my_class.rb | #{progname}
|
39
46
|
|
40
47
|
See http://wiki.github.com/kevinrutherford/reek for detailed help.
|
41
48
|
|
42
49
|
EOB
|
50
|
+
end
|
43
51
|
|
44
|
-
|
52
|
+
def parse
|
53
|
+
@parser.parse!(@argv)
|
54
|
+
@command ||= ReekCommand.new(@argv, @report_class, @show_all)
|
55
|
+
end
|
45
56
|
|
57
|
+
def set_options
|
58
|
+
@parser.banner = banner
|
59
|
+
@parser.separator "Common options:"
|
46
60
|
@parser.on("-h", "--help", "Show this message") do
|
47
|
-
|
48
|
-
exit(EXIT_STATUS[:success])
|
61
|
+
@command = HelpCommand.new(@parser)
|
49
62
|
end
|
50
63
|
@parser.on("-v", "--version", "Show version") do
|
51
|
-
|
52
|
-
exit(EXIT_STATUS[:success])
|
64
|
+
@command = VersionCommand.new(@parser.program_name)
|
53
65
|
end
|
54
66
|
|
55
67
|
@parser.separator "\nReport formatting:"
|
56
|
-
|
57
68
|
@parser.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
|
58
69
|
@show_all = opt
|
59
70
|
end
|
60
|
-
@parser.on("-q", "--quiet", "Suppress headings for smell-free source files") do
|
61
|
-
@
|
62
|
-
end
|
63
|
-
@parser.on('-f', "--format FORMAT", 'Specify the format of smell warnings') do |arg|
|
64
|
-
@format = arg unless arg.nil?
|
65
|
-
end
|
66
|
-
@parser.on('-c', '--context-first', "Sort by context; sets the format string to \"#{CTX_SORT}\"") do
|
67
|
-
@format = CTX_SORT
|
68
|
-
end
|
69
|
-
@parser.on('-s', '--smell-first', "Sort by smell; sets the format string to \"#{SMELL_SORT}\"") do
|
70
|
-
@format = SMELL_SORT
|
71
|
+
@parser.on("-q", "--[no-]quiet", "Suppress headings for smell-free source files") do |opt|
|
72
|
+
@report_class = opt ? QuietReport : VerboseReport
|
71
73
|
end
|
72
74
|
end
|
73
|
-
|
74
|
-
def create_report(sniffers)
|
75
|
-
@quiet ? QuietReport.new(sniffers, @format, @show_all) : FullReport.new(sniffers, @format, @show_all)
|
76
|
-
end
|
77
75
|
end
|
76
|
+
|
78
77
|
end
|
data/lib/reek/adapters/report.rb
CHANGED
@@ -1,34 +1,35 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'reek/adapters/command_line' # SMELL: Global Variable
|
3
|
+
require 'reek/masking_collection'
|
3
4
|
|
4
5
|
module Reek
|
5
6
|
class ReportSection
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@
|
8
|
+
SMELL_FORMAT = '%m%c %w (%s)'
|
9
|
+
|
10
|
+
def initialize(sniffer, display_masked_warnings)
|
11
|
+
@cwarnings = MaskingCollection.new
|
11
12
|
@desc = sniffer.desc
|
12
|
-
@display_masked_warnings = display_masked_warnings
|
13
|
+
@display_masked_warnings = display_masked_warnings # SMELL: Control Couple
|
13
14
|
sniffer.report_on(self)
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
@
|
17
|
+
def found_smell(warning)
|
18
|
+
@cwarnings.add(warning)
|
18
19
|
true
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
-
@
|
22
|
+
def found_masked_smell(warning)
|
23
|
+
@cwarnings.add_masked(warning)
|
23
24
|
end
|
24
25
|
|
25
26
|
def num_masked_smells # SMELL: getter
|
26
|
-
@
|
27
|
+
@cwarnings.num_masked_items
|
27
28
|
end
|
28
29
|
|
29
30
|
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
30
31
|
# this report, with a heading.
|
31
|
-
def
|
32
|
+
def verbose_report
|
32
33
|
result = header
|
33
34
|
result += ":\n#{smell_list}" if should_report
|
34
35
|
result += "\n"
|
@@ -37,56 +38,62 @@ module Reek
|
|
37
38
|
|
38
39
|
def quiet_report
|
39
40
|
return '' unless should_report
|
41
|
+
# SMELL: duplicate knowledge of the header layout
|
40
42
|
"#{header}:\n#{smell_list}\n"
|
41
43
|
end
|
42
44
|
|
43
45
|
def header
|
44
|
-
@all_warnings = SortedSet.new(@warnings) # SMELL: Temporary Field
|
45
|
-
@all_warnings.merge(@masked_warnings)
|
46
46
|
"#{@desc} -- #{visible_header}#{masked_header}"
|
47
47
|
end
|
48
48
|
|
49
49
|
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
50
50
|
# this report.
|
51
51
|
def smell_list
|
52
|
-
|
53
|
-
|
52
|
+
result = []
|
53
|
+
if @display_masked_warnings
|
54
|
+
@cwarnings.each_item {|smell| result << " #{smell.report(SMELL_FORMAT)}"}
|
55
|
+
else
|
56
|
+
@cwarnings.each_visible_item {|smell| result << " #{smell.report(SMELL_FORMAT)}"}
|
57
|
+
end
|
58
|
+
result.join("\n")
|
54
59
|
end
|
55
60
|
|
56
61
|
private
|
57
62
|
|
58
63
|
def should_report
|
59
|
-
@
|
64
|
+
@cwarnings.num_visible_items > 0 or (@display_masked_warnings and @cwarnings.num_masked_items > 0)
|
60
65
|
end
|
61
66
|
|
62
67
|
def visible_header
|
63
|
-
num_smells = @
|
68
|
+
num_smells = @cwarnings.num_visible_items
|
64
69
|
result = "#{num_smells} warning"
|
65
70
|
result += 's' unless num_smells == 1
|
66
71
|
result
|
67
72
|
end
|
68
73
|
|
69
74
|
def masked_header
|
70
|
-
num_masked_warnings = @
|
75
|
+
num_masked_warnings = @cwarnings.num_masked_items
|
71
76
|
num_masked_warnings == 0 ? '' : " (+#{num_masked_warnings} masked)"
|
72
77
|
end
|
73
78
|
end
|
74
79
|
|
75
80
|
class Report
|
76
|
-
def initialize(sniffers,
|
77
|
-
@partials = Array(sniffers).map {|sn| ReportSection.new(sn, display_masked_warnings
|
81
|
+
def initialize(sniffers, display_masked_warnings = false)
|
82
|
+
@partials = Array(sniffers).map {|sn| ReportSection.new(sn, display_masked_warnings)}
|
78
83
|
end
|
79
84
|
end
|
80
85
|
|
81
|
-
class
|
86
|
+
class VerboseReport < Report
|
87
|
+
# SMELL: Implementation Inheritance
|
82
88
|
def report
|
83
|
-
@partials.map { |
|
89
|
+
@partials.map { |section| section.verbose_report }.join
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
87
93
|
class QuietReport < Report
|
94
|
+
# SMELL: Implementation Inheritance
|
88
95
|
def report
|
89
|
-
@partials.map { |
|
96
|
+
@partials.map { |section| section.quiet_report }.join
|
90
97
|
end
|
91
98
|
end
|
92
99
|
end
|
data/lib/reek/adapters/spec.rb
CHANGED
data/lib/reek/code_context.rb
CHANGED
@@ -15,7 +15,7 @@ module Reek
|
|
15
15
|
#
|
16
16
|
class CodeContext
|
17
17
|
|
18
|
-
attr_reader :name
|
18
|
+
attr_reader :name, :exp
|
19
19
|
|
20
20
|
def initialize(outer, exp)
|
21
21
|
@outer = outer
|
@@ -23,7 +23,11 @@ module Reek
|
|
23
23
|
@myself = nil
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
26
|
+
def local_nodes(type, &blk)
|
27
|
+
each_node(type, [:class, :module], &blk)
|
28
|
+
end
|
29
|
+
|
30
|
+
def each_node(type, ignoring, &blk)
|
27
31
|
if block_given?
|
28
32
|
@exp.look_for(type, ignoring, &blk)
|
29
33
|
else
|