reek 1.2.3 → 1.2.4

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