reek 4.6.0 → 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 (294) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +79 -26
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -9
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +76 -0
  8. data/CONTRIBUTING.md +1 -1
  9. data/Gemfile +5 -5
  10. data/README.md +138 -107
  11. data/Rakefile +16 -3
  12. data/bin/code_climate_reek +1 -0
  13. data/bin/reek +2 -3
  14. data/docs/API.md +2 -9
  15. data/docs/Basic-Smell-Options.md +51 -11
  16. data/docs/Code-Smells.md +1 -1
  17. data/docs/Command-Line-Options.md +14 -4
  18. data/docs/Duplicate-Method-Call.md +49 -1
  19. data/docs/Feature-Envy.md +44 -0
  20. data/docs/How-To-Write-New-Detectors.md +7 -7
  21. data/docs/Instance-Variable-Assumption.md +1 -1
  22. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  23. data/docs/Rake-Task.md +1 -1
  24. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  25. data/docs/Reek-Driven-Development.md +1 -1
  26. data/docs/Uncommunicative-Method-Name.md +45 -6
  27. data/docs/Uncommunicative-Module-Name.md +49 -7
  28. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  29. data/docs/Uncommunicative-Variable-Name.md +73 -2
  30. data/docs/Unused-Private-Method.md +3 -3
  31. data/docs/defaults.reek.yml +129 -0
  32. data/docs/yard_plugin.rb +1 -0
  33. data/features/command_line_interface/basic_usage.feature +2 -2
  34. data/features/command_line_interface/options.feature +46 -4
  35. data/features/command_line_interface/show_progress.feature +4 -4
  36. data/features/command_line_interface/smell_selection.feature +1 -1
  37. data/features/command_line_interface/smells_count.feature +6 -6
  38. data/features/command_line_interface/stdin.feature +31 -5
  39. data/features/configuration_files/accept_setting.feature +45 -28
  40. data/features/configuration_files/directory_specific_directives.feature +80 -75
  41. data/features/configuration_files/exclude_directives.feature +11 -10
  42. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  43. data/features/configuration_files/masking_smells.feature +38 -9
  44. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  45. data/features/configuration_files/reject_setting.feature +52 -41
  46. data/features/configuration_files/schema_validation.feature +59 -0
  47. data/features/configuration_files/unused_private_method.feature +18 -16
  48. data/features/configuration_loading.feature +53 -10
  49. data/features/configuration_via_source_comments/erroneous_source_comments.feature +3 -3
  50. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  51. data/features/locales.feature +32 -0
  52. data/features/rake_task/rake_task.feature +58 -18
  53. data/features/reports/codeclimate.feature +59 -0
  54. data/features/reports/json.feature +3 -3
  55. data/features/reports/reports.feature +34 -34
  56. data/features/reports/yaml.feature +3 -3
  57. data/features/rspec_matcher.feature +40 -0
  58. data/features/samples.feature +287 -287
  59. data/features/step_definitions/reek_steps.rb +14 -2
  60. data/features/step_definitions/sample_file_steps.rb +9 -4
  61. data/features/support/env.rb +2 -11
  62. data/features/todo_list.feature +26 -23
  63. data/lib/reek/ast/ast_node_class_map.rb +1 -0
  64. data/lib/reek/ast/builder.rb +16 -0
  65. data/lib/reek/ast/node.rb +50 -58
  66. data/lib/reek/ast/object_refs.rb +2 -1
  67. data/lib/reek/ast/reference_collector.rb +3 -4
  68. data/lib/reek/ast/sexp_extensions/arguments.rb +1 -0
  69. data/lib/reek/ast/sexp_extensions/attribute_assignments.rb +1 -0
  70. data/lib/reek/ast/sexp_extensions/begin.rb +17 -0
  71. data/lib/reek/ast/sexp_extensions/block.rb +1 -0
  72. data/lib/reek/ast/sexp_extensions/case.rb +2 -1
  73. data/lib/reek/ast/sexp_extensions/constant.rb +1 -0
  74. data/lib/reek/ast/sexp_extensions/if.rb +9 -1
  75. data/lib/reek/ast/sexp_extensions/lambda.rb +1 -0
  76. data/lib/reek/ast/sexp_extensions/literal.rb +1 -0
  77. data/lib/reek/ast/sexp_extensions/logical_operators.rb +2 -1
  78. data/lib/reek/ast/sexp_extensions/methods.rb +5 -6
  79. data/lib/reek/ast/sexp_extensions/module.rb +55 -8
  80. data/lib/reek/ast/sexp_extensions/nested_assignables.rb +1 -0
  81. data/lib/reek/ast/sexp_extensions/self.rb +1 -0
  82. data/lib/reek/ast/sexp_extensions/send.rb +1 -4
  83. data/lib/reek/ast/sexp_extensions/super.rb +1 -0
  84. data/lib/reek/ast/sexp_extensions/symbols.rb +1 -0
  85. data/lib/reek/ast/sexp_extensions/variables.rb +1 -0
  86. data/lib/reek/ast/sexp_extensions/when.rb +1 -0
  87. data/lib/reek/ast/sexp_extensions/yield.rb +1 -0
  88. data/lib/reek/ast/sexp_extensions.rb +2 -0
  89. data/lib/reek/cli/application.rb +5 -3
  90. data/lib/reek/cli/command/base_command.rb +1 -0
  91. data/lib/reek/cli/command/report_command.rb +2 -2
  92. data/lib/reek/cli/command/todo_list_command.rb +9 -8
  93. data/lib/reek/cli/options.rb +32 -16
  94. data/lib/reek/cli/silencer.rb +15 -3
  95. data/lib/reek/cli/status.rb +1 -0
  96. data/lib/reek/code_comment.rb +14 -16
  97. data/lib/reek/configuration/app_configuration.rb +34 -28
  98. data/lib/reek/configuration/configuration_converter.rb +110 -0
  99. data/lib/reek/configuration/configuration_file_finder.rb +17 -41
  100. data/lib/reek/configuration/configuration_validator.rb +13 -23
  101. data/lib/reek/configuration/default_directive.rb +18 -3
  102. data/lib/reek/configuration/directory_directives.rb +18 -11
  103. data/lib/reek/configuration/excluded_paths.rb +2 -1
  104. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  105. data/lib/reek/configuration/schema.yml +210 -0
  106. data/lib/reek/configuration/schema_validator.rb +38 -0
  107. data/lib/reek/context/attribute_context.rb +4 -3
  108. data/lib/reek/context/class_context.rb +1 -0
  109. data/lib/reek/context/code_context.rb +49 -44
  110. data/lib/reek/context/ghost_context.rb +1 -2
  111. data/lib/reek/context/method_context.rb +26 -18
  112. data/lib/reek/context/module_context.rb +11 -11
  113. data/lib/reek/context/root_context.rb +1 -4
  114. data/lib/reek/context/send_context.rb +3 -2
  115. data/lib/reek/context/singleton_attribute_context.rb +1 -0
  116. data/lib/reek/context/singleton_method_context.rb +1 -0
  117. data/lib/reek/context/statement_counter.rb +1 -0
  118. data/lib/reek/context/visibility_tracker.rb +1 -0
  119. data/lib/reek/context_builder.rb +44 -44
  120. data/lib/reek/detector_repository.rb +12 -11
  121. data/lib/reek/documentation_link.rb +28 -0
  122. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +15 -13
  123. data/lib/reek/errors/bad_detector_in_comment_error.rb +13 -11
  124. data/lib/reek/errors/base_error.rb +3 -0
  125. data/lib/reek/errors/config_file_error.rb +11 -0
  126. data/lib/reek/errors/encoding_error.rb +43 -0
  127. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +14 -12
  128. data/lib/reek/errors/incomprehensible_source_error.rb +23 -24
  129. data/lib/reek/errors/syntax_error.rb +41 -0
  130. data/lib/reek/examiner.rb +24 -22
  131. data/lib/reek/logging_error_handler.rb +8 -5
  132. data/lib/reek/rake/task.rb +8 -5
  133. data/lib/reek/report/base_report.rb +9 -12
  134. data/lib/reek/report/code_climate/code_climate_configuration.rb +2 -1
  135. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -6
  136. data/lib/reek/report/code_climate/code_climate_fingerprint.rb +1 -0
  137. data/lib/reek/report/code_climate/code_climate_formatter.rb +1 -0
  138. data/lib/reek/report/code_climate/code_climate_report.rb +1 -0
  139. data/lib/reek/report/code_climate.rb +1 -0
  140. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  141. data/lib/reek/report/heading_formatter.rb +54 -0
  142. data/lib/reek/report/html_report.rb +1 -0
  143. data/lib/reek/report/json_report.rb +2 -1
  144. data/lib/reek/report/location_formatter.rb +40 -0
  145. data/lib/reek/report/progress_formatter.rb +79 -0
  146. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  147. data/lib/reek/report/text_report.rb +2 -2
  148. data/lib/reek/report/xml_report.rb +4 -3
  149. data/lib/reek/report/yaml_report.rb +2 -1
  150. data/lib/reek/report.rb +16 -10
  151. data/lib/reek/smell_configuration.rb +3 -2
  152. data/lib/reek/smell_detectors/attribute.rb +6 -11
  153. data/lib/reek/smell_detectors/base_detector.rb +31 -26
  154. data/lib/reek/smell_detectors/boolean_parameter.rb +4 -5
  155. data/lib/reek/smell_detectors/class_variable.rb +6 -14
  156. data/lib/reek/smell_detectors/control_parameter.rb +19 -33
  157. data/lib/reek/smell_detectors/data_clump.rb +16 -9
  158. data/lib/reek/smell_detectors/duplicate_method_call.rb +24 -17
  159. data/lib/reek/smell_detectors/feature_envy.rb +10 -7
  160. data/lib/reek/smell_detectors/instance_variable_assumption.rb +15 -23
  161. data/lib/reek/smell_detectors/irresponsible_module.rb +6 -12
  162. data/lib/reek/smell_detectors/long_parameter_list.rb +11 -7
  163. data/lib/reek/smell_detectors/long_yield_list.rb +11 -7
  164. data/lib/reek/smell_detectors/manual_dispatch.rb +5 -5
  165. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +21 -20
  166. data/lib/reek/smell_detectors/module_initialize.rb +4 -5
  167. data/lib/reek/smell_detectors/nested_iterators.rb +17 -24
  168. data/lib/reek/smell_detectors/nil_check.rb +9 -15
  169. data/lib/reek/smell_detectors/repeated_conditional.rb +14 -11
  170. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +8 -8
  171. data/lib/reek/smell_detectors/too_many_constants.rb +11 -9
  172. data/lib/reek/smell_detectors/too_many_instance_variables.rb +11 -6
  173. data/lib/reek/smell_detectors/too_many_methods.rb +12 -7
  174. data/lib/reek/smell_detectors/too_many_statements.rb +11 -6
  175. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +11 -11
  176. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +15 -18
  177. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +18 -22
  178. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +27 -27
  179. data/lib/reek/smell_detectors/unused_parameters.rb +5 -6
  180. data/lib/reek/smell_detectors/unused_private_method.rb +14 -20
  181. data/lib/reek/smell_detectors/utility_function.rb +13 -16
  182. data/lib/reek/smell_detectors.rb +2 -1
  183. data/lib/reek/smell_warning.rb +16 -8
  184. data/lib/reek/source/source_code.rb +65 -46
  185. data/lib/reek/source/source_locator.rb +9 -8
  186. data/lib/reek/spec/should_reek.rb +3 -2
  187. data/lib/reek/spec/should_reek_of.rb +13 -26
  188. data/lib/reek/spec/should_reek_only_of.rb +5 -4
  189. data/lib/reek/spec/smell_matcher.rb +2 -1
  190. data/lib/reek/spec.rb +7 -6
  191. data/lib/reek/tree_dresser.rb +9 -8
  192. data/lib/reek/version.rb +2 -1
  193. data/lib/reek.rb +1 -0
  194. data/reek.gemspec +5 -6
  195. data/samples/checkstyle.xml +1 -1
  196. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  197. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  198. data/samples/configuration/full_configuration.reek +8 -4
  199. data/samples/configuration/full_mask.reek +5 -4
  200. data/samples/configuration/partial_mask.reek +3 -2
  201. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  202. data/samples/paths.rb +5 -4
  203. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  204. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  205. data/spec/factories/factories.rb +2 -13
  206. data/spec/reek/ast/node_spec.rb +103 -10
  207. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  208. data/spec/reek/ast/sexp_extensions_spec.rb +15 -34
  209. data/spec/reek/cli/application_spec.rb +52 -42
  210. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  211. data/spec/reek/cli/silencer_spec.rb +28 -0
  212. data/spec/reek/code_comment_spec.rb +31 -38
  213. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  214. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -51
  215. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  216. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  217. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  218. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  219. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  220. data/spec/reek/context/code_context_spec.rb +70 -106
  221. data/spec/reek/context/ghost_context_spec.rb +9 -9
  222. data/spec/reek/context/method_context_spec.rb +2 -2
  223. data/spec/reek/context/module_context_spec.rb +3 -3
  224. data/spec/reek/context/root_context_spec.rb +1 -1
  225. data/spec/reek/context/statement_counter_spec.rb +1 -0
  226. data/spec/reek/context_builder_spec.rb +20 -0
  227. data/spec/reek/documentation_link_spec.rb +20 -0
  228. data/spec/reek/errors/base_error_spec.rb +13 -0
  229. data/spec/reek/examiner_spec.rb +137 -29
  230. data/spec/reek/rake/task_spec.rb +25 -2
  231. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -78
  232. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  233. data/spec/reek/report/code_climate/code_climate_report_spec.rb +22 -22
  234. data/spec/reek/report/json_report_spec.rb +13 -46
  235. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  236. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  237. data/spec/reek/report/text_report_spec.rb +4 -4
  238. data/spec/reek/report/xml_report_spec.rb +3 -3
  239. data/spec/reek/report/yaml_report_spec.rb +17 -46
  240. data/spec/reek/report_spec.rb +3 -3
  241. data/spec/reek/smell_detectors/base_detector_spec.rb +4 -5
  242. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  243. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  244. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  245. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  246. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  247. data/spec/reek/smell_detectors/irresponsible_module_spec.rb +59 -21
  248. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +10 -10
  249. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  250. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  251. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  252. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  253. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  254. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  255. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +36 -15
  256. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  257. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  258. data/spec/reek/smell_detectors/unused_private_method_spec.rb +21 -10
  259. data/spec/reek/smell_detectors/utility_function_spec.rb +57 -5
  260. data/spec/reek/smell_warning_spec.rb +12 -8
  261. data/spec/reek/source/source_code_spec.rb +27 -38
  262. data/spec/reek/source/source_locator_spec.rb +42 -12
  263. data/spec/reek/spec/should_reek_of_spec.rb +25 -30
  264. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  265. data/spec/reek/spec/should_reek_spec.rb +8 -8
  266. data/spec/reek/spec/smell_matcher_spec.rb +23 -23
  267. data/spec/reek/tree_dresser_spec.rb +12 -17
  268. data/spec/spec_helper.rb +7 -17
  269. data/tasks/configuration.rake +8 -5
  270. metadata +77 -40
  271. data/defaults.reek +0 -128
  272. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  273. data/lib/reek/errors/parse_error.rb +0 -19
  274. data/lib/reek/report/formatter/heading_formatter.rb +0 -51
  275. data/lib/reek/report/formatter/location_formatter.rb +0 -41
  276. data/lib/reek/report/formatter/progress_formatter.rb +0 -80
  277. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  278. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -35
  279. data/lib/reek/report/formatter.rb +0 -32
  280. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  281. data/samples/smelly_with_inline_mask.rb +0 -8
  282. data/samples/smelly_with_modifiers.rb +0 -12
  283. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  284. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  285. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  286. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  287. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  288. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  289. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  290. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  291. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  292. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  293. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  294. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -3,8 +3,8 @@ 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
