reek 3.0.4 → 3.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +6 -0
  3. data/README.md +41 -20
  4. data/features/configuration_files/directory_specific_directives.feature +280 -0
  5. data/features/configuration_files/masking_smells.feature +0 -14
  6. data/features/step_definitions/sample_file_steps.rb +2 -2
  7. data/lib/reek/ast/sexp_extensions.rb +72 -60
  8. data/lib/reek/cli/application.rb +2 -5
  9. data/lib/reek/cli/reek_command.rb +1 -1
  10. data/lib/reek/configuration/app_configuration.rb +115 -61
  11. data/lib/reek/configuration/configuration_file_finder.rb +19 -0
  12. data/lib/reek/context/code_context.rb +102 -29
  13. data/lib/reek/context/method_context.rb +11 -48
  14. data/lib/reek/context/module_context.rb +2 -6
  15. data/lib/reek/context/root_context.rb +7 -14
  16. data/lib/reek/examiner.rb +15 -10
  17. data/lib/reek/report/report.rb +5 -4
  18. data/lib/reek/smells/boolean_parameter.rb +1 -1
  19. data/lib/reek/smells/duplicate_method_call.rb +1 -5
  20. data/lib/reek/smells/irresponsible_module.rb +2 -2
  21. data/lib/reek/smells/smell_repository.rb +30 -16
  22. data/lib/reek/smells/utility_function.rb +1 -1
  23. data/lib/reek/source/source_code.rb +10 -6
  24. data/lib/reek/source/source_locator.rb +27 -26
  25. data/lib/reek/spec.rb +8 -6
  26. data/lib/reek/spec/should_reek.rb +6 -2
  27. data/lib/reek/spec/should_reek_of.rb +5 -2
  28. data/lib/reek/spec/should_reek_only_of.rb +1 -1
  29. data/lib/reek/tree_walker.rb +49 -21
  30. data/lib/reek/version.rb +1 -1
  31. data/spec/reek/ast/sexp_extensions_spec.rb +4 -12
  32. data/spec/reek/configuration/app_configuration_spec.rb +80 -52
  33. data/spec/reek/configuration/configuration_file_finder_spec.rb +27 -15
  34. data/spec/reek/context/code_context_spec.rb +66 -17
  35. data/spec/reek/context/method_context_spec.rb +66 -64
  36. data/spec/reek/context/root_context_spec.rb +3 -1
  37. data/spec/reek/context/singleton_method_context_spec.rb +1 -2
  38. data/spec/reek/examiner_spec.rb +5 -8
  39. data/spec/reek/report/xml_report_spec.rb +3 -2
  40. data/spec/reek/smells/attribute_spec.rb +12 -17
  41. data/spec/reek/smells/duplicate_method_call_spec.rb +17 -25
  42. data/spec/reek/smells/feature_envy_spec.rb +4 -5
  43. data/spec/reek/smells/irresponsible_module_spec.rb +43 -0
  44. data/spec/reek/smells/nested_iterators_spec.rb +12 -26
  45. data/spec/reek/smells/too_many_statements_spec.rb +2 -210
  46. data/spec/reek/smells/utility_function_spec.rb +49 -5
  47. data/spec/reek/source/source_locator_spec.rb +39 -43
  48. data/spec/reek/spec/should_reek_of_spec.rb +3 -2
  49. data/spec/reek/spec/should_reek_spec.rb +25 -17
  50. data/spec/reek/tree_walker_spec.rb +214 -23
  51. data/spec/samples/configuration/full_configuration.reek +9 -0
  52. data/spec/spec_helper.rb +10 -10
  53. metadata +4 -3
  54. data/features/configuration_files/overrides_defaults.feature +0 -15
@@ -1,11 +1,12 @@
1
1
  require_relative '../../spec_helper'
2
2
  require_relative '../../../lib/reek/smells/utility_function'
3
+ require_relative '../../../lib/reek/examiner'
3
4
  require_relative 'smell_detector_shared'
4
5
 
5
6
  RSpec.describe Reek::Smells::UtilityFunction do
6
7
  describe 'a detector' do
7
8
  before(:each) do
8
- @source_name = 'dummy_source'
9
+ @source_name = 'string'
9
10
  @detector = build(:smell_detector,
10
11
  smell_type: :UtilityFunction,
11
12
  source: @source_name)
@@ -20,9 +21,7 @@ RSpec.describe Reek::Smells::UtilityFunction do
20
21
  arga.b.c
21
22
  end
22
23
  EOS
23
- source = Reek::Source::SourceCode.from(src)
24
- mctx = Reek::TreeWalker.new.process_def(source.syntax_tree)
25
- @warning = @detector.examine_context(mctx)[0] # SMELL: too cumbersome!
24
+ @warning = Reek::Examiner.new(src, ['UtilityFunction']).smells[0]
26
25
  end
27
26
 
28
27
  it_should_behave_like 'common fields set correctly'
@@ -74,11 +73,56 @@ RSpec.describe Reek::Smells::UtilityFunction do
74
73
  class C
75
74
  def m1(a) a.to_s; end
76
75
  def m2(a) a.to_s; end
77
- module_function :m1, m2
76
+ module_function :m1, :m2
78
77
  end
79
78
  EOS
80
79
  expect(src).not_to reek_of(:UtilityFunction)
81
80
  end
81
+
82
+ it 'does not report module functions defined by earlier modifier' do
83
+ src = <<-EOF
84
+ module M
85
+ module_function
86
+ def simple(a) a.to_s; end
87
+ end
88
+ EOF
89
+ expect(src).not_to reek_of(:UtilityFunction)
90
+ end
91
+
92
+ it 'reports functions preceded by canceled modifier' do
93
+ src = <<-EOF
94
+ module M
95
+ module_function
96
+ public
97
+ def simple(a) a.to_s; end
98
+ end
99
+ EOF
100
+ expect(src).to reek_of(:UtilityFunction)
101
+ end
102
+
103
+ it 'does not report when module_function is called in separate scope' do
104
+ src = <<-EOF
105
+ class C
106
+ def m(a) a.to_s; end
107
+ begin
108
+ module_function :m
109
+ end
110
+ end
111
+ EOF
112
+ expect(src).not_to reek_of(:UtilityFunction)
113
+ end
114
+
115
+ it 'does not report when module_function modifier is called in separate scope' do
116
+ src = <<-EOF
117
+ class C
118
+ begin
119
+ module_function
120
+ end
121
+ def m(a) a.to_s; end
122
+ end
123
+ EOF
124
+ expect(src).not_to reek_of(:UtilityFunction)
125
+ end
82
126
  end
83
127
  end
84
128
 
@@ -1,87 +1,83 @@
1
+ require 'pathname'
1
2
  require_relative '../../spec_helper'
3
+ require_relative '../../../lib/reek/configuration/app_configuration'
2
4
  require_relative '../../../lib/reek/source/source_locator'
3
5
 
4
6
  RSpec.describe Reek::Source::SourceLocator do
5
7
  describe '#sources' do
6
8
  context 'applied to hidden directories' do
7
- let(:path) { 'spec/samples/source_with_hidden_directories' }
8
- let(:expected_files) do
9
- ['spec/samples/source_with_hidden_directories/uncommunicative_parameter_name.rb']
9
+ let(:path) { SAMPLES_PATH.join('source_with_hidden_directories') }
10
+ let(:expected_paths) do
11
+ [SAMPLES_PATH.join('source_with_hidden_directories/uncommunicative_parameter_name.rb')]
10
12
  end
