reek 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/History.txt +20 -0
  2. data/README.txt +4 -80
  3. data/Rakefile +15 -4
  4. data/bin/reek +10 -16
  5. data/config/defaults.reek +53 -0
  6. data/lib/reek.rb +1 -21
  7. data/lib/reek/block_context.rb +37 -0
  8. data/lib/reek/class_context.rb +73 -0
  9. data/lib/reek/code_context.rb +47 -0
  10. data/lib/reek/code_parser.rb +204 -0
  11. data/lib/reek/exceptions.reek +13 -0
  12. data/lib/reek/if_context.rb +25 -0
  13. data/lib/reek/method_context.rb +85 -0
  14. data/lib/reek/module_context.rb +34 -0
  15. data/lib/reek/name.rb +42 -0
  16. data/lib/reek/object_refs.rb +3 -6
  17. data/lib/reek/options.rb +60 -40
  18. data/lib/reek/rake_task.rb +20 -29
  19. data/lib/reek/report.rb +16 -27
  20. data/lib/reek/sexp_formatter.rb +52 -0
  21. data/lib/reek/singleton_method_context.rb +27 -0
  22. data/lib/reek/smell_warning.rb +49 -0
  23. data/lib/reek/smells/control_couple.rb +21 -13
  24. data/lib/reek/smells/duplication.rb +23 -27
  25. data/lib/reek/smells/feature_envy.rb +18 -25
  26. data/lib/reek/smells/large_class.rb +32 -17
  27. data/lib/reek/smells/long_method.rb +24 -16
  28. data/lib/reek/smells/long_parameter_list.rb +25 -18
  29. data/lib/reek/smells/long_yield_list.rb +7 -9
  30. data/lib/reek/smells/nested_iterators.rb +13 -9
  31. data/lib/reek/smells/smell_detector.rb +66 -0
  32. data/lib/reek/smells/smells.rb +71 -10
  33. data/lib/reek/smells/uncommunicative_name.rb +49 -41
  34. data/lib/reek/smells/utility_function.rb +18 -18
  35. data/lib/reek/source.rb +116 -0
  36. data/lib/reek/spec.rb +146 -0
  37. data/lib/reek/stop_context.rb +62 -0
  38. data/lib/reek/yield_call_context.rb +14 -0
  39. data/reek.gemspec +42 -0
  40. data/spec/integration/reek_source_spec.rb +20 -0
  41. data/spec/{script_spec.rb → integration/script_spec.rb} +11 -24
  42. data/spec/reek/class_context_spec.rb +198 -0
  43. data/spec/reek/code_context_spec.rb +92 -0
  44. data/spec/reek/code_parser_spec.rb +44 -0
  45. data/spec/reek/config_spec.rb +42 -0
  46. data/spec/reek/if_context_spec.rb +17 -0
  47. data/spec/reek/method_context_spec.rb +52 -0
  48. data/spec/reek/module_context_spec.rb +38 -0
  49. data/spec/reek/options_spec.rb +2 -28
  50. data/spec/reek/report_spec.rb +6 -40
  51. data/spec/reek/sexp_formatter_spec.rb +31 -0
  52. data/spec/reek/singleton_method_context_spec.rb +17 -0
  53. data/spec/reek/smells/control_couple_spec.rb +10 -18
  54. data/spec/reek/smells/duplication_spec.rb +53 -32
  55. data/spec/reek/smells/feature_envy_spec.rb +87 -49
  56. data/spec/reek/smells/large_class_spec.rb +45 -4
  57. data/spec/reek/smells/long_method_spec.rb +25 -41
  58. data/spec/reek/smells/long_parameter_list_spec.rb +30 -76
  59. data/spec/reek/smells/nested_iterators_spec.rb +19 -29
  60. data/spec/reek/smells/smell_spec.rb +9 -18
  61. data/spec/reek/smells/uncommunicative_name_spec.rb +88 -53
  62. data/spec/reek/smells/utility_function_spec.rb +45 -44
  63. data/spec/samples/inline_spec.rb +40 -0
  64. data/spec/samples/optparse_spec.rb +100 -0
  65. data/spec/samples/redcloth_spec.rb +93 -0
  66. data/spec/spec_helper.rb +3 -1
  67. data/tasks/reek.rake +1 -10
  68. data/tasks/rspec.rake +16 -35
  69. metadata +43 -46
  70. data/lib/reek/checker.rb +0 -66
  71. data/lib/reek/class_checker.rb +0 -25
  72. data/lib/reek/file_checker.rb +0 -20
  73. data/lib/reek/method_checker.rb +0 -198
  74. data/lib/reek/printer.rb +0 -154
  75. data/lib/reek/smells/smell.rb +0 -56
  76. data/lib/reek/version.rb +0 -9
  77. data/setup.rb +0 -1585
  78. data/spec/integration_spec.rb +0 -30
  79. data/spec/reek/class_checker_spec.rb +0 -48
  80. data/spec/reek/method_checker_spec.rb +0 -67
  81. data/spec/reek/printer_spec.rb +0 -30
  82. data/spec/reek_source_spec.rb +0 -12
  83. data/spec/samples/inline.reek +0 -27
  84. data/spec/samples/optparse.reek +0 -79
  85. data/spec/samples/optparse/date.rb +0 -17
  86. data/spec/samples/optparse/shellwords.rb +0 -6
  87. data/spec/samples/optparse/time.rb +0 -10
  88. data/spec/samples/optparse/uri.rb +0 -6
  89. data/spec/samples/optparse/version.rb +0 -70
  90. data/spec/samples/redcloth.reek +0 -65
  91. data/tasks/samples.rake +0 -17
  92. data/website/index.html +0 -71
  93. data/website/index.txt +0 -40
  94. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  95. data/website/stylesheets/screen.css +0 -138
  96. data/website/template.rhtml +0 -48
