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
@@ -6,8 +6,8 @@ When /^I pass "([^\"]*)" to reek *(.*)$/ do |stdin, args|
6
6
  reek_with_pipe(stdin, args)
7
7
  end
8
8
 
9
- When /^I run rake (\w*) with:$/ do |name, task_def|
10
- rake(name, task_def)
9
+ When /^I pass a stdin to reek *(.*) with:$/ do |args, stdin|
10
+ reek_with_pipe(stdin, args)
11
11
  end
12
12
 
13
13
  Then /^it reports nothing$/ do
@@ -84,3 +84,15 @@ end
84
84
  Then /^it reports the current version$/ do
85
85
  expect(last_command_started).to have_output("reek #{Reek::Version::STRING}")
86
86
  end
87
+
88
+ Then /^it reports this Code Climate output:$/ do |expected_output|
89
+ expected_issues = expected_output.split('NULL_BYTE_CHARACTER').map do |issue|
90
+ JSON.parse(issue)
91
+ end
92
+
93
+ actual_issues = last_command_started.stdout.split("\0").map do |issue|
94
+ JSON.parse(issue)
95
+ end
96
+
97
+ expect(actual_issues).to eq(expected_issues)
98
+ end
@@ -1,7 +1,7 @@
1
1
  require_relative '../../samples/paths'
2
2
 
3
3
  Given(/^the smelly file '(.+)'$/) do |filename|
4
- write_file(filename, SAMPLES_PATH.join(filename).read)
4
+ write_file(filename, SAMPLES_DIR.join('smelly_source').join(filename).read)
5
5
  end
6
6
 
7
7
  Given(/^the clean file 'clean.rb'$/) do
@@ -28,13 +28,18 @@ Given(/^a directory called 'smelly' containing two smelly files$/) do
28
28
  end
29
29
 
30
30
  Given(/^the smelly file '(.+)' in a subdirectory$/) do |filename|
31
- contents = SAMPLES_PATH.join(filename).read
31
+ contents = SAMPLES_DIR.join('smelly_source').join(filename).read
32
32
 
33
33
  write_file("subdir/#{filename}", contents)
34
34
  end
35
35
 
36
36
  Given(/^a configuration file '(.+)'$/) do |filename|
37
- write_file(filename, CONFIG_PATH.join(filename).read)
37
+ write_file(filename, CONFIGURATION_DIR.join(filename).read)
38
+ end
39
+
40
+ Given(/^our default configuration file$/) do
41
+ default_configuration = File.read SAMPLES_DIR.join('..').join('docs').join('defaults.reek.yml')
42
+ write_file('defaults.reek', default_configuration)
38
43
  end
39
44
 
40
45
  When(/^I run "reek (.*?)" in a subdirectory$/) do |args|
@@ -44,7 +49,7 @@ When(/^I run "reek (.*?)" in a subdirectory$/) do |args|
44
49
  end
45
50
 
46
51
  Given(/^a configuration file '(.+)' in a subdirectory$/) do |filename|
47
- contents = CONFIG_PATH.join(filename).read
52
+ contents = CONFIGURATION_DIR.join(filename).read
48
53
 
49
54
  write_file("subdir/#{filename}", contents)
50
55
  end
@@ -13,23 +13,14 @@ end
13
13
  #
14
14
  class ReekWorld
15
15
  def reek(args)
16
- run_simple("reek --no-color #{args}", false)
16
+ run_simple("reek --no-color --no-documentation #{args}", false)
17
17
  end
18
18
 
19
19
  def reek_with_pipe(stdin, args)
20
- run "reek --no-color #{args}"
20
+ run "reek --no-color --no-documentation #{args}"
21
21
  type(stdin)
22
22
  close_input
23
23
  end
24
-
25
- def rake(name, task_def)
26
- header = <<-EOS.strip_heredoc
27
- require 'reek/rake/task'
28
-
29
- EOS
30
- write_file 'Rakefile', header + task_def
31
- run_simple("rake #{name}", false)
32
- end
33
24
  end
34
25
 
35
26
  World do
@@ -15,8 +15,8 @@ Feature: Auto-generate a todo file
15
15
  And it reports:
16
16
  """
17
17
  smelly.rb -- 2 warnings:
18
- [4]:UncommunicativeMethodName: Smelly#x has the name 'x' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Method-Name.md]
19
- [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
18
+ [4]:UncommunicativeMethodName: Smelly#x has the name 'x'
19
+ [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y'
20
20
  """
21
21
  When I run reek --todo smelly.rb
22
22
  Then it succeeds
@@ -29,13 +29,14 @@ Feature: Auto-generate a todo file
29
29
  And the file ".todo.reek" should contain:
30
30
  """
31
31
  ---
32
- UncommunicativeMethodName:
33
- exclude:
34
- - Smelly#x
35
- UncommunicativeVariableName:
36
- exclude:
37
- - Smelly#x
38
- """
32
+ detectors:
33
+ UncommunicativeMethodName:
34
+ exclude:
35
+ - Smelly#x
36
+ UncommunicativeVariableName:
37
+ exclude:
38
+ - Smelly#x
39
+ """
39
40
  When I run reek -c .todo.reek smelly.rb
40
41
  Then it succeeds
41
42
 
@@ -54,20 +55,21 @@ Feature: Auto-generate a todo file
54
55
  And a file named ".todo.reek" with:
55
56
  """
56
57
  ---
57
- # smelly.rb reeks of UncommunicativeMethodName and UncommunicativeVariableName
58
- # so the configuration below will partially mask this
59
- UncommunicativeMethodName:
60
- enabled: false
58
+ detectors:
59
+ # smelly.rb reeks of UncommunicativeMethodName and UncommunicativeVariableName
60
+ # so the configuration below will partially mask this
61
+ UncommunicativeMethodName:
62
+ enabled: false
61
63
  """
62
- When I run `reek -c .todo.reek smelly.rb`
64
+ When I run reek -c .todo.reek smelly.rb
63
65
  Then it reports:
64
66
  """
65
67
  smelly.rb -- 1 warning:
66
- [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
68
+ [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y'
67
69
  """
68
- When I run `reek --todo smelly.rb`
70
+ When I run reek --todo smelly.rb
69
71
  Then it succeeds
70
- When I run `reek -c .todo.reek smelly.rb`
72
+ When I run reek -c .todo.reek smelly.rb
71
73
  Then it reports nothing
72
74
 
73
75
  Scenario: Ignore existing other configuration files that are passed explicitly
@@ -77,16 +79,17 @@ Feature: Auto-generate a todo file
77
79
  ---
78
80
  # smelly.rb reeks of UncommunicativeMethodName and UncommunicativeVariableName
79
81
  # so the configuration below will partially mask this
80
- UncommunicativeMethodName:
81
- enabled: false
82
+ detectors:
83
+ UncommunicativeMethodName:
84
+ enabled: false
82
85
  """
83
- When I run `reek -c config.reek smelly.rb`
86
+ When I run reek -c config.reek smelly.rb
84
87
  Then it reports:
85
88
  """
86
89
  smelly.rb -- 1 warning:
87
- [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
90
+ [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y'
88
91
  """
89
- When I run `reek -c config.reek --todo smelly.rb`
92
+ When I run reek -c config.reek --todo smelly.rb
90
93
  Then it succeeds
91
- When I run `reek -c .todo.reek smelly.rb`
94
+ When I run reek -c .todo.reek smelly.rb
92
95
  Then it reports nothing
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'node'
3
4
  require_relative 'sexp_extensions'
4
5
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ module AST
5
+ #
6
+ # An AST Builder for ruby parser.
7
+ #
8
+ class Builder < ::Parser::Builders::Default
9
+ # This is a work around for parsing ruby code that has a string with invalid sequence as UTF-8.
10
+ # See. https://github.com/whitequark/parser/issues/283
11
+ def string_value(token)
12
+ value(token)
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/reek/ast/node.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require_relative '../cli/silencer'
3
2
 
4
- Reek::CLI::Silencer.silently do
5
- require 'parser'
6
- end
3
+ require_relative '../cli/silencer'
4
+ Reek::CLI::Silencer.without_warnings { require 'parser' }
7
5
 
8
6
  module Reek
9
7
  module AST
@@ -11,11 +9,8 @@ module Reek
11
9
  # methods to ease the transition from Sexp to AST::Node.
12
10
  #
13
11
  class Node < ::Parser::AST::Node
14
- attr_reader :parent
15
-
16
12
  def initialize(type, children = [], options = {})
17
13
  @comments = options.fetch(:comments, [])
18
- @parent = options.fetch(:parent, nil)
19
14
  super
20
15
  end
21
16
 
@@ -35,59 +30,47 @@ module Reek
35
30
  loc && loc.line
36
31
  end
37
32
 
33
+ def name
34
+ to_s
35
+ end
36
+
38
37
  #
39
- # Carries out a depth-first traversal of this syntax tree, yielding
40
- # every Sexp of type `target_type`. The traversal ignores any node
41
- # whose type is listed in the Array `ignoring`.
42
- # Takes a block as well.
43
- #
44
- # target_type - the type to look for, e.g. :send, :block
45
- # ignoring - types to ignore, e.g. [:casgn, :class, :module]
46
- # blk - block to execute for every hit. Gets passed in the matching element itself.
38
+ # Carries out a depth-first traversal of this syntax tree, yielding every
39
+ # Sexp of the searched for type or types. The traversal stops at any node
40
+ # whose type is listed in `ignoring`.
47
41
  #
48
- # Examples:
49
- # context.each_node(:send, [:mlhs]) do |call_node| .... end
50
- # context.each_node(:lvar).any? { |it| it.var_name == 'something' }
42
+ # If a type is searched for *and* listed in ignoring, it will be yielded
43
+ # but traversal will not continue to its children.
51
44
  #
52
- # Returns an array with all matching nodes.
53
- # TODO: without a block, this doesn't do what one might expect
54
- def each_node(target_type, ignoring = [], &blk)
55
- if block_given?
56
- look_for_type(target_type, ignoring, &blk)
57
- else
58
- result = []
59
- look_for_type(target_type, ignoring) { |exp| result << exp }
60
- result
61
- end
62
- end
63
-
45
+ # If the root's type is ignored, traversal does not stop, unless the root
46
+ # is of a target type.
64
47
  #
65
- # Carries out a depth-first traversal of this syntax tree, yielding
66
- # every Sexp of type `target_type`. The traversal ignores any node
67
- # whose type is listed in the Array `ignoring`, including the top node.
68
48
  # Takes a block as well.
69
49
  #
70
- # target_types - the types to look for, e.g. [:send, :block]
71
- # ignoring - types to ignore, e.g. [:casgn, :class, :module]
72
- # blk - block to execute for every hit
50
+ # @param target_types [Symbol, Array<Symbol>] the type or types to look
51
+ # for
52
+ # @param ignoring [Array<Symbol>] types to ignore
53
+ # @param blk block to execute for every hit. Gets passed in the
54
+ # matching element itself.
73
55
  #
74
- # Examples:
75
- # exp.find_nodes([:block]).flat_map do |elem| ... end
56
+ # @example
57
+ # node.each_node(:send, [:mlhs]) do |call_node| .... end
58
+ # node.each_node(:lvar).any? { |it| it.var_name == 'something' }
59
+ # node.each_node([:block]).flat_map do |elem| ... end
76
60
  #
77
61
  # Returns an array with all matching nodes.
78
- def find_nodes(target_types, ignoring = [])
79
- result = []
80
- look_for_types(target_types, ignoring) { |exp| result << exp }
81
- result
62
+ def each_node(target_types, ignoring = [], &blk)
63
+ return enum_for(:each_node, target_types, ignoring) unless block_given?
64
+
65
+ look_for(Array(target_types), ignoring, &blk)
82
66
  end
83
67
 
84
68
  def contains_nested_node?(target_type)
85
- look_for_type(target_type) { |_elem| return true }
86
- false
69
+ each_node(target_type).any?
87
70
  end
88
71
 
89
- # :reek:DuplicateMethodCall { max_calls: 2 } is ok for lines.first
90
- # :reek:FeatureEnvy
72
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 } is ok for lines.first
73
+ # @quality :reek:FeatureEnvy
91
74
  def format_to_ruby
92
75
  if location
93
76
  lines = location.expression.source.split("\n").map(&:strip)
@@ -106,6 +89,15 @@ module Reek
106
89
  1
107
90
  end
108
91
 
92
+ # Most nodes represent only one statement (although they can have nested
93
+ # statements). The special type :begin exists primarily to contain more
94
+ # statements.
95
+ #
96
+ # @return Array of unique outer-level statements contained in this node
97
+ def statements
98
+ [self]
99
+ end
100
+
109
101
  def source
110
102
  loc.expression.source_buffer.name
111
103
  end
@@ -113,22 +105,22 @@ module Reek
113
105
  protected
114
106
 
115
107
  # See ".each_node" for documentation.
116
- def look_for_type(target_type, ignoring = [], &blk)
108
+ def look_for(target_types, ignoring, &blk)
109
+ if target_types.include? type
110
+ yield self
111
+ return if ignoring.include?(type)
112
+ end
117
113
  each_sexp do |elem|
118
- elem.look_for_type(target_type, ignoring, &blk) unless ignoring.include?(elem.type)
114
+ elem.look_for_recurse(target_types, ignoring, &blk)
119
115
  end
120
- yield self if type == target_type
121
116
  end
122
117
 
123
- # See ".find_nodes" for documentation.
124
- def look_for_types(target_types, ignoring = [], &blk)
125
- return if ignoring.include?(type)
126
- if target_types.include? type
127
- yield self
128
- else
129
- each_sexp do |elem|
130
- elem.look_for_types(target_types, ignoring, &blk)
131
- end
118
+ # See ".each_node" for documentation.
119
+ def look_for_recurse(target_types, ignoring, &blk)
120
+ yield self if target_types.include? type
121
+ return if ignoring.include? type
122
+ each_sexp do |elem|
123
+ elem.look_for_recurse(target_types, ignoring, &blk)
132
124
  end
133
125
  end
134
126
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  # Represents functionality related to an Abstract Syntax Tree.
4
5
  module AST
@@ -49,7 +50,7 @@ module Reek
49
50
  end
50
51
 
51
52
  def self_is_max?
52
- refs.empty? || most_popular.keys.include?(:self)
53
+ refs.empty? || most_popular.key?(:self)
53
54
  end
54
55
 
55
56
  private
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  #
@@ -11,7 +12,7 @@ module Reek
11
12
  end
12
13
 
13
14
  def num_refs_to_self
14
- (explicit_self_calls + implicit_self_calls).size
15
+ (explicit_self_calls.to_a + implicit_self_calls.to_a).size
15
16
  end
16
17
 
17
18
  private
@@ -19,9 +20,7 @@ module Reek
19
20
  attr_reader :ast
20
21
 
21
22
  def explicit_self_calls
22
- [:self, :super, :zsuper, :ivar, :ivasgn].flat_map do |node_type|
23
- ast.each_node(node_type)
24
- end
23
+ ast.each_node([:self, :super, :zsuper, :ivar, :ivasgn])
25
24
  end
26
25
 
27
26
  def implicit_self_calls
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ module AST
5
+ module SexpExtensions
6
+ # Utility methods for :begin nodes.
7
+ module BeginNode
8
+ # The special type :begin exists primarily to contain more statements.
9
+ # Therefore, this method overrides the default implementation to return
10
+ # this node's children.
11
+ def statements
12
+ children
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -9,7 +10,7 @@ module Reek
9
10
  end
10
11
 
11
12
  def body_nodes(type, ignoring = [])
12
- children[1..-1].compact.flat_map { |child| child.find_nodes(type, ignoring) }
13
+ children[1..-1].compact.flat_map { |child| child.each_node(type, ignoring).to_a }
13
14
  end
14
15
 
15
16
  def else_body
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -8,8 +9,15 @@ module Reek
8
9
  children.first
9
10
  end
10
11
 
12
+ # @quality :reek:FeatureEnvy
11
13
  def body_nodes(type, ignoring = [])
12
- children[1..-1].compact.flat_map { |child| child.find_nodes(type, ignoring) }
14
+ children[1..-1].compact.flat_map do |child|
15
+ if ignoring.include? child.type
16
+ []
17
+ else
18
+ child.each_node(type, ignoring).to_a
19
+ end
20
+ end
13
21
  end
14
22
  end
15
23
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -9,7 +10,7 @@ module Reek
9
10
  end
10
11
 
11
12
  def body_nodes(type, ignoring = [])
12
- children[1].find_nodes type, ignoring
13
+ children[1].each_node type, ignoring
13
14
  end
14
15
  end
15
16
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -29,11 +30,9 @@ module Reek
29
30
  end
30
31
 
31
32
  def body_nodes(types, ignoring = [])
32
- if body
33
- body.find_nodes(types, ignoring)
34
- else
35
- []
36
- end
33
+ return [] unless body
34
+ return [] if ignoring.include?(body.type)
35
+ body.each_node(types, ignoring | types)
37
36
  end
38
37
  end
39
38
 
@@ -58,7 +57,7 @@ module Reek
58
57
  end
59
58
 
60
59
  def depends_on_instance?
61
- ReferenceCollector.new(self).num_refs_to_self > 0
60
+ ReferenceCollector.new(self).num_refs_to_self.positive?
62
61
  end
63
62
  end
64
63
 
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
5
- # Base module for utility methods for module nodes.
6
- module ModuleNodeBase
7
- # The full name of the module or class, including the name of any
6
+ # Base module for utility methods for nodes that define constants: module
7
+ # definition, class definition and constant assignment.
8
+ module ConstantDefiningNodeBase
9
+ # The full name of the constant, including the name of any
8
10
  # module or class it is nested inside of.
9
11
  #
10
12
  # For example, given code like this:
@@ -15,7 +17,8 @@ module Reek
15
17
  # end
16
18
  #
17
19
  # The full name for the inner class will be 'Foo::Bar::Baz'. To return
18
- # the correct name, the name of the outer context has to be passed into this method.
20
+ # the correct name, the name of the outer context has to be passed into
21
+ # this method.
19
22
  #
20
23
  # @param outer [String] full name of the wrapping module or class
21
24
  # @return the module's full name
@@ -31,27 +34,71 @@ module Reek
31
34
  def simple_name
32
35
  name.split('::').last
33
36
  end
37
+ end
38
+
39
+ # Base module for utility methods for module nodes.
40
+ module ModuleNodeBase
41
+ include ConstantDefiningNodeBase
34
42
 
35
43
  def name
36
44
  children.first.format_to_ruby
37
45
  end
46
+
47
+ # In the AST, the set of children of a module that a human might identify
48
+ # is coded in three different ways.
49
+ #
50
+ # If there are no children, the last element of the module node is nil,
51
+ # like so:
52
+ #
53
+ # s(:class,
54
+ # s(:const, nil, :C),
55
+ # nil,
56
+ # nil)
57
+ #
58
+ # If there is one child, the last element of the module node is that
59
+ # child, like so:
60
+ #
61
+ # s(:class,
62
+ # s(:const, nil, :C),
63
+ # nil,
64
+ # s(:def, :f, s(:args), nil))
65
+ #
66
+ # If there is more than one child, those are wrapped as children in a
67
+ # node of type :begin, like so:
68
+ #
69
+ # s(:class,
70
+ # s(:const, nil, :Alfa),
71
+ # nil,
72
+ # s(:begin,
73
+ # s(:def, :bravo, s(:args), nil),
74
+ # s(:class, s(:const, nil, :Charlie), nil, nil)))
75
+ #
76
+ # This method unifies those three ways to avoid having to handle them
77
+ # differently.
78
+ #
79
+ # @return an array of directly visible children of the module
80
+ #
81
+ def direct_children
82
+ contents = children.last or return []
83
+ contents.statements
84
+ end
38
85
  end
39
86
 
40
- # Utility methods for :module nodes.
87
+ # Utility methods for module definition (:module) nodes.
41
88
  module ModuleNode
42
89
  include ModuleNodeBase
43
90
  end
44
91
 
45
- # Utility methods for :class nodes.
92
+ # Utility methods for class definition (:class) nodes.
46
93
  module ClassNode
47
94
  include ModuleNodeBase
48
95
 
49
96
  def superclass() children[1] end
50
97
  end
51
98
 
52
- # Utility methods for :casgn nodes.
99
+ # Utility methods for constant assignment (:casgn) nodes.
53
100
  module CasgnNode
54
- include ModuleNodeBase
101
+ include ConstantDefiningNodeBase
55
102
 
56
103
  def defines_module?
57
104
  call = constant_definition
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions