reek 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -0
- data/README.rdoc +1 -0
- data/config/defaults.reek +5 -2
- data/features/samples.feature +4 -1
- data/lib/reek.rb +1 -1
- data/lib/reek/adapters/application.rb +4 -7
- data/lib/reek/{command_line.rb → adapters/command_line.rb} +13 -20
- data/lib/reek/adapters/object_source.rb +3 -0
- data/lib/reek/adapters/report.rb +15 -14
- data/lib/reek/adapters/spec.rb +6 -1
- data/lib/reek/class_context.rb +6 -1
- data/lib/reek/code_parser.rb +15 -1
- data/lib/reek/module_context.rb +13 -0
- data/lib/reek/smell_warning.rb +9 -18
- data/lib/reek/smells/class_variable.rb +31 -0
- data/lib/reek/sniffer.rb +2 -0
- data/reek.gemspec +4 -4
- data/spec/quality/reek_source_spec.rb +1 -1
- data/spec/reek/adapters/report_spec.rb +4 -4
- data/spec/reek/adapters/should_reek_of_spec.rb +118 -94
- 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/class_context_spec.rb +0 -10
- data/spec/reek/code_parser_spec.rb +105 -1
- data/spec/reek/smell_warning_spec.rb +100 -34
- data/spec/reek/smells/class_variable_spec.rb +81 -0
- data/spec/reek/stop_context_spec.rb +27 -0
- data/spec/spec_helper.rb +2 -0
- data/tasks/reek.rake +5 -0
- metadata +7 -4
data/History.txt
CHANGED
data/README.rdoc
CHANGED
data/config/defaults.reek
CHANGED
@@ -16,6 +16,10 @@ LongParameterList:
|
|
16
16
|
FeatureEnvy:
|
17
17
|
exclude:
|
18
18
|
- initialize
|
19
|
+
enabled: true
|
20
|
+
ClassVariable:
|
21
|
+
exclude: &id001 []
|
22
|
+
|
19
23
|
enabled: true
|
20
24
|
UncommunicativeName:
|
21
25
|
accept:
|
@@ -27,8 +31,7 @@ UncommunicativeName:
|
|
27
31
|
- !ruby/regexp /^.$/
|
28
32
|
- !ruby/regexp /[0-9]$/
|
29
33
|
NestedIterators:
|
30
|
-
exclude:
|
31
|
-
|
34
|
+
exclude: *id001
|
32
35
|
enabled: true
|
33
36
|
LongMethod:
|
34
37
|
max_statements: 5
|
data/features/samples.feature
CHANGED
@@ -9,7 +9,10 @@ Feature: Basic smell detection
|
|
9
9
|
Then it fails with exit status 2
|
10
10
|
And it reports:
|
11
11
|
"""
|
12
|
-
spec/samples/inline.rb --
|
12
|
+
spec/samples/inline.rb -- 39 warnings (+1 masked):
|
13
|
+
Inline declares the class variable @@directory (Class Variable)
|
14
|
+
Inline declares the class variable @@rootdir (Class Variable)
|
15
|
+
Inline::C declares the class variable @@type_map (Class Variable)
|
13
16
|
Inline::C has at least 13 instance variables (Large Class)
|
14
17
|
Inline::C takes parameters [options, src] to 5 methods (Data Clump)
|
15
18
|
Inline::C tests $DEBUG at least 7 times (Simulated Polymorphism)
|
data/lib/reek.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
require 'reek/command_line'
|
1
|
+
require 'reek/adapters/command_line'
|
2
2
|
require 'reek/adapters/source'
|
3
3
|
require 'reek/adapters/core_extras'
|
4
|
-
require 'reek/adapters/report'
|
5
4
|
|
6
5
|
module Reek
|
7
6
|
#
|
@@ -11,13 +10,13 @@ module Reek
|
|
11
10
|
#
|
12
11
|
class Application
|
13
12
|
def initialize(argv)
|
14
|
-
@
|
13
|
+
@options = Options.new(argv)
|
15
14
|
end
|
16
15
|
|
17
16
|
def examine_sources
|
18
17
|
# SMELL: Greedy Method
|
19
18
|
# Options.parse executes the -v and -h commands and throws a SystemExit
|
20
|
-
args =
|
19
|
+
args = @options.parse
|
21
20
|
if args.length > 0
|
22
21
|
@sniffer = args.sniff
|
23
22
|
else
|
@@ -27,9 +26,7 @@ module Reek
|
|
27
26
|
|
28
27
|
def reek
|
29
28
|
examine_sources
|
30
|
-
|
31
|
-
# This should use the actual type of report selected by the user's options
|
32
|
-
puts Report.new(@sniffer.sniffers).full_report
|
29
|
+
puts @options.create_report(@sniffer.sniffers).report
|
33
30
|
return @sniffer.smelly? ? 2 : 0
|
34
31
|
end
|
35
32
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'reek'
|
3
|
+
require 'reek/adapters/report'
|
3
4
|
|
4
5
|
module Reek
|
5
6
|
|
@@ -12,24 +13,12 @@ module Reek
|
|
12
13
|
CTX_SORT = '%m%c %w (%s)'
|
13
14
|
SMELL_SORT = '%m[%s] %c %w'
|
14
15
|
|
15
|
-
def self.default_options
|
16
|
-
{
|
17
|
-
:format => CTX_SORT,
|
18
|
-
:show_all => false,
|
19
|
-
:quiet => false
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
# SMELL: Global Variable
|
24
|
-
@@opts = default_options
|
25
|
-
|
26
|
-
def self.[](key)
|
27
|
-
@@opts[key]
|
28
|
-
end
|
29
|
-
|
30
16
|
def initialize(argv)
|
31
17
|
@argv = argv
|
32
18
|
@parser = OptionParser.new
|
19
|
+
@quiet = false
|
20
|
+
@show_all = false
|
21
|
+
@format = CTX_SORT
|
33
22
|
set_options
|
34
23
|
end
|
35
24
|
|
@@ -66,20 +55,24 @@ EOB
|
|
66
55
|
@parser.separator "\nReport formatting:"
|
67
56
|
|
68
57
|
@parser.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
|
69
|
-
|
58
|
+
@show_all = opt
|
70
59
|
end
|
71
60
|
@parser.on("-q", "--quiet", "Suppress headings for smell-free source files") do
|
72
|
-
|
61
|
+
@quiet = true
|
73
62
|
end
|
74
63
|
@parser.on('-f', "--format FORMAT", 'Specify the format of smell warnings') do |arg|
|
75
|
-
|
64
|
+
@format = arg unless arg.nil?
|
76
65
|
end
|
77
66
|
@parser.on('-c', '--context-first', "Sort by context; sets the format string to \"#{CTX_SORT}\"") do
|
78
|
-
|
67
|
+
@format = CTX_SORT
|
79
68
|
end
|
80
69
|
@parser.on('-s', '--smell-first', "Sort by smell; sets the format string to \"#{SMELL_SORT}\"") do
|
81
|
-
|
70
|
+
@format = SMELL_SORT
|
82
71
|
end
|
83
72
|
end
|
73
|
+
|
74
|
+
def create_report(sniffers)
|
75
|
+
@quiet ? QuietReport.new(sniffers, @format, @show_all) : FullReport.new(sniffers, @format, @show_all)
|
76
|
+
end
|
84
77
|
end
|
85
78
|
end
|
data/lib/reek/adapters/report.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'set'
|
2
|
-
require 'reek/command_line' # SMELL: Global Variable
|
2
|
+
require 'reek/adapters/command_line' # SMELL: Global Variable
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
class ReportSection
|
6
6
|
|
7
|
-
def initialize(sniffer) # :nodoc:
|
7
|
+
def initialize(sniffer, display_masked_warnings, format) # :nodoc:
|
8
|
+
@format = format
|
8
9
|
@masked_warnings = SortedSet.new
|
9
10
|
@warnings = SortedSet.new
|
10
11
|
@desc = sniffer.desc
|
12
|
+
@display_masked_warnings = display_masked_warnings
|
11
13
|
sniffer.report_on(self)
|
12
14
|
end
|
13
15
|
|
@@ -27,7 +29,6 @@ module Reek
|
|
27
29
|
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
28
30
|
# this report, with a heading.
|
29
31
|
def full_report
|
30
|
-
return quiet_report if Options[:quiet]
|
31
32
|
result = header
|
32
33
|
result += ":\n#{smell_list}" if should_report
|
33
34
|
result += "\n"
|
@@ -48,14 +49,14 @@ module Reek
|
|
48
49
|
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
49
50
|
# this report.
|
50
51
|
def smell_list
|
51
|
-
smells =
|
52
|
-
smells.map {|smell| " #{smell.report}"}.join("\n")
|
52
|
+
smells = @display_masked_warnings ? @all_warnings : @warnings
|
53
|
+
smells.map {|smell| " #{smell.report(@format)}"}.join("\n")
|
53
54
|
end
|
54
55
|
|
55
56
|
private
|
56
57
|
|
57
58
|
def should_report
|
58
|
-
@warnings.length > 0 or (
|
59
|
+
@warnings.length > 0 or (@display_masked_warnings and @masked_warnings.length > 0)
|
59
60
|
end
|
60
61
|
|
61
62
|
def visible_header
|
@@ -72,19 +73,19 @@ module Reek
|
|
72
73
|
end
|
73
74
|
|
74
75
|
class Report
|
75
|
-
|
76
|
-
|
77
|
-
@partials = Array(sniffers).map {|sn| ReportSection.new(sn)}
|
76
|
+
def initialize(sniffers, format, display_masked_warnings = false)
|
77
|
+
@partials = Array(sniffers).map {|sn| ReportSection.new(sn, display_masked_warnings, format)}
|
78
78
|
end
|
79
|
+
end
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
# kind of report.
|
83
|
-
def full_report
|
81
|
+
class FullReport < Report
|
82
|
+
def report
|
84
83
|
@partials.map { |rpt| rpt.full_report }.join
|
85
84
|
end
|
85
|
+
end
|
86
86
|
|
87
|
-
|
87
|
+
class QuietReport < Report
|
88
|
+
def report
|
88
89
|
@partials.map { |rpt| rpt.quiet_report }.join
|
89
90
|
end
|
90
91
|
end
|
data/lib/reek/adapters/spec.rb
CHANGED
@@ -44,9 +44,14 @@ module Reek
|
|
44
44
|
#
|
45
45
|
module Spec
|
46
46
|
module ReekMatcher
|
47
|
+
def create_reporter(sniffers)
|
48
|
+
QuietReport.new(sniffers, '%c %w (%s)', false)
|
49
|
+
end
|
47
50
|
def report
|
48
|
-
|
51
|
+
create_reporter(@sniffer.sniffers).report
|
49
52
|
end
|
53
|
+
|
54
|
+
module_function :create_reporter
|
50
55
|
end
|
51
56
|
|
52
57
|
class ShouldReek # :nodoc:
|
data/lib/reek/class_context.rb
CHANGED
@@ -24,7 +24,7 @@ module Reek
|
|
24
24
|
CodeParser.new(sniffer).process_class(source.syntax_tree)
|
25
25
|
end
|
26
26
|
|
27
|
-
attr_reader :conditionals, :parsed_methods
|
27
|
+
attr_reader :conditionals, :parsed_methods, :class_variables
|
28
28
|
|
29
29
|
# SMELL: inconsistent with other contexts (not linked to the sexp)
|
30
30
|
def initialize(outer, name, superclass = nil)
|
@@ -34,6 +34,7 @@ module Reek
|
|
34
34
|
@parsed_methods = []
|
35
35
|
@instance_variables = Set.new
|
36
36
|
@conditionals = []
|
37
|
+
@class_variables = Set.new
|
37
38
|
end
|
38
39
|
|
39
40
|
def myself
|
@@ -58,6 +59,10 @@ module Reek
|
|
58
59
|
@parsed_methods.length
|
59
60
|
end
|
60
61
|
|
62
|
+
def record_class_variable(cvar)
|
63
|
+
@class_variables << Name.new(cvar)
|
64
|
+
end
|
65
|
+
|
61
66
|
def record_instance_variable(sym)
|
62
67
|
@instance_variables << Name.new(sym)
|
63
68
|
end
|
data/lib/reek/code_parser.rb
CHANGED
@@ -51,10 +51,12 @@ module Reek
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def process_module(exp)
|
54
|
-
|
54
|
+
scope = ModuleContext.create(@element, exp)
|
55
|
+
push(scope) do
|
55
56
|
process_default(exp)
|
56
57
|
check_smells(:module)
|
57
58
|
end
|
59
|
+
scope
|
58
60
|
end
|
59
61
|
|
60
62
|
def process_class(exp)
|
@@ -66,6 +68,18 @@ module Reek
|
|
66
68
|
scope
|
67
69
|
end
|
68
70
|
|
71
|
+
def process_cvar(exp)
|
72
|
+
@element.record_class_variable(exp[1])
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_cvasgn(exp)
|
76
|
+
process_cvar(exp)
|
77
|
+
end
|
78
|
+
|
79
|
+
def process_cvdecl(exp)
|
80
|
+
process_cvar(exp)
|
81
|
+
end
|
82
|
+
|
69
83
|
def process_defn(exp)
|
70
84
|
handle_context(MethodContext, :defn, exp)
|
71
85
|
end
|
data/lib/reek/module_context.rb
CHANGED
@@ -8,9 +8,18 @@ module Reek
|
|
8
8
|
ModuleContext.new(res[0], res[1])
|
9
9
|
end
|
10
10
|
|
11
|
+
def ModuleContext.from_s(src)
|
12
|
+
source = src.to_reek_source
|
13
|
+
sniffer = Sniffer.new(source)
|
14
|
+
CodeParser.new(sniffer).process_module(source.syntax_tree)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :class_variables
|
18
|
+
|
11
19
|
def initialize(outer, name)
|
12
20
|
super(outer, nil)
|
13
21
|
@name = name
|
22
|
+
@class_variables = Set.new
|
14
23
|
end
|
15
24
|
|
16
25
|
def myself
|
@@ -26,6 +35,10 @@ module Reek
|
|
26
35
|
"#{@outer.outer_name}#{@name}::"
|
27
36
|
end
|
28
37
|
|
38
|
+
def record_class_variable(cvar)
|
39
|
+
@class_variables << Name.new(cvar)
|
40
|
+
end
|
41
|
+
|
29
42
|
def variable_names
|
30
43
|
[]
|
31
44
|
end
|
data/lib/reek/smell_warning.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'reek/command_line' # SMELL: Global Variable used for options
|
2
|
-
|
3
1
|
module Reek
|
4
2
|
|
5
3
|
#
|
@@ -16,11 +14,11 @@ module Reek
|
|
16
14
|
end
|
17
15
|
|
18
16
|
def hash # :nodoc:
|
19
|
-
|
17
|
+
sort_key.hash
|
20
18
|
end
|
21
19
|
|
22
20
|
def <=>(other)
|
23
|
-
|
21
|
+
sort_key <=> other.sort_key
|
24
22
|
end
|
25
23
|
|
26
24
|
alias eql? <=> # :nodoc:
|
@@ -35,25 +33,18 @@ module Reek
|
|
35
33
|
end
|
36
34
|
|
37
35
|
def contains_all?(patterns)
|
38
|
-
rpt =
|
36
|
+
rpt = sort_key.to_s
|
39
37
|
return patterns.all? {|exp| exp === rpt}
|
40
38
|
end
|
41
39
|
|
42
|
-
def
|
43
|
-
|
40
|
+
def sort_key
|
41
|
+
[@context.to_s, @warning, @detector.smell_name]
|
44
42
|
end
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# * %c <-- a description of the +CodeContext+ containing the smell
|
51
|
-
# * %m <-- "(is_masked) " if this is a is_masked smell
|
52
|
-
# * %s <-- the name of the smell that was detected
|
53
|
-
# * %w <-- the specific problem that was detected
|
54
|
-
#
|
55
|
-
def report
|
56
|
-
basic_report.gsub(/\%m/, @is_masked ? '(masked) ' : '')
|
44
|
+
protected :sort_key
|
45
|
+
|
46
|
+
def report(format)
|
47
|
+
format.gsub(/\%s/, @detector.smell_name).gsub(/\%c/, @context.to_s).gsub(/\%w/, @warning).gsub(/\%m/, @is_masked ? '(masked) ' : '')
|
57
48
|
end
|
58
49
|
|
59
50
|
def report_on(report)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Smells
|
5
|
+
|
6
|
+
#
|
7
|
+
# Class variables form part of the global runtime state, and as such make
|
8
|
+
# it easy for one part of the system to accidentally or inadvertently
|
9
|
+
# depend on another part of the system. So the system becomes more prone to
|
10
|
+
# problems where changing something over here breaks something over there.
|
11
|
+
# In particular, class variables can make it hard to set up tests (because
|
12
|
+
# the context of the test includes all global state).
|
13
|
+
#
|
14
|
+
class ClassVariable < SmellDetector
|
15
|
+
|
16
|
+
def self.contexts # :nodoc:
|
17
|
+
[:class, :module]
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Checks whether the given class declares any class variables.
|
22
|
+
# Remembers any smells found.
|
23
|
+
#
|
24
|
+
def examine_context(klass)
|
25
|
+
klass.class_variables.each do |cvar|
|
26
|
+
found(klass, "declares the class variable #{cvar}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/reek/sniffer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'reek/detector_stack'
|
2
2
|
|
3
3
|
# SMELL: Duplication -- all these should be found automagically
|
4
|
+
require 'reek/smells/class_variable'
|
4
5
|
require 'reek/smells/control_couple'
|
5
6
|
require 'reek/smells/data_clump'
|
6
7
|
require 'reek/smells/duplication'
|
@@ -48,6 +49,7 @@ module Reek
|
|
48
49
|
def self.smell_classes
|
49
50
|
# SMELL: Duplication -- these should be loaded by listing the files
|
50
51
|
[
|
52
|
+
Smells::ClassVariable,
|
51
53
|
Smells::ControlCouple,
|
52
54
|
Smells::DataClump,
|
53
55
|
Smells::Duplication,
|