reek 4.7.3 → 5.0.0

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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +18 -3
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -4
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +45 -0
  8. data/Gemfile +4 -4
  9. data/README.md +134 -104
  10. data/Rakefile +16 -3
  11. data/bin/reek +1 -3
  12. data/docs/API.md +2 -9
  13. data/docs/Basic-Smell-Options.md +51 -11
  14. data/docs/Code-Smells.md +1 -1
  15. data/docs/Command-Line-Options.md +14 -4
  16. data/docs/Duplicate-Method-Call.md +49 -1
  17. data/docs/Feature-Envy.md +44 -0
  18. data/docs/How-To-Write-New-Detectors.md +2 -3
  19. data/docs/Instance-Variable-Assumption.md +1 -1
  20. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  21. data/docs/Rake-Task.md +1 -1
  22. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  23. data/docs/Reek-Driven-Development.md +1 -1
  24. data/docs/Uncommunicative-Method-Name.md +45 -6
  25. data/docs/Uncommunicative-Module-Name.md +49 -7
  26. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  27. data/docs/Uncommunicative-Variable-Name.md +73 -2
  28. data/docs/Unused-Private-Method.md +2 -2
  29. data/docs/defaults.reek.yml +129 -0
  30. data/docs/yard_plugin.rb +1 -0
  31. data/features/command_line_interface/basic_usage.feature +2 -2
  32. data/features/command_line_interface/options.feature +46 -4
  33. data/features/command_line_interface/show_progress.feature +4 -4
  34. data/features/command_line_interface/smell_selection.feature +1 -1
  35. data/features/command_line_interface/smells_count.feature +6 -6
  36. data/features/command_line_interface/stdin.feature +30 -8
  37. data/features/configuration_files/accept_setting.feature +45 -28
  38. data/features/configuration_files/directory_specific_directives.feature +78 -73
  39. data/features/configuration_files/exclude_directives.feature +11 -10
  40. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  41. data/features/configuration_files/masking_smells.feature +38 -9
  42. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  43. data/features/configuration_files/reject_setting.feature +52 -41
  44. data/features/configuration_files/schema_validation.feature +59 -0
  45. data/features/configuration_files/unused_private_method.feature +18 -16
  46. data/features/configuration_loading.feature +53 -10
  47. data/features/configuration_via_source_comments/erroneous_source_comments.feature +2 -2
  48. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  49. data/features/locales.feature +2 -2
  50. data/features/rake_task/rake_task.feature +15 -15
  51. data/features/reports/json.feature +3 -3
  52. data/features/reports/reports.feature +34 -34
  53. data/features/reports/yaml.feature +3 -3
  54. data/features/rspec_matcher.feature +9 -1
  55. data/features/samples.feature +287 -287
  56. data/features/step_definitions/reek_steps.rb +4 -0
  57. data/features/step_definitions/sample_file_steps.rb +9 -4
  58. data/features/support/env.rb +2 -2
  59. data/features/todo_list.feature +26 -23
  60. data/lib/reek/ast/node.rb +40 -55
  61. data/lib/reek/ast/object_refs.rb +1 -1
  62. data/lib/reek/ast/reference_collector.rb +2 -4
  63. data/lib/reek/ast/sexp_extensions/case.rb +1 -1
  64. data/lib/reek/ast/sexp_extensions/if.rb +8 -1
  65. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  66. data/lib/reek/ast/sexp_extensions/methods.rb +4 -6
  67. data/lib/reek/cli/application.rb +4 -3
  68. data/lib/reek/cli/command/report_command.rb +1 -2
  69. data/lib/reek/cli/command/todo_list_command.rb +8 -8
  70. data/lib/reek/cli/options.rb +29 -14
  71. data/lib/reek/cli/silencer.rb +14 -3
  72. data/lib/reek/code_comment.rb +14 -16
  73. data/lib/reek/configuration/app_configuration.rb +32 -28
  74. data/lib/reek/configuration/configuration_converter.rb +110 -0
  75. data/lib/reek/configuration/configuration_file_finder.rb +15 -40
  76. data/lib/reek/configuration/configuration_validator.rb +12 -23
  77. data/lib/reek/configuration/default_directive.rb +17 -3
  78. data/lib/reek/configuration/directory_directives.rb +17 -11
  79. data/lib/reek/configuration/excluded_paths.rb +1 -1
  80. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  81. data/lib/reek/configuration/schema.yml +210 -0
  82. data/lib/reek/configuration/schema_validator.rb +38 -0
  83. data/lib/reek/context/attribute_context.rb +1 -1
  84. data/lib/reek/context/code_context.rb +8 -11
  85. data/lib/reek/context/method_context.rb +7 -12
  86. data/lib/reek/context/module_context.rb +4 -4
  87. data/lib/reek/context_builder.rb +11 -11
  88. data/lib/reek/detector_repository.rb +6 -0
  89. data/lib/reek/documentation_link.rb +28 -0
  90. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +13 -12
  91. data/lib/reek/errors/bad_detector_in_comment_error.rb +11 -10
  92. data/lib/reek/errors/base_error.rb +3 -0
  93. data/lib/reek/errors/config_file_error.rb +11 -0
  94. data/lib/reek/errors/encoding_error.rb +16 -11
  95. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +11 -10
  96. data/lib/reek/errors/incomprehensible_source_error.rb +20 -22
  97. data/lib/reek/errors/syntax_error.rb +41 -0
  98. data/lib/reek/examiner.rb +19 -25
  99. data/lib/reek/logging_error_handler.rb +7 -5
  100. data/lib/reek/rake/task.rb +3 -3
  101. data/lib/reek/report/base_report.rb +8 -12
  102. data/lib/reek/report/code_climate/code_climate_configuration.rb +1 -1
  103. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -10
  104. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  105. data/lib/reek/report/heading_formatter.rb +54 -0
  106. data/lib/reek/report/json_report.rb +1 -1
  107. data/lib/reek/report/location_formatter.rb +40 -0
  108. data/lib/reek/report/progress_formatter.rb +79 -0
  109. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  110. data/lib/reek/report/text_report.rb +1 -2
  111. data/lib/reek/report/xml_report.rb +3 -3
  112. data/lib/reek/report/yaml_report.rb +1 -1
  113. data/lib/reek/report.rb +15 -10
  114. data/lib/reek/smell_configuration.rb +2 -2
  115. data/lib/reek/smell_detectors/attribute.rb +0 -1
  116. data/lib/reek/smell_detectors/base_detector.rb +9 -12
  117. data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
  118. data/lib/reek/smell_detectors/class_variable.rb +3 -11
  119. data/lib/reek/smell_detectors/control_parameter.rb +17 -32
  120. data/lib/reek/smell_detectors/data_clump.rb +3 -4
  121. data/lib/reek/smell_detectors/duplicate_method_call.rb +6 -7
  122. data/lib/reek/smell_detectors/feature_envy.rb +1 -1
  123. data/lib/reek/smell_detectors/instance_variable_assumption.rb +1 -10
  124. data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
  125. data/lib/reek/smell_detectors/long_parameter_list.rb +1 -2
  126. data/lib/reek/smell_detectors/long_yield_list.rb +2 -3
  127. data/lib/reek/smell_detectors/manual_dispatch.rb +3 -3
  128. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +6 -7
  129. data/lib/reek/smell_detectors/module_initialize.rb +1 -2
  130. data/lib/reek/smell_detectors/nested_iterators.rb +6 -6
  131. data/lib/reek/smell_detectors/nil_check.rb +0 -1
  132. data/lib/reek/smell_detectors/repeated_conditional.rb +3 -4
  133. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
  134. data/lib/reek/smell_detectors/too_many_constants.rb +2 -3
  135. data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -2
  136. data/lib/reek/smell_detectors/too_many_methods.rb +1 -2
  137. data/lib/reek/smell_detectors/too_many_statements.rb +1 -2
  138. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +2 -3
  139. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +2 -3
  140. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +2 -3
  141. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +6 -7
  142. data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
  143. data/lib/reek/smell_detectors/unused_private_method.rb +0 -1
  144. data/lib/reek/smell_detectors/utility_function.rb +2 -3
  145. data/lib/reek/smell_detectors.rb +1 -2
  146. data/lib/reek/smell_warning.rb +15 -8
  147. data/lib/reek/source/source_code.rb +50 -72
  148. data/lib/reek/source/source_locator.rb +7 -7
  149. data/lib/reek/spec/should_reek.rb +2 -2
  150. data/lib/reek/spec/should_reek_of.rb +9 -16
  151. data/lib/reek/spec/should_reek_only_of.rb +4 -4
  152. data/lib/reek/spec.rb +6 -6
  153. data/lib/reek/tree_dresser.rb +5 -5
  154. data/lib/reek/version.rb +1 -1
  155. data/reek.gemspec +5 -5
  156. data/samples/checkstyle.xml +1 -1
  157. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  158. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  159. data/samples/configuration/full_configuration.reek +8 -4
  160. data/samples/configuration/full_mask.reek +5 -4
  161. data/samples/configuration/partial_mask.reek +3 -2
  162. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  163. data/samples/paths.rb +5 -4
  164. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  165. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  166. data/spec/factories/factories.rb +2 -13
  167. data/spec/reek/ast/node_spec.rb +103 -10
  168. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  169. data/spec/reek/ast/sexp_extensions_spec.rb +2 -2
  170. data/spec/reek/cli/application_spec.rb +50 -38
  171. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  172. data/spec/reek/cli/silencer_spec.rb +28 -0
  173. data/spec/reek/code_comment_spec.rb +31 -38
  174. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  175. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -49
  176. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  177. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  178. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  179. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  180. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  181. data/spec/reek/context/code_context_spec.rb +60 -96
  182. data/spec/reek/context/ghost_context_spec.rb +1 -1
  183. data/spec/reek/context/root_context_spec.rb +1 -1
  184. data/spec/reek/documentation_link_spec.rb +20 -0
  185. data/spec/reek/errors/base_error_spec.rb +13 -0
  186. data/spec/reek/examiner_spec.rb +100 -30
  187. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
  188. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  189. data/spec/reek/report/json_report_spec.rb +13 -46
  190. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  191. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  192. data/spec/reek/report/text_report_spec.rb +4 -4
  193. data/spec/reek/report/xml_report_spec.rb +3 -3
  194. data/spec/reek/report/yaml_report_spec.rb +9 -38
  195. data/spec/reek/report_spec.rb +3 -3
  196. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  197. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  198. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  199. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  200. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  201. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +9 -9
  202. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  203. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  204. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  205. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  206. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  207. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  208. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +6 -4
  209. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  210. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  211. data/spec/reek/smell_detectors/unused_private_method_spec.rb +10 -10
  212. data/spec/reek/smell_detectors/utility_function_spec.rb +5 -5
  213. data/spec/reek/smell_warning_spec.rb +12 -8
  214. data/spec/reek/source/source_code_spec.rb +17 -43
  215. data/spec/reek/source/source_locator_spec.rb +17 -17
  216. data/spec/reek/spec/should_reek_of_spec.rb +7 -11
  217. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  218. data/spec/reek/spec/should_reek_spec.rb +3 -3
  219. data/spec/reek/spec/smell_matcher_spec.rb +3 -3
  220. data/spec/reek/tree_dresser_spec.rb +12 -17
  221. data/spec/spec_helper.rb +6 -17
  222. data/tasks/configuration.rake +8 -5
  223. metadata +71 -41
  224. data/defaults.reek +0 -131
  225. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  226. data/lib/reek/report/formatter/heading_formatter.rb +0 -52
  227. data/lib/reek/report/formatter/location_formatter.rb +0 -42
  228. data/lib/reek/report/formatter/progress_formatter.rb +0 -81
  229. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  230. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -36
  231. data/lib/reek/report/formatter.rb +0 -33
  232. data/lib/reek/smell_detectors/syntax.rb +0 -37
  233. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  234. data/samples/smelly_with_inline_mask.rb +0 -8
  235. data/samples/smelly_with_modifiers.rb +0 -12
  236. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  237. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  238. data/spec/reek/smell_detectors/syntax_spec.rb +0 -17
  239. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  240. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  241. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  242. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  243. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  244. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  245. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  246. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  247. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  248. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -0,0 +1,33 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/configuration/rake_task_converter'
