kevinrutherford-reek 0.3.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +92 -0
  2. data/README.txt +6 -0
  3. data/Rakefile +7 -0
  4. data/bin/reek +19 -0
  5. data/lib/reek/block_context.rb +37 -0
  6. data/lib/reek/class_context.rb +73 -0
  7. data/lib/reek/code_context.rb +47 -0
  8. data/lib/reek/code_parser.rb +204 -0
  9. data/lib/reek/exceptions.reek +13 -0
  10. data/lib/reek/if_context.rb +25 -0
  11. data/lib/reek/method_context.rb +85 -0
  12. data/lib/reek/module_context.rb +34 -0
  13. data/lib/reek/name.rb +42 -0
  14. data/lib/reek/object_refs.rb +53 -0
  15. data/lib/reek/options.rb +92 -0
  16. data/lib/reek/rake_task.rb +121 -0
  17. data/lib/reek/report.rb +42 -0
  18. data/lib/reek/sexp_formatter.rb +52 -0
  19. data/lib/reek/singleton_method_context.rb +27 -0
  20. data/lib/reek/smell_warning.rb +49 -0
  21. data/lib/reek/smells/control_couple.rb +61 -0
  22. data/lib/reek/smells/duplication.rb +50 -0
  23. data/lib/reek/smells/feature_envy.rb +58 -0
  24. data/lib/reek/smells/large_class.rb +50 -0
  25. data/lib/reek/smells/long_method.rb +43 -0
  26. data/lib/reek/smells/long_parameter_list.rb +43 -0
  27. data/lib/reek/smells/long_yield_list.rb +18 -0
  28. data/lib/reek/smells/nested_iterators.rb +28 -0
  29. data/lib/reek/smells/smell_detector.rb +66 -0
  30. data/lib/reek/smells/smells.rb +85 -0
  31. data/lib/reek/smells/uncommunicative_name.rb +80 -0
  32. data/lib/reek/smells/utility_function.rb +34 -0
  33. data/lib/reek/source.rb +116 -0
  34. data/lib/reek/spec.rb +130 -0
  35. data/lib/reek/stop_context.rb +62 -0
  36. data/lib/reek/yield_call_context.rb +14 -0
  37. data/lib/reek.rb +8 -0
  38. data/spec/integration/reek_source_spec.rb +20 -0
  39. data/spec/integration/script_spec.rb +55 -0
  40. data/spec/reek/class_context_spec.rb +198 -0
  41. data/spec/reek/code_context_spec.rb +92 -0
  42. data/spec/reek/code_parser_spec.rb +44 -0
  43. data/spec/reek/config_spec.rb +42 -0
  44. data/spec/reek/module_context_spec.rb +38 -0
  45. data/spec/reek/object_refs_spec.rb +129 -0
  46. data/spec/reek/options_spec.rb +13 -0
  47. data/spec/reek/report_spec.rb +48 -0
  48. data/spec/reek/sexp_formatter_spec.rb +31 -0
  49. data/spec/reek/singleton_method_context_spec.rb +17 -0
  50. data/spec/reek/smells/control_couple_spec.rb +23 -0
  51. data/spec/reek/smells/duplication_spec.rb +81 -0
  52. data/spec/reek/smells/feature_envy_spec.rb +129 -0
  53. data/spec/reek/smells/large_class_spec.rb +86 -0
  54. data/spec/reek/smells/long_method_spec.rb +59 -0
  55. data/spec/reek/smells/long_parameter_list_spec.rb +92 -0
  56. data/spec/reek/smells/nested_iterators_spec.rb +33 -0
  57. data/spec/reek/smells/smell_spec.rb +24 -0
  58. data/spec/reek/smells/uncommunicative_name_spec.rb +118 -0
  59. data/spec/reek/smells/utility_function_spec.rb +96 -0
  60. data/spec/samples/inline.rb +704 -0
  61. data/spec/samples/inline_spec.rb +40 -0
  62. data/spec/samples/optparse.rb +1788 -0
  63. data/spec/samples/optparse_spec.rb +100 -0
  64. data/spec/samples/redcloth.rb +1130 -0
  65. data/spec/samples/redcloth_spec.rb +93 -0
  66. data/spec/spec.opts +1 -0
  67. data/spec/spec_helper.rb +15 -0
  68. data/tasks/reek.rake +20 -0
  69. data/tasks/rspec.rake +22 -0
  70. metadata +167 -0