11
- let(:files_that_are_expected_to_be_ignored) do
12
- ['spec/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb']
13
+ let(:paths_that_are_expected_to_be_ignored) do
14
+ [SAMPLES_PATH.join('source_with_hidden_directories/.hidden/\
15
+ uncommunicative_parameter_nameicative_method_name.rb')]
13
16
  end
14
17
 
15
18
  it 'does not scan hidden directories' do
16
19
  sources = described_class.new([path]).sources
17
20
 
18
- expect(sources.map(&:path)).
19
- not_to include(*files_that_are_expected_to_be_ignored)
20
-
21
- expect(sources.map(&:path)).to eq expected_files
21
+ expect(sources).not_to include(*paths_that_are_expected_to_be_ignored)
22
+ expect(sources).to eq expected_paths
22
23
  end
23
24
  end
24
25
 
25
26
  context 'exclude paths' do
26
- let(:config) { 'spec/samples/configuration/with_excluded_paths.reek' }
27
- let(:path) { 'spec/samples/source_with_exclude_paths' }
28
- let(:files_that_are_expected_to_be_ignored) do
27
+ let(:configuration) do
28
+ test_configuration_for(SAMPLES_PATH.join('configuration/with_excluded_paths.reek'))
29
+ end
30
+ let(:path) { SAMPLES_PATH.join('source_with_exclude_paths') }
31
+ let(:paths_that_are_expected_to_be_ignored) do
29
32
  [
30
- 'spec/samples/source_with_exclude_paths/ignore_me/uncommunicative_method_name.rb',
31
- 'spec/samples/source_with_exclude_paths/nested/ignore_me_as_well/irresponsible_module.rb'
33
+ SAMPLES_PATH.join('source_with_exclude_paths/ignore_me/uncommunicative_method_name.rb'),
34
+ SAMPLES_PATH.join('source_with_exclude_paths/nested/' \
35
+ 'ignore_me_as_well/irresponsible_module.rb')
32
36
  ]
33
37
  end
34
38
 
35
39
  it 'does not use excluded paths' do
36
- with_test_config(config) do
37
- sources = described_class.new([path]).sources
38
-
39
- expect(sources.map(&:path).sort).
40
- not_to include(*files_that_are_expected_to_be_ignored)
40
+ sources = described_class.new([path], configuration: configuration).sources
41
+ expect(sources).not_to include(*paths_that_are_expected_to_be_ignored)
41
42
 
42
- expect(sources.map(&:path).sort).to eq [
43
- 'spec/samples/source_with_exclude_paths/nested/uncommunicative_parameter_name.rb'
44
- ]
45
- end
43
+ expect(sources).to eq [
44
+ SAMPLES_PATH.join('source_with_exclude_paths/nested/uncommunicative_parameter_name.rb')
45
+ ]
46
46
  end
47
47
  end
48
48
 
49
- context 'non-ruby files' do
50
- let(:path) { 'spec/samples/source_with_non_ruby_files' }
51
- let(:expected_files) do
52
- ['spec/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb']
49
+ context 'non-ruby paths' do
50
+ let(:path) { SAMPLES_PATH.join('source_with_non_ruby_files') }
51
+ let(:expected_sources) do
52
+ [SAMPLES_PATH.join('source_with_non_ruby_files/uncommunicative_parameter_name.rb')]
53
53
  end
54
- let(:files_that_are_expected_to_be_ignored) do
54
+ let(:paths_that_are_expected_to_be_ignored) do
55
55
  [
56
- 'spec/samples/source_with_non_ruby_files/gibberish',
57
- 'spec/samples/source_with_non_ruby_files/python_source.py'
56
+ SAMPLES_PATH.join('source_with_non_ruby_files/gibberish'),
57
+ SAMPLES_PATH.join('source_with_non_ruby_files/python_source.py')
58
58
  ]
59
59
  end
60
60
 
61
- it 'does only use ruby source files' do
61
+ it 'does only use ruby source paths' do
62
62
  sources = described_class.new([path]).sources
63
63
 
64
- expect(sources.map(&:path)).
65
- not_to include(*files_that_are_expected_to_be_ignored)
64
+ expect(sources).not_to include(*paths_that_are_expected_to_be_ignored)
66
65
 
67
- expect(sources.map(&:path)).to eq expected_files
66
+ expect(sources).to eq expected_sources
68
67
  end
69
68
  end
70
69
 
71
70
  context 'passing "." or "./" as argument' do
72
- let(:expected_files) do
73
- [
74
- 'spec/spec_helper.rb',
75
- 'lib/reek.rb'
76
- ]
71
+ let(:expected_sources) do
72
+ [Pathname.new('spec/spec_helper.rb'), Pathname.new('lib/reek.rb')]
77
73
  end
78
74
 
79
75
  it 'expands it correctly' do
80
- sources_for_dot = described_class.new(['.']).sources
81
- sources_for_dot_slash = described_class.new(['./']).sources
76
+ sources_for_dot = described_class.new([Pathname.new('.')]).sources
77
+ sources_for_dot_slash = described_class.new([Pathname.new('./')]).sources
82
78
 
83
- expect(sources_for_dot.map(&:path)).to include(*expected_files)
84
- expect(sources_for_dot.map(&:path)).to eq(sources_for_dot_slash.map(&:path))
79
+ expect(sources_for_dot).to include(*expected_sources)
80
+ expect(sources_for_dot).to eq(sources_for_dot_slash)
85
81
  end
86
82
  end
87
83
  end
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  require_relative '../../spec_helper'
2
3
  require_relative '../../../lib/reek/spec'
3
4
 
@@ -56,8 +57,8 @@ RSpec.describe Reek::Spec::ShouldReekOf do
56
57
 
57
58
  context 'checking code in a File' do
58
59
  before :each do
59
- @clean_file = File.new(Dir['spec/samples/three_clean_files/*.rb'][0])
60
- @smelly_file = File.new(Dir['spec/samples/two_smelly_files/*.rb'][0])
60
+ @clean_file = Pathname.glob("#{SAMPLES_PATH}/three_clean_files/*.rb").first
61
+ @smelly_file = Pathname.glob("#{SAMPLES_PATH}/two_smelly_files/*.rb").first
61
62
  @matcher = Reek::Spec::ShouldReekOf.new(:UncommunicativeVariableName,
62
63
  name: '@s')
63
64
  end
@@ -2,9 +2,8 @@ require_relative '../../spec_helper'
2
2
  require_relative '../../../lib/reek/spec'
3
3
 
4
4
  RSpec.describe Reek::Spec::ShouldReek do
5
- let(:matcher) { Reek::Spec::ShouldReek.new }
6
-
7
5
  describe 'checking code in a string' do
6
+ let(:matcher) { Reek::Spec::ShouldReek.new }
8
7
  let(:clean_code) { 'def good() true; end' }
9
8
  let(:smelly_code) { 'def x() y = 4; end' }
10
9
 
@@ -23,27 +22,36 @@ RSpec.describe Reek::Spec::ShouldReek do
23
22
  end
24
23
 
25
24
  describe 'checking code in a File' do
26
- let(:clean_file) { File.new('spec/samples/three_clean_files/clean_one.rb') }
27
- let(:smelly_file) { File.new('spec/samples/two_smelly_files/dirty_one.rb') }
28
- let(:masked_file) { File.new('spec/samples/clean_due_to_masking/dirty_one.rb') }
25
+ let(:clean_file) { SAMPLES_PATH.join('three_clean_files/clean_one.rb') }
26
+ let(:smelly_file) { SAMPLES_PATH.join('two_smelly_files/dirty_one.rb') }
27
+ let(:masked_file) { SAMPLES_PATH.join('clean_due_to_masking/dirty_one.rb') }
29
28
 
30
- it 'matches a smelly File' do
31
- expect(matcher.matches?(smelly_file)).to be_truthy
32
- end
29
+ context 'matcher without masking' do
30
+ let(:matcher) { Reek::Spec::ShouldReek.new }
33
31
 
34
- it 'doesnt match a fragrant File' do
35
- expect(matcher.matches?(clean_file)).to be_falsey
36
- end
32
+ it 'matches a smelly File' do
33
+ expect(matcher.matches?(smelly_file)).to be_truthy
34
+ end
37
35
 
38
- it 'masks smells using the relevant configuration' do
39
- with_test_config('spec/samples/clean_due_to_masking/masked.reek') do
40
- expect(matcher.matches?(masked_file)).to be_falsey
36
+ it 'doesnt match a fragrant File' do
37
+ expect(matcher.matches?(clean_file)).to be_falsey
38
+ end
39
+
40
+ it 'reports the smells when should_not fails' do
41
+ matcher.matches?(smelly_file)
42
+ expect(matcher.failure_message_when_negated).to match('UncommunicativeVariableName')
41
43
  end
42
44
  end
43
45
 
44
- it 'reports the smells when should_not fails' do
45
- matcher.matches?(smelly_file)
46
- expect(matcher.failure_message_when_negated).to match('UncommunicativeVariableName')
46
+ context 'matcher without masking' do
47
+ let(:path) { SAMPLES_PATH.join('clean_due_to_masking/masked.reek') }
48
+ let(:configuration) { test_configuration_for(path) }
49
+ let(:matcher) { Reek::Spec::ShouldReek.new configuration }
50
+ let(:masked_file) { SAMPLES_PATH.join('clean_due_to_masking/dirty_one.rb') }
51
+
52
+ it 'masks smells using the relevant configuration' do
53
+ expect(matcher.matches?(masked_file)).to be_falsey
54
+ end
47
55
  end
48
56
  end
49
57
  end
@@ -1,34 +1,225 @@
1
1
  require_relative '../spec_helper'
2
2
  require_relative '../../lib/reek/tree_walker'
3
+ require_relative '../../lib/reek/source/source_code'
3
4
 
4
- RSpec.describe Reek::TreeWalker, 'with no method definitions' do
5
- it 'reports no problems for empty source code' do
6
- expect('').not_to reek
7
- end
8
- it 'reports no problems for empty class' do
9
- expect('# clean class for testing purposes
10
- class Fred; end').not_to reek
5
+ # Dummy repository to inject into TreeWalker in order to count statements in
6
+ # all contexts.
7
+ class TestSmellRepository
8
+ attr_reader :num_statements
9
+ def examine(context)
10
+ @num_statements = context.num_statements
11
11
  end
12
12
  end
13
13
 
14
- RSpec.describe Reek::TreeWalker, 'with a global method definition' do
15
- it 'reports no problems for simple method' do
16
- src = 'def Outermost::fred() true; end'
17
- expect(src).not_to reek
18
- end
14
+ def process_method(source)
15
+ exp = Reek::Source::SourceCode.from(source).syntax_tree
16
+ repository = TestSmellRepository.new
17
+ Reek::TreeWalker.new(repository, exp).walk
18
+ repository
19
19
  end
20
20
 
21
- RSpec.describe Reek::TreeWalker, 'when a yield is the receiver' do
22
- it 'reports no problems' do
23
- src = <<EOS
24
- def values(*args)
25
- @to_sql += case
26
- when block_given? then yield.to_sql
27
- else args.to_sql
28
- end
29
- self
21
+ def process_singleton_method(source)
22
+ exp = Reek::Source::SourceCode.from(source).syntax_tree
23
+ repository = TestSmellRepository.new
24
+ Reek::TreeWalker.new(repository, exp).walk
25
+ repository
30
26
  end
31
- EOS
32
- expect(src).not_to reek
27
+
28
+ RSpec.describe Reek::TreeWalker, 'statement counting' do
29
+ it 'counts 1 assignment' do
30
+ method = process_method('def one() val = 4; end')
31
+ expect(method.num_statements).to eq(1)
32
+ end
33
+
34
+ it 'counts 3 assignments' do
35
+ method = process_method('def one() val = 4; val = 4; val = 4; end')
36
+ expect(method.num_statements).to eq(3)
37
+ end
38
+
39
+ it 'counts 1 attr assignment' do
40
+ method = process_method('def one() val[0] = 4; end')
41
+ expect(method.num_statements).to eq(1)
42
+ end
43
+
44
+ it 'counts 1 increment assignment' do
45
+ method = process_method('def one() val += 4; end')
46
+ expect(method.num_statements).to eq(1)
47
+ end
48
+
49
+ it 'counts 1 increment attr assignment' do
50
+ method = process_method('def one() val[0] += 4; end')
51
+ expect(method.num_statements).to eq(1)
52
+ end
53
+
54
+ it 'counts 1 nested assignment' do
55
+ method = process_method('def one() val = fred = 4; end')
56
+ expect(method.num_statements).to eq(1)
57
+ end
58
+
59
+ it 'counts returns' do
60
+ method = process_method('def one() val = 4; true; end')
61
+ expect(method.num_statements).to eq(2)
62
+ end
63
+
64
+ it 'counts nil returns' do
65
+ method = process_method('def one() val = 4; nil; end')
66
+ expect(method.num_statements).to eq(2)
67
+ end
68
+
69
+ context 'with control statements' do
70
+ it 'counts 1 statement in a conditional expression' do
71
+ method = process_method('def one() if val == 4; callee(); end; end')
72
+ expect(method.num_statements).to eq(1)
73
+ end
74
+
75
+ it 'counts 3 statements in a conditional expression' do
76
+ method = process_method('def one() if val == 4; callee(); callee(); callee(); end; end')
77
+ expect(method.num_statements).to eq(3)
78
+ end
79
+
80
+ it 'counts 1 statements in an else' do
81
+ method = process_method('def one() if val == 4; callee(); else; callee(); end; end')
82
+ expect(method.num_statements).to eq(2)
83
+ end
84
+
85
+ it 'counts 3 statements in an else' do
86
+ method = process_method('
87
+ def one()
88
+ if val == 4
89
+ callee(); callee(); callee()
90
+ else
91
+ callee(); callee(); callee()
92
+ end
93
+ end
94
+ ')
95
+ expect(method.num_statements).to eq(6)
96
+ end
97
+
98
+ it 'does not count empty conditional expression' do
99
+ method = process_method('def one() if val == 4; ; end; end')
100
+ expect(method.num_statements).to eq(0)
101
+ end
102
+
103
+ it 'does not count empty else' do
104
+ method = process_method('def one() if val == 4; ; else; ; end; end')
105
+ expect(method.num_statements).to eq(0)
106
+ end
107
+
108
+ it 'counts extra statements in an if condition' do
109
+ method = process_method('def one() if begin val = callee(); val < 4 end; end; end')
110
+ expect(method.num_statements).to eq(1)
111
+ end
112
+
113
+ it 'counts 1 statement in a while loop' do
114
+ method = process_method('def one() while val < 4; callee(); end; end')
115
+ expect(method.num_statements).to eq(1)
116
+ end
117
+
118
+ it 'counts 3 statements in a while loop' do
119
+ source = 'def one() while val < 4; callee(); callee(); callee(); end; end'
120
+ expect(process_method(source).num_statements).to eq(3)
121
+ end
122
+
123
+ it 'counts extra statements in a while condition' do
124
+ method = process_method('def one() while begin val = callee(); val < 4 end; end; end')
125
+ expect(method.num_statements).to eq(1)
126
+ end
127
+
128
+ it 'counts 1 statement in a until loop' do
129
+ method = process_method('def one() until val < 4; callee(); end; end')
130
+ expect(method.num_statements).to eq(1)
131
+ end
132
+
133
+ it 'counts 3 statements in a until loop' do
134
+ source = 'def one() until val < 4; callee(); callee(); callee(); end; end'
135
+ expect(process_method(source).num_statements).to eq(3)
136
+ end
137
+
138
+ it 'counts 1 statement in a for loop' do
139
+ method = process_method('def one() for i in 0..4; callee(); end; end')
140
+ expect(method.num_statements).to eq(1)
141
+ end
142
+
143
+ it 'counts 3 statements in a for loop' do
144
+ source = 'def one() for i in 0..4; callee(); callee(); callee(); end; end'
145
+ expect(process_method(source).num_statements).to eq(3)
146
+ end
147
+
148
+ it 'counts 1 statement in a rescue' do
149
+ method = process_method('def one() begin; callee(); rescue; callee(); end; end')
150
+ expect(method.num_statements).to eq(2)
151
+ end
152
+
153
+ it 'counts 3 statements in a rescue' do
154
+ method = process_method('
155
+ def one()
156
+ begin
157
+ callee(); callee(); callee()
158
+ rescue
159
+ callee(); callee(); callee()
160
+ end
161
+ end
162
+ ')
163
+ expect(method.num_statements).to eq(6)
164
+ end
165
+
166
+ it 'counts 1 statement in a when' do
167
+ method = process_method('def one() case fred; when "hi"; callee(); end; end')
168
+ expect(method.num_statements).to eq(1)
169
+ end
170
+
171
+ it 'counts 3 statements in a when' do
172
+ method = process_method('
173
+ def one()
174
+ case fred
175
+ when "hi" then callee(); callee()
176
+ when "lo" then callee()
177
+ end
178
+ end
179
+ ')
180
+ expect(method.num_statements).to eq(3)
181
+ end
182
+
183
+ it 'counts 1 statement in a case else' do
184
+ source = 'def one() case fred; when "hi"; callee(); else; callee(); end; end'
185
+ expect(process_method(source).num_statements).to eq(2)
186
+ end
187
+
188
+ it 'counts 3 statements in a case else' do
189
+ method = process_method('
190
+ def one()
191
+ case fred
192
+ when "hi" then callee(); callee(); callee()
193
+ else callee(); callee(); callee()
194
+ end
195
+ end
196
+ ')
197
+ expect(method.num_statements).to eq(6)
198
+ end
199
+
200
+ it 'does not count empty case' do
201
+ method = process_method('def one() case fred; when "hi"; ; when "lo"; ; end; end')
202
+ expect(method.num_statements).to eq(0)
203
+ end
204
+
205
+ it 'does not count empty case else' do
206
+ method = process_method('def one() case fred; when "hi"; ; else; ; end; end')
207
+ expect(method.num_statements).to eq(0)
208
+ end
209
+
210
+ it 'counts 2 statement in an iterator' do
211
+ method = process_method('def one() fred.each do; callee(); end; end')
212
+ expect(method.num_statements).to eq(2)
213
+ end
214
+
215
+ it 'counts 4 statements in an iterator' do
216
+ source = 'def one() fred.each do; callee(); callee(); callee(); end; end'
217
+ expect(process_method(source).num_statements).to eq(4)
218
+ end
219
+
220
+ it 'counts 1 statement in a singleton method' do
221
+ method = process_singleton_method('def self.foo; callee(); end')
222
+ expect(method.num_statements).to eq(1)
223
+ end
33
224
  end
34
225
  end