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.
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