reek 1.2.7.2 → 1.2.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/History.txt +9 -1
  2. data/config/defaults.reek +4 -4
  3. data/features/masking_smells.feature +14 -57
  4. data/features/options.feature +1 -2
  5. data/features/rake_task.feature +6 -6
  6. data/features/reports.feature +8 -38
  7. data/features/samples.feature +181 -181
  8. data/features/stdin.feature +3 -3
  9. data/lib/reek.rb +1 -1
  10. data/lib/reek/cli/command_line.rb +2 -7
  11. data/lib/reek/cli/reek_command.rb +6 -6
  12. data/lib/reek/cli/report.rb +27 -49
  13. data/lib/reek/core/code_parser.rb +6 -15
  14. data/lib/reek/core/method_context.rb +1 -1
  15. data/lib/reek/core/module_context.rb +0 -18
  16. data/lib/reek/core/smell_configuration.rb +0 -4
  17. data/lib/reek/core/sniffer.rb +11 -19
  18. data/lib/reek/core/warning_collector.rb +27 -0
  19. data/lib/reek/examiner.rb +43 -35
  20. data/lib/reek/rake/task.rb +2 -0
  21. data/lib/reek/smell_warning.rb +10 -25
  22. data/lib/reek/smells/boolean_parameter.rb +1 -1
  23. data/lib/reek/smells/control_couple.rb +4 -1
  24. data/lib/reek/smells/data_clump.rb +40 -33
  25. data/lib/reek/smells/feature_envy.rb +1 -1
  26. data/lib/reek/smells/long_parameter_list.rb +4 -1
  27. data/lib/reek/smells/long_yield_list.rb +6 -3
  28. data/lib/reek/smells/simulated_polymorphism.rb +1 -1
  29. data/lib/reek/smells/smell_detector.rb +4 -32
  30. data/lib/reek/smells/uncommunicative_method_name.rb +1 -1
  31. data/lib/reek/smells/uncommunicative_module_name.rb +1 -1
  32. data/lib/reek/smells/uncommunicative_parameter_name.rb +2 -2
  33. data/lib/reek/smells/uncommunicative_variable_name.rb +11 -18
  34. data/lib/reek/smells/utility_function.rb +7 -4
  35. data/lib/reek/source/reference_collector.rb +9 -2
  36. data/lib/reek/source/source_locator.rb +6 -0
  37. data/lib/reek/spec/should_reek.rb +3 -6
  38. data/lib/reek/spec/should_reek_only_of.rb +4 -3
  39. data/reek.gemspec +4 -4
  40. data/spec/reek/cli/reek_command_spec.rb +3 -4
  41. data/spec/reek/cli/report_spec.rb +10 -6
  42. data/spec/reek/cli/yaml_command_spec.rb +1 -1
  43. data/spec/reek/core/code_context_spec.rb +1 -3
  44. data/spec/reek/core/module_context_spec.rb +1 -1
  45. data/spec/reek/core/warning_collector_spec.rb +27 -0
  46. data/spec/reek/examiner_spec.rb +80 -19
  47. data/spec/reek/smell_warning_spec.rb +4 -61
  48. data/spec/reek/smells/attribute_spec.rb +4 -7
  49. data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
  50. data/spec/reek/smells/class_variable_spec.rb +0 -1
  51. data/spec/reek/smells/control_couple_spec.rb +8 -15
  52. data/spec/reek/smells/data_clump_spec.rb +85 -1
  53. data/spec/reek/smells/duplication_spec.rb +7 -8
  54. data/spec/reek/smells/feature_envy_spec.rb +2 -32
  55. data/spec/reek/smells/long_parameter_list_spec.rb +9 -16
  56. data/spec/reek/smells/long_yield_list_spec.rb +8 -15
  57. data/spec/reek/smells/smell_detector_shared.rb +12 -0
  58. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +9 -10
  59. data/spec/reek/smells/utility_function_spec.rb +11 -15
  60. data/spec/reek/spec/should_reek_only_of_spec.rb +6 -6
  61. data/spec/reek/spec/should_reek_spec.rb +3 -3
  62. metadata +36 -22
  63. data/lib/reek/core/class_context.rb +0 -22
  64. data/lib/reek/core/detector_stack.rb +0 -33
  65. data/lib/reek/core/masking_collection.rb +0 -52
  66. data/spec/reek/core/class_context_spec.rb +0 -53
  67. data/spec/reek/core/masking_collection_spec.rb +0 -235
