reek 1.2.0 → 1.2.1

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.
@@ -1,3 +1,10 @@
1
+ == 1.2.1 (2009-10-02)
2
+
3
+ === Minor Changes
4
+ * New smell: Class Variable
5
+
6
+ See http://wiki.github.com/kevinrutherford/reek for details
7
+
1
8
  == 1.2 2009-09-20
2
9
 
3
10
  === Major Changes
@@ -73,6 +73,7 @@ Reek makes use of the following other gems:
73
73
 
74
74
  * ruby_parser
75
75
  * sexp_processor
76
+ * ruby2ruby
76
77
 
77
78
  == Learn More
78
79
 
@@ -16,6 +16,10 @@ LongParameterList:
16
16
  FeatureEnvy:
17
17
  exclude:
18
18
  - initialize
19
+ enabled: true
20
+ ClassVariable:
21
+ exclude: &id001 []
22
+
19
23
  enabled: true
20
24
  UncommunicativeName:
21
25
  accept:
@@ -27,8 +31,7 @@ UncommunicativeName:
27
31
  - !ruby/regexp /^.$/
28
32
  - !ruby/regexp /[0-9]$/
29
33
  NestedIterators:
30
- exclude: []
31
-
34
+ exclude: *id001
32
35
  enabled: true
33
36
  LongMethod:
34
37
  max_statements: 5
@@ -9,7 +9,10 @@ Feature: Basic smell detection
9
9
  Then it fails with exit status 2
10
10
  And it reports:
