reek 1.2.7.2 → 1.2.7.3

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