@@ -33,9 +33,9 @@ Feature: Reek reads from $stdin when no files are given
33
33
  And it reports:
34
34
  """
35
35
  $stdin -- 3 warnings:
36
- Turn has no descriptive comment (Irresponsible Module)
37
- Turn has the variable name '@x' (Uncommunicative Name)
38
- Turn#y has the name 'y' (Uncommunicative Name)
36
+ Turn has no descriptive comment (IrresponsibleModule)
37
+ Turn has the variable name '@x' (UncommunicativeName)
38
+ Turn#y has the name 'y' (UncommunicativeName)
39
39
 
40
40
  """
41
41
 
@@ -2,7 +2,7 @@
2
2
  # Reek's core functionality
3
3
  #
4
4
  module Reek
5
- VERSION = '1.2.7.2'
5
+ VERSION = '1.2.7.3'
6
6
  end
7
7
 
8
8
  require File.join(File.dirname(File.expand_path(__FILE__)), 'reek', 'examiner')
@@ -18,7 +18,6 @@ module Reek
18
18
  @argv = argv
19
19
  @parser = OptionParser.new
20
20
  @report_class = VerboseReport
21
- @collection_strategy = ActiveSmellsOnly
22
21
  @command_class = ReekCommand
23
22
  set_options
24
23
  end
@@ -36,7 +35,6 @@ module Reek
36
35
  # reek -v|--version Output the tool's version number
37
36
  #
38
37
  # reek [options] files List the smells in the given files
39
- # -a|--[no-]show-all Report masked smells
40
38
  # -q|-[no-]quiet Only list files that have smells
41
39
  # files Names of files or dirs to be checked
42
40
  #
@@ -46,7 +44,7 @@ Usage: #{progname} [options] [files]
46
44
  Examples:
47
45
 
48
46
  #{progname} lib/*.rb
49
- #{progname} -q -a lib
47
+ #{progname} -q lib
50
48
  cat my_class.rb | #{progname}
51
49
 
52
50
  See http://wiki.github.com/kevinrutherford/reek for detailed help.
@@ -65,9 +63,6 @@ EOB
65
63
  end
66
64
 
67
65
  @parser.separator "\nReport formatting:"
68
- @parser.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
69
- @collection_strategy = opt ? ActiveAndMaskedSmells : ActiveSmellsOnly
70
- end
71
66
  @parser.on("-q", "--[no-]quiet", "Suppress headings for smell-free source files") do |opt|
72
67
  @report_class = opt ? QuietReport : VerboseReport
73
68
  end
@@ -90,7 +85,7 @@ EOB
90
85
  YamlCommand.create(sources)
91
86
  else
92
87
  sources = get_sources
93
- ReekCommand.create(sources, @report_class, @collection_strategy)
88
+ ReekCommand.create(sources, @report_class)
94
89
  end
95
90
  end
96
91
 
@@ -8,19 +8,19 @@ module Reek
8
8
  # text report format.
9
9
  #
10
10
  class ReekCommand
11
- def self.create(sources, report_class, strategy = ActiveSmellsOnly)
12
- examiners = sources.map { |src| Examiner.new(src, strategy.new) }
13
- new(examiners, report_class)
11
+ def self.create(sources, report_class)
12
+ new(report_class, sources)
14
13
  end
15
14
 
16
- def initialize(examiners, report_class)
17
- @examiners = examiners
15
+ def initialize(report_class, sources)
16
+ @sources = sources
18
17
  @report_class = report_class
19
18
  end
20
19
 
21
20
  def execute(view)
22
21
  had_smells = false
23
- @examiners.each do |examiner|
22
+ @sources.each do |source|
23
+ examiner = Examiner.new(source)
24
24
  rpt = @report_class.new(examiner)
25
25
  had_smells ||= examiner.smelly?
26
26
  view.output(rpt.report)
@@ -1,67 +1,39 @@
1
1
  module Reek
2
2
  module Cli
3
3
 
