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
@@ -5,42 +5,42 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
5
5
  describe '#compute' do
6
6
  let(:computed) { described_class.new(warning).compute }
7
7
 
8
- shared_examples_for 'computes a fingerprint with no parameters' do
8
+ context 'when fingerprinting a warning with no parameters' do
9
9
  let(:expected_fingerprint) { 'e68badd29db51c92363a7c6a2438d722' }
10
10
  let(:warning) do
11
- FactoryGirl.build(:smell_warning,
12
- smell_detector: Reek::SmellDetectors::UtilityFunction.new,
13
- context: 'alfa',
14
- message: "doesn't depend on instance state (maybe move it to another class?)",
15
- lines: lines,
16
- source: 'a/ruby/source/file.rb')
11
+ build(:smell_warning,
12
+ smell_detector: Reek::SmellDetectors::UtilityFunction.new,
13
+ context: 'alfa',
14
+ message: "doesn't depend on instance state (maybe move it to another class?)",
15
+ lines: lines,
16
+ source: 'a/ruby/source/file.rb')
17
17
  end
18
18
 
19
- it 'computes the fingerprint' do
20
- expect(computed).to eq expected_fingerprint
21
- end
22
- end
23
-
24
- context 'with code at a specific location' do
25
- let(:lines) { [1] }
19
+ context 'with code at a specific location' do
20
+ let(:lines) { [1] }
26
21
 
27
- it_behaves_like 'computes a fingerprint with no parameters'
28
- end
22
+ it 'computes the fingerprint' do
23
+ expect(computed).to eq expected_fingerprint
24
+ end
25
+ end
29
26
 
30
- context 'with code at a different location' do
31
- let(:lines) { [5] }
27
+ context 'with code at a different location' do
28
+ let(:lines) { [5] }
32
29
 
33
- it_behaves_like 'computes a fingerprint with no parameters'
30
+ it 'computes the same fingerprint' do
31
+ expect(computed).to eq expected_fingerprint
32
+ end
33
+ end
34
34
  end
35
35
 
36
36
  context 'when the fingerprint should not be computed' do
37
37
  let(:warning) do
38
- FactoryGirl.build(:smell_warning,
39
- smell_detector: Reek::SmellDetectors::ManualDispatch.new,
40
- context: 'Alfa#bravo',
41
- message: 'manually dispatches method call',
42
- lines: [4],
43
- source: 'a/ruby/source/file.rb')
38
+ build(:smell_warning,
39
+ smell_detector: Reek::SmellDetectors::ManualDispatch.new,
40
+ context: 'Alfa#bravo',
41
+ message: 'manually dispatches method call',
42
+ lines: [4],
43
+ source: 'a/ruby/source/file.rb')
44
44
  end
45
45
 
46
46
  it 'returns nil' do
@@ -48,77 +48,79 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
48
48
  end
49
49
  end
50
50
 
51
- shared_examples_for 'computes a fingerprint with identifying parameters' do
51
+ context 'when the smell warning has only identifying parameters' do
52
52
  let(:warning) do
53
- FactoryGirl.build(:smell_warning,
54
- smell_detector: Reek::SmellDetectors::ClassVariable.new,
55
- context: 'Alfa',
56
- message: "declares the class variable '@@#{name}'",
57
- lines: [4],
58
- parameters: { name: "@@#{name}" },
59
- source: 'a/ruby/source/file.rb')
53
+ build(:smell_warning,
54
+ smell_detector: Reek::SmellDetectors::ClassVariable.new,
55
+ context: 'Alfa',
56
+ message: "declares the class variable '@@#{name}'",
57
+ lines: [4],
58
+ parameters: { name: "@@#{name}" },
59
+ source: 'a/ruby/source/file.rb')
60
60
  end
61
61
 
