reek 1.2.3 → 1.2.4

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 (47) hide show
  1. data/History.txt +9 -1
  2. data/features/options.feature +1 -9
  3. data/features/samples.feature +7 -2
  4. data/features/step_definitions/reek_steps.rb +3 -3
  5. data/lib/reek.rb +1 -1
  6. data/lib/reek/adapters/application.rb +22 -27
  7. data/lib/reek/adapters/command_line.rb +41 -42
  8. data/lib/reek/adapters/report.rb +30 -23
  9. data/lib/reek/adapters/spec.rb +1 -1
  10. data/lib/reek/code_context.rb +6 -2
  11. data/lib/reek/code_parser.rb +3 -7
  12. data/lib/reek/detector_stack.rb +2 -4
  13. data/lib/reek/help_command.rb +14 -0
  14. data/lib/reek/masking_collection.rb +33 -0
  15. data/lib/reek/method_context.rb +18 -6
  16. data/lib/reek/module_context.rb +0 -13
  17. data/lib/reek/reek_command.rb +28 -0
  18. data/lib/reek/singleton_method_context.rb +1 -1
  19. data/lib/reek/smell_warning.rb +5 -3
  20. data/lib/reek/smells/attribute.rb +17 -1
  21. data/lib/reek/smells/class_variable.rb +1 -1
  22. data/lib/reek/smells/control_couple.rb +13 -10
  23. data/lib/reek/smells/large_class.rb +1 -1
  24. data/lib/reek/smells/long_method.rb +0 -2
  25. data/lib/reek/smells/simulated_polymorphism.rb +2 -2
  26. data/lib/reek/sniffer.rb +1 -3
  27. data/lib/reek/tree_dresser.rb +35 -23
  28. data/lib/reek/version_command.rb +14 -0
  29. data/reek.gemspec +3 -3
  30. data/spec/reek/adapters/report_spec.rb +8 -8
  31. data/spec/reek/adapters/should_reek_of_spec.rb +1 -1
  32. data/spec/reek/adapters/should_reek_only_of_spec.rb +2 -2
  33. data/spec/reek/adapters/should_reek_spec.rb +3 -3
  34. data/spec/reek/code_context_spec.rb +11 -11
  35. data/spec/reek/code_parser_spec.rb +0 -88
  36. data/spec/reek/help_command_spec.rb +24 -0
  37. data/spec/reek/masking_collection_spec.rb +236 -0
  38. data/spec/reek/method_context_spec.rb +43 -1
  39. data/spec/reek/reek_command_spec.rb +45 -0
  40. data/spec/reek/smell_warning_spec.rb +12 -4
  41. data/spec/reek/smells/attribute_spec.rb +79 -7
  42. data/spec/reek/smells/control_couple_spec.rb +40 -11
  43. data/spec/reek/smells/long_parameter_list_spec.rb +1 -1
  44. data/spec/reek/smells/smell_detector_spec.rb +0 -17
  45. data/spec/reek/tree_dresser_spec.rb +20 -0
  46. data/spec/reek/version_command_spec.rb +29 -0
  47. metadata +11 -2
data/History.txt CHANGED
@@ -1,4 +1,12 @@
1
- == 1.2.2 (under development in github)
1
+ == 1.2.4 (2009-11-17)
2
+
3
+ === Major Changes
4
+ * The -f, -c and -s options for formatting smell warnings have been removed
5
+
6
+ === Minor Changes
7
+ * ControlCouple now warns about parameters defaulted to true/false
8
+
9
+ == 1.2.3 (2009-11-2)
2
10
 
3
11
  === Minor Changes
4
12
  * New smell: Attribute (disabled by default)
@@ -9,11 +9,6 @@ Feature: Reek can be controlled using command-line options
9
9
  Then the exit status indicates an error
10
10
  And it reports the error "Error: invalid option: --no-such-option"
11
11
 
12
- Scenario: return non-zero status on missing argument
13
- When I run reek -f
14
- Then the exit status indicates an error
15
- And it reports the error "Error: missing argument: -f"
16
-
17
12
  Scenario: display the current version number
18
13
  When I run reek --version
19
14
  Then it succeeds
@@ -41,9 +36,6 @@ Feature: Reek can be controlled using command-line options
41
36
 
42
37
  Report formatting:
43
38
  -a, --[no-]show-all Show all smells, including those masked by config settings