@@ -0,0 +1,13 @@
1
+ ---
2
+ FeatureEnvy:
3
+ exclude:
4
+ - examine_context
5
+ LargeClass:
6
+ exclude:
7
+ - CodeParser
8
+ LongMethod:
9
+ exclude:
10
+ - Reek::SexpFormatter#self.format
11
+ UtilityFunction:
12
+ exclude:
13
+ - Reek::Spec
@@ -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
@@ -1,12 +1,9 @@
1
- $:.unshift File.dirname(__FILE__)
2
-
3
1
  require 'rubygems'
4
- require 'sexp'
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 {|k,v| v != max}.keys
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
- :sort_order => Report::SORT_ORDERS[:context],
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 do |opts|
28
- opts.banner = <<EOB
29
- Usage: #{File.basename($0)} [options] SOURCES
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
- The SOURCES may be any combination of file paths and Ruby source code.
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
- opts.separator ""
35
- opts.separator "Options:"
36
-
37
- opts.on("-h", "--help", "Show this message") do
38
- puts opts
39
- exit(0)
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
- opts.on('-s', "--sort ORDER", Report::SORT_ORDERS.keys,
43
- "Select sort order for report (#{Report::SORT_ORDERS.keys.join(', ')})") do |arg|
44
- result[:sort_order] = Report::SORT_ORDERS[arg] unless arg.nil?
45
- end
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
- opts.on("-v", "--version", "Show version") do
48
- puts "#{File.basename($0)} #{Reek::VERSION::STRING}"
49
- exit(0)
50
- end
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
- parser.parse!(args)
54
- result
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(e) # :nodoc:
58
- puts "Error: #{e}"
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
@@ -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 # run specs normally
34
- # rake reek SPEC=just_one_file.rb # run just one spec file.
35
- # rake reek REEK_SORT=smell # sort warnings by smell
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. (default is :reek)
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. (default is 'lib/**/*.rb')
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
- # Set the sort order for reported smells (see 'reek --help' for possible values).
51
- # Setting the REEK_SORT environment variable overrides this.
52
- attr_accessor :sort
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__) + '/../../../lib')]
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 "Check for code smells" unless ::Rake.application.last_comment
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("Smells found!") if !system(cmd) and fail_on_error
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
- env_sort = ENV['REEK_SORT']
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
- env_src = ENV['REEK_SRC']
124
- return env_src if env_src
125
- return FileList[@source_files] if @source_files
126
- []
115
+ files = ENV['REEK_SRC'] || @source_files
116
+ return [] unless files
117
+ return FileList[files]
127
118
  end
128
119
 
129
120
  end