62
- it 'computes the fingerprint' do
63
- expect(computed).to eq expected_fingerprint
64
- end
65
- end
62
+ context 'when the name is one thing' do
63
+ let(:name) { 'bravo' }
64
+ let(:expected_fingerprint) { '9c3fd378178118a67e9509f87cae24f9' }
66
65
 
67
- context 'when the name is one thing it has one fingerprint' do
68
- let(:name) { 'bravo' }
69
- let(:expected_fingerprint) { '9c3fd378178118a67e9509f87cae24f9' }
70
-
71
- it_behaves_like 'computes a fingerprint with identifying parameters'
72
- end
66
+ it 'computes the fingerprint' do
67
+ expect(computed).to eq expected_fingerprint
68
+ end
69
+ end
73
70
 
74
- context 'when the name is another thing it has another fingerprint' do
75
- let(:name) { 'echo' }
76
- let(:expected_fingerprint) { 'd2a6d2703ce04cca65e7300b7de4b89f' }
71
+ context 'when the name is another thing' do
72
+ let(:name) { 'echo' }
73
+ let(:expected_fingerprint) { 'd2a6d2703ce04cca65e7300b7de4b89f' }
77
74
 
78
- it_behaves_like 'computes a fingerprint with identifying parameters'
75
+ it 'computes another fingerprint' do
76
+ expect(computed).to eq expected_fingerprint
77
+ end
78
+ end
79
79
  end
80
80
 
81
- shared_examples_for 'computes a fingerprint with identifying and non-identifying parameters' do
81
+ context 'when the smell warning has identifying and non-identifying parameters' do
82
82
  let(:warning) do
83
- FactoryGirl.build(:smell_warning,
84
- smell_detector: Reek::SmellDetectors::DuplicateMethodCall.new,
85
- context: "Alfa##{name}",
86
- message: "calls '#{name}' #{count} times",
87
- lines: lines,
88
- parameters: { name: "@@#{name}", count: count },
89
- source: 'a/ruby/source/file.rb')
83
+ build(:smell_warning,
84
+ smell_detector: Reek::SmellDetectors::DuplicateMethodCall.new,
85
+ context: "Alfa##{name}",
86
+ message: "calls '#{name}' #{count} times",
87
+ lines: lines,
88
+ parameters: { name: "@@#{name}", count: count },
89
+ source: 'a/ruby/source/file.rb')
90
90
  end
91
91
 
92
- it 'computes the fingerprint' do
93
- expect(computed).to eq expected_fingerprint
94
- end
95
- end
96
-
97
- context 'when the parameters are provided' do
98
- let(:name) { 'bravo' }
99
- let(:count) { 5 }
100
- let(:lines) { [1, 7, 10, 13, 15] }
101
- let(:expected_fingerprint) { '238733f4f51ba5473dcbe94a43ec5400' }
92
+ context 'when the parameters are provided' do
93
+ let(:name) { 'bravo' }
94
+ let(:count) { 5 }
95
+ let(:lines) { [1, 7, 10, 13, 15] }
96
+ let(:expected_fingerprint) { '238733f4f51ba5473dcbe94a43ec5400' }
102
97
 
103
- it_behaves_like 'computes a fingerprint with identifying and non-identifying parameters'
104
- end
98
+ it 'computes the fingerprint' do
99
+ expect(computed).to eq expected_fingerprint
100
+ end
101
+ end
105
102
 
106
- context 'when the non-identifying parameters change, it computes the same fingerprint' do
107
- let(:name) { 'bravo' }
108
- let(:count) { 9 }
109
- let(:lines) { [1, 7, 10, 13, 15, 17, 19, 20, 25] }
110
- let(:expected_fingerprint) { '238733f4f51ba5473dcbe94a43ec5400' }
103
+ context 'when the non-identifying parameters change' do
104
+ let(:name) { 'bravo' }
105
+ let(:count) { 9 }
106
+ let(:lines) { [1, 7, 10, 13, 15, 17, 19, 20, 25] }
107
+ let(:expected_fingerprint) { '238733f4f51ba5473dcbe94a43ec5400' }
111
108
 
