reek 1.2.7.2 → 1.2.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -1
- data/config/defaults.reek +4 -4
- data/features/masking_smells.feature +14 -57
- data/features/options.feature +1 -2
- data/features/rake_task.feature +6 -6
- data/features/reports.feature +8 -38
- data/features/samples.feature +181 -181
- data/features/stdin.feature +3 -3
- data/lib/reek.rb +1 -1
- data/lib/reek/cli/command_line.rb +2 -7
- data/lib/reek/cli/reek_command.rb +6 -6
- data/lib/reek/cli/report.rb +27 -49
- data/lib/reek/core/code_parser.rb +6 -15
- data/lib/reek/core/method_context.rb +1 -1
- data/lib/reek/core/module_context.rb +0 -18
- data/lib/reek/core/smell_configuration.rb +0 -4
- data/lib/reek/core/sniffer.rb +11 -19
- data/lib/reek/core/warning_collector.rb +27 -0
- data/lib/reek/examiner.rb +43 -35
- data/lib/reek/rake/task.rb +2 -0
- data/lib/reek/smell_warning.rb +10 -25
- data/lib/reek/smells/boolean_parameter.rb +1 -1
- data/lib/reek/smells/control_couple.rb +4 -1
- data/lib/reek/smells/data_clump.rb +40 -33
- data/lib/reek/smells/feature_envy.rb +1 -1
- data/lib/reek/smells/long_parameter_list.rb +4 -1
- data/lib/reek/smells/long_yield_list.rb +6 -3
- data/lib/reek/smells/simulated_polymorphism.rb +1 -1
- data/lib/reek/smells/smell_detector.rb +4 -32
- data/lib/reek/smells/uncommunicative_method_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_module_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_parameter_name.rb +2 -2
- data/lib/reek/smells/uncommunicative_variable_name.rb +11 -18
- data/lib/reek/smells/utility_function.rb +7 -4
- data/lib/reek/source/reference_collector.rb +9 -2
- data/lib/reek/source/source_locator.rb +6 -0
- data/lib/reek/spec/should_reek.rb +3 -6
- data/lib/reek/spec/should_reek_only_of.rb +4 -3
- data/reek.gemspec +4 -4
- data/spec/reek/cli/reek_command_spec.rb +3 -4
- data/spec/reek/cli/report_spec.rb +10 -6
- data/spec/reek/cli/yaml_command_spec.rb +1 -1
- data/spec/reek/core/code_context_spec.rb +1 -3
- data/spec/reek/core/module_context_spec.rb +1 -1
- data/spec/reek/core/warning_collector_spec.rb +27 -0
- data/spec/reek/examiner_spec.rb +80 -19
- data/spec/reek/smell_warning_spec.rb +4 -61
- data/spec/reek/smells/attribute_spec.rb +4 -7
- data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
- data/spec/reek/smells/class_variable_spec.rb +0 -1
- data/spec/reek/smells/control_couple_spec.rb +8 -15
- data/spec/reek/smells/data_clump_spec.rb +85 -1
- data/spec/reek/smells/duplication_spec.rb +7 -8
- data/spec/reek/smells/feature_envy_spec.rb +2 -32
- data/spec/reek/smells/long_parameter_list_spec.rb +9 -16
- data/spec/reek/smells/long_yield_list_spec.rb +8 -15
- data/spec/reek/smells/smell_detector_shared.rb +12 -0
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +9 -10
- data/spec/reek/smells/utility_function_spec.rb +11 -15
- data/spec/reek/spec/should_reek_only_of_spec.rb +6 -6
- data/spec/reek/spec/should_reek_spec.rb +3 -3
- metadata +36 -22
- data/lib/reek/core/class_context.rb +0 -22
- data/lib/reek/core/detector_stack.rb +0 -33
- data/lib/reek/core/masking_collection.rb +0 -52
- data/spec/reek/core/class_context_spec.rb +0 -53
- data/spec/reek/core/masking_collection_spec.rb +0 -235
data/features/stdin.feature
CHANGED
@@ -33,9 +33,9 @@ Feature: Reek reads from $stdin when no files are given
|
|
33
33
|
And it reports:
|
34
34
|
"""
|
35
35
|
$stdin -- 3 warnings:
|
36
|
-
Turn has no descriptive comment (
|
37
|
-
Turn has the variable name '@x' (
|
38
|
-
Turn#y has the name 'y' (
|
36
|
+
Turn has no descriptive comment (IrresponsibleModule)
|
37
|
+
Turn has the variable name '@x' (UncommunicativeName)
|
38
|
+
Turn#y has the name 'y' (UncommunicativeName)
|
39
39
|
|
40
40
|
"""
|
41
41
|
|
data/lib/reek.rb
CHANGED
@@ -18,7 +18,6 @@ module Reek
|
|
18
18
|
@argv = argv
|
19
19
|
@parser = OptionParser.new
|
20
20
|
@report_class = VerboseReport
|
21
|
-
@collection_strategy = ActiveSmellsOnly
|
22
21
|
@command_class = ReekCommand
|
23
22
|
set_options
|
24
23
|
end
|
@@ -36,7 +35,6 @@ module Reek
|
|
36
35
|
# reek -v|--version Output the tool's version number
|
37
36
|
#
|
38
37
|
# reek [options] files List the smells in the given files
|
39
|
-
# -a|--[no-]show-all Report masked smells
|
40
38
|
# -q|-[no-]quiet Only list files that have smells
|
41
39
|
# files Names of files or dirs to be checked
|
42
40
|
#
|
@@ -46,7 +44,7 @@ Usage: #{progname} [options] [files]
|
|
46
44
|
Examples:
|
47
45
|
|
48
46
|
#{progname} lib/*.rb
|
49
|
-
#{progname} -q
|
47
|
+
#{progname} -q lib
|
50
48
|
cat my_class.rb | #{progname}
|
51
49
|
|
52
50
|
See http://wiki.github.com/kevinrutherford/reek for detailed help.
|
@@ -65,9 +63,6 @@ EOB
|
|
65
63
|
end
|
66
64
|
|
67
65
|
@parser.separator "\nReport formatting:"
|
68
|
-
@parser.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
|
69
|
-
@collection_strategy = opt ? ActiveAndMaskedSmells : ActiveSmellsOnly
|
70
|
-
end
|
71
66
|
@parser.on("-q", "--[no-]quiet", "Suppress headings for smell-free source files") do |opt|
|
72
67
|
@report_class = opt ? QuietReport : VerboseReport
|
73
68
|
end
|
@@ -90,7 +85,7 @@ EOB
|
|
90
85
|
YamlCommand.create(sources)
|
91
86
|
else
|
92
87
|
sources = get_sources
|
93
|
-
ReekCommand.create(sources, @report_class
|
88
|
+
ReekCommand.create(sources, @report_class)
|
94
89
|
end
|
95
90
|
end
|
96
91
|
|
@@ -8,19 +8,19 @@ module Reek
|
|
8
8
|
# text report format.
|
9
9
|
#
|
10
10
|
class ReekCommand
|
11
|
-
def self.create(sources, report_class
|
12
|
-
|
13
|
-
new(examiners, report_class)
|
11
|
+
def self.create(sources, report_class)
|
12
|
+
new(report_class, sources)
|
14
13
|
end
|
15
14
|
|
16
|
-
def initialize(
|
17
|
-
@
|
15
|
+
def initialize(report_class, sources)
|
16
|
+
@sources = sources
|
18
17
|
@report_class = report_class
|
19
18
|
end
|
20
19
|
|
21
20
|
def execute(view)
|
22
21
|
had_smells = false
|
23
|
-
@
|
22
|
+
@sources.each do |source|
|
23
|
+
examiner = Examiner.new(source)
|
24
24
|
rpt = @report_class.new(examiner)
|
25
25
|
had_smells ||= examiner.smelly?
|
26
26
|
view.output(rpt.report)
|
data/lib/reek/cli/report.rb
CHANGED
@@ -1,67 +1,39 @@
|
|
1
1
|
module Reek
|
2
2
|
module Cli
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
class ReportSection
|
10
|
-
|
11
|
-
SMELL_FORMAT = '%m%c %w (%s)'
|
12
|
-
|
13
|
-
def initialize(examiner)
|
14
|
-
@examiner = examiner
|
15
|
-
end
|
16
|
-
|
17
|
-
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
18
|
-
# this report, with a heading.
|
19
|
-
def verbose_report
|
20
|
-
result = header
|
21
|
-
result += ":\n#{smell_list}" if should_report
|
22
|
-
result += "\n"
|
4
|
+
module ReportFormatter
|
5
|
+
def header(desc, count)
|
6
|
+
result = "#{desc} -- #{count} warning"
|
7
|
+
result += 's' unless count == 1
|
23
8
|
result
|
24
9
|
end
|
25
10
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
"
|
30
|
-
end
|
31
|
-
|
32
|
-
def header
|
33
|
-
"#{@examiner.description} -- #{visible_header}"
|
34
|
-
end
|
35
|
-
|
36
|
-
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
37
|
-
# this report.
|
38
|
-
def smell_list
|
39
|
-
@examiner.smells.map {|smell| " #{smell.report(SMELL_FORMAT)}"}.join("\n")
|
11
|
+
def format_list(warnings)
|
12
|
+
warnings.map do |warning|
|
13
|
+
" #{warning.context} #{warning.message} (#{warning.smell_class})"
|
14
|
+
end.join("\n")
|
40
15
|
end
|
41
16
|
|
42
|
-
|
43
|
-
|
44
|
-
def should_report
|
45
|
-
@examiner.num_smells > 0
|
46
|
-
end
|
47
|
-
|
48
|
-
def visible_header
|
49
|
-
num_smells = @examiner.smells.length
|
50
|
-
result = "#{num_smells} warning"
|
51
|
-
result += 's' unless num_smells == 1
|
52
|
-
result
|
53
|
-
end
|
17
|
+
module_function :format_list
|
54
18
|
end
|
55
19
|
|
56
20
|
#
|
57
21
|
# A report that lists every source, including those that have no smells.
|
58
22
|
#
|
59
23
|
class VerboseReport
|
24
|
+
|
25
|
+
include ReportFormatter
|
26
|
+
|
60
27
|
def initialize(examiner)
|
61
|
-
@
|
28
|
+
@examiner = examiner
|
62
29
|
end
|
30
|
+
|
63
31
|
def report
|
64
|
-
@
|
32
|
+
warnings = @examiner.smells
|
33
|
+
warning_count = warnings.length
|
34
|
+
result = header(@examiner.description, warning_count)
|
35
|
+
result += ":\n#{format_list(warnings)}" if warning_count > 0
|
36
|
+
result + "\n"
|
65
37
|
end
|
66
38
|
end
|
67
39
|
|
@@ -69,11 +41,17 @@ module Reek
|
|
69
41
|
# A report that lists a section for each source that has smells.
|
70
42
|
#
|
71
43
|
class QuietReport
|
44
|
+
|
45
|
+
include ReportFormatter
|
46
|
+
|
72
47
|
def initialize(examiner)
|
73
|
-
@
|
48
|
+
@warnings = examiner.smells
|
49
|
+
@smell_count = @warnings.length
|
50
|
+
@desc = examiner.description
|
74
51
|
end
|
52
|
+
|
75
53
|
def report
|
76
|
-
@
|
54
|
+
@smell_count > 0 ? "#{header(@desc, @smell_count)}:\n#{format_list(@warnings)}\n" : ''
|
77
55
|
end
|
78
56
|
end
|
79
57
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'sexp'
|
2
|
-
require File.join(File.dirname(File.expand_path(__FILE__)), 'class_context')
|
3
2
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
|
4
3
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'module_context')
|
5
4
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'stop_context')
|
@@ -26,24 +25,21 @@ module Reek
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def process_default(exp)
|
29
|
-
exp
|
28
|
+
exp.each { |sub| process(sub) if Array === sub }
|
30
29
|
end
|
31
30
|
|
32
|
-
def
|
33
|
-
|
31
|
+
def process_module(exp)
|
32
|
+
name = Source::SexpFormatter.format(exp[1])
|
33
|
+
scope = ModuleContext.new(@element, name, exp)
|
34
34
|
push(scope) do
|
35
|
-
process_default(exp) unless
|
35
|
+
process_default(exp) unless exp.superclass == [:const, :Struct]
|
36
36
|
check_smells(exp[0])
|
37
37
|
end
|
38
38
|
scope
|
39
39
|
end
|
40
40
|
|
41
|
-
def process_module(exp)
|
42
|
-
do_module_or_class(exp, ModuleContext)
|
43
|
-
end
|
44
|
-
|
45
41
|
def process_class(exp)
|
46
|
-
|
42
|
+
process_module(exp)
|
47
43
|
end
|
48
44
|
|
49
45
|
def process_defn(exp)
|
@@ -122,11 +118,6 @@ module Reek
|
|
122
118
|
process_iasgn(exp)
|
123
119
|
end
|
124
120
|
|
125
|
-
def process_lasgn(exp)
|
126
|
-
@element.record_local_variable(exp[1])
|
127
|
-
process_default(exp)
|
128
|
-
end
|
129
|
-
|
130
121
|
def process_iasgn(exp)
|
131
122
|
@element.record_use_of_self
|
132
123
|
process_default(exp)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_context')
|
1
2
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'object_refs')
|
2
3
|
|
3
4
|
module Reek
|
@@ -53,7 +54,6 @@ module Reek
|
|
53
54
|
@parameters.extend(MethodParameters)
|
54
55
|
@num_statements = 0
|
55
56
|
@refs = ObjectRefs.new
|
56
|
-
@outer.record_method(self) # SMELL: these could be found by tree walking
|
57
57
|
end
|
58
58
|
|
59
59
|
def count_statements(num)
|
@@ -11,27 +11,9 @@ module Reek
|
|
11
11
|
#
|
12
12
|
class ModuleContext < CodeContext
|
13
13
|
|
14
|
-
class << self
|
15
|
-
def create(outer, exp)
|
16
|
-
res = Source::SexpFormatter.format(exp[1])
|
17
|
-
new(outer, res, exp)
|
18
|
-
end
|
19
|
-
|
20
|
-
def from_s(src)
|
21
|
-
source = src.to_reek_source
|
22
|
-
sniffer = Sniffer.new(source)
|
23
|
-
CodeParser.new(sniffer).do_module_or_class(source.syntax_tree, self)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
14
|
def initialize(outer, name, exp)
|
28
15
|
super(outer, exp)
|
29
16
|
@name = name
|
30
|
-
@parsed_methods = []
|
31
|
-
end
|
32
|
-
|
33
|
-
def record_method(meth)
|
34
|
-
@parsed_methods << meth
|
35
17
|
end
|
36
18
|
end
|
37
19
|
end
|
data/lib/reek/core/sniffer.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require File.join(File.dirname(File.expand_path(__FILE__)), 'detector_stack')
|
2
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_parser')
|
3
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smells')
|
4
3
|
require 'yaml'
|
@@ -65,36 +64,29 @@ module Reek
|
|
65
64
|
]
|
66
65
|
end
|
67
66
|
|
68
|
-
def initialize(src
|
69
|
-
@config_strategy = config_strategy
|
70
|
-
@already_checked_for_smells = false
|
67
|
+
def initialize(src)
|
71
68
|
@typed_detectors = nil
|
72
69
|
@detectors = Hash.new
|
73
70
|
Sniffer.smell_classes.each do |klass|
|
74
|
-
@detectors[klass] =
|
71
|
+
@detectors[klass] = klass.new(src.desc)
|
75
72
|
end
|
76
73
|
@source = src
|
77
74
|
src.configure(self)
|
78
75
|
end
|
79
76
|
|
80
|
-
def check_for_smells
|
81
|
-
return if @already_checked_for_smells
|
82
|
-
CodeParser.new(self).process(@source.syntax_tree)
|
83
|
-
@already_checked_for_smells = true
|
84
|
-
end
|
85
|
-
|
86
77
|
def configure(klass, config)
|
87
|
-
@
|
78
|
+
@detectors[klass].configure_with(config)
|
88
79
|
end
|
89
80
|
|
90
|
-
def report_on(
|
91
|
-
|
92
|
-
@detectors.each_value { |
|
81
|
+
def report_on(listener)
|
82
|
+
CodeParser.new(self).process(@source.syntax_tree)
|
83
|
+
@detectors.each_value { |detector| detector.report_on(listener) }
|
93
84
|
end
|
94
85
|
|
95
|
-
def examine(scope,
|
96
|
-
|
97
|
-
|
86
|
+
def examine(scope, node_type)
|
87
|
+
smell_listeners[node_type].each do |detector|
|
88
|
+
detector.examine(scope)
|
89
|
+
end
|
98
90
|
end
|
99
91
|
|
100
92
|
private
|
@@ -102,7 +94,7 @@ module Reek
|
|
102
94
|
def smell_listeners()
|
103
95
|
unless @typed_detectors
|
104
96
|
@typed_detectors = Hash.new {|hash,key| hash[key] = [] }
|
105
|
-
@detectors.each_value { |
|
97
|
+
@detectors.each_value { |detector| detector.register(@typed_detectors) }
|
106
98
|
end
|
107
99
|
@typed_detectors
|
108
100
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Core
|
5
|
+
|
6
|
+
#
|
7
|
+
# Collects and sorts smells warnings.
|
8
|
+
#
|
9
|
+
class WarningCollector
|
10
|
+
def initialize
|
11
|
+
@warnings = Set.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def found_smell(warning)
|
15
|
+
@warnings.add(warning)
|
16
|
+
end
|
17
|
+
|
18
|
+
def warnings
|
19
|
+
@warnings.to_a.sort do |first,second|
|
20
|
+
first_sig = [first.context, first.message, first.smell_class]
|
21
|
+
second_sig = [second.context, second.message, second.smell_class]
|
22
|
+
first_sig <=> second_sig
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/reek/examiner.rb
CHANGED
@@ -1,29 +1,9 @@
|
|
1
|
-
require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'masking_collection')
|
2
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'sniffer')
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'warning_collector')
|
3
3
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'source')
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
|
7
|
-
class ActiveSmellsOnly
|
8
|
-
def configure(detectors, config)
|
9
|
-
detectors.adopt(config)
|
10
|
-
end
|
11
|
-
|
12
|
-
def smells_in(sources)
|
13
|
-
Core::MaskingCollection.new.collect_from(sources, self).all_active_items.to_a
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class ActiveAndMaskedSmells
|
18
|
-
def configure(detectors, config)
|
19
|
-
detectors.push(config)
|
20
|
-
end
|
21
|
-
|
22
|
-
def smells_in(sources)
|
23
|
-
Core::MaskingCollection.new.collect_from(sources, self).all_items
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
7
|
#
|
28
8
|
# Finds the active code smells in Ruby source code.
|
29
9
|
#
|
@@ -47,13 +27,7 @@ module Reek
|
|
47
27
|
# and if it is an Array, it is assumed to be a list of file paths,
|
48
28
|
# each of which is opened and parsed for source code.
|
49
29
|
#
|
50
|
-
|
51
|
-
# The +collector+ will be asked to examine the sources and report
|
52
|
-
# an array of SmellWarning objects. The default collector is an
|
53
|
-
# instance of ActiveSmellsOnly, which completely ignores all smells
|
54
|
-
# that have been masked by configuration options.
|
55
|
-
#
|
56
|
-
def initialize(source, collector = ActiveSmellsOnly.new)
|
30
|
+
def initialize(source)
|
57
31
|
sources = case source
|
58
32
|
when Array
|
59
33
|
@description = 'dir'
|
@@ -66,12 +40,13 @@ module Reek
|
|
66
40
|
@description = src.desc
|
67
41
|
[src]
|
68
42
|
end
|
69
|
-
|
43
|
+
collector = Core::WarningCollector.new
|
44
|
+
sources.each { |src| Core::Sniffer.new(src).report_on(collector) }
|
45
|
+
@smells = collector.warnings
|
70
46
|
end
|
71
47
|
|
72
48
|
#
|
73
|
-
#
|
74
|
-
# in the source.
|
49
|
+
# List the smells found in the source.
|
75
50
|
#
|
76
51
|
# @return [Array<SmellWarning>]
|
77
52
|
#
|
@@ -79,18 +54,51 @@ module Reek
|
|
79
54
|
@smells
|
80
55
|
end
|
81
56
|
|
57
|
+
#
|
58
|
+
# True if and only if there are code smells in the source.
|
59
|
+
#
|
60
|
+
def smelly?
|
61
|
+
not @smells.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Returns an Array of SmellWarning objects, one for each non-masked smell
|
66
|
+
# in the source.
|
67
|
+
#
|
68
|
+
# @deprecated Use #smells instead.
|
69
|
+
#
|
70
|
+
def all_active_smells
|
71
|
+
@smells
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Returns an Array of SmellWarning objects, one for each smell
|
76
|
+
# in the source; includes active smells and masked smells.
|
77
|
+
#
|
78
|
+
# @return [Array<SmellWarning>]
|
79
|
+
#
|
80
|
+
# @deprecated Use #smells instead.
|
81
|
+
#
|
82
|
+
def all_smells
|
83
|
+
@smells
|
84
|
+
end
|
85
|
+
|
82
86
|
#
|
83
87
|
# Returns the number of non-masked smells in the source.
|
84
88
|
#
|
85
|
-
|
89
|
+
# @deprecated Use #smells instead.
|
90
|
+
#
|
91
|
+
def num_active_smells
|
86
92
|
@smells.length
|
87
93
|
end
|
88
94
|
|
89
95
|
#
|
90
|
-
#
|
96
|
+
# Returns the number of masked smells in the source.
|
91
97
|
#
|
92
|
-
|
93
|
-
|
98
|
+
# @deprecated Masked smells are no longer reported; this method always returns 0.
|
99
|
+
#
|
100
|
+
def num_masked_smells
|
101
|
+
0
|
94
102
|
end
|
95
103
|
end
|
96
104
|
end
|