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
@@ -1,6 +1,5 @@
1
- $:.unshift File.dirname(__FILE__)
2
-
3
- require 'reek/smells/smell'
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -8,27 +7,28 @@ module Reek
8
7
  #
9
8
  # A Utility Function is any instance method that has no
10
9
  # dependency on the state of the instance.
10
+ #
11
+ # Currently +UtilityFunction+ will warn about any method that:
12
+ #
13
+ # * is non-empty
14
+ # * does not override an inherited method
15
+ # * calls at least one method on another object
16
+ # * doesn't use any of self's instance variables
17
+ # * doesn't use any of self's methods
11
18
  #
12
- class UtilityFunction < Smell
19
+ class UtilityFunction < SmellDetector
13
20
 
14
21
  #
15
22
  # Checks whether the given +method+ is a utility function.
16
- # Any smells found are added to the +report+; returns true in that case,
17
- # and false otherwise.
23
+ # Any smells found are added to the +report+.
18
24
  #
19
- def self.examine(method, report)
20
- return false if method.name == 'initialize'
21
- if method.num_statements > 0 and !method.depends_on_self
22
- report << new(method)
23
- true
24
- end
25
- false
26
- end
27
-
28
- def detailed_report
29
- "#{@context} doesn't depend on instance state"
25
+ def examine_context(method, report)
26
+ return false if method.calls.keys.length == 0 or
27
+ method.num_statements == 0 or
28
+ method.depends_on_instance?
29
+ report << SmellWarning.new(self, method,
30
+ "doesn't depend on instance state")
30
31
  end
31
32
  end
32
-
33
33
  end
34
34
  end