112
- it_behaves_like 'computes a fingerprint with identifying and non-identifying parameters'
113
- end
109
+ it 'computes the same fingerprint' do
110
+ expect(computed).to eq expected_fingerprint
111
+ end
112
+ end
114
113
 
115
- context 'but when the identifying parameters change, it computes a different fingerprint' do
116
- let(:name) { 'echo' }
117
- let(:count) { 5 }
118
- let(:lines) { [1, 7, 10, 13, 15] }
119
- let(:expected_fingerprint) { 'e0c35e9223cc19bdb9a04fb3e60573e1' }
114
+ context 'when the identifying parameters change' do
115
+ let(:name) { 'echo' }
116
+ let(:count) { 5 }
117
+ let(:lines) { [1, 7, 10, 13, 15] }
118
+ let(:expected_fingerprint) { 'e0c35e9223cc19bdb9a04fb3e60573e1' }
120
119
 
121
- it_behaves_like 'computes a fingerprint with identifying and non-identifying parameters'
120
+ it 'computes a different fingerprint' do
121
+ expect(computed).to eq expected_fingerprint
122
+ end
123
+ end
122
124
  end
123
125
  end
124
126
  end
@@ -4,12 +4,12 @@ require_lib 'reek/report/code_climate/code_climate_formatter'
4
4
  RSpec.describe Reek::Report::CodeClimateFormatter do
5
5
  describe '#render' do
6
6
  let(:warning) do
7
- FactoryGirl.build(:smell_warning,
8
- smell_detector: Reek::SmellDetectors::UtilityFunction.new,
9
- context: 'context foo',
10
- message: 'message bar',
11
- lines: [1, 2],
12
- source: 'a/ruby/source/file.rb')
7
+ build(:smell_warning,
8
+ smell_detector: Reek::SmellDetectors::UtilityFunction.new,
9
+ context: 'context foo',
10
+ message: 'message bar',
11
+ lines: [1, 2],
12
+ source: 'a/ruby/source/file.rb')
13
13
  end
14
14
  let(:rendered) { described_class.new(warning).render }
15
15
  let(:json) { JSON.parse rendered.chop }
@@ -5,7 +5,7 @@ require_lib 'reek/report/xml_report'
5
5
  RSpec.describe Reek::Report::XMLReport do
6
6
  let(:xml_report) { described_class.new }
7
7
 
8
- context 'empty source' do
8
+ context 'with an empty source' do
9
9
  it 'prints empty checkstyle XML' do
10
10
  xml_report.add_examiner Reek::Examiner.new('')
11
11
  xml = "<?xml version='1.0'?>\n<checkstyle/>\n"
@@ -13,7 +13,7 @@ RSpec.describe Reek::Report::XMLReport do
13
13
  end
14
14
  end
15
15
 
16
- context 'source with voliations' do
16
+ context 'with a source with violations' do
17
17
  it 'prints non-empty checkstyle XML' do
18
18
  xml_report.add_examiner Reek::Examiner.new(SMELLY_FILE)
19
19
  xml = SAMPLES_PATH.join('checkstyle.xml').read
@@ -27,7 +27,7 @@ RSpec.describe Reek::SmellDetectors::BooleanParameter do
27
27
  and reek_of(:BooleanParameter, lines: [1], context: 'alfa', parameter: 'charlie')
28
28
  end
29
29
 
30
- context 'in a method' do
30
+ context 'when examining an instance method' do
31
31
  it 'reports a parameter defaulted to false' do
32
32
  src = 'def alfa(bravo = false) end'
33
33
  expect(src).to reek_of(:BooleanParameter)
@@ -70,7 +70,7 @@ RSpec.describe Reek::SmellDetectors::BooleanParameter do
70
70
  end
71
71
  end
72
72
 
73
- context 'in a singleton method' do
73
+ context 'when examining a singleton method' do
74
74
  it 'reports a parameter defaulted to true' do
