kevinrutherford-reek 0.3.1.4

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