7
- let(:ctx) { described_class.new(nil, exp) }
6
+ describe '#full_name' do
7
+ let(:ctx) { described_class.new(exp) }
8
8
  let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode') }
9
9
  let(:exp_name) { 'random_name' }
10
10
  let(:full_name) { "::::::::::::::::::::#{exp_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,31 +76,38 @@ 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(outer, exp) }
47
103
  let(:outer_name) { 'another_random sting' }
48
- let(:outer) { described_class.new(nil, instance_double('Reek::AST::Node')) }
104
+ let(:outer) { described_class.new(instance_double('Reek::AST::Node')) }
49
105
 
50
106
  before do
51
107
  ctx.register_with_parent outer
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(nil, 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(nil, 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(nil, 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!'
@@ -166,7 +130,7 @@ RSpec.describe Reek::Context::CodeContext do
166
130
  end
167
131
  let(:expression) { Reek::Source::SourceCode.from(src).syntax_tree }
168
132
  let(:outer) { nil }
169
- let(:context) { described_class.new(outer, expression) }
133
+ let(:context) { described_class.new(expression) }
170
134
  let(:sniffer) { class_double('Reek::SmellDetectors::BaseDetector') }
171
135
 
172
136
  before do
@@ -181,7 +145,7 @@ RSpec.describe Reek::Context::CodeContext do
181
145
  end
182
146
 
183
147
  context 'when there is an outer context' do
184
- let(:outer) { described_class.new(nil, instance_double('Reek::AST::Node')) }
148
+ let(:outer) { described_class.new(instance_double('Reek::AST::Node')) }
185
149
 
186
150
  before do
187
151
  allow(outer).to receive(:config_for).with(sniffer).and_return(
@@ -196,9 +160,9 @@ RSpec.describe Reek::Context::CodeContext do
196
160
  end
197
161
 
198
162
  describe '#register_with_parent' do
199
- let(:context) { described_class.new(nil, instance_double('Reek::AST::Node')) }
200
- let(:first_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
201
- let(:second_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
163
+ let(:context) { described_class.new(instance_double('Reek::AST::Node')) }
164
+ let(:first_child) { described_class.new(instance_double('Reek::AST::Node')) }
165
+ let(:second_child) { described_class.new(instance_double('Reek::AST::Node')) }
202
166
 
203
167
  it "appends the element to the parent context's list of children" do
204
168
  first_child.register_with_parent context
@@ -209,9 +173,9 @@ RSpec.describe Reek::Context::CodeContext do
209
173
  end
210
174
 
211
175
  describe '#each' do
212
- let(:context) { described_class.new(nil, instance_double('Reek::AST::Node')) }
213
- let(:first_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
214
- let(:second_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
176
+ let(:context) { described_class.new(instance_double('Reek::AST::Node')) }
177
+ let(:first_child) { described_class.new(instance_double('Reek::AST::Node')) }
178
+ let(:second_child) { described_class.new(instance_double('Reek::AST::Node')) }
215
179
 
216
180
  it 'yields each child' do
217
181
  first_child.register_with_parent context
@@ -4,53 +4,53 @@ require_lib 'reek/context/ghost_context'
4
4
 
5
5
  RSpec.describe Reek::Context::GhostContext do
6
6
  let(:exp) { instance_double('Reek::AST::Node') }
7
- let(:parent) { Reek::Context::CodeContext.new(nil, exp) }
7
+ let(:parent) { Reek::Context::CodeContext.new(exp) }
8
8
 
9
9
  describe '#register_with_parent' do
10
10
  it 'does not append itself to its parent' do
11
- ghost = described_class.new(parent, nil)
11
+ ghost = described_class.new(nil)
12
12
  ghost.register_with_parent(parent)
13
13
  expect(parent.children).not_to include ghost
14
14
  end
15
15
  end
16
16
 
17
17
  describe '#append_child_context' do
18
- let(:ghost) { described_class.new(parent, nil) }
18
+ let(:ghost) { described_class.new(nil) }
19
19
 
20
20
  before do
21
21
  ghost.register_with_parent(parent)
22
22
  end
23
23
 
24
24
  it 'appends the child to the grandparent context' do
25
- child = Reek::Context::CodeContext.new(ghost, sexp(:foo))
25
+ child = Reek::Context::CodeContext.new(sexp(:foo))
26
26
  child.register_with_parent(ghost)
27
27
 
28
28
  expect(parent.children).to include child
29
29
  end
30
30
 
31
31
  it "sets the child's parent to the grandparent context" do
32
- child = Reek::Context::CodeContext.new(ghost, sexp(:foo))
32
+ child = Reek::Context::CodeContext.new(sexp(:foo))
33
33
  child.register_with_parent(ghost)
34
34
 
35
35
  expect(child.parent).to eq parent
36
36
  end
37
37
 
38
38
  it 'appends the child to the list of children' do
39
- child = Reek::Context::CodeContext.new(ghost, sexp(:foo))
39
+ child = Reek::Context::CodeContext.new(sexp(:foo))
40
40
  child.register_with_parent(ghost)
41
41
 
42
42
  expect(ghost.children).to include child
43
43
  end
44
44
 
45
- context 'if the grandparent is also a ghost' do
46
- let(:child_ghost) { described_class.new(ghost, nil) }
45
+ context 'when the grandparent is also a ghost' do
46
+ let(:child_ghost) { described_class.new(nil) }
47
47
 
48
48
  before do
49
49
  child_ghost.register_with_parent(ghost)
50
50
  end
51
51
 
52
52
  it 'sets the childs parent to its remote ancestor' do
53
- child = Reek::Context::CodeContext.new(child_ghost, sexp(:foo))
53
+ child = Reek::Context::CodeContext.new(sexp(:foo))
54
54
  child.register_with_parent(child_ghost)
55
55
 
56
56
  expect(child.parent).to eq parent
@@ -2,7 +2,7 @@ require_relative '../../spec_helper'
2
2
  require_lib 'reek/context/method_context'
3
3
 
4
4
  RSpec.describe Reek::Context::MethodContext do
5
- let(:method_context) { described_class.new(nil, exp) }
5
+ let(:method_context) { described_class.new(exp, nil) }
6
6
 
7
7
  describe '#matches?' do
8
8
  let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode').as_null_object }
@@ -31,7 +31,7 @@ RSpec.describe Reek::Context::MethodContext do
31
31
  describe '#default_assignments' do
32
32
  def assignments_from(src)
33
33
  exp = Reek::Source::SourceCode.from(src).syntax_tree
34
- ctx = Reek::Context::MethodContext.new(nil, exp)
34
+ ctx = Reek::Context::MethodContext.new(exp, nil)
35
35
  ctx.default_assignments
36
36
  end
37
37
 
@@ -32,9 +32,9 @@ RSpec.describe Reek::Context::ModuleContext do
32
32
  let(:first_def) { instance_double('Reek::AST::SexpExtensions::DefNode', name: :foo) }
33
33
  let(:second_def) { instance_double('Reek::AST::SexpExtensions::DefNode') }
34
34
 
35
- let(:context) { described_class.new(nil, main_exp) }
36
- let(:first_child) { Reek::Context::MethodContext.new(context, first_def) }
37
- let(:second_child) { Reek::Context::MethodContext.new(context, second_def) }
35
+ let(:context) { described_class.new(main_exp) }
36
+ let(:first_child) { Reek::Context::MethodContext.new(first_def, main_exp) }
37
+ let(:second_child) { Reek::Context::MethodContext.new(second_def, main_exp) }
38
38
 
39
39
  it 'sets visibility on subsequent child contexts' do
40
40
  context.append_child_context first_child
@@ -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)
@@ -3,6 +3,7 @@ require_lib 'reek/context/statement_counter'
3
3
 
4
4
  RSpec.describe Reek::Context::StatementCounter do
5
5
  let(:counter) { described_class.new }
6
+
6
7
  describe '#increase_by' do
7
8
  it 'does not increase if passed a falsy value' do
8
9
  counter.increase_by(nil)
@@ -220,6 +220,26 @@ RSpec.describe Reek::ContextBuilder do
220
220
  described_class.new(syntax_tree(code)).context_tree
221
221
  end
222
222
 
223
+ it 'marks instance methods when using a def modifier' do
224
+ code = <<-EOS
225
+ class Foo
226
+ private def bar
227
+ end
228
+
229
+ def baz
230
+ end
231
+ end
232
+ EOS
233
+
234
+ root = context_tree_for(code)
235
+ module_context = root.children.first
236
+ method_contexts = module_context.children
237
+ aggregate_failures do
238
+ expect(method_contexts[0].visibility).to eq :private
239
+ expect(method_contexts[1].visibility).to eq :public
240
+ end
241
+ end
242
+
223
243
  it 'does not mark class methods with instance visibility' do
224
244
  code = <<-EOS
225
245
  class Foo
@@ -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
@@ -1,5 +1,6 @@
1
1
  require_relative '../spec_helper'
2
2
  require_lib 'reek/examiner'
3
+ require_lib 'reek/logging_error_handler'
3
4
 
4
5
  RSpec.shared_examples_for 'no smells found' do
5
6
  it 'is not smelly' do
@@ -45,7 +46,7 @@ RSpec.describe Reek::Examiner do
45
46
  filter_by_smells: [],
46
47
  configuration: configuration)
47
48
  end
48
- let(:path) { CONFIG_PATH.join('partial_mask.reek') }
49
+ let(:path) { CONFIGURATION_DIR.join('partial_mask.reek') }
49
50
  let(:expected_first_smell) { 'UncommunicativeVariableName' }
50
51
 
51
52
  it_behaves_like 'one smell found'
@@ -57,27 +58,12 @@ RSpec.describe Reek::Examiner do
57
58
  it_behaves_like 'no smells found'
58
59
  end
59
60
 
60
- describe '.new' do
61
- context 'returns a proper Examiner' do
62
- let(:source) { 'class C; def f; end; end' }
63
- let(:examiner) do
64
- described_class.new(source)
65
- end
66
-
67
- it 'has been run on the given source' do
68
- expect(examiner.origin).to eq('string')
69
- end
70
-
71
- it 'has the right smells' do
72
- smells = examiner.smells
73
- expect(smells[0].message).to eq('has no descriptive comment')
74
- expect(smells[1].message).to eq("has the name 'f'")
75
- expect(smells[2].message).to eq("has the name 'C'")
76
- end
61
+ describe '#origin' do
62
+ let(:source) { 'class C; def f; end; end' }
63
+ let(:examiner) { described_class.new(source) }
77
64
 
78
- it 'has the right smell count' do
79
- expect(examiner.smells_count).to eq(3)
80
- end
65
+ it 'returns "string" for a string source' do
66
+ expect(examiner.origin).to eq('string')
81
67
  end
82
68
  end
83
69
 
@@ -91,7 +77,20 @@ RSpec.describe Reek::Examiner do
91
77
  expect(smell.message).to eq("calls 'bar.call_me()' 2 times")
92
78
  end
93
79
 
94
- 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
95
94
  let(:source) do
96
95
  <<-EOS
97
96
  # Just a comment
@@ -107,7 +106,7 @@ RSpec.describe Reek::Examiner do
107
106
  end
108
107
  end
109
108
 
110
- context 'with an incomprehensible source that causes Reek to crash' do
109
+ context 'with an incomprehensible source that causes the detectors to crash' do
111
110
  let(:source) { 'class C; def does_crash_reek; end; end' }
112
111
 
113
112
  let(:examiner) do
@@ -128,24 +127,133 @@ RSpec.describe Reek::Examiner do
128
127
 
129
128
  it 'explains the origin of the error' do
130
129
  origin = 'string'
131
- 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.")
132
132
  end
133
133
 
134
134
  it 'explains what to do' do
135
- explanation = 'Please double check your Reek configuration'
136
- 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}/) }
137
138
  end
138
139
 
139
140
  it 'contains the original error message' do
140
141
  original = 'Looks like bad source'
141
- 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/) }
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)
159
+ end
160
+ end
161
+
162
+ context 'when the source causes the source buffer to crash' do
163
+ let(:source) { 'I make the buffer crash' }
164
+
165
+ before do
166
+ buffer = double
167
+ allow(buffer).to receive(:source=) { raise RuntimeError }
168
+ allow(Parser::Source::Buffer).to receive(:new).and_return(buffer)
169
+ end
170
+
171
+ context 'when the error handler does not handle the error' do
172
+ let(:examiner) { described_class.new(source) }
173
+
174
+ it 'does not raise an error during initialization' do
175
+ expect { examiner }.not_to raise_error
142
176
  end
177
+
178
+ it 'raises an incomprehensible source error when asked for smells' do
179
+ expect { examiner.smells }.to raise_error Reek::Errors::IncomprehensibleSourceError
180
+ end
181
+ end
182
+
183
+ context 'when the error handler handles the error' do
184
+ let(:handler) { instance_double(Reek::LoggingErrorHandler, handle: true) }
185
+ let(:examiner) { described_class.new(source, error_handler: handler) }
186
+
187
+ it 'does not raise an error when asked for smells' do
188
+ expect { examiner.smells }.not_to raise_error
189
+ end
190
+
191
+ it 'passes the wrapped error to the handler' do
192
+ examiner.smells
193
+ expect(handler).to have_received(:handle).with(Reek::Errors::IncomprehensibleSourceError)
194
+ end
195
+ end
196
+ end
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/) }
143
250
  end
144
251
  end
145
252
 
146
253
  describe 'bad comment config' do
147
254
  let(:examiner) { described_class.new(source) }
148
- context 'unknown smell detector' do
255
+
256
+ context 'with an unknown smell detector' do
149
257
  let(:source) do
150
258
  <<-EOS
151
259
  # :reek:DoesNotExist
@@ -171,7 +279,7 @@ RSpec.describe Reek::Examiner do
171
279
  end
172
280
  end
173
281
 
174
- context 'garbage in detector config' do
282
+ context 'with garbage in detector config' do
175
283
  let(:source) do
176
284
  <<-EOS
177
285
  # :reek:UncommunicativeMethodName { thats: a: bad: config }