4
- #
5
- # A section of a text report; has a heading that identifies the source
6
- # and summarises the smell counts, and a body listing details of all
7
- # smells found.
8
- #
9
- class ReportSection
10
-
11
- SMELL_FORMAT = '%m%c %w (%s)'
12
-
13
- def initialize(examiner)
14
- @examiner = examiner
15
- end
16
-
17
- # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
18
- # this report, with a heading.
19
- def verbose_report
20
- result = header
21
- result += ":\n#{smell_list}" if should_report
22
- result += "\n"
4
+ module ReportFormatter
5
+ def header(desc, count)
6
+ result = "#{desc} -- #{count} warning"
7
+ result += 's' unless count == 1
23
8
  result
24
9
  end
25
10
 
26
- def quiet_report
27
- return '' unless should_report
28
- # SMELL: duplicate knowledge of the header layout
29
- "#{header}:\n#{smell_list}\n"
30
- end
31
-
32
- def header
33
- "#{@examiner.description} -- #{visible_header}"
34
- end
35
-
36
- # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
37
- # this report.
38
- def smell_list
39
- @examiner.smells.map {|smell| " #{smell.report(SMELL_FORMAT)}"}.join("\n")
11
+ def format_list(warnings)
12
+ warnings.map do |warning|
13
+ " #{warning.context} #{warning.message} (#{warning.smell_class})"
14
+ end.join("\n")
40
15
  end
41
16
 
42
- private
43
-
44
- def should_report
45
- @examiner.num_smells > 0
46
- end
47
-
48
- def visible_header
49
- num_smells = @examiner.smells.length
50
- result = "#{num_smells} warning"
51
- result += 's' unless num_smells == 1
52
- result
53
- end
17
+ module_function :format_list
54
18
  end
55
19
 
56
20
  #
57
21
  # A report that lists every source, including those that have no smells.
58
22
  #
59
23
  class VerboseReport
24
+
25
+ include ReportFormatter
26
+
60
27
  def initialize(examiner)
61
- @reporter = ReportSection.new(examiner)
28
+ @examiner = examiner
62
29
  end
30
+
63
31
  def report
64
- @reporter.verbose_report
32
+ warnings = @examiner.smells
33
+ warning_count = warnings.length
34
+ result = header(@examiner.description, warning_count)
35
+ result += ":\n#{format_list(warnings)}" if warning_count > 0
36
+ result + "\n"
65
37
  end
66
38
  end
67
39
 
@@ -69,11 +41,17 @@ module Reek
69
41
  # A report that lists a section for each source that has smells.
70
42
  #
71
43
  class QuietReport
44
+
45
+ include ReportFormatter
46
+
72
47
  def initialize(examiner)
73
- @reporter = ReportSection.new(examiner)
48
+ @warnings = examiner.smells
49
+ @smell_count = @warnings.length
50
+ @desc = examiner.description
74
51
  end
52
+
75
53
  def report
76
- @reporter.quiet_report
54
+ @smell_count > 0 ? "#{header(@desc, @smell_count)}:\n#{format_list(@warnings)}\n" : ''
77
55
  end
78
56
  end
79
57
  end
@@ -1,5 +1,4 @@
1
1
  require 'sexp'
2
- require File.join(File.dirname(File.expand_path(__FILE__)), 'class_context')
3
2
  require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
4
3
  require File.join(File.dirname(File.expand_path(__FILE__)), 'module_context')
5
4
  require File.join(File.dirname(File.expand_path(__FILE__)), 'stop_context')
@@ -26,24 +25,21 @@ module Reek
26
25
  end
27
26
 
28
27
  def process_default(exp)
29
- exp[0..-1].each { |sub| process(sub) if Array === sub }
28
+ exp.each { |sub| process(sub) if Array === sub }
30
29
  end
31
30
 
32
- def do_module_or_class(exp, context_class)
33
- scope = context_class.create(@element, exp)
31
+ def process_module(exp)
32
+ name = Source::SexpFormatter.format(exp[1])
33
+ scope = ModuleContext.new(@element, name, exp)
34
34
  push(scope) do
35
- process_default(exp) unless @element.is_struct?
35
+ process_default(exp) unless exp.superclass == [:const, :Struct]
36
36
  check_smells(exp[0])
37
37
  end
38
38
  scope
39
39
  end
40
40
 
41
- def process_module(exp)
42
- do_module_or_class(exp, ModuleContext)
43
- end
44
-
45
41
  def process_class(exp)
46
- do_module_or_class(exp, ClassContext)
42
+ process_module(exp)
47
43
  end
48
44
 
49
45
  def process_defn(exp)
@@ -122,11 +118,6 @@ module Reek
122
118
  process_iasgn(exp)