@@ -0,0 +1,116 @@
1
+ require 'reek/code_parser'
2
+ require 'reek/report'
3
+ require 'reek/smells/smells'
4
+
5
+ module Reek
6
+
7
+ #
8
+ # A +Source+ object represents a chunk of Ruby source code.
9
+ #
10
+ # The various class methods are factories that will create +Source+
11
+ # instances from various types of input.
12
+ #
13
+ class Source
14
+
15
+ #
16
+ # Factory method: creates a +Source+ object by reading Ruby code from
17
+ # the +IO+ stream. The stream is consumed upto end-of-file, but the
18
+ # source code is not parsed until +report+ is called. +desc+ provides
19
+ # a string description to be used in the header of formatted reports.
20
+ #
21
+ def self.from_io(ios, desc)
22
+ code = ios.readlines.join
23
+ return new(code, desc)
24
+ end
25
+
26
+ #
27
+ # Factory method: creates a +Source+ object by reading Ruby code from
28
+ # the +code+ string. The code is not parsed until +report+ is called.
29
+ #
30
+ def self.from_s(code)
31
+ return new(code, 'string')
32
+ end
33
+
34
+ #
35
+ # Factory method: creates a +Source+ object by reading Ruby code from
36
+ # File +file+. The source code is not parsed until +report+ is called.
37
+ #
38
+ def self.from_f(file)
39
+ from_path(file.path)
40
+ end
41
+
42
+ #
43
+ # Factory method: creates a +Source+ object by reading Ruby code from
44
+ # the named file. The source code is not parsed until +report+ is called.
45
+ #
46
+ def self.from_path(filename)
47
+ code = IO.readlines(filename).join
48
+ return new(code, filename, File.dirname(filename))
49
+ end
50
+
51
+ #
52
+ # Factory method: creates a +Source+ object from an array of file paths.
53
+ # No source code is actually parsed until the report is accessed.
54
+ #
55
+ def self.from_pathlist(paths)
56
+ sources = paths.map {|path| Source.from_path(path) }
57
+ SourceList.new(sources)
58
+ end
59
+
60
+ def initialize(code, desc, dir = '.') # :nodoc:
61
+ @source = code
62
+ @dir = dir
63
+ @desc = desc
64
+ end
65
+
66
+ #
67
+ # Returns a +Report+ listing the smells found in this source. The first
68
+ # call to +report+ parses the source code and constructs a list of
69
+ # +SmellWarning+s found; subsequent calls simply return this same list.
70
+ #
71
+ def report
72
+ unless @report
73
+ @report = Report.new
74
+ smells = SmellConfig.new.load_local(@dir).smell_listeners
75
+ CodeParser.new(@report, smells).check_source(@source)
76
+ end
77
+ @report
78
+ end
79
+
80
+ def smelly?
81
+ report.length > 0
82
+ end
83
+
84
+ #
85
+ # Checks this source for instances of +smell_class+, and returns +true+
86
+ # only if one of them has a report string matching all of the +patterns+.
87
+ #
88
+ def has_smell?(smell_class, patterns)
89
+ report.any? { |smell| smell.matches?(smell_class, patterns) }
90
+ end
91
+
92
+ def to_s
93
+ @desc
94
+ end
95
+ end
96
+
97
+ #
98
+ # Represents a list of Sources as if they were a single source.
99
+ #
100
+ class SourceList
101
+ def initialize(sources)
102
+ @sources = sources
103
+ end
104
+
105
+ def smelly?
106
+ @sources.any? {|source| source.smelly? }
107
+ end
108
+
109
+ def report
110
+ @sources.select {|src| src.smelly? }.map do |src|
111
+ warnings = src.report
112
+ "\"#{src}\" -- #{warnings.length} warnings:\n#{warnings.to_s}\n"
113
+ end.join("\n")
114
+ end
115
+ end
116
+ end
data/lib/reek/spec.rb ADDED
@@ -0,0 +1,146 @@
1
+ require 'reek/source'
2
+
3
+ module Reek
4
+
5
+ #
6
+ # Provides matchers for Rspec, making it easy to check code quality.
7
+ #
8
+ # If you require this module somewhere within your spec (or in your spec_helper),
9
+ # Reek will arrange to update Spec::Runner's config so that it knows about the
10
+ # matchers defined here.
11
+ #
12
+ # === Examples
13
+ #
14
+ # Here's a spec that ensures there are no smell warnings in the current project:
15
+ #
16
+ # describe 'source code quality' do
17
+ # Dir['lib/**/*.rb'].each do |path|
18
+ # it "reports no smells in #{path}" do
19
+ # File.new(path).should_not reek
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # And here's an even simpler way to do the same:
25
+ #
26
+ # it 'has no code smells' do
27
+ # Dir['lib/**/*.rb'].should_not reek
28
+ # end
29
+ #
30
+ # Here's a simple check of a code fragment:
31
+ #
32
+ # 'def equals(other) other.thing == self.thing end'.should_not reek
33
+ #
34
+ # And a more complex example, making use of one of the factory methods for
35
+ # +Source+ so that the code is parsed and analysed only once:
36
+ #
37
+ # ruby = 'def double_thing() @other.thing.foo + @other.thing.foo end'.to_source
38
+ # ruby.should reek_of(:Duplication, /@other.thing[^\.]/)
39
+ # ruby.should reek_of(:Duplication, /@other.thing.foo/)
40
+ # ruby.should_not reek_of(:FeatureEnvy)
41
+ #
42
+ module Spec
43
+ class ShouldReek # :nodoc:
44
+ def matches?(actual)
45
+ @source = actual.to_source
46
+ @source.smelly?
47
+ end
48
+ def failure_message_for_should
49
+ "Expected source to reek, but it didn't"
50
+ end
51
+ def failure_message_for_should_not
52
+ "Expected no smells, but got:\n#{@source.report}"
53
+ end
54
+ end
55
+
56
+ #
57
+ # Returns +true+ if and only if the target source code contains smells.
58
+ #
59
+ def reek
60
+ ShouldReek.new
61
+ end
62
+
63
+ class ShouldReekOf # :nodoc:
64
+ def initialize(klass, patterns)
65
+ @klass = klass
66
+ @patterns = patterns
67
+ end
68
+ def matches?(actual)
69
+ @source = actual.to_source
70
+ @source.has_smell?(@klass, @patterns)
71
+ end
72
+ def failure_message_for_should
73
+ "Expected #{@source} to reek of #{@klass}, but it didn't"
74
+ end
75
+ def failure_message_for_should_not
76
+ "Expected #{@source} not to reek of #{@klass}, but got:\n#{@source.report}"
77
+ end
78
+ end
79
+
80
+ #
81
+ # Checks the target source code for instances of +smell_class+,
82
+ # and returns +true+ only if one of them has a report string matching
83
+ # all of the +patterns+.
84
+ #
85
+ def reek_of(smell_class, *patterns)
86
+ ShouldReekOf.new(smell_class, patterns)
87
+ end
88
+
89
+ class ShouldReekOnlyOf # :nodoc:
90
+ def initialize(klass, patterns)
91
+ @klass = klass
92
+ @patterns = patterns
93
+ end
94
+ def matches?(actual)
95
+ @source = actual.to_source
96
+ @source.report.length == 1 and @source.has_smell?(@klass, @patterns)
97
+ end
98
+ def failure_message_for_should
99
+ "Expected source to reek only of #{@klass}, but got:\n#{@source.report}"
100
+ end
101
+ def failure_message_for_should_not
102
+ "Expected source not to reek only of #{@klass}, but it did"
103
+ end
104
+ end
105
+
106
+ #
107
+ # As for reek_of, but the matched smell warning must be the only warning of
108
+ # any kind in the target source code's Reek report.
109
+ #
110
+ def reek_only_of(smell_class, *patterns)
111
+ ShouldReekOnlyOf.new(smell_class, patterns)
112
+ end
113
+ end
114
+ end
115
+
116
+ class File
117
+ def to_source
118
+ Reek::Source.from_f(self)
119
+ end
120
+ end
121
+
122
+ class String
123
+ def to_source
124
+ Reek::Source.from_s(self)
125
+ end
126
+ end
127
+
128
+ class Array
129
+ def to_source
130
+ Reek::Source.from_pathlist(self)
131
+ end
132
+ end
133
+
134
+ module Reek
135
+ class Source
136
+ def to_source
137
+ self
138
+ end
139
+ end
140
+ end
141
+
142
+ if Object.const_defined?(:Spec)
143
+ Spec::Runner.configure do |config|
144
+ config.include(Reek::Spec)
145
+ end
146
+ end
@@ -0,0 +1,62 @@
1
+ module Reek
2
+ class StopContext
3
+
4
+ def initialize
5
+ @refs = ObjectRefs.new
6
+ @myself = Object
7
+ end
8
+
9
+ def count_statements(num)
10
+ 0
11
+ end
12
+
13
+ def find_module(name)
14
+ sym = name.to_s
15
+ @myself.const_defined?(sym) ? @myself.const_get(sym) : nil
16
+ end
17
+
18
+ def has_parameter(sym)
19
+ false
20
+ end
21
+
22
+ def inside_a_block?
23
+ false
24
+ end
25
+
26
+ def is_overriding_method?(name)
27
+ false
28
+ end
29
+
30
+ def num_statements
31
+ 0
32
+ end
33
+
34
+ def refs
35
+ @refs
36
+ end
37
+
38
+ def record_depends_on_self
39
+ false
40
+ end
41
+
42
+ def record_call_to(exp)
43
+ nil
44
+ end
45
+
46
+ def record_method(name)
47
+ end
48
+
49
+ def record_parameter(sym)
50
+ end
51
+
52
+ def record_instance_variable(sym)
53
+ end
54
+
55
+ def record_local_variable(sym)
56
+ end
57
+
58
+ def outer_name
59
+ ''
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,14 @@
1
+ require 'reek/code_context'
2
+
3
+ module Reek
4
+ class YieldCallContext < CodeContext
5
+ attr_reader :parameters
6
+
7
+ def initialize(outer, exp)
8
+ super
9
+ @parameters = []
10
+ args = exp[1]
11
+ @parameters = args[0...-1] if args
12
+ end
13
+ end
14
+ end
data/reek.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{reek}
5
+ s.version = "0.3.1.6"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Kevin Rutherford"]
9
+ s.date = %q{2009-04-03}
10
+ s.default_executable = %q{reek}
11
+ s.description = %q{Code smell detector for Ruby}
12
+ s.email = ["kevin@rutherford-software.com"]
13
+ s.executables = ["reek"]
14
+ s.extra_rdoc_files = ["History.txt", "README.txt"]
15
+ s.files = ["History.txt", "README.txt", "Rakefile", "bin/reek", "config/defaults.reek", "lib/reek.rb", "lib/reek/block_context.rb", "lib/reek/class_context.rb", "lib/reek/code_context.rb", "lib/reek/code_parser.rb", "lib/reek/exceptions.reek", "lib/reek/if_context.rb", "lib/reek/method_context.rb", "lib/reek/module_context.rb", "lib/reek/name.rb", "lib/reek/object_refs.rb", "lib/reek/options.rb", "lib/reek/rake_task.rb", "lib/reek/report.rb", "lib/reek/sexp_formatter.rb", "lib/reek/singleton_method_context.rb", "lib/reek/smell_warning.rb", "lib/reek/smells/control_couple.rb", "lib/reek/smells/duplication.rb", "lib/reek/smells/feature_envy.rb", "lib/reek/smells/large_class.rb", "lib/reek/smells/long_method.rb", "lib/reek/smells/long_parameter_list.rb", "lib/reek/smells/long_yield_list.rb", "lib/reek/smells/nested_iterators.rb", "lib/reek/smells/smell_detector.rb", "lib/reek/smells/smells.rb", "lib/reek/smells/uncommunicative_name.rb", "lib/reek/smells/utility_function.rb", "lib/reek/source.rb", "lib/reek/spec.rb", "lib/reek/stop_context.rb", "lib/reek/yield_call_context.rb", "reek.gemspec", "spec/integration/reek_source_spec.rb", "spec/integration/script_spec.rb", "spec/reek/class_context_spec.rb", "spec/reek/code_context_spec.rb", "spec/reek/code_parser_spec.rb", "spec/reek/config_spec.rb", "spec/reek/if_context_spec.rb", "spec/reek/method_context_spec.rb", "spec/reek/module_context_spec.rb", "spec/reek/object_refs_spec.rb", "spec/reek/options_spec.rb", "spec/reek/report_spec.rb", "spec/reek/sexp_formatter_spec.rb", "spec/reek/singleton_method_context_spec.rb", "spec/reek/smells/control_couple_spec.rb", "spec/reek/smells/duplication_spec.rb", "spec/reek/smells/feature_envy_spec.rb", "spec/reek/smells/large_class_spec.rb", "spec/reek/smells/long_method_spec.rb", "spec/reek/smells/long_parameter_list_spec.rb", "spec/reek/smells/nested_iterators_spec.rb", "spec/reek/smells/smell_spec.rb", "spec/reek/smells/uncommunicative_name_spec.rb", "spec/reek/smells/utility_function_spec.rb", "spec/samples/inline.rb", "spec/samples/inline_spec.rb", "spec/samples/optparse.rb", "spec/samples/optparse_spec.rb", "spec/samples/redcloth.rb", "spec/samples/redcloth_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/reek.rake", "tasks/rspec.rake"]
16
+ s.has_rdoc = true
17
+ s.homepage = %q{http://wiki.github.com/kevinrutherford/reek}
18
+ s.post_install_message = %q{
19
+ For more information on reek, see http://wiki.github.com/kevinrutherford/reek
20
+ }
21
+ s.rdoc_options = ["--main", "README.txt"]
22
+ s.require_paths = ["lib"]
23
+ s.rubyforge_project = %q{reek}
24
+ s.rubygems_version = %q{1.3.1}
25
+ s.summary = %q{Code smell detector for Ruby}
26
+
27
+ if s.respond_to? :specification_version then
28
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
29
+ s.specification_version = 2
30
+
31
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
32
+ s.add_runtime_dependency(%q<ParseTree>, ["~> 3.0"])
33
+ s.add_runtime_dependency(%q<sexp_processor>, ["~> 3.0"])
34
+ else
35
+ s.add_dependency(%q<ParseTree>, ["~> 3.0"])
36
+ s.add_dependency(%q<sexp_processor>, ["~> 3.0"])
37
+ end
38
+ else
39
+ s.add_dependency(%q<ParseTree>, ["~> 3.0"])
40
+ s.add_dependency(%q<sexp_processor>, ["~> 3.0"])
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe 'Reek source code:' do
4
+ Dir['lib/**/*.rb'].each do |path|
5
+ it "reports no smells in #{path}" do
6
+ File.new(path).should_not reek
7
+ end
8
+ end
9
+
10
+ it 'reports no smells via the Dir matcher' do
11
+ Dir['lib/**/*.rb'].should_not reek
12
+ end
13
+ end
14
+
15
+ describe 'RakeTask' do
16
+ it 'should report no duplication' do
17
+ report = `rake reek`.split("\n")
18
+ report.length.should == 1
19
+ end
20
+ end