reek 4.7.3 → 4.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +1 -0
  4. data/.travis.yml +4 -2
  5. data/CHANGELOG.md +10 -0
  6. data/Gemfile +3 -3
  7. data/README.md +19 -4
  8. data/lib/reek/ast/node.rb +37 -49
  9. data/lib/reek/ast/reference_collector.rb +2 -4
  10. data/lib/reek/ast/sexp_extensions/case.rb +1 -1
  11. data/lib/reek/ast/sexp_extensions/if.rb +8 -1
  12. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  13. data/lib/reek/ast/sexp_extensions/methods.rb +3 -5
  14. data/lib/reek/configuration/configuration_file_finder.rb +3 -3
  15. data/lib/reek/context/code_context.rb +4 -7
  16. data/lib/reek/context/method_context.rb +5 -10
  17. data/lib/reek/context/module_context.rb +3 -3
  18. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +9 -9
  19. data/lib/reek/errors/bad_detector_in_comment_error.rb +7 -7
  20. data/lib/reek/errors/base_error.rb +3 -0
  21. data/lib/reek/errors/encoding_error.rb +16 -11
  22. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +7 -7
  23. data/lib/reek/errors/incomprehensible_source_error.rb +20 -22
  24. data/lib/reek/examiner.rb +18 -14
  25. data/lib/reek/logging_error_handler.rb +7 -5
  26. data/lib/reek/smell_detectors/class_variable.rb +3 -10
  27. data/lib/reek/smell_detectors/duplicate_method_call.rb +1 -1
  28. data/lib/reek/smell_detectors/instance_variable_assumption.rb +1 -9
  29. data/lib/reek/smell_detectors/manual_dispatch.rb +1 -1
  30. data/lib/reek/smell_detectors/module_initialize.rb +1 -1
  31. data/lib/reek/smell_detectors/nested_iterators.rb +2 -1
  32. data/lib/reek/smell_detectors/too_many_constants.rb +1 -1
  33. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +2 -2
  34. data/lib/reek/smell_detectors/utility_function.rb +1 -1
  35. data/lib/reek/source/source_code.rb +9 -23
  36. data/lib/reek/version.rb +1 -1
  37. data/reek.gemspec +2 -2
  38. data/spec/factories/factories.rb +2 -13
  39. data/spec/reek/ast/node_spec.rb +98 -5
  40. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  41. data/spec/reek/ast/sexp_extensions_spec.rb +2 -2
  42. data/spec/reek/cli/application_spec.rb +39 -41
  43. data/spec/reek/cli/command/todo_list_command_spec.rb +2 -2
  44. data/spec/reek/code_comment_spec.rb +32 -32
  45. data/spec/reek/configuration/app_configuration_spec.rb +3 -3
  46. data/spec/reek/configuration/configuration_file_finder_spec.rb +1 -1
  47. data/spec/reek/configuration/directory_directives_spec.rb +3 -3
  48. data/spec/reek/configuration/excluded_paths_spec.rb +1 -1
  49. data/spec/reek/context/code_context_spec.rb +59 -95
  50. data/spec/reek/context/ghost_context_spec.rb +1 -1
  51. data/spec/reek/context/root_context_spec.rb +1 -1
  52. data/spec/reek/errors/base_error_spec.rb +13 -0
  53. data/spec/reek/examiner_spec.rb +72 -29
  54. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
  55. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  56. data/spec/reek/report/xml_report_spec.rb +2 -2
  57. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  58. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  59. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  60. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  61. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  62. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  63. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +3 -3
  64. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  65. data/spec/reek/smell_detectors/unused_private_method_spec.rb +9 -9
  66. data/spec/reek/smell_detectors/utility_function_spec.rb +5 -5
  67. data/spec/reek/smell_warning_spec.rb +8 -8
  68. data/spec/reek/source/source_code_spec.rb +5 -25
  69. data/spec/reek/source/source_locator_spec.rb +6 -6
  70. data/spec/reek/spec/should_reek_of_spec.rb +7 -7
  71. data/spec/reek/spec/should_reek_spec.rb +2 -2
  72. data/spec/reek/spec/smell_matcher_spec.rb +3 -3
  73. data/spec/reek/tree_dresser_spec.rb +11 -12
  74. data/spec/spec_helper.rb +3 -12
  75. metadata +10 -9
@@ -75,7 +75,7 @@ RSpec.describe Reek::Configuration::AppConfiguration do
75
75
  end
76
76
 
77
77
  describe '#directive_for' do
78
- context 'multiple directory directives and no default directive present' do
78
+ context 'with multiple directory directives and no default directive present' do
79
79
  let(:source_via) { 'samples/three_clean_files/dummy.rb' }
80
80
  let(:baz_config) { { Reek::SmellDetectors::IrresponsibleModule => { enabled: false } } }
81
81
  let(:bang_config) { { Reek::SmellDetectors::Attribute => { enabled: true } } }
@@ -93,7 +93,7 @@ RSpec.describe Reek::Configuration::AppConfiguration do
93
93
  end
94
94
  end
95
95
 
96
- context 'directory directive and default directive present' do
96
+ context 'with directory directive and default directive present' do
97
97
  let(:directory) { 'spec/samples/two_smelly_files/' }
98
98
  let(:directory_config) { { Reek::SmellDetectors::TooManyStatements => { max_statements: 8 } } }
99
99
  let(:directory_directives) { { directory => directory_config } }
@@ -116,7 +116,7 @@ RSpec.describe Reek::Configuration::AppConfiguration do
116
116
  end
117
117
  end
118
118
 
119
- context 'no directory directive but a default directive present' do
119
+ context 'with no directory directive but a default directive present' do
120
120
  let(:source_via) { 'spec/samples/three_clean_files/dummy.rb' }
121
121
  let(:default_directive) { { Reek::SmellDetectors::IrresponsibleModule => { enabled: false } } }
122
122
  let(:attribute_config) { { Reek::SmellDetectors::Attribute => { enabled: false } } }
@@ -90,7 +90,7 @@ RSpec.describe Reek::Configuration::ConfigurationFileFinder do
90
90
  end
91
91
  end
92
92
 
93
- context 'more than one configuration file' do
93
+ context 'with more than one configuration file' do
94
94
  let(:path) { CONFIG_PATH.join('more_than_one_configuration_file') }
95
95
 
96
96
  it 'prints a message on STDERR' do
@@ -13,13 +13,13 @@ RSpec.describe Reek::Configuration::DirectoryDirectives do
13
13
  end
14
14
  let(:source_via) { 'foo/bar/bang/dummy.rb' }
15
15
 
16
- context 'our source is in a directory for which we have a directive' do
16
+ context 'when our source is in a directory for which we have a directive' do
17
17
  it 'returns the corresponding directive' do
18
18
  expect(directives.directive_for(source_via)).to eq(bang_config)
19
19
  end
20
20
  end
21
21
 
22
- context 'our source is not in a directory for which we have a directive' do
22
+ context 'when our source is not in a directory for which we have a directive' do
23
23
  it 'returns nil' do
24
24
  expect(directives.directive_for('does/not/exist')).to eq(nil)
25
25
  end
@@ -31,7 +31,7 @@ RSpec.describe Reek::Configuration::DirectoryDirectives do
31
31
  {}.extend(described_class)
32
32
  end
33
33
 
34
- context 'one of given paths is a file' do
34
+ context 'when one of given paths is a file' do
35
35
  let(:file_as_path) { SAMPLES_PATH.join('inline.rb') }
36
36
 
37
37
  it 'raises an error' do
@@ -5,7 +5,7 @@ RSpec.describe Reek::Configuration::ExcludedPaths do
5
5
  describe '#add' do