123
119
  end
124
120
 
125
- def process_lasgn(exp)
126
- @element.record_local_variable(exp[1])
127
- process_default(exp)
128
- end
129
-
130
121
  def process_iasgn(exp)
131
122
  @element.record_use_of_self
132
123
  process_default(exp)
@@ -1,3 +1,4 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'code_context')
1
2
  require File.join(File.dirname(File.expand_path(__FILE__)), 'object_refs')
2
3
 
3
4
  module Reek
@@ -53,7 +54,6 @@ module Reek
53
54
  @parameters.extend(MethodParameters)
54
55
  @num_statements = 0
55
56
  @refs = ObjectRefs.new
56
- @outer.record_method(self) # SMELL: these could be found by tree walking
57
57
  end
58
58
 
59
59
  def count_statements(num)
@@ -11,27 +11,9 @@ module Reek
11
11
  #
12
12
  class ModuleContext < CodeContext
13
13
 
14
- class << self
15
- def create(outer, exp)
16
- res = Source::SexpFormatter.format(exp[1])
17
- new(outer, res, exp)
18
- end
19
-
20
- def from_s(src)
21
- source = src.to_reek_source
22
- sniffer = Sniffer.new(source)
23
- CodeParser.new(sniffer).do_module_or_class(source.syntax_tree, self)
24
- end
25
- end
26
-
27
14
  def initialize(outer, name, exp)
28
15
  super(outer, exp)
29
16
  @name = name
30
- @parsed_methods = []
31
- end
32
-
33
- def record_method(meth)
34
- @parsed_methods << meth
35
17
  end
36
18
  end
37
19
  end
@@ -22,10 +22,6 @@ module Reek
22
22
  @options.adopt!(options)
23
23
  end
24
24
 
25
- def deep_copy
26
- @options.deep_copy # SMELL: Open Secret -- returns a Hash
27
- end
28
-
29
25
  #
30
26
  # Is this smell detector active?
31
27
  #--
@@ -1,4 +1,3 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'detector_stack')
2
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'code_parser')
3
2
  require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smells')
4
3
  require 'yaml'
@@ -65,36 +64,29 @@ module Reek
65
64
  ]
66
65
  end
67
66
 
68
- def initialize(src, config_strategy = ActiveSmellsOnly.new) # SMELL: open secret -- need a Strategy
69
- @config_strategy = config_strategy
70
- @already_checked_for_smells = false
67
+ def initialize(src)
71
68
  @typed_detectors = nil
72
69
  @detectors = Hash.new
73
70
  Sniffer.smell_classes.each do |klass|
74
- @detectors[klass] = DetectorStack.new(klass.new(src.desc))
71
+ @detectors[klass] = klass.new(src.desc)
75
72
  end
76
73
  @source = src
77
74
  src.configure(self)
78
75
  end
79
76
 
80
- def check_for_smells
81
- return if @already_checked_for_smells
82
- CodeParser.new(self).process(@source.syntax_tree)
83
- @already_checked_for_smells = true
84
- end
85
-
86
77
  def configure(klass, config)
87
- @config_strategy.configure(@detectors[klass], config)
78
+ @detectors[klass].configure_with(config)
88
79
  end
89
80
 
90
- def report_on(report)
91
- check_for_smells
92
- @detectors.each_value { |stack| stack.report_on(report) }
81
+ def report_on(listener)
82
+ CodeParser.new(self).process(@source.syntax_tree)
83
+ @detectors.each_value { |detector| detector.report_on(listener) }
93
84
  end
94
85
 
95
- def examine(scope, type)
96
- listeners = smell_listeners[type]
97
- listeners.each {|smell| smell.examine(scope) } if listeners
86
+ def examine(scope, node_type)
87
+ smell_listeners[node_type].each do |detector|
88
+ detector.examine(scope)
89
+ end
98
90
  end
99
91
 
100
92
  private
@@ -102,7 +94,7 @@ module Reek
102
94
  def smell_listeners()
103
95
  unless @typed_detectors
104
96
  @typed_detectors = Hash.new {|hash,key| hash[key] = [] }
105
- @detectors.each_value { |stack| stack.listen_to(@typed_detectors) }
97
+ @detectors.each_value { |detector| detector.register(@typed_detectors) }
106
98
  end
107
99
  @typed_detectors
108
100
  end