@@ -0,0 +1,53 @@
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
+ record_reference_to_self
10
+ end
11
+
12
+ def record_reference_to_self
13
+ record_ref(SELF_REF)
14
+ end
15
+
16
+ def record_ref(exp)
17
+ type = exp[0]
18
+ case type
19
+ when :gvar
20
+ return
21
+ when :self
22
+ record_reference_to_self
23
+ else
24
+ @refs[exp] += 1
25
+ end
26
+ end
27
+
28
+ def refs_to_self
29
+ @refs[SELF_REF]
30
+ end
31
+
32
+ def max_refs
33
+ @refs.values.max or 0
34
+ end
35
+
36
+ # TODO
37
+ # Should be moved to Hash; but Hash has 58 methods, and there's currently
38
+ # no way to turn off that report; which would therefore make the tests fail
39
+ def max_keys
40
+ max = max_refs
41
+ @refs.reject {|key,val| val != max}.keys
42
+ end
43
+
44
+ def self_is_max?
45
+ max_keys.length == 0 || @refs[SELF_REF] == max_refs
46
+ end
47
+
48
+ private
49
+
50
+ SELF_REF = Sexp.from_array([:lit, :self])
51
+
52
+ end
53
+ end
@@ -0,0 +1,92 @@
1
+ require 'optparse'
2
+ require 'reek/source'
3
+
4
+ module Reek
5
+
6
+ class Options
7
+
8
+ CTX_SORT = '%c %w (%s)'
9
+ SMELL_SORT = '[%s] %c %w'
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
+ end
43
+
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')
51
+ end
52
+ rescue OptionParser::ParseError, SystemCallError => err
53
+ fatal_error(err)
54
+ end
55
+ end
56
+
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
65
+
66
+ def self.set_help_option(opts)
67
+ opts.on("-h", "--help", "Show this message") do
68
+ puts opts
69
+ exit(0)
70
+ end
71
+ end
72
+
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
83
+ end
84
+
85
+ def self.fatal_error(err) # :nodoc:
86
+ puts "Error: #{err}"
87
+ puts "Use '-h' for help."
88
+ exit(1)
89
+ end
90
+
91
+ end
92
+ 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
@@ -0,0 +1,42 @@
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
+ def each
13
+ @report.each { |smell| yield smell }
14
+ end
15
+
16
+ def <<(smell) # :nodoc:
17
+ @report << smell
18
+ true
19
+ end
20
+
21
+ def empty? # :nodoc:
22
+ @report.empty?
23
+ end
24
+
25
+ def length # :nodoc:
26
+ @report.length
27
+ end
28
+
29
+ alias size length
30
+
31
+ def [](index) # :nodoc:
32
+ @report.to_a[index]
33
+ end
34
+
35
+ # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
36
+ # this report.
37
+ def to_s
38
+ @report.map {|smell| smell.report}.join("\n")
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,52 @@
1
+ module Reek
2
+ class SexpFormatter
3
+ def self.format(sexp)
4
+ return sexp.to_s unless Array === sexp
5
+ first = sexp[1]
6
+ case sexp[0]
7
+ when :array
8
+ format_all(sexp, ', ')
9
+ when :call
10
+ meth, args = sexp[2..3]
11
+ result = format(first)
12
+ if meth.to_s == '[]'
13
+ result += (args.nil? ? '[]' : "[#{format(args)}]")
14
+ else
15
+ result += ".#{meth}" + (args ? "(#{format(args)})" : '')
16
+ end
17
+ result
18
+ when :colon2
19
+ format_all(sexp, '::')
20
+ when :const, :cvar, :dvar
21
+ format(first)
22
+ when :dot2
23
+ format_all(sexp, '..')
24
+ when :dstr
25
+ '"' + format_all(sexp, '') + '"'
26
+ when :evstr
27
+ "\#\{#{format(first)}\}"
28
+ when :fcall, :vcall
29
+ args = sexp[2]
30
+ result = first.to_s
31
+ result += "(#{format(args)})" if args
32
+ result
33
+ when :iter
34
+ 'block'
35
+ when :lasgn
36
+ format_all(sexp, '=')
37
+ when :nth_ref
38
+ "$#{first}"
39
+ when :str
40
+ first
41
+ when :xstr
42
+ "`#{first}`"
43
+ else
44
+ sexp[-1].to_s
45
+ end
46
+ end
47
+
48
+ def self.format_all(sexp, glue)
49
+ sexp[1..-1].map {|arg| format(arg)}.join(glue)
50
+ end
51
+ end
52
+ 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
@@ -0,0 +1,61 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+ require 'reek/sexp_formatter'
4
+
5
+ module Reek
6
+ module Smells
7
+
8
+ #
9
+ # Control Coupling occurs when a method or block checks the value of
10
+ # a parameter in order to decide which execution path to take. The
11
+ # offending parameter is often called a Control Couple.
12
+ #
13
+ # A simple example would be the <tt>quoted</tt> parameter
14
+ # in the following method:
15
+ #
16
+ # def write(quoted)
17
+ # if quoted
18
+ # write_quoted(@value)
19
+ # else
20
+ # puts @value
21
+ # end
22
+ # end
23
+ #
24
+ # Control Coupling is a kind of duplication, because the calling method
25
+ # already knows which path should be taken.
26
+ #
27
+ # Control Coupling reduces the code's flexibility by creating a
28
+ # dependency between the caller and callee:
29
+ # any change to the possible values of the controlling parameter must
30
+ # be reflected on both sides of the call.
31
+ #
32
+ # A Control Couple also reveals a loss of simplicity: the called
33
+ # method probably has more than one responsibility,
34
+ # because it includes at least two different code paths.
35
+ #
36
+ class ControlCouple < SmellDetector
37
+
38
+ def self.contexts # :nodoc:
39
+ [:if]
40
+ end
41
+
42
+ def self.default_config
43
+ super.adopt(EXCLUDE_KEY => ['initialize'])
44
+ end
45
+
46
+ def initialize(config = ControlCouple.default_config)
47
+ super
48
+ end
49
+
50
+ #
51
+ # Checks whether the given conditional statement relies on a control couple.
52
+ # Any smells found are added to the +report+.
53
+ #
54
+ def examine_context(cond, report)
55
+ return unless cond.tests_a_parameter?
56
+ report << SmellWarning.new(self, cond,
57
+ "is controlled by argument #{SexpFormatter.format(cond.if_expr)}")
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+ require 'reek/sexp_formatter'
4
+
5
+ module Reek
6
+ module Smells
7
+
8
+ #
9
+ # Duplication occurs when two fragments of code look nearly identical,
10
+ # or when two fragments of code have nearly identical effects
11
+ # at some conceptual level.
12
+ #
13
+ # Currently +Duplication+ checks for repeated identical method calls
14
+ # within any one method definition. For example, the following method
15
+ # will report a warning:
16
+ #
17
+ # def double_thing()
18
+ # @other.thing + @other.thing
19
+ # end
20
+ #
21
+ class Duplication < SmellDetector
22
+
23
+ # The name of the config field that sets the maximum number of
24
+ # identical calls to be permitted within any single method.
25
+ MAX_ALLOWED_CALLS_KEY = 'max_calls'
26
+
27
+ def self.default_config
28
+ super.adopt(MAX_ALLOWED_CALLS_KEY => 1)
29
+ end
30
+
31
+ def initialize(config = Duplication.default_config)
32
+ super
33
+ @max_calls = config[MAX_ALLOWED_CALLS_KEY]
34
+ end
35
+
36
+ def examine_context(method, report)
37
+ smelly_calls(method).each do |call|
38
+ report << SmellWarning.new(self, method,
39
+ "calls #{SexpFormatter.format(call)} multiple times")
40
+ end
41
+ end
42
+
43
+ def smelly_calls(method) # :nodoc:
44
+ method.calls.select do |key,val|
45
+ val > @max_calls and key[2] != :new
46
+ end.map { |call_exp| call_exp[0] }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,58 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+ require 'reek/sexp_formatter'
4
+
5
+ module Reek
6
+ module Smells
7
+
8
+ #
9
+ # Feature Envy occurs when a code fragment references another object
10
+ # more often than it references itself, or when several clients do
11
+ # the same series of manipulations on a particular type of object.
12
+ #
13
+ # A simple example would be the following method, which "belongs"
14
+ # on the Item class and not on the Cart class:
15
+ #
16
+ # class Cart
17
+ # def price
18
+ # @item.price + @item.tax
19
+ # end
20
+ # end
21
+ #
22
+ # Feature Envy reduces the code's ability to communicate intent:
23
+ # code that "belongs" on one class but which is located in another
24
+ # can be hard to find, and may upset the "System of Names"
25
+ # in the host class.
26
+ #
27
+ # Feature Envy also affects the design's flexibility: A code fragment
28
+ # that is in the wrong class creates couplings that may not be natural
29
+ # within the application's domain, and creates a loss of cohesion
30
+ # in the unwilling host class.
31
+ #
32
+ # Currently +FeatureEnvy+ reports any method that refers to self less
33
+ # often than it refers to (ie. send messages to) some other object.
34
+ #
35
+ class FeatureEnvy < SmellDetector
36
+
37
+ def self.default_config
38
+ super.adopt(EXCLUDE_KEY => ['initialize'])
39
+ end
40
+
41
+ def initialize(config = FeatureEnvy.default_config)
42
+ super
43
+ end
44
+
45
+ #
46
+ # Checks whether the given +context+ includes any code fragment that
47
+ # might "belong" on another class.
48
+ # Any smells found are added to the +report+.
49
+ #
50
+ def examine_context(context, report)
51
+ context.envious_receivers.each do |ref|
52
+ report << SmellWarning.new(self, context,
53
+ "refers to #{SexpFormatter.format(ref)} more than self")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # A Large Class is a class or module that has a large number of
9
+ # instance variables, methods or lines of code.
10
+ #
11
+ # Currently +LargeClass+ only reports classes having more than a
12
+ # configurable number of methods. This includes public, protected and
13
+ # private methods, but excludes methods inherited from superclasses or
14
+ # included modules.
15
+ #
16
+ class LargeClass < SmellDetector
17
+
18
+ # The name of the config field that sets the maximum number of methods
19
+ # permitted in a class.
20
+ MAX_ALLOWED_METHODS_KEY = 'max_methods'
21
+
22
+ def self.contexts # :nodoc:
23
+ [:class]
24
+ end
25
+
26
+ def self.default_config
27
+ super.adopt(
28
+ MAX_ALLOWED_METHODS_KEY => 25,
29
+ EXCLUDE_KEY => ['Array', 'Hash', 'Module', 'String']
30
+ )
31
+ end
32
+
33
+ def initialize(config = LargeClass.default_config)
34
+ super
35
+ @max_methods = config[MAX_ALLOWED_METHODS_KEY]
36
+ end
37
+
38
+ #
39
+ # Checks the length of the given +klass+.
40
+ # Any smells found are added to the +report+.
41
+ #
42
+ def examine_context(klass, report)
43
+ num_methods = klass.num_methods
44
+ return false if num_methods <= @max_methods
45
+ report << SmellWarning.new(self, klass,
46
+ "has at least #{num_methods} methods")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # A Long Method is any method that has a large number of lines.
9
+ #
10
+ # Currently +LongMethod+ reports any method with more than
11
+ # 5 statements.
12
+ #
13
+ class LongMethod < SmellDetector
14
+
15
+ # The name of the config field that sets the maximum number of
16
+ # statements permitted in any method.
17
+ MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
18
+
19
+ def self.default_config
20
+ super.adopt(
21
+ MAX_ALLOWED_STATEMENTS_KEY => 5,
22
+ EXCLUDE_KEY => ['initialize']
23
+ )
24
+ end
25
+
26
+ def initialize(config = LongMethod.default_config)
27
+ super
28
+ @max_statements = config[MAX_ALLOWED_STATEMENTS_KEY]
29
+ end
30
+
31
+ #
32
+ # Checks the length of the given +method+.
33
+ # Any smells found are added to the +report+.
34
+ #
35
+ def examine_context(method, report)
36
+ num = method.num_statements
37
+ return false if num <= @max_statements
38
+ report << SmellWarning.new(self, method,
39
+ "has approx #{num} statements")
40
+ end
41
+ end
42
+ end
43
+ end