reek 0.3.1 → 1.0.0
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 +20 -0
- data/README.txt +4 -80
- data/Rakefile +15 -4
- data/bin/reek +10 -16
- data/config/defaults.reek +53 -0
- data/lib/reek.rb +1 -21
- data/lib/reek/block_context.rb +37 -0
- data/lib/reek/class_context.rb +73 -0
- data/lib/reek/code_context.rb +47 -0
- data/lib/reek/code_parser.rb +204 -0
- data/lib/reek/exceptions.reek +13 -0
- data/lib/reek/if_context.rb +25 -0
- data/lib/reek/method_context.rb +85 -0
- data/lib/reek/module_context.rb +34 -0
- data/lib/reek/name.rb +42 -0
- data/lib/reek/object_refs.rb +3 -6
- data/lib/reek/options.rb +60 -40
- data/lib/reek/rake_task.rb +20 -29
- data/lib/reek/report.rb +16 -27
- data/lib/reek/sexp_formatter.rb +52 -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 +21 -13
- data/lib/reek/smells/duplication.rb +23 -27
- data/lib/reek/smells/feature_envy.rb +18 -25
- data/lib/reek/smells/large_class.rb +32 -17
- data/lib/reek/smells/long_method.rb +24 -16
- data/lib/reek/smells/long_parameter_list.rb +25 -18
- data/lib/reek/smells/long_yield_list.rb +7 -9
- data/lib/reek/smells/nested_iterators.rb +13 -9
- data/lib/reek/smells/smell_detector.rb +66 -0
- data/lib/reek/smells/smells.rb +71 -10
- data/lib/reek/smells/uncommunicative_name.rb +49 -41
- data/lib/reek/smells/utility_function.rb +18 -18
- data/lib/reek/source.rb +116 -0
- data/lib/reek/spec.rb +146 -0
- data/lib/reek/stop_context.rb +62 -0
- data/lib/reek/yield_call_context.rb +14 -0
- data/reek.gemspec +42 -0
- data/spec/integration/reek_source_spec.rb +20 -0
- data/spec/{script_spec.rb → integration/script_spec.rb} +11 -24
- data/spec/reek/class_context_spec.rb +198 -0
- data/spec/reek/code_context_spec.rb +92 -0
- data/spec/reek/code_parser_spec.rb +44 -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 +52 -0
- data/spec/reek/module_context_spec.rb +38 -0
- data/spec/reek/options_spec.rb +2 -28
- data/spec/reek/report_spec.rb +6 -40
- data/spec/reek/sexp_formatter_spec.rb +31 -0
- data/spec/reek/singleton_method_context_spec.rb +17 -0
- data/spec/reek/smells/control_couple_spec.rb +10 -18
- data/spec/reek/smells/duplication_spec.rb +53 -32
- data/spec/reek/smells/feature_envy_spec.rb +87 -49
- data/spec/reek/smells/large_class_spec.rb +45 -4
- data/spec/reek/smells/long_method_spec.rb +25 -41
- data/spec/reek/smells/long_parameter_list_spec.rb +30 -76
- data/spec/reek/smells/nested_iterators_spec.rb +19 -29
- data/spec/reek/smells/smell_spec.rb +9 -18
- data/spec/reek/smells/uncommunicative_name_spec.rb +88 -53
- data/spec/reek/smells/utility_function_spec.rb +45 -44
- data/spec/samples/inline_spec.rb +40 -0
- data/spec/samples/optparse_spec.rb +100 -0
- data/spec/samples/redcloth_spec.rb +93 -0
- data/spec/spec_helper.rb +3 -1
- data/tasks/reek.rake +1 -10
- data/tasks/rspec.rake +16 -35
- metadata +43 -46
- data/lib/reek/checker.rb +0 -66
- data/lib/reek/class_checker.rb +0 -25
- data/lib/reek/file_checker.rb +0 -20
- data/lib/reek/method_checker.rb +0 -198
- data/lib/reek/printer.rb +0 -154
- data/lib/reek/smells/smell.rb +0 -56
- data/lib/reek/version.rb +0 -9
- data/setup.rb +0 -1585
- data/spec/integration_spec.rb +0 -30
- data/spec/reek/class_checker_spec.rb +0 -48
- data/spec/reek/method_checker_spec.rb +0 -67
- data/spec/reek/printer_spec.rb +0 -30
- data/spec/reek_source_spec.rb +0 -12
- data/spec/samples/inline.reek +0 -27
- data/spec/samples/optparse.reek +0 -79
- data/spec/samples/optparse/date.rb +0 -17
- data/spec/samples/optparse/shellwords.rb +0 -6
- data/spec/samples/optparse/time.rb +0 -10
- data/spec/samples/optparse/uri.rb +0 -6
- data/spec/samples/optparse/version.rb +0 -70
- data/spec/samples/redcloth.reek +0 -65
- data/tasks/samples.rake +0 -17
- data/website/index.html +0 -71
- data/website/index.txt +0 -40
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -138
- data/website/template.rhtml +0 -48
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'reek/code_context'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
class IfContext < CodeContext
|
5
|
+
attr_reader :if_expr
|
6
|
+
|
7
|
+
def initialize(outer, exp)
|
8
|
+
@outer = outer
|
9
|
+
@exp = exp
|
10
|
+
@if_expr = exp[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def tests_a_parameter?
|
14
|
+
@if_expr[0] == :lvar and has_parameter(@if_expr[1])
|
15
|
+
end
|
16
|
+
|
17
|
+
def outer_name
|
18
|
+
@outer.outer_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@outer.to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,85 @@
|
|
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
|
+
if receiver.nil?
|
40
|
+
record_depends_on_self
|
41
|
+
else
|
42
|
+
case receiver[0]
|
43
|
+
when :lvar
|
44
|
+
@refs.record_ref(receiver) unless meth == :new
|
45
|
+
when :ivar
|
46
|
+
record_depends_on_self
|
47
|
+
@refs.record_reference_to_self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def record_depends_on_self
|
53
|
+
@depends_on_self = true
|
54
|
+
end
|
55
|
+
|
56
|
+
def record_local_variable(sym)
|
57
|
+
@local_variables << Name.new(sym)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.is_block_arg?(param)
|
61
|
+
Array === param and param[0] == :block
|
62
|
+
end
|
63
|
+
|
64
|
+
def record_parameter(param)
|
65
|
+
@parameters << Name.new(param) unless MethodContext.is_block_arg?(param)
|
66
|
+
end
|
67
|
+
|
68
|
+
def outer_name
|
69
|
+
"#{@outer.outer_name}#{@name}/"
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
"#{@outer.outer_name}#{@name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def envious_receivers
|
77
|
+
return [] if @refs.self_is_max?
|
78
|
+
@refs.max_keys
|
79
|
+
end
|
80
|
+
|
81
|
+
def variable_names
|
82
|
+
@parameters + @local_variables
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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
|
+
sym = modname.to_s
|
23
|
+
myself.const_defined?(sym) ? myself.const_get(sym) : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def outer_name
|
27
|
+
"#{@outer.outer_name}#{@name}::"
|
28
|
+
end
|
29
|
+
|
30
|
+
def variable_names
|
31
|
+
[]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/reek/name.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Reek
|
2
|
+
class Name
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def self.resolve(exp, context)
|
6
|
+
return [context, new(exp)] unless Array === exp
|
7
|
+
name = exp[1]
|
8
|
+
case exp[0]
|
9
|
+
when :colon2
|
10
|
+
return [resolve(name, context)[0], new(exp[2])]
|
11
|
+
when :const
|
12
|
+
return [ModuleContext.create(context, exp), new(name)]
|
13
|
+
when :colon3
|
14
|
+
return [StopContext.new, new(name)]
|
15
|
+
else
|
16
|
+
return [context, new(name)]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(sym)
|
21
|
+
@name = sym.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash # :nodoc:
|
25
|
+
@name.hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def <=>(other) # :nodoc:
|
29
|
+
@name <=> other.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
alias eql? <=>
|
33
|
+
|
34
|
+
def effective_name
|
35
|
+
@name.gsub(/^@*/, '')
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
@name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/reek/object_refs.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
1
|
require 'rubygems'
|
4
|
-
require '
|
5
|
-
require 'reek/printer'
|
2
|
+
require 'sexp_processor'
|
6
3
|
|
7
4
|
module Reek
|
8
5
|
|
9
|
-
class ObjectRefs
|
6
|
+
class ObjectRefs # :nodoc:
|
10
7
|
def initialize
|
11
8
|
@refs = Hash.new(0)
|
12
9
|
record_reference_to_self
|
@@ -41,7 +38,7 @@ module Reek
|
|
41
38
|
# no way to turn off that report; which would therefore make the tests fail
|
42
39
|
def max_keys
|
43
40
|
max = max_refs
|
44
|
-
@refs.reject {|
|
41
|
+
@refs.reject {|key,val| val != max}.keys
|
45
42
|
end
|
46
43
|
|
47
44
|
def self_is_max?
|
data/lib/reek/options.rb
CHANGED
@@ -1,18 +1,16 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
|
-
require 'reek/report'
|
4
|
-
require 'reek/version'
|
5
1
|
require 'optparse'
|
6
|
-
|
7
|
-
include Reek
|
2
|
+
require 'reek/source'
|
8
3
|
|
9
4
|
module Reek
|
10
|
-
|
5
|
+
|
11
6
|
class Options
|
7
|
+
|
8
|
+
CTX_SORT = '%c %w (%s)'
|
9
|
+
SMELL_SORT = '[%s] %c %w'
|
10
|
+
|
12
11
|
def self.default_options
|
13
12
|
{
|
14
|
-
:
|
15
|
-
:expressions => []
|
13
|
+
:format => CTX_SORT
|
16
14
|
}
|
17
15
|
end
|
18
16
|
|
@@ -24,49 +22,71 @@ module Reek
|
|
24
22
|
|
25
23
|
def self.parse_args(args)
|
26
24
|
result = default_options
|
27
|
-
parser = OptionParser.new
|
28
|
-
|
29
|
-
|
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...
|
30
33
|
|
31
|
-
|
34
|
+
If no files are given, Reek reads source code from standard input.
|
35
|
+
See http://wiki.github.com/kevinrutherford/reek for detailed help.
|
32
36
|
EOB
|
37
|
+
|
38
|
+
opts.separator "\nOptions:"
|
39
|
+
set_help_option(opts)
|
40
|
+
set_sort_option(config, opts)
|
41
|
+
set_version_option(opts)
|
42
|
+
end
|
33
43
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
44
|
+
def self.parse(args)
|
45
|
+
begin
|
46
|
+
@@opts = parse_args(args)
|
47
|
+
if ARGV.length > 0
|
48
|
+
return Source.from_pathlist(ARGV)
|
49
|
+
else
|
50
|
+
return Source.from_io($stdin, 'stdin')
|
40
51
|
end
|
52
|
+
rescue OptionParser::ParseError, SystemCallError => err
|
53
|
+
fatal_error(err)
|
54
|
+
end
|
55
|
+
end
|
41
56
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
57
|
+
private
|
58
|
+
|
59
|
+
def self.set_version_option(opts)
|
60
|
+
opts.on("-v", "--version", "Show version") do
|
61
|
+
puts "#{opts.program_name} #{Reek::VERSION}"
|
62
|
+
exit(0)
|
63
|
+
end
|
64
|
+
end
|
46
65
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
66
|
+
def self.set_help_option(opts)
|
67
|
+
opts.on("-h", "--help", "Show this message") do
|
68
|
+
puts opts
|
69
|
+
exit(0)
|
51
70
|
end
|
71
|
+
end
|
52
72
|
|
53
|
-
|
54
|
-
|
73
|
+
def self.set_sort_option(config, opts)
|
74
|
+
opts.on('-f', "--format FORMAT", 'Specify the format of smell warnings') do |arg|
|
75
|
+
config[:format] = arg unless arg.nil?
|
76
|
+
end
|
77
|
+
opts.on('-c', '--context-first', "Sort by context; sets the format string to \"#{CTX_SORT}\"") do
|
78
|
+
config[:format] = CTX_SORT
|
79
|
+
end
|
80
|
+
opts.on('-s', '--smell-first', "Sort by smell; sets the format string to \"#{SMELL_SORT}\"") do
|
81
|
+
config[:format] = SMELL_SORT
|
82
|
+
end
|
55
83
|
end
|
56
84
|
|
57
|
-
def self.fatal_error(
|
58
|
-
puts "Error: #{
|
85
|
+
def self.fatal_error(err) # :nodoc:
|
86
|
+
puts "Error: #{err}"
|
59
87
|
puts "Use '-h' for help."
|
60
88
|
exit(1)
|
61
89
|
end
|
62
|
-
|
63
|
-
def self.parse(args)
|
64
|
-
begin
|
65
|
-
@@opts = parse_args(args)
|
66
|
-
ARGV
|
67
|
-
rescue OptionParser::ParseError => e
|
68
|
-
fatal_error(e)
|
69
|
-
end
|
70
|
-
end
|
90
|
+
|
71
91
|
end
|
72
92
|
end
|
data/lib/reek/rake_task.rb
CHANGED
@@ -19,37 +19,31 @@ module Reek
|
|
19
19
|
#
|
20
20
|
# rake reek
|
21
21
|
#
|
22
|
-
# If rake is invoked with a "SPEC=filename" command line option,
|
23
|
-
# then the list of spec files will be overridden to include only the
|
24
|
-
# filename specified on the command line. This provides an easy way
|
25
|
-
# to run just one spec.
|
26
|
-
#
|
27
|
-
# If rake is invoked with a "REEK_SORT=order" command line option,
|
28
|
-
# then the given sort order will override the value of the +sort+
|
29
|
-
# attribute.
|
30
|
-
#
|
31
22
|
# Examples:
|
32
23
|
#
|
33
|
-
# rake reek
|
34
|
-
# rake reek
|
35
|
-
# rake reek
|
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
|
36
27
|
#
|
37
28
|
class RakeTask < ::Rake::TaskLib
|
38
29
|
|
39
|
-
# Name of reek task.
|
30
|
+
# Name of reek task.
|
31
|
+
# Defaults to :reek.
|
40
32
|
attr_accessor :name
|
41
33
|
|
42
34
|
# Array of directories to be added to $LOAD_PATH before running reek.
|
43
35
|
# Defaults to ['<the absolute path to reek's lib directory>']
|
44
36
|
attr_accessor :libs
|
45
37
|
|
46
|
-
# Glob pattern to match source files.
|
38
|
+
# Glob pattern to match source files.
|
47
39
|
# Setting the REEK_SRC environment variable overrides this.
|
40
|
+
# Defaults to 'lib/**/*.rb'.
|
48
41
|
attr_accessor :source_files
|
49
42
|
|
50
|
-
#
|
51
|
-
# Setting the
|
52
|
-
|
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
|
53
47
|
|
54
48
|
# Array of commandline options to pass to ruby. Defaults to [].
|
55
49
|
attr_accessor :ruby_opts
|
@@ -65,9 +59,10 @@ module Reek
|
|
65
59
|
# Defines a new task, using the name +name+.
|
66
60
|
def initialize(name = :reek)
|
67
61
|
@name = name
|
68
|
-
@libs = [File.expand_path(File.dirname(__FILE__) + '
|
62
|
+
@libs = [File.expand_path(File.dirname(__FILE__) + '/../../lib')]
|
69
63
|
@source_files = nil
|
70
64
|
@ruby_opts = []
|
65
|
+
@reek_opts = ''
|
71
66
|
@fail_on_error = true
|
72
67
|
@sort = nil
|
73
68
|
|
@@ -79,7 +74,7 @@ module Reek
|
|
79
74
|
private
|
80
75
|
|
81
76
|
def define # :nodoc:
|
82
|
-
desc
|
77
|
+
desc 'Check for code smells' unless ::Rake.application.last_comment
|
83
78
|
task(name) { run_task }
|
84
79
|
self
|
85
80
|
end
|
@@ -88,9 +83,9 @@ private
|
|
88
83
|
return if source_file_list.empty?
|
89
84
|
cmd = cmd_words.join(' ')
|
90
85
|
puts cmd if @verbose
|
91
|
-
raise(
|
86
|
+
raise('Smells found!') if !system(cmd) and fail_on_error
|
92
87
|
end
|
93
|
-
|
88
|
+
|
94
89
|
def self.reek_script
|
95
90
|
File.expand_path(File.dirname(__FILE__) + '/../../bin/reek')
|
96
91
|
end
|
@@ -113,17 +108,13 @@ private
|
|
113
108
|
end
|
114
109
|
|
115
110
|
def sort_option
|
116
|
-
|
117
|
-
return "--sort #{env_sort}" if env_sort
|
118
|
-
return "--sort #{@sort}" if @sort
|
119
|
-
''
|
111
|
+
ENV['REEK_OPTS'] || @reek_opts
|
120
112
|
end
|
121
113
|
|
122
114
|
def source_file_list # :nodoc:
|
123
|
-
|
124
|
-
return
|
125
|
-
return FileList[
|
126
|
-
[]
|
115
|
+
files = ENV['REEK_SRC'] || @source_files
|
116
|
+
return [] unless files
|
117
|
+
return FileList[files]
|
127
118
|
end
|
128
119
|
|
129
120
|
end
|