3
+
4
+ RSpec.describe Reek::Configuration::RakeTaskConverter do
5
+ describe 'convert' do
6
+ let(:configuration_for_smell_detector) do
7
+ {
8
+ 'exclude' => [/Klass#foobar$/, /^Klass#omg$/],
9
+ 'reject' => [/^[a-z]$/, /[0-9]$/, /[A-Z]/],
10
+ 'accept' => [/^_$/]
11
+ }
12
+ end
13
+
14
+ let(:expected_exclude) { ['/Klass#foobar$/', '/^Klass#omg$/'] }
15
+ let(:expected_reject) { ['/^[a-z]$/', '/[0-9]$/', '/[A-Z]/'] }
16
+ let(:expected_accept) { ['/^_$/'] }
17
+
18
+ it 'converts exclude regexes to strings' do
19
+ converted_configuration = described_class.convert configuration_for_smell_detector
20
+ expect(converted_configuration['exclude']).to eq(expected_exclude)
21
+ end
22
+
23
+ it 'converts reject regexes to strings' do
24
+ converted_configuration = described_class.convert configuration_for_smell_detector
25
+ expect(converted_configuration['reject']).to eq(expected_reject)
26
+ end
27
+
28
+ it 'converts accept regexes to strings' do
29
+ converted_configuration = described_class.convert configuration_for_smell_detector
30
+ expect(converted_configuration['accept']).to eq(expected_accept)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,165 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/configuration/schema_validator'
3
+ require_lib 'reek/errors/config_file_error'
4
+
5
+ RSpec.describe Reek::Configuration::SchemaValidator do
6
+ describe 'validate' do
7
+ subject(:validator) { described_class.new configuration }
8
+
9
+ context 'when configuration is valid' do
10
+ let(:configuration) do
11
+ {
12
+ Reek::Configuration::AppConfiguration::DETECTORS_KEY => {
13
+ 'UncommunicativeVariableName' => { 'enabled' => false },
14
+ 'UncommunicativeMethodName' => { 'enabled' => false }
15
+ }
16
+ }
17
+ end
18
+
19
+ it 'returns nil' do
20
+ expect(validator.validate).to eq(nil)
21
+ end
22
+ end
23
+
24
+ context 'when detector is invalid' do
25
+ let(:configuration) do
26
+ {
27
+ Reek::Configuration::AppConfiguration::DETECTORS_KEY => {
28
+ 'DoesNotExist' => { 'enabled' => false }
29
+ }
30
+ }
31
+ end
32
+
33
+ it 'raises an error' do
34
+ message = %r{\[/detectors/DoesNotExist\] key 'DoesNotExist:' is undefined}
35
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
36
+ end
37
+ end
38
+
39
+ context 'when `enabled` has a non-boolean value' do
40
+ let(:configuration) do
41
+ {
42
+ Reek::Configuration::AppConfiguration::DETECTORS_KEY => {
43
+ 'FeatureEnvy' => { 'enabled' => 'foo' }
44
+ }
45
+ }
46
+ end
47
+
48
+ it 'raises an error' do
49
+ message = %r{\[/detectors/FeatureEnvy/enabled\] 'foo': not a boolean}
50
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
51
+ end
52
+ end
53
+
54
+ context 'when detector has an unknown option' do
55
+ let(:configuration) do
56
+ {
57
+ Reek::Configuration::AppConfiguration::DETECTORS_KEY => {
58
+ 'DataClump' => { 'does_not_exist' => 42 }
59
+ }
60
+ }
61
+ end
62
+
63
+ it 'raises an error' do
64
+ message = %r{\[/detectors/DataClump/does_not_exist\] key 'does_not_exist:' is undefined}
65
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
66
+ end
67
+ end
68
+
69
+ context 'when `exclude`, `reject` and `accept`' do
70
+ %w(exclude reject accept).each do |attribute|
71
+ context 'when a scalar' do
72
+ let(:configuration) do
73
+ {
74
+ Reek::Configuration::AppConfiguration::DETECTORS_KEY => {
75
+ 'UncommunicativeMethodName' => { attribute => 42 }
76
+ }
77
+ }
78
+ end
79
+
80
+ it 'raises an error' do
81
+ message = %r{\[/detectors/UncommunicativeMethodName/#{attribute}\] '42': not a sequence}
82
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
83
+ end
84
+ end
85
+
86
+ context 'when types are mixed' do
87
+ let(:configuration) do
88
+ {
89
+ Reek::Configuration::AppConfiguration::DETECTORS_KEY => {
90
+ 'UncommunicativeMethodName' => { attribute => [42, 'foo'] }
91
+ }
92
+ }
93
+ end
94
+
95
+ it 'raises an error' do
96
+ message = %r{\[/detectors/UncommunicativeMethodName/#{attribute}/0\] '42': not a string}
97
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'when `exclude_paths` is a scalar' do
104
+ let(:configuration) do
105
+ {
106
+ Reek::Configuration::AppConfiguration::EXCLUDE_PATHS_KEY => 42
107
+ }
108
+ end
109
+
110
+ it 'raises an error' do
111
+ message = %r{\[/exclude_paths\] '42': not a sequence}
112
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
113
+ end
114
+ end
115
+
116
+ context 'when `exclude_paths` mixes types' do
117
+ let(:configuration) do
118
+ {
119
+ Reek::Configuration::AppConfiguration::EXCLUDE_PATHS_KEY => [42, 'foo']
120
+ }
121
+ end
122
+
123
+ it 'raises an error' do
124
+ message = %r{\[/exclude_paths/0\] '42': not a string}
125
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
126
+ end
127
+ end
128
+
129
+ context 'with directory directives' do
130
+ context 'when bad detector' do
131
+ let(:configuration) do
132
+ {
133
+ Reek::Configuration::AppConfiguration::DIRECTORIES_KEY => {
134
+ 'web_app/app/helpers' => {
135
+ 'Bar' => { 'enabled' => false }
136
+ }
137
+ }
138
+ }
139
+ end
140
+
141
+ it 'raises an error' do
142
+ message = %r{\[/directories/web_app/app/helpers/Bar\] key 'Bar:' is undefined}
143
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
144
+ end
145
+ end
146
+
147
+ context 'when unknown attribute' do
148
+ let(:configuration) do
149
+ {
150
+ Reek::Configuration::AppConfiguration::DIRECTORIES_KEY => {
151
+ 'web_app/app/controllers' => {
152
+ 'NestedIterators' => { 'foo' => false }
153
+ }
154
+ }
155
+ }
156
+ end
157
+
158
+ it 'raises an error' do
159
+ message = %r{\[/directories/web_app/app/controllers/NestedIterators/foo\] key 'foo:' is undefined}
160
+ expect { validator.validate }.to raise_error(Reek::Errors::ConfigFileError, message)
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -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,98 +118,10 @@ 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
160
- # :reek:DuplicateMethodCall: { allow_calls: [ puts ] }')
124
+ # :reek:DuplicateMethodCall { allow_calls: [ puts ] }')
161
125
  def repeated_greeting
162
126
  puts 'Hello!'
163
127
  puts 'Hello!'
@@ -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,20 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Reek::DocumentationLink do
4
+ describe '.build' do
5
+ it 'returns the correct link for a smell type' do
6
+ expect(described_class.build('FeatureEnvy')).
7
+ to eq "https://github.com/troessner/reek/blob/v#{Reek::Version::STRING}/docs/Feature-Envy.md"
8
+ end
9
+
10
+ it 'returns the correct link for general documentation' do
11
+ expect(described_class.build('Rake Task')).
12
+ to eq "https://github.com/troessner/reek/blob/v#{Reek::Version::STRING}/docs/Rake-Task.md"
13
+ end
14
+
15
+ it 'returns the correct link for subjects with abbreviations' do
16
+ expect(described_class.build('YAML Report')).
17
+ to eq "https://github.com/troessner/reek/blob/v#{Reek::Version::STRING}/docs/YAML-Report.md"
18
+ end
19
+ end
20
+ end
@@ -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
@@ -46,7 +46,7 @@ RSpec.describe Reek::Examiner do
46
46
  filter_by_smells: [],
47
47
  configuration: configuration)
48
48
  end
49
- let(:path) { CONFIG_PATH.join('partial_mask.reek') }
49
+ let(:path) { CONFIGURATION_DIR.join('partial_mask.reek') }
50
50
  let(:expected_first_smell) { 'UncommunicativeVariableName' }
51
51
 
52
52
  it_behaves_like 'one smell found'
@@ -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,21 +127,38 @@ 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}/) }
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/) }
143
149
  end
