reek 1.2.7.2 → 1.2.7.3
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/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
|