6
6
  let(:exclusions) { [].extend(described_class) }
7
7
 
8
- context 'one of given paths is a file' do
8
+ context 'when one of given paths is a file' do
9
9
  let(:file_as_path) { SAMPLES_PATH.join('inline.rb') }
10
10
  let(:paths) { [SAMPLES_PATH, file_as_path] }
11
11
 
@@ -3,7 +3,7 @@ require_lib 'reek/context/method_context'
3
3
  require_lib 'reek/context/module_context'
4
4
 
5
5
  RSpec.describe Reek::Context::CodeContext do
6
- context 'name recognition' do
6
+ describe '#full_name' do
7
7
  let(:ctx) { described_class.new(exp) }
8
8
  let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode') }
9
9
  let(:exp_name) { 'random_name' }
@@ -14,9 +14,54 @@ RSpec.describe Reek::Context::CodeContext do
14
14
  allow(exp).to receive(:full_name).and_return(full_name)
15
15
  end
16
16
 
17
+ it 'creates the correct full name' do
18
+ expect(ctx.full_name).to eq(full_name)
19
+ end
20
+
21
+ context 'when there is an outer' do
22
+ let(:outer_name) { 'another_random sting' }
23
+ let(:outer) { described_class.new(instance_double('Reek::AST::Node')) }
24
+
25
+ before do
26
+ ctx.register_with_parent outer
27
+ allow(outer).to receive(:full_name).at_least(:once).and_return(outer_name)
28
+ end
29
+
30
+ it 'creates the correct full name' do
31
+ expect(ctx.full_name).to eq(full_name)
32
+ end
33
+
34
+ it 'passes the outer name to exp#full_name' do
35
+ ctx.full_name
36
+ expect(exp).to have_received(:full_name).with outer_name
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#name' do
42
+ let(:ctx) { described_class.new(exp) }
43
+ let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode') }
44
+ let(:exp_name) { 'random_name' }
45
+
46
+ before do
47
+ allow(exp).to receive(:name).and_return(exp_name)
48
+ end
49
+
17
50
  it 'gets its short name from the exp' do
18
51
  expect(ctx.name).to eq(exp_name)
19
52
  end
53
+ end
54
+
55
+ describe '#matches?' do
56
+ let(:ctx) { described_class.new(exp) }
57
+ let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode') }
58
+ let(:exp_name) { 'random_name' }
59
+ let(:full_name) { "::::::::::::::::::::#{exp_name}" }
60
+
61
+ before do
62
+ allow(exp).to receive(:name).and_return(exp_name)
63
+ allow(exp).to receive(:full_name).and_return(full_name)
64
+ end
20
65
 
21
66
  it 'does not match an empty list' do
22
67
  expect(ctx.matches?([])).to eq(false)
@@ -31,19 +76,30 @@ RSpec.describe Reek::Context::CodeContext do
31
76
  end
32
77
 
33
78
  it 'recognises its own short name' do
79
+ expect(ctx.matches?([exp_name])).to eq(true)
80
+ end
81
+
82
+ it 'recognises its own short name in a list' do
34
83
  expect(ctx.matches?(['banana', exp_name])).to eq(true)
35
84
  end
36
85
 
37
86
  it 'recognises its short name as a regex' do