75
75
  src = 'def self.alfa(bravo = true) end'
76
76
  expect(src).to reek_of(:BooleanParameter)
@@ -63,49 +63,43 @@ RSpec.describe Reek::SmellDetectors::ClassVariable do
63
63
  end
64
64
 
65
65
  ['class', 'module'].each do |scope|
66
- context "Scoped to #{scope}" do
67
- context 'set in a method' do
68
- it 'reports correctly' do
69
- src = <<-EOS
70
- #{scope} Alfa
71
- def bravo
72
- @@charlie = {}
73
- end
66
+ context "when examining a #{scope}" do
67
+ it 'reports a class variable set in a method' do
68
+ src = <<-EOS
69
+ #{scope} Alfa
70
+ def bravo
71
+ @@charlie = {}
74
72
  end
75
- EOS
73
+ end
74
+ EOS
76
75
 
77
- expect(src).to reek_of(:ClassVariable)
78
- end
76
+ expect(src).to reek_of(:ClassVariable, name: '@@charlie')
79
77
  end
80
78
 
81
- context 'used in a method' do
82
- it 'reports correctly' do
83
- src = <<-EOS
84
- #{scope} Alfa
85
- def bravo
86
- puts @@charlie
87
- end
79
+ it 'reports a class variable used in a method' do
80
+ src = <<-EOS
81
+ #{scope} Alfa
82
+ def bravo
83
+ puts @@charlie
88
84
  end
89
- EOS
85
+ end
86
+ EOS
90
87
 
91
- expect(src).to reek_of(:ClassVariable)
92
- end
88
+ expect(src).to reek_of(:ClassVariable, name: '@@charlie')
93
89
  end
94
90
 
95
- context "set in #{scope} and used in a method" do
96
- it 'reports correctly' do
97
- src = <<-EOS
98
- #{scope} Alfa
99
- @@bravo = 42
91
+ it "reports a class variable set in the #{scope} body and used in a method" do
92
+ src = <<-EOS
93
+ #{scope} Alfa
94
+ @@bravo = 42
100
95
 
101
- def charlie
102
- puts @@bravo
103
- end
96
+ def charlie
97
+ puts @@bravo
104
98
  end
105
- EOS
99
+ end
100
+ EOS
106
101
 
107
- expect(src).to reek_of(:ClassVariable)
108
- end
102
+ expect(src).to reek_of(:ClassVariable, name: '@@bravo')
109
103
  end
110
104
  end
111
105
  end
@@ -30,7 +30,7 @@ RSpec.describe Reek::SmellDetectors::ControlParameter do
30
30
  and reek_of(:ControlParameter, lines: [3], argument: 'charlie')
31
31
  end
32
32
 
33
- context 'parameter not used to determine code path' do
33
+ context 'when a parameter is not used to determine code path' do
34
34
  it 'does not report a ternary check on an ivar' do
35
35
  src = 'def alfa(bravo) @charlie ? bravo : false end'
36
36
  expect(src).not_to reek_of(:ControlParameter)
@@ -52,7 +52,7 @@ RSpec.describe Reek::SmellDetectors::ControlParameter do
52
52
  end
53
53
  end
54
54
 
55
- context 'parameter only used to determine code path' do
55
+ context 'when a parameter is only used to determine code path' do
56
56
  it 'reports a ternary check on a parameter' do
57
57
  src = 'def alfa(bravo); bravo ? true : false; end'
58
58
  expect(src).to reek_of(:ControlParameter)
@@ -144,7 +144,7 @@ RSpec.describe Reek::SmellDetectors::ControlParameter do
144
144
  expect(src).to reek_of(:ControlParameter)
145
145
  end
146
146
 
147
- it 'reports on nested if statements where the inner if is a control parameter' do
147
+ it 'reports on nested suffix if statements where the inner if is a control parameter' do
148
148
  src = <<-EOS
