reek 1.2.1 → 1.2.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 (68) hide show
  1. data/History.txt +10 -0
  2. data/Rakefile +0 -1
  3. data/config/defaults.reek +8 -6
  4. data/features/masking_smells.feature +9 -9
  5. data/features/options.feature +2 -2
  6. data/features/profile.feature +34 -0
  7. data/features/rake_task.feature +74 -0
  8. data/features/reports.feature +1 -1
  9. data/features/samples.feature +4 -4
  10. data/features/stdin.feature +1 -1
  11. data/features/step_definitions/reek_steps.rb +11 -7
  12. data/features/support/env.rb +26 -18
  13. data/lib/reek/adapters/application.rb +9 -2
  14. data/lib/reek/adapters/command_line.rb +2 -2
  15. data/lib/reek/adapters/core_extras.rb +0 -8
  16. data/lib/reek/adapters/source.rb +4 -1
  17. data/lib/reek/adapters/spec.rb +1 -4
  18. data/lib/reek/block_context.rb +14 -8
  19. data/lib/reek/class_context.rb +6 -55
  20. data/lib/reek/code_context.rb +10 -0
  21. data/lib/reek/code_parser.rb +26 -53
  22. data/lib/reek/configuration.rb +12 -6
  23. data/lib/reek/if_context.rb +2 -3
  24. data/lib/reek/method_context.rb +8 -12
  25. data/lib/reek/module_context.rb +35 -16
  26. data/lib/reek/name.rb +2 -0
  27. data/lib/reek/object_refs.rb +0 -3
  28. data/lib/reek/sexp_formatter.rb +0 -2
  29. data/lib/reek/smells/attribute.rb +48 -0
  30. data/lib/reek/smells/class_variable.rb +17 -4
  31. data/lib/reek/smells/control_couple.rb +3 -10
  32. data/lib/reek/smells/data_clump.rb +10 -10
  33. data/lib/reek/smells/feature_envy.rb +1 -8
  34. data/lib/reek/smells/large_class.rb +3 -3
  35. data/lib/reek/smells/simulated_polymorphism.rb +17 -3
  36. data/lib/reek/smells/smell_detector.rb +11 -2
  37. data/lib/reek/smells/utility_function.rb +1 -1
  38. data/lib/reek/sniffer.rb +2 -8
  39. data/lib/reek/stop_context.rb +1 -1
  40. data/lib/reek/tree_dresser.rb +74 -0
  41. data/lib/reek.rb +1 -1
  42. data/reek.gemspec +3 -3
  43. data/spec/reek/adapters/should_reek_of_spec.rb +7 -1
  44. data/spec/reek/block_context_spec.rb +6 -6
  45. data/spec/reek/class_context_spec.rb +2 -23
  46. data/spec/reek/code_context_spec.rb +149 -67
  47. data/spec/reek/code_parser_spec.rb +35 -51
  48. data/spec/reek/method_context_spec.rb +4 -4
  49. data/spec/reek/singleton_method_context_spec.rb +1 -1
  50. data/spec/reek/smells/attribute_spec.rb +26 -0
  51. data/spec/reek/smells/behaves_like_variable_detector.rb +39 -0
  52. data/spec/reek/smells/class_variable_spec.rb +77 -43
  53. data/spec/reek/smells/control_couple_spec.rb +1 -1
  54. data/spec/reek/smells/data_clump_spec.rb +31 -13
  55. data/spec/reek/smells/feature_envy_spec.rb +1 -1
  56. data/spec/reek/smells/large_class_spec.rb +32 -69
  57. data/spec/reek/smells/long_parameter_list_spec.rb +0 -12
  58. data/spec/reek/smells/simulated_polymorphism_spec.rb +66 -18
  59. data/spec/reek/smells/utility_function_spec.rb +0 -21
  60. data/spec/reek/sniffer_spec.rb +1 -0
  61. data/spec/samples/not_quite_masked/dirty.rb +2 -0
  62. data/spec/spec_helper.rb +1 -1
  63. data/tasks/reek.rake +1 -1
  64. data/tasks/test.rake +3 -4
  65. metadata +8 -5
  66. data/lib/reek/adapters/object_source.rb +0 -52
  67. data/lib/reek/exceptions.reek +0 -20
  68. data/spec/quality/reek_source_spec.rb +0 -15