38
- expect(ctx.matches?([/banana/, /#{exp_name}/])).to eq(true)
87
+ expect(ctx.matches?([/#{exp_name}/])).to eq(true)
39
88
  end
40
89
 
41
90
  it 'does not blow up on []-ended Strings' do
42
91
  expect(ctx.matches?(['banana[]', exp_name])).to eq(true)
43
92
  end
44
93
 
94
+ it 'recognises its own full name' do
95
+ expect(ctx.matches?(['banana', full_name])).to eq(true)
96
+ end
97
+
98
+ it 'recognises its full name as a regex' do
99
+ expect(ctx.matches?([/banana/, /#{full_name}/])).to eq(true)
100
+ end
101
+
45
102
  context 'when there is an outer' do
46
- let(:ctx) { described_class.new(exp) }
47
103
  let(:outer_name) { 'another_random sting' }
48
104
  let(:outer) { described_class.new(instance_double('Reek::AST::Node')) }
49
105
 
@@ -52,10 +108,6 @@ RSpec.describe Reek::Context::CodeContext do
52
108
  allow(outer).to receive(:full_name).at_least(:once).and_return(outer_name)
53
109
  end
54
110
 
55
- it 'creates the correct full name' do
56
- expect(ctx.full_name).to eq(full_name)
57
- end
58
-
59
111
  it 'recognises its own full name' do
60
112
  expect(ctx.matches?(['banana', full_name])).to eq(true)
61
113
  end
@@ -66,94 +118,6 @@ RSpec.describe Reek::Context::CodeContext do
66
118
  end
67
119
  end
68
120
 
69
- context 'enumerating syntax elements' do
70
- context 'in an empty module' do
71
- let(:ctx) do
72
- src = 'module Emptiness; end'
73
- ast = Reek::Source::SourceCode.from(src).syntax_tree
74
- described_class.new(ast)
75
- end
76
-
77
- it 'yields no calls' do
78
- ctx.each_node(:send, []) { |exp| raise "#{exp} yielded by empty module!" }
79
- end
80
-
81
- it 'yields one module' do
82
- mods = 0
83
- ctx.each_node(:module, []) { |_exp| mods += 1 }
84
- expect(mods).to eq(1)
85
- end
86
-
87
- it "yields the module's full AST" do
88
- ctx.each_node(:module, []) do |exp|
89
- expect(exp).to eq(sexp(:module, sexp(:const, nil, :Emptiness), nil))
90
- end
91
- end
92
-
93
- it 'returns an empty array of ifs when no block is passed' do
94
- expect(ctx.each_node(:if, [])).to be_empty
95
- end
96
- end
97
-
98
- context 'with a nested element' do
99
- let(:ctx) do
100
- src = "module Loneliness; def calloo; puts('hello') end; end"
101
- ast = Reek::Source::SourceCode.from(src).syntax_tree
102
- described_class.new(ast)
103
- end
104
-
105
- it 'yields no ifs' do
106
- ctx.each_node(:if, []) { |exp| raise "#{exp} yielded by empty module!" }
107
- end
108
- it 'yields one module' do
109
- expect(ctx.each_node(:module, []).length).to eq(1)
110
- end
111
-
112
- it "yields the module's full AST" do
113
- ctx.each_node(:module, []) do |exp|
114
- expect(exp).to eq sexp(:module,
115
- sexp(:const, nil, :Loneliness),
116
- sexp(:def, :calloo,
117
- sexp(:args),
118
- sexp(:send, nil, :puts, sexp(:str, 'hello'))))
119
- end
120
- end
121
-
122
- it 'yields one method' do
123
- expect(ctx.each_node(:def, []).length).to eq(1)
124
- end
125
-
126
- it "yields the method's full AST" do
127
- ctx.each_node(:def, []) { |exp| expect(exp.children.first).to eq(:calloo) }
128
- end
129
-
130
- it 'ignores the call inside the method if the traversal is pruned' do
131
- expect(ctx.each_node(:send, [:def])).to be_empty
132
- end
133
- end
134
-
135
- it 'finds 3 ifs in a class' do
136
- src = <<-EOS
137
- class Scrunch
138
- def first
139
- return @field == :sym ? 0 : 3;
140
- end
141
- def second
142
- if @field == :sym
143
- @other += " quarts"
144
- end
145
- end
146
- def third
147
- raise 'flu!' unless @field == :sym
148
- end
149
- end
150
- EOS
151
- ast = Reek::Source::SourceCode.from(src).syntax_tree
152
- ctx = described_class.new(ast)
153
- expect(ctx.each_node(:if, []).length).to eq(3)
154
- end
155
- end
156
-
157
121
  describe '#config_for' do
158
122
  let(:src) do
159
123
  <<-EOS
@@ -42,7 +42,7 @@ RSpec.describe Reek::Context::GhostContext do
42
42
  expect(ghost.children).to include child
43
43
  end
44
44
 
45
- context 'if the grandparent is also a ghost' do
45
+ context 'when the grandparent is also a ghost' do
46
46
  let(:child_ghost) { described_class.new(nil) }
47
47
 
48
48
  before do
@@ -2,7 +2,7 @@ require_relative '../../spec_helper'
2
2
  require_lib 'reek/context/root_context'
3
3
 
4
4
  RSpec.describe Reek::Context::RootContext do
5
- context 'full_name' do
5
+ describe '#full_name' do
6
6
  it 'reports full context' do
7
7
  ast = Reek::Source::SourceCode.from('foo = 1').syntax_tree
8
8
  root = described_class.new(ast)
@@ -0,0 +1,13 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require_lib 'reek/errors/base_error'
4
+
5
+ RSpec.describe Reek::Errors::BaseError do
6
+ let(:error) { described_class.new }
7
+
8
+ describe '#long_message' do
9
+ it 'returns the message' do
10
+ expect(error.long_message).to eq error.message
11
+ end
12
+ end
13
+ end
@@ -58,27 +58,12 @@ RSpec.describe Reek::Examiner do
58
58
  it_behaves_like 'no smells found'
59
59
  end
60
60
 
61
- describe '.new' do
62
- context 'returns a proper Examiner' do
63
- let(:source) { 'class C; def f; end; end' }
64
- let(:examiner) do
65
- described_class.new(source)
66
- end
67
-
68
- it 'has been run on the given source' do
69
- expect(examiner.origin).to eq('string')
70
- end
71
-
72
- it 'has the right smells' do
73
- smells = examiner.smells
74
- expect(smells[0].message).to eq('has no descriptive comment')
75
- expect(smells[1].message).to eq("has the name 'f'")
76
- expect(smells[2].message).to eq("has the name 'C'")
77
- end
61
+ describe '#origin' do
62
+ let(:source) { 'class C; def f; end; end' }
63
+ let(:examiner) { described_class.new(source) }
78
64
 
79
- it 'has the right smell count' do
80
- expect(examiner.smells_count).to eq(3)
81
- end
65
+ it 'returns "string" for a string source' do
66
+ expect(examiner.origin).to eq('string')
82
67
  end
83
68
  end
84
69
 
@@ -92,7 +77,20 @@ RSpec.describe Reek::Examiner do
92
77
  expect(smell.message).to eq("calls 'bar.call_me()' 2 times")
93
78
  end
94
79
 
95
- context 'source is empty' do
80
+ context 'with a source with three smells' do
81
+ let(:source) { 'class C; def f; end; end' }
82
+ let(:examiner) { described_class.new(source) }
83
+
84
+ it 'has the right smells' do
85
+ smells = examiner.smells
86
+ expect(smells.map(&:message)).
87
+ to eq ['has no descriptive comment',
88
+ "has the name 'f'",
89
+ "has the name 'C'"]
90
+ end
91
+ end
92
+
93
+ context 'when source only contains comments' do
96
94
  let(:source) do
97
95
  <<-EOS
98
96
  # Just a comment
@@ -129,18 +127,35 @@ RSpec.describe Reek::Examiner do
129
127
 
130
128
  it 'explains the origin of the error' do
131
129
  origin = 'string'
132
- expect { examiner.smells }.to raise_error.with_message(/#{origin}/)
130
+ expect { examiner.smells }.
131
+ to raise_error.with_message("Source #{origin} cannot be processed by Reek.")
133
132
  end
134
133
 
135
134
  it 'explains what to do' do
136
- explanation = 'Please double check your Reek configuration'
137
- expect { examiner.smells }.to raise_error.with_message(/#{explanation}/)
135
+ explanation = 'It would be great if you could report this back to the Reek team'
136
+ expect { examiner.smells }.
137
+ to raise_error { |it| expect(it.long_message).to match(/#{explanation}/) }
138
138
  end
139
139
 
140
140
  it 'contains the original error message' do
141
141
  original = 'Looks like bad source'
142
- expect { examiner.smells }.to raise_error.with_message(/#{original}/)
142
+ expect { examiner.smells }.
143
+ to raise_error { |it| expect(it.long_message).to match(/#{original}/) }
143
144
  end
145
+
146
+ it 'shows the original exception class' do
147
+ expect { examiner.smells }.
148
+ to raise_error { |it| expect(it.long_message).to match(/ArgumentError/) }
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#smells_count' do
154
+ let(:source) { 'class C; def f; end; end' }
155
+ let(:examiner) { described_class.new(source) }
156
+
157
+ it 'has the right smell count' do
158
+ expect(examiner.smells_count).to eq(3)
144
159
  end
145
160
  end
146
161
 
@@ -153,7 +168,7 @@ RSpec.describe Reek::Examiner do
153
168
  allow(Parser::Source::Buffer).to receive(:new).and_return(buffer)
154
169
  end
155
170
 
156
- context 'if the error handler does not handle the error' do
171
+ context 'when the error handler does not handle the error' do
157
172
  let(:examiner) { described_class.new(source) }
158
173
 
159
174
  it 'does not raise an error during initialization' do
@@ -165,7 +180,7 @@ RSpec.describe Reek::Examiner do
165
180
  end
166
181
  end
167
182
 
168
- context 'if the error handler handles the error' do
183
+ context 'when the error handler handles the error' do
169
184
  let(:handler) { instance_double(Reek::LoggingErrorHandler, handle: true) }
170
185
  let(:examiner) { described_class.new(source, error_handler: handler) }
171
186
 
@@ -180,10 +195,38 @@ RSpec.describe Reek::Examiner do
180
195
  end
181
196
  end
182
197
 
198
+ context 'with a source that triggers an encoding error' do
199
+ let(:examiner) { described_class.new(source) }
200
+ let(:source) do
201
+ <<-SRC.strip_heredoc
202
+ # encoding: US-ASCII
203
+ puts 'こんにちは世界'
204
+ SRC
205
+ end
206
+
207
+ it 'does not raise an error during initialization' do
208
+ expect { examiner }.not_to raise_error
209
+ end
210
+
211
+ it 'raises an encoding error when asked for smells' do
212
+ expect { examiner.smells }.to raise_error Reek::Errors::EncodingError
213
+ end
214
+
215
+ it 'explains the origin of the error' do
216
+ message = "Source 'string' cannot be processed by Reek due to an encoding error in the source file."
217
+ expect { examiner.smells }.to raise_error.with_message(/#{message}/)
218
+ end
219
+
220
+ it 'shows the original exception class' do
221
+ expect { examiner.smells }.
222
+ to raise_error { |it| expect(it.long_message).to match(/InvalidByteSequenceError/) }
223
+ end
224
+ end
225
+
183
226
  describe 'bad comment config' do
184
227
  let(:examiner) { described_class.new(source) }
185
228
 
186
- context 'unknown smell detector' do
229
+ context 'with an unknown smell detector' do
187
230
  let(:source) do
188
231
  <<-EOS
189
232
  # :reek:DoesNotExist
@@ -209,7 +252,7 @@ RSpec.describe Reek::Examiner do
209
252
  end
210
253
  end
211
254
 
212
- context 'garbage in detector config' do
255
+ context 'with garbage in detector config' do
213
256
  let(:source) do
214
257
  <<-EOS
215
258
  # :reek:UncommunicativeMethodName { thats: a: bad: config }