kevinrutherford-reek 1.1.3.9 → 1.1.3.10
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -1
- data/License.txt +20 -0
- data/bin/reek +5 -5
- data/features/options.feature +1 -0
- data/features/reports.feature +40 -0
- data/features/stdin.feature +10 -1
- data/features/step_definitions/reek_steps.rb +2 -2
- data/features/support/env.rb +2 -2
- data/lib/reek/class_context.rb +2 -2
- data/lib/reek/code_parser.rb +4 -0
- data/lib/reek/core_extras.rb +50 -0
- data/lib/reek/object_source.rb +7 -18
- data/lib/reek/options.rb +13 -3
- data/lib/reek/report.rb +27 -28
- data/lib/reek/sexp_formatter.rb +2 -0
- data/lib/reek/smells/control_couple.rb +5 -0
- data/lib/reek/sniffer.rb +98 -3
- data/lib/reek/source.rb +9 -112
- data/lib/reek/spec.rb +29 -55
- data/lib/reek.rb +1 -1
- data/reek.gemspec +4 -4
- data/spec/reek/object_source_spec.rb +3 -3
- data/spec/reek/report_spec.rb +9 -5
- data/spec/reek/should_reek_of_spec.rb +105 -0
- data/spec/reek/should_reek_only_of_spec.rb +85 -0
- data/spec/reek/{spec_spec.rb → should_reek_spec.rb} +24 -3
- data/spec/reek/smells/duplication_spec.rb +1 -1
- data/spec/reek/smells/large_class_spec.rb +1 -0
- data/spec/reek/smells/long_method_spec.rb +4 -4
- data/spec/reek/smells/long_parameter_list_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_spec.rb +1 -1
- data/spec/reek/smells/uncommunicative_name_spec.rb +2 -1
- data/spec/reek/sniffer_spec.rb +10 -0
- data/spec/samples/all_but_one_masked/clean_one.rb +6 -0
- data/spec/samples/all_but_one_masked/dirty.rb +7 -0
- data/spec/samples/all_but_one_masked/masked.reek +5 -0
- data/spec/samples/clean_due_to_masking/clean_one.rb +6 -0
- data/spec/samples/clean_due_to_masking/clean_three.rb +6 -0
- data/spec/samples/clean_due_to_masking/clean_two.rb +6 -0
- data/spec/samples/clean_due_to_masking/dirty_one.rb +7 -0
- data/spec/samples/clean_due_to_masking/dirty_two.rb +7 -0
- data/spec/samples/clean_due_to_masking/masked.reek +7 -0
- data/spec/samples/mixed_results/clean_one.rb +6 -0
- data/spec/samples/mixed_results/clean_three.rb +6 -0
- data/spec/samples/mixed_results/clean_two.rb +6 -0
- data/spec/samples/mixed_results/dirty_one.rb +7 -0
- data/spec/samples/mixed_results/dirty_two.rb +7 -0
- data/spec/slow/inline_spec.rb +6 -2
- data/spec/slow/optparse_spec.rb +6 -2
- data/spec/slow/redcloth_spec.rb +6 -2
- data/tasks/test.rake +2 -0
- metadata +23 -4
- data/spec/slow/source_list_spec.rb +0 -40
data/History.txt
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
== 1.2 (in progress -- see github)
|
2
2
|
|
3
3
|
=== Major Changes
|
4
|
-
* Reek's RDoc is now hosted at http://rdoc.info/projects/kevinrutherford/reek
|
5
4
|
* Reek's output reports are now formatted differently:
|
6
5
|
** Reek is no longer silent about smell-free source code
|
7
6
|
** Output now reports on all files examined, even if they have no smells
|
8
7
|
** Smell warnings are indented in the report; file summary headers are not
|
9
8
|
** Reports for multiple sources are run together; no more blank lines
|
9
|
+
** Reports in spec matcher failures are quiet
|
10
10
|
* The smells masked by *.reek config files can now be seen:
|
11
11
|
** The header for each source file now counts masked smells
|
12
12
|
** The --show-all (-a) option shows masked warnings in the report
|
13
13
|
|
14
14
|
=== Minor Changes
|
15
|
+
* Reek's RDoc is now hosted at http://rdoc.info/projects/kevinrutherford/reek
|
15
16
|
* Several changes to the LongMethod counting algorithm:
|
16
17
|
** LongMethod now counts statements deeper into each method (fixed #25)
|
17
18
|
** LongMethod no longer counts control structures, only their contained stmts
|
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008,2009 Kevin Rutherford
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/reek
CHANGED
@@ -6,15 +6,15 @@
|
|
6
6
|
# Author: Kevin Rutherford
|
7
7
|
#
|
8
8
|
|
9
|
-
require 'reek'
|
10
|
-
require 'reek/source'
|
11
9
|
require 'reek/options'
|
12
10
|
|
13
11
|
def reek(args)
|
14
12
|
begin
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
sniffer = Reek::Options.parse(args)
|
14
|
+
# SMELL:
|
15
|
+
# This should use the actual type of report selected by the user's options
|
16
|
+
puts sniffer.full_report
|
17
|
+
return sniffer.smelly? ? 2 : 0
|
18
18
|
rescue SystemExit => ex
|
19
19
|
return ex.status
|
20
20
|
rescue Exception => error
|
data/features/options.feature
CHANGED
@@ -32,6 +32,7 @@ Feature: Reek can be controlled using command-line options
|
|
32
32
|
|
33
33
|
Options:
|
34
34
|
-a, --[no-]show-all Show all smells, including those masked by config settings
|
35
|
+
-q, --quiet Suppress headings for smell-free source files
|
35
36
|
-h, --help Show this message
|
36
37
|
-f, --format FORMAT Specify the format of smell warnings
|
37
38
|
-c, --context-first Sort by context; sets the format string to "%m%c %w (%s)"
|
data/features/reports.feature
CHANGED
@@ -37,4 +37,44 @@ Feature: Correctly formatted reports
|
|
37
37
|
|
38
38
|
"""
|
39
39
|
|
40
|
+
Scenario Outline: --quiet turns off headers for fragrant files
|
41
|
+
When I run reek <option> spec/samples/three_clean_files/*.rb
|
42
|
+
Then it succeeds
|
43
|
+
And it reports:
|
44
|
+
"""
|
45
|
+
|
46
|
+
|
47
|
+
"""
|
48
|
+
|
49
|
+
Examples:
|
50
|
+
| option |
|
51
|
+
| -q |
|
52
|
+
| --quiet |
|
53
|
+
|
54
|
+
|
55
|
+
Scenario Outline: -a turns on details in presence of -q
|
56
|
+
When I run reek <options> spec/samples/clean_due_to_masking/*.rb
|
57
|
+
Then it succeeds
|
58
|
+
And it reports:
|
59
|
+
"""
|
60
|
+
spec/samples/clean_due_to_masking/dirty_one.rb -- 0 warnings (+6 masked):
|
61
|
+
(masked) Dirty has the variable name '@s' (Uncommunicative Name)
|
62
|
+
(masked) Dirty#a calls @s.title multiple times (Duplication)
|
63
|
+
(masked) Dirty#a calls puts(@s.title) multiple times (Duplication)
|
64
|
+
(masked) Dirty#a has the name 'a' (Uncommunicative Name)
|
65
|
+
(masked) Dirty#a/block has the variable name 'x' (Uncommunicative Name)
|
66
|
+
(masked) Dirty#a/block/block is nested (Nested Iterators)
|
67
|
+
spec/samples/clean_due_to_masking/dirty_two.rb -- 0 warnings (+6 masked):
|
68
|
+
(masked) Dirty has the variable name '@s' (Uncommunicative Name)
|
69
|
+
(masked) Dirty#a calls @s.title multiple times (Duplication)
|
70
|
+
(masked) Dirty#a calls puts(@s.title) multiple times (Duplication)
|
71
|
+
(masked) Dirty#a has the name 'a' (Uncommunicative Name)
|
72
|
+
(masked) Dirty#a/block has the variable name 'x' (Uncommunicative Name)
|
73
|
+
(masked) Dirty#a/block/block is nested (Nested Iterators)
|
74
|
+
|
75
|
+
"""
|
40
76
|
|
77
|
+
Examples:
|
78
|
+
| options |
|
79
|
+
| -q -a |
|
80
|
+
| -a -q |
|
data/features/stdin.feature
CHANGED
@@ -13,13 +13,22 @@ Feature: Reek reads from $stdin when no files are given
|
|
13
13
|
|
14
14
|
"""
|
15
15
|
|
16
|
-
Scenario: outputs
|
16
|
+
Scenario: outputs header only on empty stdin
|
17
17
|
When I pass "" to reek
|
18
18
|
Then it succeeds
|
19
19
|
And it reports:
|
20
20
|
"""
|
21
21
|
$stdin -- 0 warnings
|
22
22
|
|
23
|
+
"""
|
24
|
+
|
25
|
+
Scenario: outputs nothing on empty stdin in quiet mode
|
26
|
+
When I pass "" to reek --quiet
|
27
|
+
Then it succeeds
|
28
|
+
And it reports:
|
29
|
+
"""
|
30
|
+
|
31
|
+
|
23
32
|
"""
|
24
33
|
|
25
34
|
Scenario: return non-zero status when there are smells
|
data/features/support/env.rb
CHANGED
@@ -16,10 +16,10 @@ class CucumberWorld
|
|
16
16
|
@last_stderr = IO.read(stderr_file.path)
|
17
17
|
end
|
18
18
|
|
19
|
-
def run_with_pipe(stdin)
|
19
|
+
def run_with_pipe(stdin, args)
|
20
20
|
stderr_file = Tempfile.new('cucumber')
|
21
21
|
stderr_file.close
|
22
|
-
@last_stdout = `echo \"#{stdin}\" | ruby -Ilib bin/reek 2> #{stderr_file.path}`
|
22
|
+
@last_stdout = `echo \"#{stdin}\" | ruby -Ilib bin/reek #{args} 2> #{stderr_file.path}`
|
23
23
|
@last_exit_status = $?.exitstatus
|
24
24
|
@last_stderr = IO.read(stderr_file.path)
|
25
25
|
end
|
data/lib/reek/class_context.rb
CHANGED
@@ -16,8 +16,8 @@ module Reek
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def ClassContext.from_s(src)
|
19
|
-
|
20
|
-
CodeParser.new(
|
19
|
+
sniffer = src.sniff
|
20
|
+
CodeParser.new(sniffer).process_class(sniffer.source.syntax_tree)
|
21
21
|
end
|
22
22
|
|
23
23
|
def initialize(outer, name, superclass = nil)
|
data/lib/reek/code_parser.rb
CHANGED
@@ -9,6 +9,10 @@ require 'reek/method_context'
|
|
9
9
|
require 'reek/singleton_method_context'
|
10
10
|
require 'reek/yield_call_context'
|
11
11
|
|
12
|
+
#
|
13
|
+
# Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
|
14
|
+
# syntax tree more easily.
|
15
|
+
#
|
12
16
|
class Sexp
|
13
17
|
def children
|
14
18
|
find_all { |item| Sexp === item }
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'reek/source' # SMELL: should refer to Sniffer
|
2
|
+
require 'reek/sniffer'
|
3
|
+
|
4
|
+
class File
|
5
|
+
#
|
6
|
+
# Creates a new +Sniffer+ that assumes this File contains Ruby source
|
7
|
+
# code and examines that code for smells.
|
8
|
+
#
|
9
|
+
def sniff
|
10
|
+
result = Reek::Sniffer.new
|
11
|
+
Reek::Source.from_path(self.path, result)
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class IO
|
17
|
+
#
|
18
|
+
# Creates a new +Sniffer+ that assumes this IO stream contains Ruby source
|
19
|
+
# code and examines that code for smells.
|
20
|
+
#
|
21
|
+
def sniff(description = 'io')
|
22
|
+
code = self.readlines.join
|
23
|
+
result = Reek::Sniffer.new
|
24
|
+
Reek::Source.new(code, description, result)
|
25
|
+
result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class String
|
30
|
+
#
|
31
|
+
# Creates a new +Sniffer+ that assumes this String contains Ruby source
|
32
|
+
# code and examines that code for smells.
|
33
|
+
#
|
34
|
+
def sniff
|
35
|
+
result = Reek::Sniffer.new
|
36
|
+
Reek::Source.new(self, 'string', result)
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Array
|
42
|
+
#
|
43
|
+
# Creates a new +Sniffer+ that assumes this Array contains the names
|
44
|
+
# of Ruby source files and examines those files for smells.
|
45
|
+
#
|
46
|
+
def sniff
|
47
|
+
sniffers = self.map {|path| File.new(path).sniff }
|
48
|
+
Reek::SnifferSet.new(sniffers, 'dir')
|
49
|
+
end
|
50
|
+
end
|
data/lib/reek/object_source.rb
CHANGED
@@ -1,15 +1,4 @@
|
|
1
1
|
module Reek
|
2
|
-
class Source
|
3
|
-
#
|
4
|
-
# Factory method: creates a +Source+ from obj.
|
5
|
-
# The code is not parsed until +report+ is called.
|
6
|
-
# (This feature is only enabled if you have the ParseTree gem installed.)
|
7
|
-
#
|
8
|
-
def self.from_object(obj)
|
9
|
-
return ObjectSource.new(obj, obj.to_s)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
2
|
class ObjectSource < Source # :nodoc:
|
14
3
|
|
15
4
|
def self.unify(sexp) # :nodoc:
|
@@ -20,7 +9,7 @@ module Reek
|
|
20
9
|
return unifier.process(sexp[0])
|
21
10
|
end
|
22
11
|
|
23
|
-
def initialize(code, desc) # :nodoc:
|
12
|
+
def initialize(code, desc, sniffer) # :nodoc:
|
24
13
|
super
|
25
14
|
@sniffer.disable(LargeClass)
|
26
15
|
end
|
@@ -35,7 +24,7 @@ module Reek
|
|
35
24
|
end
|
36
25
|
end
|
37
26
|
|
38
|
-
def
|
27
|
+
def syntax_tree
|
39
28
|
if can_parse_objects?
|
40
29
|
ObjectSource.unify(ParseTree.new.parse_tree(@source))
|
41
30
|
else
|
@@ -47,12 +36,12 @@ end
|
|
47
36
|
|
48
37
|
class Object
|
49
38
|
#
|
50
|
-
# Constructs a
|
51
|
-
# to generate an abstract syntax tree for the object, which can in turn then
|
52
|
-
# be examined for code smells.
|
39
|
+
# Constructs a Sniffer which examines this object for code smells.
|
53
40
|
# (This feature is only enabled if you have the ParseTree gem installed.)
|
54
41
|
#
|
55
|
-
def
|
56
|
-
|
42
|
+
def sniff
|
43
|
+
result = Sniffer.new
|
44
|
+
ObjectSource.new(self, self.to_s, result)
|
45
|
+
result
|
57
46
|
end
|
58
47
|
end
|
data/lib/reek/options.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'optparse'
|
2
|
+
require 'reek'
|
2
3
|
require 'reek/source'
|
4
|
+
require 'reek/core_extras' # SMELL
|
3
5
|
|
4
6
|
module Reek
|
5
7
|
|
@@ -11,7 +13,8 @@ module Reek
|
|
11
13
|
def self.default_options
|
12
14
|
{
|
13
15
|
:format => CTX_SORT,
|
14
|
-
:show_all => false
|
16
|
+
:show_all => false,
|
17
|
+
:quiet => false
|
15
18
|
}
|
16
19
|
end
|
17
20
|
|
@@ -40,12 +43,16 @@ EOB
|
|
40
43
|
set_all_options(opts, config)
|
41
44
|
end
|
42
45
|
|
46
|
+
# SMELL: Greedy Module
|
47
|
+
# This creates the command-line parser AND invokes it. And for the
|
48
|
+
# -v and -h options it also executes them. And it holds the config
|
49
|
+
# options for the rest of the application.
|
43
50
|
def self.parse(args)
|
44
51
|
@@opts = parse_args(args)
|
45
52
|
if args.length > 0
|
46
|
-
return
|
53
|
+
return args.sniff
|
47
54
|
else
|
48
|
-
return
|
55
|
+
return $stdin.sniff('$stdin')
|
49
56
|
end
|
50
57
|
end
|
51
58
|
|
@@ -69,6 +76,9 @@ EOB
|
|
69
76
|
opts.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
|
70
77
|
config[:show_all] = opt
|
71
78
|
end
|
79
|
+
opts.on("-q", "--quiet", "Suppress headings for smell-free source files") do
|
80
|
+
config[:quiet] = true
|
81
|
+
end
|
72
82
|
end
|
73
83
|
|
74
84
|
def self.set_help_option(opts)
|
data/lib/reek/report.rb
CHANGED
@@ -1,22 +1,15 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'reek/sniffer'
|
3
|
-
require 'reek/smells/smell_detector'
|
4
3
|
|
5
4
|
module Reek
|
6
5
|
class Report
|
7
6
|
include Enumerable
|
8
7
|
|
9
|
-
def initialize(sniffer
|
8
|
+
def initialize(sniffer) # :nodoc:
|
10
9
|
@masked_warnings = SortedSet.new
|
11
10
|
@warnings = SortedSet.new
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
# Yields, in turn, each SmellWarning in this report.
|
17
|
-
#
|
18
|
-
def each
|
19
|
-
@warnings.each { |smell| yield smell }
|
11
|
+
@desc = sniffer.desc
|
12
|
+
sniffer.report_on(self)
|
20
13
|
end
|
21
14
|
|
22
15
|
#
|
@@ -52,19 +45,25 @@ module Reek
|
|
52
45
|
|
53
46
|
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
54
47
|
# this report, with a heading.
|
55
|
-
def full_report
|
56
|
-
|
57
|
-
result
|
48
|
+
def full_report
|
49
|
+
return quiet_report if Options[:quiet]
|
50
|
+
result = header(@warnings.length)
|
51
|
+
result += ":\n#{smell_list}" if should_report
|
58
52
|
result += "\n"
|
59
53
|
result
|
60
54
|
end
|
61
55
|
|
56
|
+
def quiet_report
|
57
|
+
return '' unless should_report
|
58
|
+
"#{header(@warnings.length)}:\n#{smell_list}\n"
|
59
|
+
end
|
60
|
+
|
62
61
|
def should_report
|
63
62
|
@warnings.length > 0 or (Options[:show_all] and @masked_warnings.length > 0)
|
64
63
|
end
|
65
64
|
|
66
|
-
def header(
|
67
|
-
result = "#{desc} -- #{num_smells} warning"
|
65
|
+
def header(num_smells)
|
66
|
+
result = "#{@desc} -- #{num_smells} warning"
|
68
67
|
result += 's' unless num_smells == 1
|
69
68
|
result += " (+#{@masked_warnings.length} masked)" unless @masked_warnings.empty?
|
70
69
|
result
|
@@ -72,7 +71,7 @@ module Reek
|
|
72
71
|
|
73
72
|
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
74
73
|
# this report.
|
75
|
-
def
|
74
|
+
def smell_list
|
76
75
|
all = SortedSet.new(@warnings)
|
77
76
|
all.merge(@masked_warnings) if Options[:show_all]
|
78
77
|
all.map {|smell| " #{smell.report}"}.join("\n")
|
@@ -82,15 +81,8 @@ module Reek
|
|
82
81
|
class ReportList
|
83
82
|
include Enumerable
|
84
83
|
|
85
|
-
def initialize(
|
86
|
-
@
|
87
|
-
end
|
88
|
-
|
89
|
-
#
|
90
|
-
# Yields, in turn, each SmellWarning in every report in this report.
|
91
|
-
#
|
92
|
-
def each(&blk)
|
93
|
-
@sources.each {|src| src.report.each(&blk) }
|
84
|
+
def initialize(sniffers)
|
85
|
+
@sniffers = sniffers
|
94
86
|
end
|
95
87
|
|
96
88
|
def empty?
|
@@ -98,11 +90,18 @@ module Reek
|
|
98
90
|
end
|
99
91
|
|
100
92
|
def length
|
101
|
-
@
|
93
|
+
@sniffers.inject(0) {|sum, sniffer| sum + sniffer.num_smells }
|
102
94
|
end
|
103
95
|
|
96
|
+
# SMELL: Shotgun Surgery
|
97
|
+
# This method and the next will have to be repeated for every new
|
98
|
+
# kind of report.
|
104
99
|
def full_report
|
105
|
-
@
|
100
|
+
@sniffers.map { |sniffer| sniffer.full_report }.join
|
101
|
+
end
|
102
|
+
|
103
|
+
def quiet_report
|
104
|
+
@sniffers.map { |sniffer| sniffer.quiet_report }.join
|
106
105
|
end
|
107
106
|
|
108
107
|
#
|
@@ -110,7 +109,7 @@ module Reek
|
|
110
109
|
# only if one of them has a report string matching all of the +patterns+.
|
111
110
|
#
|
112
111
|
def has_smell?(smell_class, patterns)
|
113
|
-
@
|
112
|
+
@sniffers.any? { |sniffer| sniffer.has_smell?(smell_class, patterns) }
|
114
113
|
end
|
115
114
|
end
|
116
115
|
end
|
data/lib/reek/sexp_formatter.rb
CHANGED
@@ -53,6 +53,11 @@ module Reek
|
|
53
53
|
#
|
54
54
|
def examine_context(cond)
|
55
55
|
return unless cond.tests_a_parameter?
|
56
|
+
# SMELL: Duplication
|
57
|
+
# This smell is reported once for each conditional that tests the
|
58
|
+
# same parameter. Which means that the same smell can recur within
|
59
|
+
# a single sniffer. Which in turn means that the sniffer can't count
|
60
|
+
# its smells without knowing which are duplicates.
|
56
61
|
found(cond, "is controlled by argument #{SexpFormatter.format(cond.if_expr)}")
|
57
62
|
end
|
58
63
|
end
|
data/lib/reek/sniffer.rb
CHANGED
@@ -10,6 +10,8 @@ require 'reek/smells/nested_iterators'
|
|
10
10
|
require 'reek/smells/uncommunicative_name'
|
11
11
|
require 'reek/smells/utility_function'
|
12
12
|
require 'reek/config_file'
|
13
|
+
require 'reek/code_parser'
|
14
|
+
require 'reek/report'
|
13
15
|
require 'yaml'
|
14
16
|
|
15
17
|
class Hash
|
@@ -41,6 +43,8 @@ end
|
|
41
43
|
module Reek
|
42
44
|
class Sniffer
|
43
45
|
|
46
|
+
# SMELL: Duplication
|
47
|
+
# This list should be calculated by looking in the source folder.
|
44
48
|
SMELL_CLASSES = [
|
45
49
|
Smells::ControlCouple,
|
46
50
|
Smells::Duplication,
|
@@ -54,13 +58,12 @@ module Reek
|
|
54
58
|
Smells::UtilityFunction,
|
55
59
|
]
|
56
60
|
|
61
|
+
attr_accessor :source
|
62
|
+
|
57
63
|
def initialize
|
58
|
-
defaults_file = File.join(File.dirname(__FILE__), '..', '..', 'config', 'defaults.reek')
|
59
|
-
@config = YAML.load_file(defaults_file)
|
60
64
|
@typed_detectors = nil
|
61
65
|
@detectors = Hash.new
|
62
66
|
SMELL_CLASSES.each { |klass| @detectors[klass] = DetectorStack.new(klass.new) }
|
63
|
-
@listeners = []
|
64
67
|
end
|
65
68
|
|
66
69
|
#
|
@@ -93,6 +96,60 @@ module Reek
|
|
93
96
|
listeners.each {|smell| smell.examine(scope) } if listeners
|
94
97
|
end
|
95
98
|
|
99
|
+
#
|
100
|
+
# Returns a +Report+ listing the smells found in this source. The first
|
101
|
+
# call to +report+ parses the source code and constructs a list of
|
102
|
+
# +SmellWarning+s found; subsequent calls simply return this same list.
|
103
|
+
#
|
104
|
+
def report
|
105
|
+
unless @report
|
106
|
+
CodeParser.new(self).process(@source.syntax_tree)
|
107
|
+
@report = Report.new(self)
|
108
|
+
end
|
109
|
+
@report
|
110
|
+
end
|
111
|
+
|
112
|
+
def smelly?
|
113
|
+
report.length > 0
|
114
|
+
end
|
115
|
+
|
116
|
+
def quiet_report
|
117
|
+
report.quiet_report
|
118
|
+
end
|
119
|
+
|
120
|
+
# SMELL: Shotgun Surgery
|
121
|
+
# This and the above method will need to be replicated for every new
|
122
|
+
# kind of report.
|
123
|
+
def full_report
|
124
|
+
report.full_report
|
125
|
+
end
|
126
|
+
|
127
|
+
def num_smells
|
128
|
+
report.length
|
129
|
+
end
|
130
|
+
|
131
|
+
def desc
|
132
|
+
# SMELL: Special Case
|
133
|
+
# Only used in the Report tests, because they don't always create a Source.
|
134
|
+
@source ? @source.desc : "unknown"
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Checks for instances of +smell_class+, and returns +true+
|
139
|
+
# only if one of them has a report string matching all of the +patterns+.
|
140
|
+
#
|
141
|
+
def has_smell?(smell_class, patterns=[])
|
142
|
+
report.has_smell?(smell_class, patterns)
|
143
|
+
end
|
144
|
+
|
145
|
+
def smells_only_of?(klass, patterns)
|
146
|
+
report.length == 1 and has_smell?(klass, patterns)
|
147
|
+
end
|
148
|
+
|
149
|
+
def sniff
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
96
153
|
private
|
97
154
|
|
98
155
|
def smell_listeners()
|
@@ -110,4 +167,42 @@ private
|
|
110
167
|
all_reekfiles(parent) + Dir["#{path}/*.reek"]
|
111
168
|
end
|
112
169
|
end
|
170
|
+
|
171
|
+
class SnifferSet
|
172
|
+
|
173
|
+
attr_reader :sniffers, :desc
|
174
|
+
|
175
|
+
def initialize(sniffers, desc)
|
176
|
+
@sniffers = sniffers
|
177
|
+
@desc = desc
|
178
|
+
end
|
179
|
+
|
180
|
+
def smelly?
|
181
|
+
@sniffers.any? {|sniffer| sniffer.smelly? }
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Checks for instances of +smell_class+, and returns +true+
|
186
|
+
# only if one of them has a report string matching all of the +patterns+.
|
187
|
+
#
|
188
|
+
def has_smell?(smell_class, patterns=[])
|
189
|
+
@sniffers.any? {|sniffer| sniffer.has_smell?(smell_class, patterns)}
|
190
|
+
end
|
191
|
+
|
192
|
+
def smells_only_of?(klass, patterns)
|
193
|
+
ReportList.new(@sniffers).length == 1 and has_smell?(klass, patterns)
|
194
|
+
end
|
195
|
+
|
196
|
+
def quiet_report
|
197
|
+
ReportList.new(@sniffers).quiet_report
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# SMELL: Shotgun Surgery
|
202
|
+
# This and the above method will need to be replicated for every new
|
203
|
+
# kind of report.
|
204
|
+
def full_report
|
205
|
+
ReportList.new(@sniffers).full_report
|
206
|
+
end
|
207
|
+
end
|
113
208
|
end
|