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