@@ -0,0 +1,27 @@
1
+ require 'set'
2
+
3
+ module Reek
4
+ module Core
5
+
6
+ #
7
+ # Collects and sorts smells warnings.
8
+ #
9
+ class WarningCollector
10
+ def initialize
11
+ @warnings = Set.new
12
+ end
13
+
14
+ def found_smell(warning)
15
+ @warnings.add(warning)
16
+ end
17
+
18
+ def warnings
19
+ @warnings.to_a.sort do |first,second|
20
+ first_sig = [first.context, first.message, first.smell_class]
21
+ second_sig = [second.context, second.message, second.smell_class]
22
+ first_sig <=> second_sig
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,29 +1,9 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'masking_collection')
2
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'sniffer')
2
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'warning_collector')
3
3
  require File.join(File.dirname(File.expand_path(__FILE__)), 'source')
4
4
 
5
5
  module Reek
6
6
 
7
- class ActiveSmellsOnly
8
- def configure(detectors, config)
9
- detectors.adopt(config)
10
- end
11
-
12
- def smells_in(sources)
13
- Core::MaskingCollection.new.collect_from(sources, self).all_active_items.to_a
14
- end
15
- end
16
-
17
- class ActiveAndMaskedSmells
18
- def configure(detectors, config)
19
- detectors.push(config)
20
- end
21
-
22
- def smells_in(sources)
23
- Core::MaskingCollection.new.collect_from(sources, self).all_items
24
- end
25
- end
26
-
27
7
  #
28
8
  # Finds the active code smells in Ruby source code.
29
9
  #
@@ -47,13 +27,7 @@ module Reek
47
27
  # and if it is an Array, it is assumed to be a list of file paths,
48
28
  # each of which is opened and parsed for source code.
49
29
  #
50
- # @param [#smells_in]
51
- # The +collector+ will be asked to examine the sources and report
52
- # an array of SmellWarning objects. The default collector is an
53
- # instance of ActiveSmellsOnly, which completely ignores all smells
54
- # that have been masked by configuration options.
55
- #
56
- def initialize(source, collector = ActiveSmellsOnly.new)
30
+ def initialize(source)
57
31
  sources = case source
58
32
  when Array
59
33
  @description = 'dir'
@@ -66,12 +40,13 @@ module Reek
66
40
  @description = src.desc
67
41
  [src]
68
42
  end
69
- @smells = collector.smells_in(sources)
43
+ collector = Core::WarningCollector.new
44
+ sources.each { |src| Core::Sniffer.new(src).report_on(collector) }
45
+ @smells = collector.warnings
70
46
  end
71
47
 
72
48
  #
73
- # Returns an Array of SmellWarning objects, one for each active smell
74
- # in the source.
49
+ # List the smells found in the source.
75
50
  #
76
51
  # @return [Array<SmellWarning>]
77
52
  #
@@ -79,18 +54,51 @@ module Reek
79
54
  @smells
80
55
  end
81
56
 
57
+ #
58
+ # True if and only if there are code smells in the source.
59
+ #
60
+ def smelly?
61
+ not @smells.empty?
62
+ end
63
+
64
+ #
65
+ # Returns an Array of SmellWarning objects, one for each non-masked smell
66
+ # in the source.
67
+ #
68
+ # @deprecated Use #smells instead.
69
+ #
70
+ def all_active_smells
71
+ @smells
72
+ end
73
+
74
+ #
75
+ # Returns an Array of SmellWarning objects, one for each smell
76
+ # in the source; includes active smells and masked smells.
77
+ #
78
+ # @return [Array<SmellWarning>]
79
+ #
80
+ # @deprecated Use #smells instead.
81
+ #
82
+ def all_smells
83
+ @smells
84
+ end
85
+
82
86
  #
83
87
  # Returns the number of non-masked smells in the source.
84
88
  #
85
- def num_smells
89
+ # @deprecated Use #smells instead.
90
+ #
91
+ def num_active_smells
86
92
  @smells.length
87
93
  end
88
94
 
89
95
  #
90
- # True if and only if there are non-masked code smells in the given source.
96
+ # Returns the number of masked smells in the source.
91
97
  #
92
- def smelly?
93
- not @smells.empty?
98
+ # @deprecated Masked smells are no longer reported; this method always returns 0.
99
+ #
100
+ def num_masked_smells
101
+ 0
94
102
  end
95
103
  end
96
104
  end