144
150
  end
145
151
  end
146
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)
159
+ end
160
+ end
161
+
147
162
  context 'when the source causes the source buffer to crash' do
148
163
  let(:source) { 'I make the buffer crash' }
149
164
 
@@ -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,65 @@ RSpec.describe Reek::Examiner do
180
195
  end
181
196
  end
182
197
 
198
+ context 'with a source that triggers a syntax error' do
199
+ let(:examiner) { described_class.new(source) }
200
+ let(:source) do
201
+ <<-SRC.strip_heredoc
202
+ 1 2 3
203
+ SRC
204
+ end
205
+
206
+ it 'does not raise an error during initialization' do
207
+ expect { examiner }.not_to raise_error
208
+ end
209
+
210
+ it 'raises an encoding error when asked for smells' do
211
+ expect { examiner.smells }.to raise_error Reek::Errors::SyntaxError
212
+ end
213
+
214
+ it 'explains the origin of the error' do
215
+ message = "Source 'string' cannot be processed by Reek due to a syntax error in the source file."
216
+ expect { examiner.smells }.to raise_error.with_message(/#{message}/)
217
+ end
218
+
219
+ it 'shows the original exception class' do
220
+ expect { examiner.smells }.
221
+ to raise_error { |it| expect(it.long_message).to match(/Parser::SyntaxError/) }
222
+ end
223
+ end
224
+
225
+ context 'with a source that triggers an encoding error' do
226
+ let(:examiner) { described_class.new(source) }
227
+ let(:source) do
228
+ <<-SRC.strip_heredoc
229
+ # encoding: US-ASCII
230
+ puts 'こんにちは世界'
231
+ SRC
232
+ end
233
+
234
+ it 'does not raise an error during initialization' do
235
+ expect { examiner }.not_to raise_error
236
+ end
237
+
238
+ it 'raises an encoding error when asked for smells' do
239
+ expect { examiner.smells }.to raise_error Reek::Errors::EncodingError
240
+ end
241
+
242
+ it 'explains the origin of the error' do
243
+ message = "Source 'string' cannot be processed by Reek due to an encoding error in the source file."
244
+ expect { examiner.smells }.to raise_error.with_message(/#{message}/)
245
+ end
246
+
247
+ it 'shows the original exception class' do
248
+ expect { examiner.smells }.
249
+ to raise_error { |it| expect(it.long_message).to match(/InvalidByteSequenceError/) }
250
+ end
251
+ end
252
+
183
253
  describe 'bad comment config' do
184
254
  let(:examiner) { described_class.new(source) }
185
255
 
186
- context 'unknown smell detector' do
256
+ context 'with an unknown smell detector' do
187
257
  let(:source) do
188
258
  <<-EOS
189
259
  # :reek:DoesNotExist
@@ -209,7 +279,7 @@ RSpec.describe Reek::Examiner do
209
279
  end
210
280
  end
211
281
 
212
- context 'garbage in detector config' do
282
+ context 'with garbage in detector config' do
213
283
  let(:source) do
214
284
  <<-EOS
215
285
  # :reek:UncommunicativeMethodName { thats: a: bad: config }