@@ -0,0 +1,74 @@
1
+ module Reek
2
+
3
+ #
4
+ # Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
5
+ # syntax tree more easily.
6
+ #
7
+ module SexpNode
8
+ def children
9
+ find_all { |item| Sexp === item }
10
+ end
11
+
12
+ def is_language_node?
13
+ first.class == Symbol
14
+ end
15
+
16
+ def has_type?(type)
17
+ is_language_node? and first == type
18
+ end
19
+
20
+ #
21
+ # Carries out a depth-first traversal of this syntax tree, yielding
22
+ # every Sexp of type +target_type+. The traversal ignores any node
23
+ # whose type is listed in the Array +ignoring+.
24
+ #
25
+ def look_for(target_type, ignoring, &blk)
26
+ each do |elem|
27
+ if Sexp === elem then
28
+ elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
29
+ end
30
+ end
31
+ blk.call(self) if first == target_type
32
+ end
33
+ end
34
+
35
+ module CvarNode
36
+ def name
37
+ self[1]
38
+ end
39
+ end
40
+
41
+ module IfNode
42
+ def condition
43
+ self[1]
44
+ end
45
+ end
46
+
47
+ module CaseNode
48
+ def condition
49
+ self[1]
50
+ end
51
+ end
52
+
53
+ class TreeDresser
54
+ # SMELL: Duplication
55
+ # Put these into a new module and build the mapping automagically
56
+ # based on the node type
57
+ EXTENSIONS = {
58
+ :cvar => CvarNode,
59
+ :cvasgn => CvarNode,
60
+ :cvdecl => CvarNode,
61
+ :if => IfNode,
62
+ :case => CaseNode,
63
+ }
64
+
65
+ def dress(sexp)
66
+ sexp.extend(SexpNode)
67
+ if EXTENSIONS.has_key?(sexp[0])
68
+ sexp.extend(EXTENSIONS[sexp[0]])
69
+ end
70
+ sexp[0..-1].each { |sub| dress(sub) if Array === sub }
71
+ sexp
72
+ end
73
+ end
74
+ end
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.1'
4
+ VERSION = '1.2.3'
5
5
  end
data/reek.gemspec CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{reek}
5
- s.version = "1.2.1"
5
+ s.version = "1.2.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Kevin Rutherford"]
9
- s.date = %q{2009-10-03}
9
+ s.date = %q{2009-11-02}
10
10
  s.default_executable = %q{reek}
11
11
  s.description = %q{Code smell detector for Ruby}
12
12
  s.email = ["kevin@rutherford-software.com"]
13
13
  s.executables = ["reek"]
14
14
  s.extra_rdoc_files = ["History.txt", "License.txt", "README.rdoc"]