44
- -q, --quiet Suppress headings for smell-free source files
45
- -f, --format FORMAT Specify the format of smell warnings
46
- -c, --context-first Sort by context; sets the format string to "%m%c %w (%s)"
47
- -s, --smell-first Sort by smell; sets the format string to "%m[%s] %c %w"
39
+ -q, --[no-]quiet Suppress headings for smell-free source files
48
40
 
49
41
  """
@@ -4,6 +4,7 @@ Feature: Basic smell detection
4
4
  As a developer
5
5
  I want to detect the smels in my Ruby code
6
6
 
7
+ @inline
7
8
  Scenario: Correct smells from inline.rb
8
9
  When I run reek spec/samples/inline.rb
9
10
  Then the exit status indicates smells
@@ -57,7 +58,7 @@ Feature: Basic smell detection
57
58
  Then the exit status indicates smells
58
59
  And it reports:
59
60
  """
60
- spec/samples/optparse.rb -- 121 warnings:
61
+ spec/samples/optparse.rb -- 124 warnings:
61
62
  OptionParser has at least 42 methods (Large Class)
62
63
  OptionParser tests ((argv.size == 1) and Array.===(argv[0])) at least 3 times (Simulated Polymorphism)
63
64
  OptionParser tests a at least 7 times (Simulated Polymorphism)
@@ -70,6 +71,7 @@ Feature: Basic smell detection
70
71
  OptionParser#Completion::complete has approx 22 statements (Long Method)
71
72
  OptionParser#Completion::complete has the variable name 'k' (Uncommunicative Name)
72
73
  OptionParser#Completion::complete has the variable name 'v' (Uncommunicative Name)
74
+ OptionParser#Completion::complete is controlled by argument icase (Control Couple)
73
75
  OptionParser#Completion::complete refers to candidates more than self (Feature Envy)
74
76
  OptionParser#Completion::complete/block has the variable name 'k' (Uncommunicative Name)
75
77
  OptionParser#Completion::complete/block has the variable name 'v' (Uncommunicative Name)
@@ -78,6 +80,7 @@ Feature: Basic smell detection
78
80
  OptionParser#List#accept refers to pat more than self (Feature Envy)
79
81
  OptionParser#List#add_banner refers to opt more than self (Feature Envy)
80
82
  OptionParser#List#complete has 4 parameters (Long Parameter List)
83
+ OptionParser#List#complete is controlled by argument icase (Control Couple)
81
84
  OptionParser#List#reject has the variable name 't' (Uncommunicative Name)
82
85
  OptionParser#List#summarize refers to opt more than self (Feature Envy)
83
86
  OptionParser#List#update has 5 parameters (Long Parameter List)
@@ -127,6 +130,7 @@ Feature: Basic smell detection
127
130
  OptionParser#block/block is controlled by argument pkg (Control Couple)
128
131
  OptionParser#block/block is nested (Nested Iterators)
129
132
  OptionParser#complete has 4 parameters (Long Parameter List)
133
+ OptionParser#complete is controlled by argument icase (Control Couple)
130
134
  OptionParser#complete/block/block is nested (Nested Iterators)
131
135
  OptionParser#getopts calls result[opt] = false twice (Duplication)
132
136
  OptionParser#getopts has approx 17 statements (Long Method)
@@ -187,7 +191,7 @@ Feature: Basic smell detection
187
191
  Then the exit status indicates smells
188
192
  And it reports:
189
193
  """
190
- spec/samples/redcloth.rb -- 95 warnings:
194
+ spec/samples/redcloth.rb -- 96 warnings:
191
195
  RedCloth has at least 44 methods (Large Class)
192
196
  RedCloth takes parameters [atts, cite, content, tag] to 3 methods (Data Clump)
193
197
  RedCloth tests atts at least 6 times (Simulated Polymorphism)
@@ -217,6 +221,7 @@ Feature: Basic smell detection
217
221
  RedCloth#block_textile_table/block/block is nested (Nested Iterators)
218
222
  RedCloth#block_textile_table/block/block/block is nested (Nested Iterators)
219
223
  RedCloth#blocks has approx 18 statements (Long Method)
224
+ RedCloth#blocks is controlled by argument deep_code (Control Couple)
220
225
  RedCloth#blocks/block is controlled by argument deep_code (Control Couple)
221
226
  RedCloth#blocks/block/block is nested (Nested Iterators)
222
227
  RedCloth#check_refs is controlled by argument text (Control Couple)
@@ -15,15 +15,15 @@ Then /^stdout equals "([^\"]*)"$/ do |report|
15
15
  end
16
16
 
17
17
  Then /^it succeeds$/ do
18
- @last_exit_status.should == Reek::EXIT_STATUS[:success]
18
+ @last_exit_status.should == Reek::Application::STATUS_SUCCESS
19
19
  end
20
20
 
21
21
  Then /^the exit status indicates an error$/ do
22
- @last_exit_status.should == Reek::EXIT_STATUS[:error]
22
+ @last_exit_status.should == Reek::Application::STATUS_ERROR
23
23
  end
24
24
 
25
25
  Then /^the exit status indicates smells$/ do
26
- @last_exit_status.should == Reek::EXIT_STATUS[:smells]
26
+ @last_exit_status.should == Reek::Application::STATUS_SMELLS
27
27
  end
28
28
 
29
29
  Then /^it reports:$/ do |report|
data/lib/reek.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
3
  module Reek # :doc:
4
- VERSION = '1.2.3'
4
+ VERSION = '1.2.4'
5
5
  end
@@ -4,48 +4,43 @@ require 'reek/adapters/core_extras'
4
4
 
5
5
  module Reek
6
6
 
7
- EXIT_STATUS = {
8
- :success => 0,
9
- :error => 1,
10
- :smells => 2
11
- }
12
-
13
7
  #
14
8
  # Represents an instance of a Reek application.
15
9
  # This is the entry point for all invocations of Reek from the
16
10
  # command line.
17
11
  #
18
12
  class Application
19
- def initialize(argv)
20
- @options = Options.new(argv)
21
- end
22
13
 
23
- def examine_sources
24
- # SMELL: Greedy Method
25
- # Options.parse executes the -v and -h commands and throws a SystemExit
26
- args = @options.parse
27
- if args.length > 0
28
- @sniffer = args.sniff
29
- else
30
- @sniffer = Reek::Sniffer.new($stdin.to_reek_source('$stdin'))
31
- end
32
- end
14
+ STATUS_SUCCESS = 0
15
+ STATUS_ERROR = 1
16
+ STATUS_SMELLS = 2
33
17
 
34
- def reek
35
- examine_sources
36
- puts @options.create_report(@sniffer.sniffers).report
37
- return EXIT_STATUS[@sniffer.smelly? ? :smells : :success]
18
+ def initialize(argv)
19
+ @options = Options.new(argv)
20
+ @status = STATUS_SUCCESS
38
21
  end
39
22
 
40
23
  def execute
41
24
  begin
42
- return reek
43
- rescue SystemExit => ex
44
- return ex.status
25
+ cmd = @options.parse
26
+ cmd.execute(self)
45
27
  rescue Exception => error
46
28
  $stderr.puts "Error: #{error}"
47
- return EXIT_STATUS[:error]
29
+ @status = STATUS_ERROR
48
30
  end
31
+ return @status
32
+ end
33
+
34
+ def output(text)
35
+ puts text
36
+ end
37
+
38
+ def report_success
39
+ @status = STATUS_SUCCESS
40
+ end
41
+
42
+ def report_smells
43
+ @status = STATUS_SMELLS
49
44
  end
50
45
  end
51
46
  end
@@ -1,78 +1,77 @@
1
1
  require 'optparse'
2
2
  require 'reek'
3
3
  require 'reek/adapters/report'
4
+ require 'reek/help_command'
5
+ require 'reek/reek_command'
6
+ require 'reek/version_command'
4
7
 
5
8
  module Reek
6
-
7
- # SMELL: Greedy Module
8
- # This creates the command-line parser AND invokes it. And for the
9
- # -v and -h options it also executes them. And it holds the config
10
- # options for the rest of the application.
11
- class Options
12
9
 
13
- CTX_SORT = '%m%c %w (%s)'
14
- SMELL_SORT = '%m[%s] %c %w'
10
+ class Options
15
11
 
16
12
  def initialize(argv)
17
13
  @argv = argv
18
14
  @parser = OptionParser.new
19
- @quiet = false
15
+ @report_class = VerboseReport
20
16
  @show_all = false
21
- @format = CTX_SORT
17
+ @command = nil
22
18
  set_options
23
19
  end
24
20
 
25
- def parse
26
- @parser.parse!(@argv)
27
- @argv
28
- end
29
-
30
- def set_options
31
- @parser.banner = <<EOB
32
- Usage: #{@parser.program_name} [options] [files]
21
+ def banner
22
+ progname = @parser.program_name
23
+ # SMELL:
24
+ # The following banner isn't really correct. Help, Version and Reek
25
+ # are really sub-commands (in the git/svn sense) and so the usage
26
+ # banner should show three different command-lines. The other
27
+ # options are all flags for the Reek sub-command.
28
+ #
29
+ # reek -h|--help Display a help message
30
+ #
31
+ # reek -v|--version Output the tool's version number
32
+ #
33
+ # reek [options] files List the smells in the given files
34
+ # -a|--[no-]show-all Report masked smells
35
+ # -q|-[no-]quiet Only list files that have smells
36
+ # files Names of files or dirs to be checked
37
+ #
38
+ return <<EOB
39
+ Usage: #{progname} [options] [files]
33
40
 
34
41
  Examples:
35
42
 
36
- #{@parser.program_name} lib/*.rb
37
- #{@parser.program_name} -q -a lib
38
- cat my_class.rb | #{@parser.program_name}
43
+ #{progname} lib/*.rb
44
+ #{progname} -q -a lib
45
+ cat my_class.rb | #{progname}
39
46
 
40
47
  See http://wiki.github.com/kevinrutherford/reek for detailed help.
41
48
 
42
49
  EOB
50
+ end
43
51
 
44
- @parser.separator "Common options:"
52
+ def parse
53
+ @parser.parse!(@argv)
54
+ @command ||= ReekCommand.new(@argv, @report_class, @show_all)
55
+ end
45
56
 
57
+ def set_options
58
+ @parser.banner = banner
59
+ @parser.separator "Common options:"
46
60
  @parser.on("-h", "--help", "Show this message") do
47
- puts @parser
48
- exit(EXIT_STATUS[:success])
61
+ @command = HelpCommand.new(@parser)
49
62
  end
50
63
  @parser.on("-v", "--version", "Show version") do
51
- puts "#{@parser.program_name} #{Reek::VERSION}"
52
- exit(EXIT_STATUS[:success])
64
+ @command = VersionCommand.new(@parser.program_name)
53
65
  end
54
66
 
55
67
  @parser.separator "\nReport formatting:"
56
-
57
68
  @parser.on("-a", "--[no-]show-all", "Show all smells, including those masked by config settings") do |opt|
58
69
  @show_all = opt
59
70
  end
60
- @parser.on("-q", "--quiet", "Suppress headings for smell-free source files") do
61
- @quiet = true
62
- end
63
- @parser.on('-f', "--format FORMAT", 'Specify the format of smell warnings') do |arg|
64
- @format = arg unless arg.nil?
65
- end
66
- @parser.on('-c', '--context-first', "Sort by context; sets the format string to \"#{CTX_SORT}\"") do
67
- @format = CTX_SORT
68
- end
69
- @parser.on('-s', '--smell-first', "Sort by smell; sets the format string to \"#{SMELL_SORT}\"") do
70
- @format = SMELL_SORT
71
+ @parser.on("-q", "--[no-]quiet", "Suppress headings for smell-free source files") do |opt|
72
+ @report_class = opt ? QuietReport : VerboseReport
71
73
  end
72
74
  end
73
-
74
- def create_report(sniffers)
75
- @quiet ? QuietReport.new(sniffers, @format, @show_all) : FullReport.new(sniffers, @format, @show_all)
76
- end
77
75
  end
76
+
78
77
  end
@@ -1,34 +1,35 @@
1
1
  require 'set'
2
2
  require 'reek/adapters/command_line' # SMELL: Global Variable
3
+ require 'reek/masking_collection'
3
4
 
4
5
  module Reek
5
6
  class ReportSection
6
7
 
7
- def initialize(sniffer, display_masked_warnings, format) # :nodoc:
8
- @format = format
9
- @masked_warnings = SortedSet.new
10
- @warnings = SortedSet.new
8
+ SMELL_FORMAT = '%m%c %w (%s)'
9
+
10
+ def initialize(sniffer, display_masked_warnings)
11
+ @cwarnings = MaskingCollection.new
11
12
  @desc = sniffer.desc
12
- @display_masked_warnings = display_masked_warnings
13
+ @display_masked_warnings = display_masked_warnings # SMELL: Control Couple
13
14
  sniffer.report_on(self)
14
15
  end
15
16
 
16
- def <<(smell) # :nodoc:
17
- @warnings << smell
17
+ def found_smell(warning)
18
+ @cwarnings.add(warning)
18
19
  true
19
20
  end
20
21
 
21
- def record_masked_smell(smell)
22
- @masked_warnings << smell
22
+ def found_masked_smell(warning)
23
+ @cwarnings.add_masked(warning)
23
24
  end
24
25
 
25
26
  def num_masked_smells # SMELL: getter
26
- @masked_warnings.length
27
+ @cwarnings.num_masked_items
27
28
  end
28
29
 
29
30
  # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
30
31
  # this report, with a heading.
31
- def full_report
32
+ def verbose_report
32
33
  result = header
33
34
  result += ":\n#{smell_list}" if should_report
34
35
  result += "\n"
@@ -37,56 +38,62 @@ module Reek
37
38
 
38
39
  def quiet_report
39
40
  return '' unless should_report
41
+ # SMELL: duplicate knowledge of the header layout
40
42
  "#{header}:\n#{smell_list}\n"
41
43
  end
42
44
 
43
45
  def header
44
- @all_warnings = SortedSet.new(@warnings) # SMELL: Temporary Field
45
- @all_warnings.merge(@masked_warnings)
46
46
  "#{@desc} -- #{visible_header}#{masked_header}"
47
47
  end
48
48
 
49
49
  # Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
50
50
  # this report.
51
51
  def smell_list
52
- smells = @display_masked_warnings ? @all_warnings : @warnings
53
- smells.map {|smell| " #{smell.report(@format)}"}.join("\n")
52
+ result = []
53
+ if @display_masked_warnings
54
+ @cwarnings.each_item {|smell| result << " #{smell.report(SMELL_FORMAT)}"}
55
+ else
56
+ @cwarnings.each_visible_item {|smell| result << " #{smell.report(SMELL_FORMAT)}"}
57
+ end
58
+ result.join("\n")
54
59
  end
55
60
 
56
61
  private
57
62
 
58
63
  def should_report
59
- @warnings.length > 0 or (@display_masked_warnings and @masked_warnings.length > 0)
64
+ @cwarnings.num_visible_items > 0 or (@display_masked_warnings and @cwarnings.num_masked_items > 0)
60
65
  end
61
66
 
62
67
  def visible_header
63
- num_smells = @warnings.length
68
+ num_smells = @cwarnings.num_visible_items
64
69
  result = "#{num_smells} warning"
65
70
  result += 's' unless num_smells == 1
66
71
  result
67
72
  end
68
73
 
69
74
  def masked_header
70
- num_masked_warnings = @all_warnings.length - @warnings.length
75
+ num_masked_warnings = @cwarnings.num_masked_items
71
76
  num_masked_warnings == 0 ? '' : " (+#{num_masked_warnings} masked)"
72
77
  end
73
78
  end
74
79
 
75
80
  class Report
76
- def initialize(sniffers, format, display_masked_warnings = false)
77
- @partials = Array(sniffers).map {|sn| ReportSection.new(sn, display_masked_warnings, format)}
81
+ def initialize(sniffers, display_masked_warnings = false)
82
+ @partials = Array(sniffers).map {|sn| ReportSection.new(sn, display_masked_warnings)}
78
83
  end
79
84
  end
80
85
 
81
- class FullReport < Report
86
+ class VerboseReport < Report
87
+ # SMELL: Implementation Inheritance
82
88
  def report
83
- @partials.map { |rpt| rpt.full_report }.join
89
+ @partials.map { |section| section.verbose_report }.join
84
90
  end
85
91
  end
86
92
 
87
93
  class QuietReport < Report
94
+ # SMELL: Implementation Inheritance
88
95
  def report
89
- @partials.map { |rpt| rpt.quiet_report }.join
96
+ @partials.map { |section| section.quiet_report }.join
90
97
  end
91
98
  end
92
99
  end
@@ -44,7 +44,7 @@ module Reek
44
44
  module Spec
45
45
  module ReekMatcher
46
46
  def create_reporter(sniffers)
47
- QuietReport.new(sniffers, '%c %w (%s)', false)
47
+ QuietReport.new(sniffers, false)
48
48
  end
49
49
  def report
50
50
  create_reporter(@sniffer.sniffers).report
@@ -15,7 +15,7 @@ module Reek
15
15
  #
16
16
  class CodeContext
17
17
 
18
- attr_reader :name
18
+ attr_reader :name, :exp
19
19
 
20
20
  def initialize(outer, exp)
21
21
  @outer = outer
@@ -23,7 +23,11 @@ module Reek
23
23
  @myself = nil
24
24
  end
25
25
 
26
- def each(type, ignoring, &blk)
26
+ def local_nodes(type, &blk)
27
+ each_node(type, [:class, :module], &blk)
28
+ end
29
+
30
+ def each_node(type, ignoring, &blk)
27
31
  if block_given?
28
32
  @exp.look_for(type, ignoring, &blk)
29
33
  else