reek 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,