15
- s.files = ["History.txt", "License.txt", "README.rdoc", "Rakefile", "bin/reek", "config/defaults.reek", "features/masking_smells.feature", "features/options.feature", "features/reports.feature", "features/samples.feature", "features/stdin.feature", "features/step_definitions/reek_steps.rb", "features/support/env.rb", "lib/reek.rb", "lib/reek/adapters/application.rb", "lib/reek/adapters/command_line.rb", "lib/reek/adapters/config_file.rb", "lib/reek/adapters/core_extras.rb", "lib/reek/adapters/object_source.rb", "lib/reek/adapters/rake_task.rb", "lib/reek/adapters/report.rb", "lib/reek/adapters/source.rb", "lib/reek/adapters/spec.rb", "lib/reek/block_context.rb", "lib/reek/class_context.rb", "lib/reek/code_context.rb", "lib/reek/code_parser.rb", "lib/reek/configuration.rb", "lib/reek/detector_stack.rb", "lib/reek/exceptions.reek", "lib/reek/if_context.rb", "lib/reek/method_context.rb", "lib/reek/module_context.rb", "lib/reek/name.rb", "lib/reek/object_refs.rb", "lib/reek/sexp_formatter.rb", "lib/reek/singleton_method_context.rb", "lib/reek/smell_warning.rb", "lib/reek/smells/class_variable.rb", "lib/reek/smells/control_couple.rb", "lib/reek/smells/data_clump.rb", "lib/reek/smells/duplication.rb", "lib/reek/smells/feature_envy.rb", "lib/reek/smells/large_class.rb", "lib/reek/smells/long_method.rb", "lib/reek/smells/long_parameter_list.rb", "lib/reek/smells/long_yield_list.rb", "lib/reek/smells/nested_iterators.rb", "lib/reek/smells/simulated_polymorphism.rb", "lib/reek/smells/smell_detector.rb", "lib/reek/smells/uncommunicative_name.rb", "lib/reek/smells/utility_function.rb", "lib/reek/sniffer.rb", "lib/reek/stop_context.rb", "lib/reek/yield_call_context.rb", "reek.gemspec", "spec/quality/reek_source_spec.rb", "spec/reek/adapters/report_spec.rb", "spec/reek/adapters/should_reek_of_spec.rb", "spec/reek/adapters/should_reek_only_of_spec.rb", "spec/reek/adapters/should_reek_spec.rb", "spec/reek/block_context_spec.rb", "spec/reek/class_context_spec.rb", "spec/reek/code_context_spec.rb", "spec/reek/code_parser_spec.rb", "spec/reek/config_spec.rb", "spec/reek/configuration_spec.rb", "spec/reek/if_context_spec.rb", "spec/reek/method_context_spec.rb", "spec/reek/module_context_spec.rb", "spec/reek/name_spec.rb", "spec/reek/object_refs_spec.rb", "spec/reek/object_source_spec.rb", "spec/reek/singleton_method_context_spec.rb", "spec/reek/smell_warning_spec.rb", "spec/reek/smells/class_variable_spec.rb", "spec/reek/smells/control_couple_spec.rb", "spec/reek/smells/data_clump_spec.rb", "spec/reek/smells/duplication_spec.rb", "spec/reek/smells/feature_envy_spec.rb", "spec/reek/smells/large_class_spec.rb", "spec/reek/smells/long_method_spec.rb", "spec/reek/smells/long_parameter_list_spec.rb", "spec/reek/smells/nested_iterators_spec.rb", "spec/reek/smells/simulated_polymorphism_spec.rb", "spec/reek/smells/smell_detector_spec.rb", "spec/reek/smells/uncommunicative_name_spec.rb", "spec/reek/smells/utility_function_spec.rb", "spec/reek/sniffer_spec.rb", "spec/reek/stop_context_spec.rb", "spec/samples/all_but_one_masked/clean_one.rb", "spec/samples/all_but_one_masked/dirty.rb", "spec/samples/all_but_one_masked/masked.reek", "spec/samples/clean_due_to_masking/clean_one.rb", "spec/samples/clean_due_to_masking/clean_three.rb", "spec/samples/clean_due_to_masking/clean_two.rb", "spec/samples/clean_due_to_masking/dirty_one.rb", "spec/samples/clean_due_to_masking/dirty_two.rb", "spec/samples/clean_due_to_masking/masked.reek", "spec/samples/corrupt_config_file/corrupt.reek", "spec/samples/corrupt_config_file/dirty.rb", "spec/samples/empty_config_file/dirty.rb", "spec/samples/empty_config_file/empty.reek", "spec/samples/exceptions.reek", "spec/samples/inline.rb", "spec/samples/masked/dirty.rb", "spec/samples/masked/masked.reek", "spec/samples/mixed_results/clean_one.rb", "spec/samples/mixed_results/clean_three.rb", "spec/samples/mixed_results/clean_two.rb", "spec/samples/mixed_results/dirty_one.rb", "spec/samples/mixed_results/dirty_two.rb", "spec/samples/not_quite_masked/dirty.rb", "spec/samples/not_quite_masked/masked.reek", "spec/samples/optparse.rb", "spec/samples/overrides/masked/dirty.rb", "spec/samples/overrides/masked/lower.reek", "spec/samples/overrides/upper.reek", "spec/samples/redcloth.rb", "spec/samples/three_clean_files/clean_one.rb", "spec/samples/three_clean_files/clean_three.rb", "spec/samples/three_clean_files/clean_two.rb", "spec/samples/two_smelly_files/dirty_one.rb", "spec/samples/two_smelly_files/dirty_two.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/reek.rake", "tasks/test.rake"]
15
+ s.files = ["History.txt", "License.txt", "README.rdoc", "Rakefile", "bin/reek", "config/defaults.reek", "features/masking_smells.feature", "features/options.feature", "features/profile.feature", "features/rake_task.feature", "features/reports.feature", "features/samples.feature", "features/stdin.feature", "features/step_definitions/reek_steps.rb", "features/support/env.rb", "lib/reek.rb", "lib/reek/adapters/application.rb", "lib/reek/adapters/command_line.rb", "lib/reek/adapters/config_file.rb", "lib/reek/adapters/core_extras.rb", "lib/reek/adapters/rake_task.rb", "lib/reek/adapters/report.rb", "lib/reek/adapters/source.rb", "lib/reek/adapters/spec.rb", "lib/reek/block_context.rb", "lib/reek/class_context.rb", "lib/reek/code_context.rb", "lib/reek/code_parser.rb", "lib/reek/configuration.rb", "lib/reek/detector_stack.rb", "lib/reek/if_context.rb", "lib/reek/method_context.rb", "lib/reek/module_context.rb", "lib/reek/name.rb", "lib/reek/object_refs.rb", "lib/reek/sexp_formatter.rb", "lib/reek/singleton_method_context.rb", "lib/reek/smell_warning.rb", "lib/reek/smells/attribute.rb", "lib/reek/smells/class_variable.rb", "lib/reek/smells/control_couple.rb", "lib/reek/smells/data_clump.rb", "lib/reek/smells/duplication.rb", "lib/reek/smells/feature_envy.rb", "lib/reek/smells/large_class.rb", "lib/reek/smells/long_method.rb", "lib/reek/smells/long_parameter_list.rb", "lib/reek/smells/long_yield_list.rb", "lib/reek/smells/nested_iterators.rb", "lib/reek/smells/simulated_polymorphism.rb", "lib/reek/smells/smell_detector.rb", "lib/reek/smells/uncommunicative_name.rb", "lib/reek/smells/utility_function.rb", "lib/reek/sniffer.rb", "lib/reek/stop_context.rb", "lib/reek/tree_dresser.rb", "lib/reek/yield_call_context.rb", "reek.gemspec", "spec/reek/adapters/report_spec.rb", "spec/reek/adapters/should_reek_of_spec.rb", "spec/reek/adapters/should_reek_only_of_spec.rb", "spec/reek/adapters/should_reek_spec.rb", "spec/reek/block_context_spec.rb", "spec/reek/class_context_spec.rb", "spec/reek/code_context_spec.rb", "spec/reek/code_parser_spec.rb", "spec/reek/config_spec.rb", "spec/reek/configuration_spec.rb", "spec/reek/if_context_spec.rb", "spec/reek/method_context_spec.rb", "spec/reek/module_context_spec.rb", "spec/reek/name_spec.rb", "spec/reek/object_refs_spec.rb", "spec/reek/object_source_spec.rb", "spec/reek/singleton_method_context_spec.rb", "spec/reek/smell_warning_spec.rb", "spec/reek/smells/attribute_spec.rb", "spec/reek/smells/behaves_like_variable_detector.rb", "spec/reek/smells/class_variable_spec.rb", "spec/reek/smells/control_couple_spec.rb", "spec/reek/smells/data_clump_spec.rb", "spec/reek/smells/duplication_spec.rb", "spec/reek/smells/feature_envy_spec.rb", "spec/reek/smells/large_class_spec.rb", "spec/reek/smells/long_method_spec.rb", "spec/reek/smells/long_parameter_list_spec.rb", "spec/reek/smells/nested_iterators_spec.rb", "spec/reek/smells/simulated_polymorphism_spec.rb", "spec/reek/smells/smell_detector_spec.rb", "spec/reek/smells/uncommunicative_name_spec.rb", "spec/reek/smells/utility_function_spec.rb", "spec/reek/sniffer_spec.rb", "spec/reek/stop_context_spec.rb", "spec/samples/all_but_one_masked/clean_one.rb", "spec/samples/all_but_one_masked/dirty.rb", "spec/samples/all_but_one_masked/masked.reek", "spec/samples/clean_due_to_masking/clean_one.rb", "spec/samples/clean_due_to_masking/clean_three.rb", "spec/samples/clean_due_to_masking/clean_two.rb", "spec/samples/clean_due_to_masking/dirty_one.rb", "spec/samples/clean_due_to_masking/dirty_two.rb", "spec/samples/clean_due_to_masking/masked.reek", "spec/samples/corrupt_config_file/corrupt.reek", "spec/samples/corrupt_config_file/dirty.rb", "spec/samples/empty_config_file/dirty.rb", "spec/samples/empty_config_file/empty.reek", "spec/samples/exceptions.reek", "spec/samples/inline.rb", "spec/samples/masked/dirty.rb", "spec/samples/masked/masked.reek", "spec/samples/mixed_results/clean_one.rb", "spec/samples/mixed_results/clean_three.rb", "spec/samples/mixed_results/clean_two.rb", "spec/samples/mixed_results/dirty_one.rb", "spec/samples/mixed_results/dirty_two.rb", "spec/samples/not_quite_masked/dirty.rb", "spec/samples/not_quite_masked/masked.reek", "spec/samples/optparse.rb", "spec/samples/overrides/masked/dirty.rb", "spec/samples/overrides/masked/lower.reek", "spec/samples/overrides/upper.reek", "spec/samples/redcloth.rb", "spec/samples/three_clean_files/clean_one.rb", "spec/samples/three_clean_files/clean_three.rb", "spec/samples/three_clean_files/clean_two.rb", "spec/samples/two_smelly_files/dirty_one.rb", "spec/samples/two_smelly_files/dirty_two.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/reek.rake", "tasks/test.rake"]
16
16
  s.homepage = %q{http://wiki.github.com/kevinrutherford/reek}
17
17
  s.post_install_message = %q{
18
18
  For more information on reek, see http://wiki.github.com/kevinrutherford/reek
@@ -8,8 +8,14 @@ include Reek::Spec
8
8
 
9
9
  # belongs in its own spec file
10
10
  describe ReekMatcher do
11
+ before :each do
12
+ smelly_code = Dir['spec/samples/two_smelly_files/*.rb']
13
+ @sniffers = smelly_code.sniff.sniffers
14
+ @full = FullReport.new(@sniffers, '%c %w (%s)', false).report
15
+ end
16
+
11
17
  it 'reports quietly' do
12
- @smelly_code = 'def x() y = 4; end'
18
+ ReekMatcher.create_reporter(@sniffers).should_not == @full
13
19
  end
14
20
  end
15
21
 
@@ -9,37 +9,37 @@ describe BlockContext do
9
9
 
10
10
  it "should record single parameter" do
11
11
  element = StopContext.new
12
- element = BlockContext.new(element, s(s(:lasgn, :x), nil))
12
+ element = BlockContext.new(element, s(:iter, nil, s(:lasgn, :x), nil))
13
13
  element.variable_names.should == [Name.new(:x)]
14
14
  end
15
15
 
16
16
  it "should record single parameter within a method" do
17
17
  element = StopContext.new
18
18
  element = MethodContext.new(element, s(:defn, :help))
19
- element = BlockContext.new(element, s(s(:lasgn, :x), nil))
19
+ element = BlockContext.new(element, s(:iter, nil, s(:lasgn, :x), nil))
20
20
  element.variable_names.should == [Name.new(:x)]
21
21
  end
22
22
 
23
23
  it "records multiple parameters" do
24
24
  element = StopContext.new
25
- element = BlockContext.new(element, s(s(:masgn, s(:array, s(:lasgn, :x), s(:lasgn, :y))), nil))
25
+ element = BlockContext.new(element, s(:iter, nil, s(:masgn, s(:array, s(:lasgn, :x), s(:lasgn, :y))), nil))
26
26
  element.variable_names.should == [Name.new(:x), Name.new(:y)]
27
27
  end
28
28
 
29
29
  it "should not pass parameters upward" do
30
30
  mc = MethodContext.new(StopContext.new, s(:defn, :help, s(:args)))
31
- element = BlockContext.new(mc, s(s(:lasgn, :x)))
31
+ element = BlockContext.new(mc, s(:iter, nil, s(:lasgn, :x)))
32
32
  mc.variable_names.should be_empty
33
33
  end
34
34
 
35
35
  it 'records local variables' do
36
- bctx = BlockContext.new(StopContext.new, nil)
36
+ bctx = BlockContext.new(StopContext.new, s(nil, nil))
37
37
  bctx.record_local_variable(:q2)
38
38
  bctx.variable_names.should include(Name.new(:q2))
39
39
  end
40
40
 
41
41
  it 'copes with a yield to an ivar' do
42
- scope = BlockContext.new(StopContext.new, [s(:iasgn, :@list), s(:self)])
42
+ scope = BlockContext.new(StopContext.new, s(:iter, nil, s(:iasgn, :@list), s(:self)))
43
43
  scope.record_instance_variable(:@list)
44
44
  scope.variable_names.should == [:@list]
45
45
  end
@@ -150,33 +150,12 @@ describe CodeContext, 'find class' do
150
150
  end
151
151
 
152
152
  describe ClassContext do
153
- it 'should not report empty class in another module' do
153
+ it 'does not report empty class in another module' do
154
154
  'class Treetop::Runtime::SyntaxNode; end'.should_not reek
155
155
  end
156
156
 
157
- it 'should deal with :: scoped names' do
157
+ it 'deals with :: scoped names' do
158
158
  element = ClassContext.create(StopContext.new, [:colon2, [:colon2, [:const, :Treetop], :Runtime], :SyntaxNode])
159
159
  element.num_methods.should == 0
160
160
  end
161
-
162
- it 'counts conditionals correctly' do
163
- src = <<EOS
164
- class Scrunch
165
- def first
166
- return @field == :sym ? 0 : 3;
167
- end
168
- def second
169
- if @field == :sym
170
- @other += " quarts"
171
- end
172
- end
173
- def third
174
- raise 'flu!' unless @field == :sym
175
- end
176
- end
177
- EOS
178
-
179
- ctx = ClassContext.from_s(src)
180
- ctx.conditionals.length.should == 3
181
- end
182
161
  end
@@ -9,85 +9,167 @@ require 'reek/stop_context'
9
9
 
10
10
  include Reek
11
11
 
12
- describe CodeContext, 'to_s' do
13
-
14
- it "should report full context" do
15
- element = StopContext.new
16
- element = ModuleContext.new(element, Name.new(:mod))
17
- element = ClassContext.new(element, [0, :klass])
18
- element = MethodContext.new(element, [0, :bad])
19
- element = BlockContext.new(element, nil)
20
- element.to_s.should match(/bad/)
21
- element.to_s.should match(/klass/)
22
- element.to_s.should match(/mod/)
23
- end
12
+ describe CodeContext do
13
+ context 'to_s' do
14
+ it "reports the full context" do
15
+ element = StopContext.new
16
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
17
+ element = ClassContext.new(element, [0, :klass], s())
18
+ element = MethodContext.new(element, [0, :bad])
19
+ element = BlockContext.new(element, s(nil, nil))
20
+ element.to_s.should match(/bad/)
21
+ element.to_s.should match(/klass/)
22
+ element.to_s.should match(/mod/)
23
+ end
24
24
 
25
- it "should report method name via if context" do
26
- element1 = StopContext.new
27
- element2 = MethodContext.new(element1, [0, :bad])
28
- element3 = IfContext.new(element2, [0,1])
29
- BlockContext.new(element3, nil).to_s.should match(/bad/)
30
- end
25
+ it 'reports the method name via if context' do
26
+ element1 = StopContext.new
27
+ element2 = MethodContext.new(element1, [0, :bad])
28
+ element3 = IfContext.new(element2, [0,1])
29
+ BlockContext.new(element3, s(nil, nil)).to_s.should match(/bad/)
30
+ end
31
31
 
32
- it "should report method name via nested blocks" do
33
- element1 = StopContext.new
34
- element2 = MethodContext.new(element1, [0, :bad])
35
- element3 = BlockContext.new(element2, nil)
36
- BlockContext.new(element3, nil).to_s.should match(/bad/)
32
+ it 'reports the method name via nested blocks' do
33
+ element1 = StopContext.new
34
+ element2 = MethodContext.new(element1, [0, :bad])
35
+ element3 = BlockContext.new(element2, s(nil, nil))
36
+ BlockContext.new(element3, s(nil, nil)).to_s.should match(/bad/)
37
+ end
37
38
  end
38
- end
39
39
 
40
- describe CodeContext, 'instance variables' do
41
- it 'should pass instance variables down to the first class' do
42
- element = StopContext.new
43
- element = ModuleContext.new(element, Name.new(:mod))
44
- class_element = ClassContext.new(element, [0, :klass])
45
- element = MethodContext.new(class_element, [0, :bad])
46
- element = BlockContext.new(element, nil)
47
- element.record_instance_variable(:fred)
48
- class_element.variable_names.size.should == 1
49
- class_element.variable_names.should include(Name.new(:fred))
40
+ context 'instance variables' do
41
+ it 'should pass instance variables down to the first class' do
42
+ element = StopContext.new
43
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
44
+ class_element = ClassContext.new(element, [0, :klass], s())
45
+ element = MethodContext.new(class_element, [0, :bad])
46
+ element = BlockContext.new(element, s(nil, nil))
47
+ element.record_instance_variable(:fred)
48
+ class_element.variable_names.size.should == 1
49
+ class_element.variable_names.should include(Name.new(:fred))
50
+ end
50
51
  end
51
- end
52
52
 
53
- describe CodeContext, 'generics' do
54
- it 'should pass unknown method calls down the stack' do
55
- stop = StopContext.new
56
- def stop.bananas(arg1, arg2) arg1 + arg2 + 43 end
57
- element = ModuleContext.new(stop, Name.new(:mod))
58
- class_element = ClassContext.new(element, [0, :klass])
59
- element = MethodContext.new(class_element, [0, :bad])
60
- element = BlockContext.new(element, nil)
61
- element.bananas(17, -5).should == 55
53
+ context 'generics' do
54
+ it 'should pass unknown method calls down the stack' do
55
+ stop = StopContext.new
56
+ def stop.bananas(arg1, arg2) arg1 + arg2 + 43 end
57
+ element = ModuleContext.new(stop, Name.new(:mod), s(:module, :mod, nil))
58
+ class_element = ClassContext.new(element, [0, :klass], s())
59
+ element = MethodContext.new(class_element, [0, :bad])
60
+ element = BlockContext.new(element, s(nil, nil))
61
+ element.bananas(17, -5).should == 55
62
+ end
62
63
  end
63
- end
64
64
 
65
- describe CodeContext do
66
- it 'should recognise itself in a collection of names' do
67
- element = StopContext.new
68
- element = ModuleContext.new(element, Name.new(:mod))
69
- element.matches?(['banana', 'mod']).should == true
70
- end
65
+ context 'name matching' do
66
+ it 'should recognise itself in a collection of names' do
67
+ element = StopContext.new
68
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
69
+ element.matches?(['banana', 'mod']).should == true
70
+ end
71
+
72
+ it 'should recognise itself in a collection of REs' do
73
+ element = StopContext.new
74
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
75
+ element.matches?([/banana/, /mod/]).should == true
76
+ end
71
77
 
72
- it 'should recognise itself in a collection of REs' do
73
- element = StopContext.new
74
- element = ModuleContext.new(element, Name.new(:mod))
75
- element.matches?([/banana/, /mod/]).should == true
78
+ it 'should recognise its fq name in a collection of names' do
79
+ element = StopContext.new
80
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
81
+ element = ClassContext.create(element, s(:class, :klass))
82
+ element.matches?(['banana', 'mod']).should == true
83
+ element.matches?(['banana', 'mod::klass']).should == true
84
+ end
85
+
86
+ it 'should recognise its fq name in a collection of names' do
87
+ element = StopContext.new
88
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
89
+ element = ClassContext.create(element, s(:class, :klass))
90
+ element.matches?([/banana/, /mod/]).should == true
91
+ element.matches?([/banana/, /mod::klass/]).should == true
92
+ end
76
93
  end
77
94
 
78
- it 'should recognise its fq name in a collection of names' do
79
- element = StopContext.new
80
- element = ModuleContext.new(element, Name.new(:mod))
81
- element = ClassContext.create(element, [0, :klass])
82
- element.matches?(['banana', 'mod']).should == true
83
- element.matches?(['banana', 'mod::klass']).should == true
95
+ context 'enumerating syntax elements' do
96
+ context 'in an empty module' do
97
+ before :each do
98
+ @module_name = 'Emptiness'
99
+ src = "module #{@module_name}; end"
100
+ ast = src.to_reek_source.syntax_tree
101
+ @ctx = CodeContext.new(nil, ast)
102
+ end
103
+ it 'yields no calls' do
104
+ @ctx.each(:call, []) {|exp| raise "#{exp} yielded by empty module!"}
105
+ end
106
+ it 'yields one module' do
107
+ mods = 0
108
+ @ctx.each(:module, []) {|exp| mods += 1}
109
+ mods.should == 1
110
+ end
111
+ it "yields the module's full AST" do
112
+ @ctx.each(:module, []) {|exp| exp[1].should == @module_name.to_sym}
113
+ end
114
+
115
+ context 'with no block' do
116
+ it 'returns an empty array of ifs' do
117
+ @ctx.each(:if, []).should be_empty
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'with a nested element' do
123
+ before :each do
124
+ @module_name = 'Loneliness'
125
+ @method_name = 'calloo'
126
+ src = "module #{@module_name}; def #{@method_name}; puts('hello') end; end"
127
+ ast = src.to_reek_source.syntax_tree
128
+ @ctx = CodeContext.new(nil, ast)
129
+ end
130
+ it 'yields no ifs' do
131
+ @ctx.each(:if, []) {|exp| raise "#{exp} yielded by empty module!"}
132
+ end
133
+ it 'yields one module' do
134
+ @ctx.each(:module, []).length.should == 1
135
+ end
136
+ it "yields the module's full AST" do
137
+ @ctx.each(:module, []) {|exp| exp[1].should == @module_name.to_sym}
138
+ end
139
+ it 'yields one method' do
140
+ @ctx.each(:defn, []).length.should == 1
141
+ end
142
+ it "yields the method's full AST" do
143
+ @ctx.each(:defn, []) {|exp| exp[1].should == @method_name.to_sym}
144
+ end
145
+
146
+ context 'pruning the traversal' do
147
+ it 'ignores the call inside the method' do
148
+ @ctx.each(:call, [:defn]).should be_empty
149
+ end
150
+ end
151
+ end
152
+
153
+ it 'finds 3 ifs in a class' do
154
+ src = <<EOS
155
+ class Scrunch
156
+ def first
157
+ return @field == :sym ? 0 : 3;
158
+ end
159
+ def second
160
+ if @field == :sym
161
+ @other += " quarts"
162
+ end
84
163
  end
164
+ def third
165
+ raise 'flu!' unless @field == :sym
166
+ end
167
+ end
168
+ EOS
85
169
 
86
- it 'should recognise its fq name in a collection of names' do
87
- element = StopContext.new
88
- element = ModuleContext.new(element, Name.new(:mod))
89
- element = ClassContext.create(element, [0, :klass])
90
- element.matches?([/banana/, /mod/]).should == true
91
- element.matches?([/banana/, /mod::klass/]).should == true
170
+ ast = src.to_reek_source.syntax_tree
171
+ ctx = CodeContext.new(nil, ast)
172
+ ctx.each(:if, []).length.should == 3
173
+ end
92
174
  end
93
175
  end
@@ -5,22 +5,22 @@ require 'reek/code_parser'
5
5
  include Reek
6
6
 
7
7
  describe CodeParser, "with no method definitions" do
8
- it 'should report no problems for empty source code' do
8
+ it 'reports no problems for empty source code' do
9
9
  ''.should_not reek
10
10
  end
11
- it 'should report no problems for empty class' do
11
+ it 'reports no problems for empty class' do
12
12
  'class Fred; end'.should_not reek
13
13
  end
14
14
  end
15
15
 
16
16
  describe CodeParser, 'with a global method definition' do
17
- it 'should report no problems for simple method' do
17
+ it 'reports no problems for simple method' do
18
18
  'def Outermost::fred() true; end'.should_not reek
19
19
  end
20
20
  end
21
21
 
22
22
  describe CodeParser, 'when a yield is the receiver' do
23
- it 'should report no problems' do
23
+ it 'reports no problems' do
24
24
  source = 'def values(*args)
25
25
  @to_sql += case
26
26
  when block_given? then " #{yield.to_sql}"
@@ -39,105 +39,89 @@ describe CodeParser do
39
39
  end
40
40
 
41
41
  describe CodeParser do
42
- context 'with no class variables' do
42
+ context 'with no attributes' do
43
43
  it 'records nothing in the class' do
44
44
  klass = ClassContext.from_s('class Fred; end')
45
- klass.class_variables.should be_empty
45
+ klass.attributes.should be_empty
46
46
  end
47
47
  it 'records nothing in the module' do
48
48
  ctx = ModuleContext.from_s('module Fred; end')
49
- ctx.class_variables.should be_empty
49
+ ctx.attributes.should be_empty
50
50
  end
51
51
  end
52
52
 
53
- context 'with one class variable' do
54
- shared_examples_for 'one variable found' do
55
- it 'records the class variable' do
56
- @ctx.class_variables.should include(Name.new(:@@tools))
53
+ context 'with one attribute' do
54
+ shared_examples_for 'one attribute found' do
55
+ it 'records the attribute' do
56
+ @ctx.attributes.should include(Name.new(:property))
57
57
  end
58
- it 'records only that class variable' do
59
- @ctx.class_variables.length.should == 1
58
+ it 'records only that attribute' do
59
+ @ctx.attributes.length.should == 1
60
60
  end
61
61
  end
62
62
 
63
63
  context 'declared in a class' do
64
64
  before :each do
65
- @ctx = ClassContext.from_s('class Fred; @@tools = {}; end')
65
+ @ctx = ClassContext.from_s('class Fred; attr :property; end')
66
66
  end
67
67
 
68
- it_should_behave_like 'one variable found'
68
+ it_should_behave_like 'one attribute found'
69
69
  end
70
70
 
71
- context 'used in a class' do
71
+ context 'reader in a class' do
72
72
  before :each do
73
- @ctx = ClassContext.from_s('class Fred; def jim() @@tools = {}; end; end')
73
+ @ctx = ClassContext.from_s('class Fred; attr_reader :property; end')
74
74
  end
75
75
 
76
- it_should_behave_like 'one variable found'
76
+ it_should_behave_like 'one attribute found'
77
77
  end
78
78
 
79
- context 'indexed in a class' do
79
+ context 'writer in a class' do
80
80
  before :each do
81
- @ctx = ClassContext.from_s('class Fred; def jim() @@tools[mash] = {}; end; end')
81
+ @ctx = ClassContext.from_s('class Fred; attr_writer :property; end')
82
82
  end
83
83
 
84
- it_should_behave_like 'one variable found'
84
+ it_should_behave_like 'one attribute found'
85
85
  end
86
86
 
87
- context 'declared and used in a class' do
87
+ context 'accessor in a class' do
88
88
  before :each do
89
- @ctx = ClassContext.from_s('class Fred; @@tools = {}; def jim() @@tools = {}; end; end')
89
+ @ctx = ClassContext.from_s('class Fred; attr_accessor :property; end')
90
90
  end
91
91
 
92
- it_should_behave_like 'one variable found'
93
- end
94
-
95
- context 'used twice in a class' do
96
- before :each do
97
- @ctx = ClassContext.from_s('class Fred; def jeff() @@tools = {}; end; def jim() @@tools = {}; end; end')
98
- end
99
-
100
- it_should_behave_like 'one variable found'
92
+ it_should_behave_like 'one attribute found'
101
93
  end
102
94
 
103
95
  context 'declared in a module' do
104
96
  before :each do
105
- @ctx = ClassContext.from_s('module Fred; @@tools = {}; end')
106
- end
107
-
108
- it_should_behave_like 'one variable found'
109
- end
110
-
111
- context 'used in a module' do
112
- before :each do
113
- @ctx = ClassContext.from_s('module Fred; def jim() @@tools = {}; end; end')
97
+ @ctx = ModuleContext.from_s('module Fred; attr :property; end')
114
98
  end
115
99
 
116
- it_should_behave_like 'one variable found'
100
+ it_should_behave_like 'one attribute found'
117
101
  end
118
102
 
119
- context 'indexed in a module' do
103
+ context 'reader in a module' do
120
104
  before :each do
121
- @ctx = ClassContext.from_s('module Fred; def jim() @@tools[mash] = {}; end; end')
105
+ @ctx = ModuleContext.from_s('module Fred; attr_reader :property; end')
122
106
  end
123
107
 
124
- it_should_behave_like 'one variable found'
108
+ it_should_behave_like 'one attribute found'
125
109
  end
126
110
 
127
- context 'declared and used in a module' do
111
+ context 'writer in a module' do
128
112
  before :each do
129
- @ctx = ClassContext.from_s('module Fred; @@tools = {}; def jim() @@tools = {}; end; end')
113
+ @ctx = ModuleContext.from_s('module Fred; attr_writer :property; end')
130
114
  end
131
115
 
132
- it_should_behave_like 'one variable found'
116
+ it_should_behave_like 'one attribute found'
133
117
  end
134
118
 
135
- context 'used twice in a module' do
119
+ context 'accessor in a module' do
136
120
  before :each do
137
- @ctx = ClassContext.from_s('module Fred; def jeff() @@tools = {}; end; def jim() @@tools = {}; end; end')
121
+ @ctx = ModuleContext.from_s('module Fred; attr_accessor :property; end')
138
122
  end
139
123
 
140
- it_should_behave_like 'one variable found'
124
+ it_should_behave_like 'one attribute found'
141
125
  end
142
126
  end
143
127
  end
@@ -7,7 +7,7 @@ include Reek
7
7
 
8
8
  describe MethodContext, 'matching' do
9
9
  before :each do
10
- @element = MethodContext.new(StopContext.new, [0, :mod])
10
+ @element = MethodContext.new(StopContext.new, s(0, :mod), s(:module, :mod, nil))
11
11
  end
12
12
 
13
13
  it 'should recognise itself in a collection of names' do
@@ -24,9 +24,9 @@ end
24
24
  describe MethodContext, 'matching fq names' do
25
25
  before :each do
26
26
  element = StopContext.new
27
- element = ModuleContext.new(element, Name.new(:mod))
28
- element = ClassContext.new(element, Name.new(:klass))
29
- @element = MethodContext.new(element, [0, :meth])
27
+ element = ModuleContext.new(element, Name.new(:mod), s(:module, :mod, nil))
28
+ element = ClassContext.new(element, Name.new(:klass), s())
29
+ @element = MethodContext.new(element, s(0, :meth))
30
30
  end
31
31
 
32
32
  it 'should recognise itself in a collection of names' do