11
11
  """
12
- spec/samples/inline.rb -- 36 warnings (+1 masked):
12
+ spec/samples/inline.rb -- 39 warnings (+1 masked):
13
+ Inline declares the class variable @@directory (Class Variable)
14
+ Inline declares the class variable @@rootdir (Class Variable)
15
+ Inline::C declares the class variable @@type_map (Class Variable)
13
16
  Inline::C has at least 13 instance variables (Large Class)
14
17
  Inline::C takes parameters [options, src] to 5 methods (Data Clump)
15
18
  Inline::C tests $DEBUG at least 7 times (Simulated Polymorphism)
@@ -1,5 +1,5 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
3
  module Reek # :doc:
4
- VERSION = '1.2.0'
4
+ VERSION = '1.2.1'
5
5
  end
@@ -1,7 +1,6 @@
1
- require 'reek/command_line'
1
+ require 'reek/adapters/command_line'
2
2
  require 'reek/adapters/source'
3
3
  require 'reek/adapters/core_extras'
4
- require 'reek/adapters/report'
5
4
 
6
5
  module Reek
7
6
  #
@@ -11,13 +10,13 @@ module Reek
11
10
  #
12
11
  class Application
13
12
  def initialize(argv)
14
- @argv = argv
13
+ @options = Options.new(argv)
15
14
  end
16
15
 
17
16
  def examine_sources
18
17
  # SMELL: Greedy Method
19
18
  # Options.parse executes the -v and -h commands and throws a SystemExit
20
- args = Options.new(@argv).parse
19
+ args = @options.parse
21
20
  if args.length > 0
22
21
  @sniffer = args.sniff
23
22
  else
@@ -27,9 +26,7 @@ module Reek
27
26
 
28
27
  def reek
29
28
  examine_sources
30
- # SMELL:
31
- # This should use the actual type of report selected by the user's options
32
- puts Report.new(@sniffer.sniffers).full_report
29
+ puts @options.create_report(@sniffer.sniffers).report
33
30
  return @sniffer.smelly? ? 2 : 0
34
31
  end
35
32
 
@@ -1,5 +1,6 @@
1
1
  require 'optparse'
2
2
  require 'reek'
3
+ require 'reek/adapters/report'
3
4
 
4
5
  module Reek
5
6
 
@@ -12,24 +13,12 @@ module Reek
12
13
  CTX_SORT = '%m%c %w (%s)'
13
14
  SMELL_SORT = '%m[%s] %c %w'
14
15
 
15
- def self.default_options
16
- {
17
- :format => CTX_SORT,
18
- :show_all => false,
19
- :quiet => false
20
- }
21
- end
22
-
23
- # SMELL: Global Variable
24
- @@opts = default_options
25
-
26
- def self.[](key)
27
- @@opts[key]
28
- end
29
-
30
16
  def initialize(argv)
31
17
  @argv = argv
32
18
  @parser = OptionParser.new
19
+ @quiet = false
20
+ @show_all = false
21
+ @format = CTX_SORT
33
22
  set_options
34
23
  end
35
24
 
@@ -66,20 +55,24 @@ EOB
66
55
  @parser.separator "\nReport formatting:"
67
56
 
68
57
  @parser.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
69
- @@opts[:show_all] = opt
58
+ @show_all = opt
70
59
  end
71
60
  @parser.on("-q", "--quiet", "Suppress headings for smell-free source files") do
72
- @@opts[:quiet] = true
61
+ @quiet = true
73
62
  end
74
63
  @parser.on('-f', "--format FORMAT", 'Specify the format of smell warnings') do |arg|
75
- @@opts[:format] = arg unless arg.nil?
64
+ @format = arg unless arg.nil?
76
65
  end
77
66
  @parser.on('-c', '--context-first', "Sort by context; sets the format string to \"#{CTX_SORT}\"") do
78
- @@opts[:format] = CTX_SORT
67
+ @format = CTX_SORT
79
68
  end
80
69
  @parser.on('-s', '--smell-first', "Sort by smell; sets the format string to \"#{SMELL_SORT}\"") do
81
- @@opts[:format] = SMELL_SORT
70
+ @format = SMELL_SORT
82
71
  end
83
72
  end
73
+
74
+ def create_report(sniffers)
75
+ @quiet ? QuietReport.new(sniffers, @format, @show_all) : FullReport.new(sniffers, @format, @show_all)
76
+ end
84
77
  end
85
78
  end
@@ -1,5 +1,8 @@
1
1
  require 'reek/adapters/source'
2
2
  require 'reek/configuration'
3
+ require 'reek/smells/large_class'
4
+
5
+ include Reek::Smells
3
6
 
4
7
  module Reek
5
8
  class ObjectSource < Source # :nodoc:
@@ -1,13 +1,15 @@
1
1
  require 'set'
2
- require 'reek/command_line' # SMELL: Global Variable
2
+ require 'reek/adapters/command_line' # SMELL: Global Variable
3
3
 
4
4
  module Reek
5
5
  class ReportSection
6
6
 
7
- def initialize(sniffer) # :nodoc:
7
+ def initialize(sniffer, display_masked_warnings, format) # :nodoc:
8
+ @format = format
8
9
  @masked_warnings = SortedSet.new
9
10
  @warnings = SortedSet.new
10
11
  @desc = sniffer.desc
12
+ @display_masked_warnings = display_masked_warnings
11
13
  sniffer.report_on(self)
12
14
  end
13
15
 
@@ -27,7 +29,6 @@ module Reek
27
29
  # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
28
30
  # this report, with a heading.
29
31
  def full_report
30
- return quiet_report if Options[:quiet]
31
32
  result = header
32
33
  result += ":\n#{smell_list}" if should_report
33
34
  result += "\n"
@@ -48,14 +49,14 @@ module Reek
48
49
  # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
49
50
  # this report.
50
51
  def smell_list
51
- smells = Options[:show_all] ? @all_warnings : @warnings
52
- smells.map {|smell| " #{smell.report}"}.join("\n")
52
+ smells = @display_masked_warnings ? @all_warnings : @warnings
53
+ smells.map {|smell| " #{smell.report(@format)}"}.join("\n")
53
54
  end
54
55
 
55
56
  private
56
57
 
57
58
  def should_report
58
- @warnings.length > 0 or (Options[:show_all] and @masked_warnings.length > 0)
59
+ @warnings.length > 0 or (@display_masked_warnings and @masked_warnings.length > 0)
59
60
  end
60
61
 
61
62
  def visible_header
@@ -72,19 +73,19 @@ module Reek
72
73
  end
73
74
 
74
75
  class Report
75
-
76
- def initialize(sniffers)
77
- @partials = Array(sniffers).map {|sn| ReportSection.new(sn)}
76
+ def initialize(sniffers, format, display_masked_warnings = false)
77
+ @partials = Array(sniffers).map {|sn| ReportSection.new(sn, display_masked_warnings, format)}
78
78
  end
79
+ end
79
80
 
80
- # SMELL: Shotgun Surgery
81
- # This method and the next will have to be repeated for every new
82
- # kind of report.
83
- def full_report
81
+ class FullReport < Report
82
+ def report
84
83
  @partials.map { |rpt| rpt.full_report }.join
85
84
  end
85
+ end
86
86
 
87
- def quiet_report
87
+ class QuietReport < Report
88
+ def report
88
89
  @partials.map { |rpt| rpt.quiet_report }.join
89
90
  end
90
91
  end
@@ -44,9 +44,14 @@ module Reek
44
44
  #
45
45
  module Spec
46
46
  module ReekMatcher
47
+ def create_reporter(sniffers)
48
+ QuietReport.new(sniffers, '%c %w (%s)', false)
49
+ end
47
50
  def report
48
- Report.new(@sniffer.sniffers).quiet_report
51
+ create_reporter(@sniffer.sniffers).report
49
52
  end
53
+
54
+ module_function :create_reporter
50
55
  end
51
56
 
52
57
  class ShouldReek # :nodoc:
@@ -24,7 +24,7 @@ module Reek
24
24
  CodeParser.new(sniffer).process_class(source.syntax_tree)
25
25
  end
26
26
 
27
- attr_reader :conditionals, :parsed_methods
27
+ attr_reader :conditionals, :parsed_methods, :class_variables
28
28
 
29
29
  # SMELL: inconsistent with other contexts (not linked to the sexp)
30
30
  def initialize(outer, name, superclass = nil)
@@ -34,6 +34,7 @@ module Reek
34
34
  @parsed_methods = []
35
35
  @instance_variables = Set.new
36
36
  @conditionals = []
37
+ @class_variables = Set.new
37
38
  end
38
39
 
39
40
  def myself
@@ -58,6 +59,10 @@ module Reek
58
59
  @parsed_methods.length
59
60
  end
60
61
 
62
+ def record_class_variable(cvar)
63
+ @class_variables << Name.new(cvar)
64
+ end
65
+
61
66
  def record_instance_variable(sym)
62
67
  @instance_variables << Name.new(sym)
63
68
  end
@@ -51,10 +51,12 @@ module Reek
51
51
  end
52
52
 
53
53
  def process_module(exp)
54
- push(ModuleContext.create(@element, exp)) do
54
+ scope = ModuleContext.create(@element, exp)
55
+ push(scope) do
55
56
  process_default(exp)
56
57
  check_smells(:module)
57
58
  end
59
+ scope
58
60
  end
59
61
 
60
62
  def process_class(exp)
@@ -66,6 +68,18 @@ module Reek
66
68
  scope
67
69
  end
68
70
 
71
+ def process_cvar(exp)
72
+ @element.record_class_variable(exp[1])
73
+ end
74
+
75
+ def process_cvasgn(exp)
76
+ process_cvar(exp)
77
+ end
78
+
79
+ def process_cvdecl(exp)
80
+ process_cvar(exp)
81
+ end
82
+
69
83
  def process_defn(exp)
70
84
  handle_context(MethodContext, :defn, exp)
71
85
  end
@@ -8,9 +8,18 @@ module Reek
8
8
  ModuleContext.new(res[0], res[1])
9
9
  end
10
10
 
11
+ def ModuleContext.from_s(src)
12
+ source = src.to_reek_source
13
+ sniffer = Sniffer.new(source)
14
+ CodeParser.new(sniffer).process_module(source.syntax_tree)
15
+ end
16
+
17
+ attr_reader :class_variables
18
+
11
19
  def initialize(outer, name)
12
20
  super(outer, nil)
13
21
  @name = name
22
+ @class_variables = Set.new
14
23
  end
15
24
 
16
25
  def myself
@@ -26,6 +35,10 @@ module Reek
26
35
  "#{@outer.outer_name}#{@name}::"
27
36
  end
28
37
 
38
+ def record_class_variable(cvar)
39
+ @class_variables << Name.new(cvar)
40
+ end
41
+
29
42
  def variable_names
30
43
  []
31
44
  end
@@ -1,5 +1,3 @@
1
- require 'reek/command_line' # SMELL: Global Variable used for options
2
-
3
1
  module Reek
4
2
 
5
3
  #
@@ -16,11 +14,11 @@ module Reek
16
14
  end
17
15
 
18
16
  def hash # :nodoc:
19
- basic_report.hash
17
+ sort_key.hash
20
18
  end
21
19
 
22
20
  def <=>(other)
23
- basic_report <=> other.basic_report
21
+ sort_key <=> other.sort_key
24
22
  end
25
23
 
26
24
  alias eql? <=> # :nodoc:
@@ -35,25 +33,18 @@ module Reek
35
33
  end
36
34
 
37
35
  def contains_all?(patterns)
38
- rpt = report
36
+ rpt = sort_key.to_s
39
37
  return patterns.all? {|exp| exp === rpt}
40
38
  end
41
39
 
42
- def basic_report
43
- Options[:format].gsub(/\%s/, @detector.smell_name).gsub(/\%c/, @context.to_s).gsub(/\%w/, @warning)
40
+ def sort_key
41
+ [@context.to_s, @warning, @detector.smell_name]
44
42
  end
45
43
 
46
- #
47
- # Returns a copy of the current report format (see +Options+)
48
- # in which the following magic tokens have been substituted:
49
- #
50
- # * %c <-- a description of the +CodeContext+ containing the smell
51
- # * %m <-- "(is_masked) " if this is a is_masked smell
52
- # * %s <-- the name of the smell that was detected
53
- # * %w <-- the specific problem that was detected
54
- #
55
- def report
56
- basic_report.gsub(/\%m/, @is_masked ? '(masked) ' : '')
44
+ protected :sort_key
45
+
46
+ def report(format)
47
+ format.gsub(/\%s/, @detector.smell_name).gsub(/\%c/, @context.to_s).gsub(/\%w/, @warning).gsub(/\%m/, @is_masked ? '(masked) ' : '')
57
48
  end
58
49
 
59
50
  def report_on(report)
@@ -0,0 +1,31 @@
1
+ require 'reek/smells/smell_detector'
2
+
3
+ module Reek
4
+ module Smells
5
+
6
+ #
7
+ # Class variables form part of the global runtime state, and as such make
8
+ # it easy for one part of the system to accidentally or inadvertently
9
+ # depend on another part of the system. So the system becomes more prone to
10
+ # problems where changing something over here breaks something over there.
11
+ # In particular, class variables can make it hard to set up tests (because
12
+ # the context of the test includes all global state).
13
+ #
14
+ class ClassVariable < SmellDetector
15
+
16
+ def self.contexts # :nodoc:
17
+ [:class, :module]
18
+ end
19
+
20
+ #
21
+ # Checks whether the given class declares any class variables.
22
+ # Remembers any smells found.
23
+ #
24
+ def examine_context(klass)
25
+ klass.class_variables.each do |cvar|
26
+ found(klass, "declares the class variable #{cvar}")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,6 +1,7 @@
1
1
  require 'reek/detector_stack'
2
2
 
3
3
  # SMELL: Duplication -- all these should be found automagically
4
+ require 'reek/smells/class_variable'
4
5
  require 'reek/smells/control_couple'
5
6
  require 'reek/smells/data_clump'
6
7
  require 'reek/smells/duplication'
@@ -48,6 +49,7 @@ module Reek
48
49
  def self.smell_classes
49
50
  # SMELL: Duplication -- these should be loaded by listing the files
50
51
  [
52
+ Smells::ClassVariable,
51
53
  Smells::ControlCouple,
52
54
  Smells::DataClump,
53
55
  Smells::Duplication,