reek 1.2.3 → 1.2.4
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.
- 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
|