149
149
  def nested(bravo)
150
150
  if true
@@ -157,6 +157,36 @@ RSpec.describe Reek::SmellDetectors::ControlParameter do
157
157
  expect(src).to reek_of(:ControlParameter)
158
158
  end
159
159
 
160
+ it 'reports on nested full if statements where the inner if is a control parameter' do
161
+ src = <<-EOS
162
+ def alfa(bravo)
163
+ if true
164
+ charlie
165
+ else
166
+ if bravo
167
+ delta
168
+ end
169
+ end
170
+ end
171
+ EOS
172
+
173
+ expect(src).to reek_of(:ControlParameter)
174
+ end
175
+
176
+ it 'reports on elsif statements' do
177
+ src = <<-EOS
178
+ def alfa(bravo)
179
+ if true
180
+ charlie
181
+ elsif bravo
182
+ delta
183
+ end
184
+ end
185
+ EOS
186
+
187
+ expect(src).to reek_of(:ControlParameter)
188
+ end
189
+
160
190
  it 'reports on explicit comparison in the condition' do
161
191
  src = 'def alfa(bravo); if bravo == charlie then charlie end; end'
162
192
  expect(src).to reek_of(:ControlParameter)
@@ -188,7 +218,7 @@ RSpec.describe Reek::SmellDetectors::ControlParameter do
188
218
  end
189
219
  end
190
220
 
191
- context 'parameter used besides determining code path' do
221
+ context 'when a parameter is used besides determining code path' do
192
222
  it 'does not report on if conditional expression' do
193
223
  src = 'def alfa(bravo); if bravo then charlie(bravo); end end'
194
224
  expect(src).not_to reek_of(:ControlParameter)
@@ -150,7 +150,7 @@ RSpec.describe Reek::SmellDetectors::DuplicateMethodCall do
150
150
  end
151
151
  end
152
152
 
153
- context 'non-repeated method calls' do
153
+ context 'with non-repeated method calls' do
154
154
  it 'does not report similar calls' do
155
155
  src = 'def alfa(bravo) bravo.charlie == self.charlie end'
156
156
  expect(src).not_to reek_of(:DuplicateMethodCall)
@@ -162,7 +162,7 @@ RSpec.describe Reek::SmellDetectors::DuplicateMethodCall do
162
162
  end
163
163
  end
164
164
 
165
- context 'allowing up to 3 calls' do
165
+ context 'when allowing up to 3 calls' do
166
166
  let(:config) do
167
167
  { Reek::SmellDetectors::DuplicateMethodCall::MAX_ALLOWED_CALLS_KEY => 3 }
168
168
  end
@@ -189,7 +189,7 @@ RSpec.describe Reek::SmellDetectors::DuplicateMethodCall do
189
189
  end
190
190
  end
191
191
 
192
- context 'allowing calls to some methods' do
192
+ context 'when allowing calls to some methods' do
193
193
  it 'does not report calls to some methods' do
194
194
  config = { Reek::SmellDetectors::DuplicateMethodCall::ALLOW_CALLS_KEY => ['@bravo.charlie'] }
195
195
  src = 'def alfa; @bravo.charlie + @bravo.charlie; end'
@@ -50,6 +50,20 @@ RSpec.describe Reek::SmellDetectors::ModuleInitialize do
50
50
  expect(src).not_to reek_of(:ModuleInitialize)
51
51
  end
52
52
 
53
+ it 'reports nothing for a method named initialize in a nested dynamic class' do
54
+ src = <<-EOF
55
+ module Alfa
56
+ def self.bravo
57
+ Class.new do
58
+ def initialize; end
59
+ end
60
+ end
61
+ end
62
+ EOF
63
+
64
+ expect(src).not_to reek_of(:ModuleInitialize)
65
+ end
66
+
53
67
  it 'can be disabled via comment' do
54
68
  src = <<-EOS
55
69
  # :reek:ModuleInitialize