teksymmetry-reek 1.1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +131 -0
- data/README.txt +36 -0
- data/Rakefile +17 -0
- data/bin/reek +27 -0
- data/config/defaults.reek +51 -0
- data/lib/reek/block_context.rb +59 -0
- data/lib/reek/class_context.rb +68 -0
- data/lib/reek/code_context.rb +54 -0
- data/lib/reek/code_parser.rb +221 -0
- data/lib/reek/exceptions.reek +13 -0
- data/lib/reek/if_context.rb +25 -0
- data/lib/reek/method_context.rb +91 -0
- data/lib/reek/module_context.rb +33 -0
- data/lib/reek/name.rb +49 -0
- data/lib/reek/object_refs.rb +52 -0
- data/lib/reek/object_source.rb +53 -0
- data/lib/reek/options.rb +100 -0
- data/lib/reek/rake_task.rb +121 -0
- data/lib/reek/report.rb +81 -0
- data/lib/reek/sexp_formatter.rb +10 -0
- data/lib/reek/singleton_method_context.rb +27 -0
- data/lib/reek/smell_warning.rb +49 -0
- data/lib/reek/smells/control_couple.rb +61 -0
- data/lib/reek/smells/duplication.rb +50 -0
- data/lib/reek/smells/feature_envy.rb +58 -0
- data/lib/reek/smells/large_class.rb +69 -0
- data/lib/reek/smells/long_method.rb +43 -0
- data/lib/reek/smells/long_parameter_list.rb +43 -0
- data/lib/reek/smells/long_yield_list.rb +18 -0
- data/lib/reek/smells/nested_iterators.rb +28 -0
- data/lib/reek/smells/smell_detector.rb +66 -0
- data/lib/reek/smells/smells.rb +81 -0
- data/lib/reek/smells/uncommunicative_name.rb +97 -0
- data/lib/reek/smells/utility_function.rb +34 -0
- data/lib/reek/source.rb +127 -0
- data/lib/reek/spec.rb +146 -0
- data/lib/reek/stop_context.rb +50 -0
- data/lib/reek/yield_call_context.rb +12 -0
- data/lib/reek.rb +7 -0
- data/reek.gemspec +44 -0
- data/spec/reek/block_context_spec.rb +40 -0
- data/spec/reek/class_context_spec.rb +169 -0
- data/spec/reek/code_context_spec.rb +93 -0
- data/spec/reek/code_parser_spec.rb +34 -0
- data/spec/reek/config_spec.rb +42 -0
- data/spec/reek/if_context_spec.rb +17 -0
- data/spec/reek/method_context_spec.rb +66 -0
- data/spec/reek/module_context_spec.rb +38 -0
- data/spec/reek/name_spec.rb +13 -0
- data/spec/reek/object_refs_spec.rb +131 -0
- data/spec/reek/options_spec.rb +13 -0
- data/spec/reek/report_spec.rb +48 -0
- data/spec/reek/singleton_method_context_spec.rb +17 -0
- data/spec/reek/smells/control_couple_spec.rb +23 -0
- data/spec/reek/smells/duplication_spec.rb +81 -0
- data/spec/reek/smells/feature_envy_spec.rb +221 -0
- data/spec/reek/smells/large_class_spec.rb +87 -0
- data/spec/reek/smells/long_method_spec.rb +195 -0
- data/spec/reek/smells/long_parameter_list_spec.rb +85 -0
- data/spec/reek/smells/nested_iterators_spec.rb +33 -0
- data/spec/reek/smells/smell_spec.rb +24 -0
- data/spec/reek/smells/uncommunicative_name_spec.rb +123 -0
- data/spec/reek/smells/utility_function_spec.rb +93 -0
- data/spec/slow/inline_spec.rb +40 -0
- data/spec/slow/optparse_spec.rb +109 -0
- data/spec/slow/redcloth_spec.rb +101 -0
- data/spec/slow/reek_source_spec.rb +20 -0
- data/spec/slow/samples/inline.rb +704 -0
- data/spec/slow/samples/optparse.rb +1788 -0
- data/spec/slow/samples/redcloth.rb +1130 -0
- data/spec/slow/script_spec.rb +55 -0
- data/spec/slow/source_list_spec.rb +40 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +13 -0
- data/tasks/reek.rake +7 -0
- data/tasks/rspec.rake +22 -0
- metadata +163 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'reek/name'
|
2
|
+
require 'reek/code_context'
|
3
|
+
require 'reek/object_refs'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
class MethodContext < CodeContext
|
7
|
+
attr_reader :parameters
|
8
|
+
attr_reader :calls
|
9
|
+
attr_reader :refs
|
10
|
+
attr_reader :num_statements
|
11
|
+
|
12
|
+
def initialize(outer, exp, record = true)
|
13
|
+
super(outer, exp)
|
14
|
+
@parameters = []
|
15
|
+
@local_variables = []
|
16
|
+
@name = Name.new(exp[1])
|
17
|
+
@num_statements = 0
|
18
|
+
@calls = Hash.new(0)
|
19
|
+
@depends_on_self = false
|
20
|
+
@refs = ObjectRefs.new
|
21
|
+
@outer.record_method(@name) # TODO: should be children of outer?
|
22
|
+
end
|
23
|
+
|
24
|
+
def count_statements(num)
|
25
|
+
@num_statements += num
|
26
|
+
end
|
27
|
+
|
28
|
+
def depends_on_instance?
|
29
|
+
@depends_on_self || is_overriding_method?(@name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_parameter(sym)
|
33
|
+
@parameters.include?(sym.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def record_call_to(exp)
|
37
|
+
@calls[exp] += 1
|
38
|
+
receiver, meth = exp[1..2]
|
39
|
+
receiver ||= [:self]
|
40
|
+
case receiver[0]
|
41
|
+
when :lvar
|
42
|
+
@refs.record_ref(receiver) unless meth == :new
|
43
|
+
when :self
|
44
|
+
record_use_of_self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def record_use_of_self
|
49
|
+
record_depends_on_self
|
50
|
+
@refs.record_reference_to_self
|
51
|
+
end
|
52
|
+
|
53
|
+
def record_instance_variable(sym)
|
54
|
+
record_use_of_self
|
55
|
+
@outer.record_instance_variable(sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def record_depends_on_self
|
59
|
+
@depends_on_self = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def record_local_variable(sym)
|
63
|
+
@local_variables << Name.new(sym)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.is_block_arg?(param)
|
67
|
+
(Array === param and param[0] == :block) or (param.to_s =~ /^\&/)
|
68
|
+
end
|
69
|
+
|
70
|
+
def record_parameter(param)
|
71
|
+
@parameters << Name.new(param) unless MethodContext.is_block_arg?(param)
|
72
|
+
end
|
73
|
+
|
74
|
+
def outer_name
|
75
|
+
"#{@outer.outer_name}#{@name}/"
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
"#{@outer.outer_name}#{@name}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def envious_receivers
|
83
|
+
return [] if @refs.self_is_max?
|
84
|
+
@refs.max_keys
|
85
|
+
end
|
86
|
+
|
87
|
+
def variable_names
|
88
|
+
@parameters + @local_variables
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'reek/code_context'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
class ModuleContext < CodeContext
|
5
|
+
|
6
|
+
def ModuleContext.create(outer, exp)
|
7
|
+
res = Name.resolve(exp[1], outer)
|
8
|
+
ModuleContext.new(res[0], res[1])
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(outer, name)
|
12
|
+
super(outer, nil)
|
13
|
+
@name = name
|
14
|
+
end
|
15
|
+
|
16
|
+
def myself
|
17
|
+
@myself ||= @outer.find_module(@name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_module(modname)
|
21
|
+
return nil unless myself
|
22
|
+
@myself.const_or_nil(modname.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def outer_name
|
26
|
+
"#{@outer.outer_name}#{@name}::"
|
27
|
+
end
|
28
|
+
|
29
|
+
def variable_names
|
30
|
+
[]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/reek/name.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Reek
|
2
|
+
class Name
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def self.resolve(exp, context)
|
6
|
+
unless Array === exp
|
7
|
+
return resolve_string(exp.to_s, context)
|
8
|
+
end
|
9
|
+
name = exp[1]
|
10
|
+
case exp[0]
|
11
|
+
when :colon2
|
12
|
+
return [resolve(name, context)[0], new(exp[2])]
|
13
|
+
when :const
|
14
|
+
return [ModuleContext.create(context, exp), new(name)]
|
15
|
+
when :colon3
|
16
|
+
return [StopContext.new, new(name)]
|
17
|
+
else
|
18
|
+
return [context, new(name)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.resolve_string(str, context)
|
23
|
+
return [context, new(str)] unless str =~ /::/
|
24
|
+
resolve(RubyParser.new.parse(str), context)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(sym)
|
28
|
+
@name = sym.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def hash # :nodoc:
|
32
|
+
@name.hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def <=>(other) # :nodoc:
|
36
|
+
@name <=> other.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
alias eql? <=>
|
40
|
+
|
41
|
+
def effective_name
|
42
|
+
@name.gsub(/^@*/, '')
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
@name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sexp_processor'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
|
6
|
+
class ObjectRefs # :nodoc:
|
7
|
+
def initialize
|
8
|
+
@refs = Hash.new(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
def record_reference_to_self
|
12
|
+
record_ref(SELF_REF)
|
13
|
+
end
|
14
|
+
|
15
|
+
def record_ref(exp)
|
16
|
+
type = exp[0]
|
17
|
+
case type
|
18
|
+
when :gvar
|
19
|
+
return
|
20
|
+
when :self
|
21
|
+
record_reference_to_self
|
22
|
+
else
|
23
|
+
@refs[exp] += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def refs_to_self
|
28
|
+
@refs[SELF_REF]
|
29
|
+
end
|
30
|
+
|
31
|
+
def max_refs
|
32
|
+
@refs.values.max or 0
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO
|
36
|
+
# Should be moved to Hash
|
37
|
+
#
|
38
|
+
def max_keys
|
39
|
+
max = max_refs
|
40
|
+
@refs.reject {|key,val| val != max}.keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def self_is_max?
|
44
|
+
max_keys.length == 0 || @refs[SELF_REF] == max_refs
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
SELF_REF = Sexp.from_array([:lit, :self])
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,53 @@
|
|
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
|
+
class ObjectSource < Source # :nodoc:
|
14
|
+
|
15
|
+
def self.unify(sexp) # :nodoc:
|
16
|
+
unifier = Unifier.new
|
17
|
+
unifier.processors.each do |proc|
|
18
|
+
proc.unsupported.delete :cfunc # HACK
|
19
|
+
end
|
20
|
+
return unifier.process(sexp[0])
|
21
|
+
end
|
22
|
+
|
23
|
+
def can_parse_objects?
|
24
|
+
return true if Object.const_defined?(:ParseTree)
|
25
|
+
begin
|
26
|
+
require 'parse_tree'
|
27
|
+
true
|
28
|
+
rescue LoadError
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_syntax_tree
|
34
|
+
if can_parse_objects?
|
35
|
+
ObjectSource.unify(ParseTree.new.parse_tree(@source))
|
36
|
+
else
|
37
|
+
throw ArgumentError.new('You must install the ParseTree gem to use this feature')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Object
|
44
|
+
#
|
45
|
+
# Constructs a Source representing this object; the source can then be used
|
46
|
+
# to generate an abstract syntax tree for the object, which can in turn then
|
47
|
+
# be examined for code smells.
|
48
|
+
# (This feature is only enabled if you have the ParseTree gem installed.)
|
49
|
+
#
|
50
|
+
def to_source
|
51
|
+
Reek::Source.from_object(self)
|
52
|
+
end
|
53
|
+
end
|
data/lib/reek/options.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'reek/source'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
|
6
|
+
class Options
|
7
|
+
|
8
|
+
CTX_SORT = '%c %w (%s)' if !const_defined?(:CTX_SORT)
|
9
|
+
SMELL_SORT = '[%s] %c %w' if !const_defined?(:SMELL_SORT)
|
10
|
+
|
11
|
+
def self.default_options
|
12
|
+
{
|
13
|
+
:format => CTX_SORT
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
@@opts = default_options
|
18
|
+
|
19
|
+
def self.[](key)
|
20
|
+
@@opts[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.parse_args(args)
|
24
|
+
result = default_options
|
25
|
+
parser = OptionParser.new { |opts| set_options(opts, result) }
|
26
|
+
parser.parse!(args)
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.set_options(opts, config)
|
31
|
+
opts.banner = <<EOB
|
32
|
+
Usage: #{opts.program_name} [options] files...
|
33
|
+
|
34
|
+
If no files are given, Reek reads source code from standard input.
|
35
|
+
See http://wiki.github.com/kevinrutherford/reek for detailed help.
|
36
|
+
EOB
|
37
|
+
|
38
|
+
opts.separator "\nOptions:"
|
39
|
+
set_help_option(opts)
|
40
|
+
set_sort_option(config, opts)
|
41
|
+
set_version_option(opts)
|
42
|
+
set_output_format_option(config, opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.parse(args)
|
46
|
+
begin
|
47
|
+
@@opts = parse_args(args)
|
48
|
+
if args.length > 0
|
49
|
+
return Source.from_pathlist(args)
|
50
|
+
else
|
51
|
+
return Source.from_io($stdin, 'stdin')
|
52
|
+
end
|
53
|
+
rescue OptionParser::ParseError, SystemCallError => err
|
54
|
+
fatal_error(err)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def self.set_output_format_option(p_config, p_opts)
|
60
|
+
p_opts.on('-of', '--output-format',
|
61
|
+
"Render output in the specified format, ie. html, " +
|
62
|
+
"by default 'text' is used.") do |value|
|
63
|
+
p_config[:output_format] = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.set_version_option(opts)
|
68
|
+
opts.on("-v", "--version", "Show version") do
|
69
|
+
puts "#{opts.program_name} #{Reek::VERSION}"
|
70
|
+
exit(0)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.set_help_option(opts)
|
75
|
+
opts.on("-h", "--help", "Show this message") do
|
76
|
+
puts opts
|
77
|
+
exit(0)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.set_sort_option(config, opts)
|
82
|
+
opts.on('-f', "--format FORMAT", 'Specify the format of smell warnings') do |arg|
|
83
|
+
config[:format] = arg unless arg.nil?
|
84
|
+
end
|
85
|
+
opts.on('-c', '--context-first', "Sort by context; sets the format string to \"#{CTX_SORT}\"") do
|
86
|
+
config[:format] = CTX_SORT
|
87
|
+
end
|
88
|
+
opts.on('-s', '--smell-first', "Sort by smell; sets the format string to \"#{SMELL_SORT}\"") do
|
89
|
+
config[:format] = SMELL_SORT
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.fatal_error(err) # :nodoc:
|
94
|
+
puts "Error: #{err}"
|
95
|
+
puts "Use '-h' for help."
|
96
|
+
exit(1)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Define a task library for running reek.
|
4
|
+
|
5
|
+
require 'rake'
|
6
|
+
require 'rake/tasklib'
|
7
|
+
|
8
|
+
module Reek
|
9
|
+
|
10
|
+
# A Rake task that runs reek on a set of source files.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# Reek::RakeTask.new do |t|
|
15
|
+
# t.fail_on_error = false
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# This will create a task that can be run with:
|
19
|
+
#
|
20
|
+
# rake reek
|
21
|
+
#
|
22
|
+
# Examples:
|
23
|
+
#
|
24
|
+
# rake reek # checks lib/**/*.rb
|
25
|
+
# rake reek REEK_SRC=just_one_file.rb # checks a single source file
|
26
|
+
# rake reek REEK_OPTS=-s # sorts the report by smell
|
27
|
+
#
|
28
|
+
class RakeTask < ::Rake::TaskLib
|
29
|
+
|
30
|
+
# Name of reek task.
|
31
|
+
# Defaults to :reek.
|
32
|
+
attr_accessor :name
|
33
|
+
|
34
|
+
# Array of directories to be added to $LOAD_PATH before running reek.
|
35
|
+
# Defaults to ['<the absolute path to reek's lib directory>']
|
36
|
+
attr_accessor :libs
|
37
|
+
|
38
|
+
# Glob pattern to match source files.
|
39
|
+
# Setting the REEK_SRC environment variable overrides this.
|
40
|
+
# Defaults to 'lib/**/*.rb'.
|
41
|
+
attr_accessor :source_files
|
42
|
+
|
43
|
+
# String containing commandline options to be passed to Reek.
|
44
|
+
# Setting the REEK_OPTS environment variable overrides this value.
|
45
|
+
# Defaults to ''.
|
46
|
+
attr_accessor :reek_opts
|
47
|
+
|
48
|
+
# Array of commandline options to pass to ruby. Defaults to [].
|
49
|
+
attr_accessor :ruby_opts
|
50
|
+
|
51
|
+
# Whether or not to fail Rake when an error occurs (typically when smells are found).
|
52
|
+
# Defaults to true.
|
53
|
+
attr_accessor :fail_on_error
|
54
|
+
|
55
|
+
# Use verbose output. If this is set to true, the task will print
|
56
|
+
# the reek command to stdout. Defaults to false.
|
57
|
+
attr_accessor :verbose
|
58
|
+
|
59
|
+
# Defines a new task, using the name +name+.
|
60
|
+
def initialize(name = :reek)
|
61
|
+
@name = name
|
62
|
+
@libs = [File.expand_path(File.dirname(__FILE__) + '/../../lib')]
|
63
|
+
@source_files = nil
|
64
|
+
@ruby_opts = []
|
65
|
+
@reek_opts = ''
|
66
|
+
@fail_on_error = true
|
67
|
+
@sort = nil
|
68
|
+
|
69
|
+
yield self if block_given?
|
70
|
+
@source_files ||= 'lib/**/*.rb'
|
71
|
+
define
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def define # :nodoc:
|
77
|
+
desc 'Check for code smells' unless ::Rake.application.last_comment
|
78
|
+
task(name) { run_task }
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_task
|
83
|
+
return if source_file_list.empty?
|
84
|
+
cmd = cmd_words.join(' ')
|
85
|
+
puts cmd if @verbose
|
86
|
+
raise('Smells found!') if !system(cmd) and fail_on_error
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.reek_script
|
90
|
+
File.expand_path(File.dirname(__FILE__) + '/../../bin/reek')
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.ruby_exe
|
94
|
+
File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
|
95
|
+
end
|
96
|
+
|
97
|
+
def cmd_words
|
98
|
+
[RakeTask.ruby_exe] +
|
99
|
+
ruby_options +
|
100
|
+
[ %Q|"#{RakeTask.reek_script}"| ] +
|
101
|
+
[sort_option] +
|
102
|
+
source_file_list.collect { |fn| %["#{fn}"] }
|
103
|
+
end
|
104
|
+
|
105
|
+
def ruby_options
|
106
|
+
lib_path = @libs.join(File::PATH_SEPARATOR)
|
107
|
+
@ruby_opts.clone << "-I\"#{lib_path}\""
|
108
|
+
end
|
109
|
+
|
110
|
+
def sort_option
|
111
|
+
ENV['REEK_OPTS'] || @reek_opts
|
112
|
+
end
|
113
|
+
|
114
|
+
def source_file_list # :nodoc:
|
115
|
+
files = ENV['REEK_SRC'] || @source_files
|
116
|
+
return [] unless files
|
117
|
+
return FileList[files]
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
data/lib/reek/report.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'reek/smells/smell_detector'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
class Report
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize # :nodoc:
|
9
|
+
@report = SortedSet.new
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Yields, in turn, each SmellWarning in this report.
|
14
|
+
#
|
15
|
+
def each
|
16
|
+
@report.each { |smell| yield smell }
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(smell) # :nodoc:
|
20
|
+
@report << smell
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def empty?
|
25
|
+
@report.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def length
|
29
|
+
@report.length
|
30
|
+
end
|
31
|
+
|
32
|
+
alias size length
|
33
|
+
|
34
|
+
def [](index) # :nodoc:
|
35
|
+
@report.to_a[index]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
39
|
+
# this report, with a heading.
|
40
|
+
def full_report(desc)
|
41
|
+
"\"#{desc}\" -- #{length} warnings:\n#{to_s}\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
45
|
+
# this report.
|
46
|
+
def to_s
|
47
|
+
@report.map {|smell| smell.report}.join("\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ReportList
|
52
|
+
include Enumerable
|
53
|
+
|
54
|
+
def initialize(sources)
|
55
|
+
@sources = sources
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Yields, in turn, each SmellWarning in every report in this report.
|
60
|
+
#
|
61
|
+
def each(&blk)
|
62
|
+
@sources.each {|src| src.report.each(&blk) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def empty?
|
66
|
+
length == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def length
|
70
|
+
@sources.inject(0) {|sum, src| sum + src.report.length }
|
71
|
+
end
|
72
|
+
|
73
|
+
def smelly_sources
|
74
|
+
@sources.select {|src| src.smelly? }
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
smelly_sources.map { |src| src.full_report }.join("\n")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'reek/name'
|
2
|
+
require 'reek/method_context'
|
3
|
+
require 'reek/sexp_formatter'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
class SingletonMethodContext < MethodContext
|
7
|
+
|
8
|
+
def initialize(outer, exp)
|
9
|
+
super(outer, exp, false)
|
10
|
+
@name = Name.new(exp[2])
|
11
|
+
@receiver = SexpFormatter.format(exp[1])
|
12
|
+
record_depends_on_self
|
13
|
+
end
|
14
|
+
|
15
|
+
def envious_receivers
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
|
19
|
+
def outer_name
|
20
|
+
"#{@outer.outer_name}#{@receiver}.#{@name}/"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{@outer.outer_name}#{@receiver}.#{@name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'reek/options'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
|
5
|
+
#
|
6
|
+
# Reports a warning that a smell has been found.
|
7
|
+
#
|
8
|
+
class SmellWarning
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
def initialize(smell, context, warning)
|
12
|
+
@smell = smell
|
13
|
+
@context = context
|
14
|
+
@warning = warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash # :nodoc:
|
18
|
+
report.hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
report <=> other.report
|
23
|
+
end
|
24
|
+
|
25
|
+
alias eql? <=> # :nodoc:
|
26
|
+
|
27
|
+
#
|
28
|
+
# Returns +true+ only if this is a warning about an instance of
|
29
|
+
# +smell_class+ and its report string matches all of the +patterns+.
|
30
|
+
#
|
31
|
+
def matches?(smell_class, patterns)
|
32
|
+
return false unless smell_class.to_s == @smell.class.class_name
|
33
|
+
rpt = report
|
34
|
+
return patterns.all? {|exp| exp === rpt}
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Returns a copy of the current report format (see +Options+)
|
39
|
+
# in which the following magic tokens have been substituted:
|
40
|
+
#
|
41
|
+
# * %s <-- the name of the smell that was detected
|
42
|
+
# * %c <-- a description of the +CodeContext+ containing the smell
|
43
|
+
# * %w <-- the specific problem that was detected
|
44
|
+
#
|
45
|
+
def report
|
46
|
+
Options[:format].gsub(/\%s/, @smell.smell_name).gsub(/\%c/, @context.to_s).gsub(/\%w/, @warning)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|