kevinrutherford-reek 1.1.3.9 → 1.1.3.10
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 +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
|