rubocop 1.69.2 → 1.74.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 (312) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +4 -4
  4. data/config/default.yml +122 -17
  5. data/config/internal_affairs.yml +20 -0
  6. data/lib/rubocop/cli/command/execute_runner.rb +3 -3
  7. data/lib/rubocop/cli/command/show_cops.rb +24 -2
  8. data/lib/rubocop/cli/command/suggest_extensions.rb +7 -1
  9. data/lib/rubocop/comment_config.rb +2 -2
  10. data/lib/rubocop/config.rb +17 -4
  11. data/lib/rubocop/config_loader.rb +48 -9
  12. data/lib/rubocop/config_loader_resolver.rb +36 -10
  13. data/lib/rubocop/config_obsoletion/extracted_cop.rb +4 -3
  14. data/lib/rubocop/config_obsoletion.rb +1 -1
  15. data/lib/rubocop/config_validator.rb +19 -9
  16. data/lib/rubocop/cop/autocorrect_logic.rb +1 -1
  17. data/lib/rubocop/cop/base.rb +6 -0
  18. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  19. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  20. data/lib/rubocop/cop/internal_affairs/cop_enabled.rb +85 -0
  21. data/lib/rubocop/cop/internal_affairs/example_description.rb +7 -3
  22. data/lib/rubocop/cop/internal_affairs/location_exists.rb +116 -0
  23. data/lib/rubocop/cop/internal_affairs/location_expression.rb +2 -1
  24. data/lib/rubocop/cop/internal_affairs/node_first_or_last_argument.rb +3 -2
  25. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
  26. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_processor.rb +63 -0
  27. data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +131 -0
  28. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +229 -0
  29. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +91 -0
  30. data/lib/rubocop/cop/internal_affairs/node_type_multiple_predicates.rb +126 -0
  31. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +4 -3
  32. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +90 -0
  33. data/lib/rubocop/cop/internal_affairs/plugin.rb +33 -0
  34. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +3 -1
  35. data/lib/rubocop/cop/internal_affairs/single_line_comparison.rb +5 -4
  36. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +7 -1
  37. data/lib/rubocop/cop/internal_affairs.rb +6 -16
  38. data/lib/rubocop/cop/layout/access_modifier_indentation.rb +1 -1
  39. data/lib/rubocop/cop/layout/argument_alignment.rb +2 -8
  40. data/lib/rubocop/cop/layout/block_alignment.rb +3 -1
  41. data/lib/rubocop/cop/layout/class_structure.rb +9 -9
  42. data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +4 -4
  43. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  44. data/lib/rubocop/cop/layout/else_alignment.rb +2 -2
  45. data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
  46. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +7 -11
  47. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +27 -1
  48. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +1 -1
  49. data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +22 -2
  50. data/lib/rubocop/cop/layout/end_alignment.rb +1 -1
  51. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
  52. data/lib/rubocop/cop/layout/first_argument_indentation.rb +3 -8
  53. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -7
  54. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +2 -7
  55. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +1 -1
  56. data/lib/rubocop/cop/layout/first_parameter_indentation.rb +2 -2
  57. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -4
  58. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -1
  59. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
  60. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +2 -2
  61. data/lib/rubocop/cop/layout/line_length.rb +4 -3
  62. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +1 -1
  63. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +25 -0
  64. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -0
  65. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +4 -4
  66. data/lib/rubocop/cop/layout/redundant_line_break.rb +7 -6
  67. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -1
  68. data/lib/rubocop/cop/layout/single_line_block_chain.rb +1 -1
  69. data/lib/rubocop/cop/layout/space_after_colon.rb +2 -2
  70. data/lib/rubocop/cop/layout/space_after_comma.rb +1 -1
  71. data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -1
  72. data/lib/rubocop/cop/layout/space_after_semicolon.rb +1 -1
  73. data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -0
  74. data/lib/rubocop/cop/layout/space_around_method_call_operator.rb +1 -1
  75. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -3
  76. data/lib/rubocop/cop/layout/space_before_comma.rb +1 -1
  77. data/lib/rubocop/cop/layout/space_before_semicolon.rb +1 -1
  78. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -3
  79. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +1 -1
  80. data/lib/rubocop/cop/lint/array_literal_in_regexp.rb +119 -0
  81. data/lib/rubocop/cop/lint/assignment_in_condition.rb +1 -3
  82. data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +1 -1
  83. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +3 -3
  84. data/lib/rubocop/cop/lint/constant_reassignment.rb +148 -0
  85. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -0
  86. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  87. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +1 -1
  88. data/lib/rubocop/cop/lint/duplicate_methods.rb +0 -14
  89. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +1 -1
  90. data/lib/rubocop/cop/lint/duplicate_set_element.rb +20 -7
  91. data/lib/rubocop/cop/lint/empty_conditional_body.rb +14 -64
  92. data/lib/rubocop/cop/lint/empty_expression.rb +0 -2
  93. data/lib/rubocop/cop/lint/erb_new_arguments.rb +0 -6
  94. data/lib/rubocop/cop/lint/float_comparison.rb +6 -8
  95. data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -1
  96. data/lib/rubocop/cop/lint/format_parameter_mismatch.rb +2 -2
  97. data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -1
  98. data/lib/rubocop/cop/lint/literal_as_condition.rb +103 -9
  99. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +24 -6
  100. data/lib/rubocop/cop/lint/missing_super.rb +2 -2
  101. data/lib/rubocop/cop/lint/mixed_case_range.rb +3 -3
  102. data/lib/rubocop/cop/lint/mixed_regexp_capture_types.rb +1 -1
  103. data/lib/rubocop/cop/lint/nested_method_definition.rb +8 -4
  104. data/lib/rubocop/cop/lint/next_without_accumulator.rb +1 -1
  105. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +4 -3
  106. data/lib/rubocop/cop/lint/non_local_exit_from_iterator.rb +3 -3
  107. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +18 -31
  108. data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +2 -1
  109. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -5
  110. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +1 -1
  111. data/lib/rubocop/cop/lint/redundant_require_statement.rb +0 -21
  112. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +2 -2
  113. data/lib/rubocop/cop/lint/redundant_type_conversion.rb +258 -0
  114. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -1
  115. data/lib/rubocop/cop/lint/return_in_void_context.rb +4 -11
  116. data/lib/rubocop/cop/lint/safe_navigation_chain.rb +8 -1
  117. data/lib/rubocop/cop/lint/shared_mutable_default.rb +76 -0
  118. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  119. data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +111 -0
  120. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  121. data/lib/rubocop/cop/lint/syntax.rb +4 -1
  122. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -4
  123. data/lib/rubocop/cop/lint/unexpected_block_arity.rb +1 -1
  124. data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +1 -1
  125. data/lib/rubocop/cop/lint/unreachable_code.rb +1 -1
  126. data/lib/rubocop/cop/lint/unreachable_loop.rb +1 -1
  127. data/lib/rubocop/cop/lint/useless_access_modifier.rb +4 -4
  128. data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
  129. data/lib/rubocop/cop/lint/useless_constant_scoping.rb +71 -0
  130. data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
  131. data/lib/rubocop/cop/lint/useless_numeric_operation.rb +2 -1
  132. data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -2
  133. data/lib/rubocop/cop/lint/void.rb +11 -9
  134. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
  135. data/lib/rubocop/cop/metrics/collection_literal_length.rb +7 -0
  136. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +1 -1
  137. data/lib/rubocop/cop/metrics/method_length.rb +8 -1
  138. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  139. data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -1
  140. data/lib/rubocop/cop/metrics/utils/repeated_attribute_discount.rb +7 -7
  141. data/lib/rubocop/cop/mixin/alignment.rb +2 -2
  142. data/lib/rubocop/cop/mixin/allowed_pattern.rb +4 -4
  143. data/lib/rubocop/cop/mixin/check_line_breakable.rb +11 -11
  144. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +1 -1
  145. data/lib/rubocop/cop/mixin/comments_help.rb +4 -2
  146. data/lib/rubocop/cop/mixin/dig_help.rb +1 -1
  147. data/lib/rubocop/cop/mixin/frozen_string_literal.rb +1 -1
  148. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +22 -22
  149. data/lib/rubocop/cop/mixin/hash_subset.rb +203 -0
  150. data/lib/rubocop/cop/mixin/hash_transform_method.rb +74 -74
  151. data/lib/rubocop/cop/mixin/method_complexity.rb +1 -1
  152. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  153. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +48 -24
  154. data/lib/rubocop/cop/mixin/range_help.rb +15 -3
  155. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  156. data/lib/rubocop/cop/mixin/statement_modifier.rb +8 -3
  157. data/lib/rubocop/cop/mixin/string_help.rb +2 -2
  158. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  159. data/lib/rubocop/cop/mixin/target_ruby_version.rb +1 -1
  160. data/lib/rubocop/cop/mixin/trailing_comma.rb +15 -3
  161. data/lib/rubocop/cop/naming/block_forwarding.rb +19 -15
  162. data/lib/rubocop/cop/naming/predicate_name.rb +44 -0
  163. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +3 -3
  164. data/lib/rubocop/cop/naming/variable_name.rb +64 -6
  165. data/lib/rubocop/cop/security/compound_hash.rb +1 -0
  166. data/lib/rubocop/cop/style/access_modifier_declarations.rb +34 -5
  167. data/lib/rubocop/cop/style/accessor_grouping.rb +19 -5
  168. data/lib/rubocop/cop/style/and_or.rb +1 -1
  169. data/lib/rubocop/cop/style/arguments_forwarding.rb +39 -23
  170. data/lib/rubocop/cop/style/array_first_last.rb +18 -2
  171. data/lib/rubocop/cop/style/block_delimiters.rb +7 -20
  172. data/lib/rubocop/cop/style/class_and_module_children.rb +35 -10
  173. data/lib/rubocop/cop/style/collection_methods.rb +1 -1
  174. data/lib/rubocop/cop/style/combinable_defined.rb +1 -1
  175. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  176. data/lib/rubocop/cop/style/commented_keyword.rb +10 -3
  177. data/lib/rubocop/cop/style/comparable_between.rb +75 -0
  178. data/lib/rubocop/cop/style/concat_array_literals.rb +1 -1
  179. data/lib/rubocop/cop/style/conditional_assignment.rb +6 -4
  180. data/lib/rubocop/cop/style/documentation.rb +1 -1
  181. data/lib/rubocop/cop/style/double_negation.rb +4 -4
  182. data/lib/rubocop/cop/style/each_for_simple_loop.rb +4 -7
  183. data/lib/rubocop/cop/style/each_with_object.rb +2 -3
  184. data/lib/rubocop/cop/style/empty_else.rb +4 -2
  185. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  186. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  187. data/lib/rubocop/cop/style/endless_method.rb +163 -18
  188. data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
  189. data/lib/rubocop/cop/style/exact_regexp_match.rb +3 -10
  190. data/lib/rubocop/cop/style/expand_path_arguments.rb +2 -7
  191. data/lib/rubocop/cop/style/explicit_block_argument.rb +15 -2
  192. data/lib/rubocop/cop/style/exponential_notation.rb +3 -3
  193. data/lib/rubocop/cop/style/fetch_env_var.rb +1 -1
  194. data/lib/rubocop/cop/style/float_division.rb +8 -4
  195. data/lib/rubocop/cop/style/format_string_token.rb +38 -11
  196. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  197. data/lib/rubocop/cop/style/hash_each_methods.rb +3 -6
  198. data/lib/rubocop/cop/style/hash_except.rb +24 -148
  199. data/lib/rubocop/cop/style/hash_slice.rb +80 -0
  200. data/lib/rubocop/cop/style/hash_syntax.rb +6 -3
  201. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  202. data/lib/rubocop/cop/style/if_unless_modifier.rb +5 -5
  203. data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -1
  204. data/lib/rubocop/cop/style/if_with_semicolon.rb +2 -2
  205. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  206. data/lib/rubocop/cop/style/inverse_methods.rb +14 -11
  207. data/lib/rubocop/cop/style/it_assignment.rb +36 -0
  208. data/lib/rubocop/cop/style/keyword_parameters_order.rb +14 -8
  209. data/lib/rubocop/cop/style/line_end_concatenation.rb +10 -4
  210. data/lib/rubocop/cop/style/map_into_array.rb +1 -1
  211. data/lib/rubocop/cop/style/map_to_hash.rb +1 -1
  212. data/lib/rubocop/cop/style/map_to_set.rb +3 -2
  213. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -15
  214. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -0
  215. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +2 -1
  216. data/lib/rubocop/cop/style/method_called_on_do_end_block.rb +2 -4
  217. data/lib/rubocop/cop/style/method_def_parentheses.rb +1 -1
  218. data/lib/rubocop/cop/style/missing_else.rb +2 -0
  219. data/lib/rubocop/cop/style/multiline_block_chain.rb +2 -2
  220. data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -9
  221. data/lib/rubocop/cop/style/multiple_comparison.rb +26 -20
  222. data/lib/rubocop/cop/style/mutable_constant.rb +3 -3
  223. data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -1
  224. data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +1 -1
  225. data/lib/rubocop/cop/style/object_then.rb +13 -15
  226. data/lib/rubocop/cop/style/open_struct_use.rb +5 -5
  227. data/lib/rubocop/cop/style/parallel_assignment.rb +1 -5
  228. data/lib/rubocop/cop/style/parentheses_around_condition.rb +2 -2
  229. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
  230. data/lib/rubocop/cop/style/proc.rb +1 -2
  231. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  232. data/lib/rubocop/cop/style/raise_args.rb +6 -4
  233. data/lib/rubocop/cop/style/random_with_offset.rb +3 -3
  234. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  235. data/lib/rubocop/cop/style/redundant_condition.rb +47 -2
  236. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +16 -5
  237. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -10
  238. data/lib/rubocop/cop/style/redundant_each.rb +1 -1
  239. data/lib/rubocop/cop/style/redundant_exception.rb +2 -2
  240. data/lib/rubocop/cop/style/redundant_format.rb +250 -0
  241. data/lib/rubocop/cop/style/redundant_freeze.rb +3 -3
  242. data/lib/rubocop/cop/style/redundant_initialize.rb +12 -3
  243. data/lib/rubocop/cop/style/redundant_line_continuation.rb +34 -13
  244. data/lib/rubocop/cop/style/redundant_parentheses.rb +28 -14
  245. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +3 -0
  246. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
  247. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  248. data/lib/rubocop/cop/style/redundant_self_assignment.rb +14 -28
  249. data/lib/rubocop/cop/style/redundant_sort.rb +2 -2
  250. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -2
  251. data/lib/rubocop/cop/style/rescue_modifier.rb +3 -0
  252. data/lib/rubocop/cop/style/return_nil.rb +1 -1
  253. data/lib/rubocop/cop/style/safe_navigation.rb +2 -2
  254. data/lib/rubocop/cop/style/semicolon.rb +1 -1
  255. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +2 -1
  256. data/lib/rubocop/cop/style/single_line_block_params.rb +1 -1
  257. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  258. data/lib/rubocop/cop/style/single_line_methods.rb +6 -7
  259. data/lib/rubocop/cop/style/slicing_with_range.rb +40 -11
  260. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -8
  261. data/lib/rubocop/cop/style/string_concatenation.rb +2 -2
  262. data/lib/rubocop/cop/style/string_literals.rb +1 -1
  263. data/lib/rubocop/cop/style/string_methods.rb +1 -1
  264. data/lib/rubocop/cop/style/super_arguments.rb +65 -17
  265. data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
  266. data/lib/rubocop/cop/style/top_level_method_definition.rb +1 -1
  267. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +4 -1
  268. data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +47 -6
  269. data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +48 -6
  270. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  271. data/lib/rubocop/cop/style/while_until_modifier.rb +0 -1
  272. data/lib/rubocop/cop/style/yoda_condition.rb +8 -4
  273. data/lib/rubocop/cop/style/yoda_expression.rb +2 -1
  274. data/lib/rubocop/cop/util.rb +12 -5
  275. data/lib/rubocop/cop/utils/format_string.rb +10 -5
  276. data/lib/rubocop/cop/variable_force/variable.rb +14 -2
  277. data/lib/rubocop/cop/variable_force/variable_table.rb +3 -3
  278. data/lib/rubocop/cops_documentation_generator.rb +25 -14
  279. data/lib/rubocop/directive_comment.rb +45 -11
  280. data/lib/rubocop/ext/regexp_node.rb +0 -1
  281. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  282. data/lib/rubocop/lsp/diagnostic.rb +189 -0
  283. data/lib/rubocop/lsp/logger.rb +2 -2
  284. data/lib/rubocop/lsp/routes.rb +7 -23
  285. data/lib/rubocop/lsp/runtime.rb +17 -49
  286. data/lib/rubocop/lsp/server.rb +0 -2
  287. data/lib/rubocop/lsp/stdin_runner.rb +83 -0
  288. data/lib/rubocop/options.rb +28 -12
  289. data/lib/rubocop/path_util.rb +15 -8
  290. data/lib/rubocop/plugin/configuration_integrator.rb +143 -0
  291. data/lib/rubocop/plugin/load_error.rb +26 -0
  292. data/lib/rubocop/plugin/loader.rb +100 -0
  293. data/lib/rubocop/plugin/not_supported_error.rb +29 -0
  294. data/lib/rubocop/plugin.rb +46 -0
  295. data/lib/rubocop/rake_task.rb +4 -1
  296. data/lib/rubocop/result_cache.rb +13 -13
  297. data/lib/rubocop/rspec/cop_helper.rb +9 -0
  298. data/lib/rubocop/rspec/expect_offense.rb +6 -2
  299. data/lib/rubocop/rspec/shared_contexts.rb +19 -1
  300. data/lib/rubocop/rspec/support.rb +2 -2
  301. data/lib/rubocop/runner.rb +5 -6
  302. data/lib/rubocop/server/cache.rb +35 -2
  303. data/lib/rubocop/server/cli.rb +2 -2
  304. data/lib/rubocop/target_finder.rb +1 -0
  305. data/lib/rubocop/target_ruby.rb +15 -0
  306. data/lib/rubocop/version.rb +17 -2
  307. data/lib/rubocop.rb +12 -1
  308. data/lib/ruby_lsp/rubocop/addon.rb +75 -0
  309. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +47 -0
  310. metadata +55 -16
  311. data/lib/rubocop/cop/utils/regexp_ranges.rb +0 -113
  312. data/lib/rubocop/rspec/host_environment_simulation_helper.rb +0 -28
@@ -2,16 +2,35 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'yaml'
5
+ require_relative 'plugin'
5
6
 
6
7
  module RuboCop
7
8
  # A help class for ConfigLoader that handles configuration resolution.
8
9
  # @api private
9
10
  class ConfigLoaderResolver # rubocop:disable Metrics/ClassLength
11
+ def resolve_plugins(rubocop_config, plugins)
12
+ plugins = Array(plugins) - ConfigLoader.loaded_plugins.map { |plugin| plugin.about.name }
13
+ return if plugins.empty?
14
+
15
+ Plugin.integrate_plugins(rubocop_config, plugins)
16
+ end
17
+
10
18
  def resolve_requires(path, hash)
11
19
  config_dir = File.dirname(path)
12
20
  hash.delete('require').tap do |loaded_features|
13
21
  Array(loaded_features).each do |feature|
14
- FeatureLoader.load(config_directory_path: config_dir, feature: feature)
22
+ if Plugin.plugin_capable?(feature)
23
+ # NOTE: Compatibility for before plugins style.
24
+ warn Rainbow(<<~MESSAGE).yellow
25
+ #{feature} extension supports plugin, specify `plugins: #{feature}` instead of `require: #{feature}` in #{path}.
26
+ For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
27
+ MESSAGE
28
+ rubocop_config = Config.create(hash, path, check: false)
29
+
30
+ resolve_plugins(rubocop_config, feature)
31
+ else
32
+ FeatureLoader.load(config_directory_path: config_dir, feature: feature)
33
+ end
15
34
  end
16
35
  end
17
36
  end
@@ -105,7 +124,7 @@ module RuboCop
105
124
  elsif merge_hashes?(base_hash, derived_hash, key)
106
125
  result[key] = merge(base_hash[key], derived_hash[key], **opts)
107
126
  elsif should_union?(derived_hash, base_hash, opts[:inherit_mode], key)
108
- result[key] = base_hash[key] | derived_hash[key]
127
+ result[key] = Array(base_hash[key]) | Array(derived_hash[key])
109
128
  elsif opts[:debug]
110
129
  warn_on_duplicate_setting(base_hash, derived_hash, key, **opts)
111
130
  end
@@ -157,20 +176,27 @@ module RuboCop
157
176
  return false if inherited_file.nil? # Not inheritance resolving merge
158
177
  return false if inherited_file.start_with?('..') # Legitimate override
159
178
  return false if base_hash[key] == derived_hash[key] # Same value
160
- return false if remote_file?(inherited_file) # Can't change
179
+ return false if PathUtil.remote_file?(inherited_file) # Can't change
161
180
 
162
181
  Gem.path.none? { |dir| inherited_file.start_with?(dir) } # Can change?
163
182
  end
164
183
 
165
184
  def warn_on_duplicate_setting(base_hash, derived_hash, key, **opts)
185
+ # If the file being considered is remote, don't bother checking for duplicates
186
+ return if remote_config?(opts[:file])
187
+
166
188
  return unless duplicate_setting?(base_hash, derived_hash, key, opts[:inherited_file])
167
189
 
168
190
  inherit_mode = opts[:inherit_mode]['merge'] || opts[:inherit_mode]['override']
169
191
  return if base_hash[key].is_a?(Array) && inherit_mode&.include?(key)
170
192
 
171
- puts "#{PathUtil.smart_path(opts[:file])}: " \
172
- "#{opts[:cop_name]}:#{key} overrides " \
173
- "the same parameter in #{opts[:inherited_file]}"
193
+ puts duplicate_setting_warning(opts, key)
194
+ end
195
+
196
+ def duplicate_setting_warning(opts, key)
197
+ "#{PathUtil.smart_path(opts[:file])}: " \
198
+ "#{opts[:cop_name]}:#{key} overrides " \
199
+ "the same parameter in #{opts[:inherited_file]}"
174
200
  end
175
201
 
176
202
  def determine_inherit_mode(hash, key)
@@ -180,7 +206,7 @@ module RuboCop
180
206
  end
181
207
 
182
208
  def should_union?(derived_hash, base_hash, root_mode, key)
183
- return false unless base_hash[key].is_a?(Array)
209
+ return false unless base_hash[key].is_a?(Array) || derived_hash[key].is_a?(Array)
184
210
 
185
211
  derived_mode = derived_hash['inherit_mode']
186
212
  return false if should_override?(derived_mode, key)
@@ -218,7 +244,7 @@ module RuboCop
218
244
  end
219
245
 
220
246
  def inherited_file(path, inherit_from, file)
221
- if remote_file?(inherit_from)
247
+ if PathUtil.remote_file?(inherit_from)
222
248
  # A remote configuration, e.g. `inherit_from: http://example.com/rubocop.yml`.
223
249
  RemoteConfig.new(inherit_from, File.dirname(path))
224
250
  elsif Pathname.new(inherit_from).absolute?
@@ -238,8 +264,8 @@ module RuboCop
238
264
  end
239
265
  end
240
266
 
241
- def remote_file?(uri)
242
- uri.start_with?('http://', 'https://')
267
+ def remote_config?(file)
268
+ file.is_a?(RemoteConfig)
243
269
  end
244
270
 
245
271
  def handle_disabled_by_default(config, new_default_configuration)
@@ -15,7 +15,7 @@ module RuboCop
15
15
  end
16
16
 
17
17
  def violated?
18
- return false if feature_loaded?
18
+ return false if plugin_loaded?
19
19
 
20
20
  affected_cops.any?
21
21
  end
@@ -38,8 +38,9 @@ module RuboCop
38
38
  end
39
39
  end
40
40
 
41
- def feature_loaded?
42
- config.loaded_features.include?(gem)
41
+ def plugin_loaded?
42
+ # Plugins loaded via `require` are included in `loaded_features`.
43
+ config.loaded_plugins.include?(gem) || config.loaded_features.include?(gem)
43
44
  end
44
45
  end
45
46
  end
@@ -50,7 +50,7 @@ module RuboCop
50
50
  # Default rules for obsoletions are in config/obsoletion.yml
51
51
  # Additional rules files can be added with `RuboCop::ConfigObsoletion.files << filename`
52
52
  def load_rules # rubocop:disable Metrics/AbcSize
53
- rules = LOAD_RULES_CACHE[self.class.files] ||=
53
+ rules = LOAD_RULES_CACHE[self.class.files.hash] ||=
54
54
  self.class.files.each_with_object({}) do |filename, hash|
55
55
  hash.merge!(YAML.safe_load(File.read(filename)) || {}) do |_key, first, second|
56
56
  case first
@@ -3,12 +3,13 @@
3
3
  module RuboCop
4
4
  # Handles validation of configuration, for example cop names, parameter
5
5
  # names, and Ruby versions.
6
+ # rubocop:disable Metrics/ClassLength
6
7
  class ConfigValidator
7
8
  extend SimpleForwardable
8
9
 
9
10
  # @api private
10
11
  COMMON_PARAMS = %w[Exclude Include Severity inherit_mode AutoCorrect StyleGuide Details
11
- Enabled].freeze
12
+ Enabled Reference].freeze
12
13
  # @api private
13
14
  INTERNAL_PARAMS = %w[Description StyleGuide
14
15
  VersionAdded VersionChanged VersionRemoved
@@ -41,8 +42,9 @@ module RuboCop
41
42
  ConfigLoader.default_configuration.key?(key)
42
43
  end
43
44
 
44
- check_obsoletions
45
+ validate_parameter_shape(valid_cop_names)
45
46
 
47
+ check_obsoletions
46
48
  alert_about_unrecognized_cops(invalid_cop_names)
47
49
  validate_new_cops_parameter
48
50
  validate_parameter_names(valid_cop_names)
@@ -64,12 +66,6 @@ module RuboCop
64
66
  target_ruby.version
65
67
  end
66
68
 
67
- def validate_section_presence(name)
68
- return unless @config.key?(name) && @config[name].nil?
69
-
70
- raise ValidationError, "empty section #{name} found in #{smart_loaded_path}"
71
- end
72
-
73
69
  private
74
70
 
75
71
  attr_reader :target_ruby
@@ -177,9 +173,22 @@ module RuboCop
177
173
  raise ValidationError, message
178
174
  end
179
175
 
176
+ def validate_parameter_shape(valid_cop_names)
177
+ valid_cop_names.each do |name|
178
+ if @config[name].nil?
179
+ raise ValidationError, "empty section #{name.inspect} found in #{smart_loaded_path}"
180
+ elsif !@config[name].is_a?(Hash)
181
+ raise ValidationError, <<~MESSAGE
182
+ The configuration for #{name.inspect} in #{smart_loaded_path} is not a Hash.
183
+
184
+ Found: #{@config[name].inspect}
185
+ MESSAGE
186
+ end
187
+ end
188
+ end
189
+
180
190
  def validate_parameter_names(valid_cop_names)
181
191
  valid_cop_names.each do |name|
182
- validate_section_presence(name)
183
192
  each_invalid_parameter(name) do |param, supported_params|
184
193
  warn Rainbow(<<~MESSAGE).yellow
185
194
  Warning: #{name} does not support #{param} parameter.
@@ -277,4 +286,5 @@ module RuboCop
277
286
  "is supposed to be #{supposed_values} and #{Rainbow(value).yellow} is not."
278
287
  end
279
288
  end
289
+ # rubocop:enable Metrics/ClassLength
280
290
  end
@@ -102,7 +102,7 @@ module RuboCop
102
102
  end
103
103
 
104
104
  def string_continuation?(node)
105
- (node.str_type? || node.dstr_type? || node.xstr_type?) && node.source.match?(/\\\s*$/)
105
+ node.type?(:str, :dstr, :xstr) && node.source.match?(/\\\s*$/)
106
106
  end
107
107
 
108
108
  def multiline_string?(node)
@@ -261,6 +261,12 @@ module RuboCop
261
261
  @config.target_ruby_version
262
262
  end
263
263
 
264
+ # Returns a gems locked versions (i.e. from Gemfile.lock or gems.locked)
265
+ # @returns [Gem::Version | nil] The locked gem version, or nil if the gem is not present.
266
+ def target_gem_version(gem_name)
267
+ @config.gem_versions_in_target && @config.gem_versions_in_target[gem_name]
268
+ end
269
+
264
270
  def parser_engine
265
271
  @config.parser_engine
266
272
  end
@@ -66,7 +66,7 @@ module RuboCop
66
66
 
67
67
  def conditional_declaration?(nodes)
68
68
  parent = nodes[0].each_ancestor.find { |ancestor| !ancestor.begin_type? }
69
- return false unless parent&.if_type? || parent&.when_type?
69
+ return false unless parent&.type?(:if, :when)
70
70
 
71
71
  root_conditional_node = parent.if_type? ? parent : parent.parent
72
72
  nodes.all? { |node| within_conditional?(node, root_conditional_node) }
@@ -161,7 +161,7 @@ module RuboCop
161
161
  end
162
162
 
163
163
  def gem_options(node)
164
- return [] unless node.last_argument&.type == :hash
164
+ return [] unless node.last_argument&.hash_type?
165
165
 
166
166
  node.last_argument.keys.map(&:value)
167
167
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # Use `config.cop_enabled?('Department/CopName')` instead of
7
+ # traversing the config hash.
8
+ #
9
+ # @example
10
+ # # `for_cop(...)['Enabled']
11
+ #
12
+ # # bad
13
+ # config.for_cop('Department/CopName')['Enabled']
14
+ #
15
+ # # good
16
+ # config.cop_enabled?('Department/CopName')
17
+ #
18
+ # @example
19
+ # # when keeping a cop's config in a local and then checking the `Enabled` key
20
+ #
21
+ # # bad
22
+ # cop_config = config.for_cop('Department/CopName')
23
+ # cop_config['Enabled'] && cop_config['Foo']
24
+ #
25
+ # # good
26
+ # config.for_enabled_cop('Department/CopName')['Foo']
27
+ #
28
+ class CopEnabled < Base
29
+ extend AutoCorrector
30
+
31
+ MSG = 'Use `%<replacement>s` instead of `%<source>s`.'
32
+ MSG_HASH = 'Consider replacing uses of `%<hash_name>s` with `config.for_enabled_cop`.'
33
+
34
+ RESTRICT_ON_SEND = [:[]].freeze
35
+
36
+ # @!method for_cop_enabled?(node)
37
+ def_node_matcher :for_cop_enabled?, <<~PATTERN
38
+ (send
39
+ (send
40
+ ${(send nil? :config) (ivar :@config)} :for_cop
41
+ $(str _)) :[]
42
+ (str "Enabled"))
43
+ PATTERN
44
+
45
+ # @!method config_enabled_lookup?(node)
46
+ def_node_matcher :config_enabled_lookup?, <<~PATTERN
47
+ (send
48
+ {(lvar $_) (ivar $_) (send nil? $_)} :[]
49
+ (str "Enabled"))
50
+ PATTERN
51
+
52
+ def on_send(node)
53
+ if (config_var, cop_name = for_cop_enabled?(node))
54
+ handle_for_cop(node, config_var, cop_name)
55
+ elsif (config_var = config_enabled_lookup?(node))
56
+ return unless config_var.end_with?('_config')
57
+
58
+ handle_hash(node, config_var)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def handle_for_cop(node, config_var, cop_name)
65
+ source = node.source
66
+ quote = cop_name.loc.begin.source
67
+ cop_name = cop_name.value
68
+
69
+ replacement = "#{config_var.source}.cop_enabled?(#{quote}#{cop_name}#{quote})"
70
+ message = format(MSG, source: source, replacement: replacement)
71
+
72
+ add_offense(node, message: message) do |corrector|
73
+ corrector.replace(node, replacement)
74
+ end
75
+ end
76
+
77
+ def handle_hash(node, config_var)
78
+ message = format(MSG_HASH, hash_name: config_var)
79
+
80
+ add_offense(node, message: message)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -50,10 +50,12 @@ module RuboCop
50
50
  }.freeze
51
51
 
52
52
  EXPECT_NO_CORRECTIONS_DESCRIPTION_MAPPING = {
53
- /\A(auto[- ]?)?correct/ => 'does not correct'
53
+ /\A(auto[- ]?)?corrects?/ => 'does not correct',
54
+ /\band (auto[- ]?)?corrects/ => 'but does not correct'
54
55
  }.freeze
55
56
 
56
57
  EXPECT_CORRECTION_DESCRIPTION_MAPPING = {
58
+ /\bbut (does not|doesn't) (auto[- ]?)?correct/ => 'and autocorrects',
57
59
  /\b(does not|doesn't) (auto[- ]?)?correct/ => 'autocorrects'
58
60
  }.freeze
59
61
 
@@ -90,8 +92,10 @@ module RuboCop
90
92
  description_text = string_contents(current_description)
91
93
  return unless (new_description = correct_description(description_text, description_map))
92
94
 
95
+ quote = current_description.dstr_type? ? '"' : "'"
96
+
93
97
  add_offense(current_description, message: message) do |corrector|
94
- corrector.replace(current_description, "'#{new_description}'")
98
+ corrector.replace(current_description, "#{quote}#{new_description}#{quote}")
95
99
  end
96
100
  end
97
101
 
@@ -106,7 +110,7 @@ module RuboCop
106
110
  end
107
111
 
108
112
  def string_contents(node)
109
- node.str_type? ? node.value : node.source
113
+ node.type?(:str, :dstr) ? node.value : node.source
110
114
  end
111
115
  end
112
116
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ # When a node location may not exist, `Node#loc?` or `Node#loc_is?`
7
+ # can be used instead of calling `Node#respond_to?` before using
8
+ # the value.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # node.loc.respond_to?(:begin) && node.loc.begin
13
+ #
14
+ # # good
15
+ # node.loc?(:begin)
16
+ #
17
+ # # bad
18
+ # node.loc.respond_to?(:begin) && node.loc.begin.is?('(')
19
+ #
20
+ # # good
21
+ # node.loc_is?(:begin, '(')
22
+ #
23
+ # # bad
24
+ # node.loc.respond_to?(:begin) && node.loc.begin.source == '('
25
+ #
26
+ # # good
27
+ # node.loc_is?(:begin, '(')
28
+ #
29
+ class LocationExists < Base
30
+ extend AutoCorrector
31
+
32
+ MSG = 'Use `%<replacement>s` instead of `%<source>s`.'
33
+
34
+ # @!method replaceable_with_loc_is(node)
35
+ def_node_matcher :replaceable_with_loc_is, <<~PATTERN
36
+ (and
37
+ (call
38
+ (call $_receiver :loc) :respond_to?
39
+ $(sym _location))
40
+ {
41
+ (call
42
+ (call
43
+ (call _receiver :loc) _location) :is?
44
+ $(str _))
45
+ (call
46
+ (call
47
+ (call
48
+ (call _receiver :loc) _location) :source) :==
49
+ $(str _))
50
+ })
51
+ PATTERN
52
+
53
+ # @!method replaceable_with_loc(node)
54
+ def_node_matcher :replaceable_with_loc, <<~PATTERN
55
+ (and
56
+ (call
57
+ (call $_receiver :loc) :respond_to?
58
+ $(sym _location))
59
+ (call
60
+ (call _receiver :loc) _location))
61
+ PATTERN
62
+
63
+ def on_and(node)
64
+ replace_with_loc(node) || replace_with_loc_is(node)
65
+ end
66
+
67
+ private
68
+
69
+ def replace_with_loc(node)
70
+ replaceable_with_loc(node) do |receiver, location|
71
+ if node.parent&.assignment?
72
+ register_offense(node, replace_assignment(receiver, location))
73
+ else
74
+ register_offense(node, replacement(receiver, "loc?(#{location.source})"))
75
+ end
76
+ end
77
+ end
78
+
79
+ def replace_with_loc_is(node)
80
+ replaceable_with_loc_is(node) do |receiver, location, value|
81
+ replacement = replacement(receiver, "loc_is?(#{location.source}, #{value.source})")
82
+ register_offense(node, replacement)
83
+ end
84
+ end
85
+
86
+ def register_offense(node, replacement)
87
+ message = format(MSG, replacement: replacement, source: node.source)
88
+
89
+ add_offense(node, message: message) do |corrector|
90
+ corrector.replace(node, replacement)
91
+ end
92
+ end
93
+
94
+ def replacement(receiver, rest)
95
+ "#{replace_receiver(receiver)}#{rest}"
96
+ end
97
+
98
+ def replace_assignment(receiver, location)
99
+ prefix = replace_receiver(receiver)
100
+
101
+ "#{prefix}loc#{dot(receiver)}#{location.value} if #{prefix}loc?(#{location.source})"
102
+ end
103
+
104
+ def replace_receiver(receiver)
105
+ return '' unless receiver
106
+
107
+ "#{receiver.source}#{dot(receiver)}"
108
+ end
109
+
110
+ def dot(node)
111
+ node.parent.loc.dot.source
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -22,7 +22,7 @@ module RuboCop
22
22
 
23
23
  def on_send(node)
24
24
  return unless (parent = node.parent)
25
- return unless parent.send_type? && parent.method?(:expression)
25
+ return unless parent.call_type? && parent.method?(:expression)
26
26
  return unless parent.receiver.receiver
27
27
 
28
28
  offense = node.loc.selector.join(parent.source_range.end)
@@ -31,6 +31,7 @@ module RuboCop
31
31
  corrector.replace(offense, 'source_range')
32
32
  end
33
33
  end
34
+ alias on_csend on_send
34
35
  end
35
36
  end
36
37
  end
@@ -27,8 +27,8 @@ module RuboCop
27
27
  # @!method arguments_first_or_last?(node)
28
28
  def_node_matcher :arguments_first_or_last?, <<~PATTERN
29
29
  {
30
- (send (send !nil? :arguments) ${:first :last})
31
- (send (send !nil? :arguments) :[] (int ${0 -1}))
30
+ (call (call !nil? :arguments) ${:first :last})
31
+ (call (call !nil? :arguments) :[] (int ${0 -1}))
32
32
  }
33
33
  PATTERN
34
34
 
@@ -47,6 +47,7 @@ module RuboCop
47
47
  end
48
48
  end
49
49
  end
50
+ alias on_csend on_send
50
51
  end
51
52
  end
52
53
  end
@@ -62,7 +62,7 @@ module RuboCop
62
62
  private
63
63
 
64
64
  def valid_method_name?(node)
65
- node.first_argument.str_type? || node.first_argument.sym_type?
65
+ node.first_argument.type?(:str, :sym)
66
66
  end
67
67
 
68
68
  def method_directives(node)
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module InternalAffairs
6
+ class NodePatternGroups
7
+ # AST Processor for NodePattern ASTs, for use with `InternalAffairs/NodePatternGroups`.
8
+ #
9
+ # Looks for sequences and subsequences where the first item is a `node_type` node,
10
+ # and converts them to `node_sequence` nodes (not a true `Rubocop::AST::NodePattern`
11
+ # node type).
12
+ #
13
+ # The resulting AST will be walked by `InternalAffairs::NodePatternGroups::ASTWalker`
14
+ # in order to find node types in a `union` node that can be rewritten as a node group.
15
+ #
16
+ # NOTE: The `on_*` methods in this class relate not to the normal node types but
17
+ # rather to the Node Pattern node types. Not every node type is handled.
18
+ #
19
+ class ASTProcessor
20
+ include ::AST::Processor::Mixin
21
+
22
+ def handler_missing(node)
23
+ node.updated(nil, process_children(node))
24
+ end
25
+
26
+ # Look for `sequence` and `subsequence` nodes that contain a `node_type` node as
27
+ # their first child. These are rewritten as `node_sequence` nodes so that it is
28
+ # possible to compare nodes while looking for replacement candidates for node groups.
29
+ # This is necessary so that extended patterns can be matched and replaced.
30
+ # ie. `{(send _ :foo ...) (csend _ :foo ...)}` can become `(call _ :foo ...)`
31
+ def on_sequence(node)
32
+ first_child = node.child
33
+
34
+ if first_child.type == :node_type
35
+ children = [first_child.child, *process_children(node, 1..)]
36
+
37
+ # The `node_sequence` node contains the `node_type` symbol as its first child,
38
+ # followed by all the other nodes contained in the `sequence` node.
39
+ # The location is copied from the sequence, so that the entire sequence can
40
+ # eventually be corrected in the cop.
41
+ n(:node_sequence, children, location: node.location)
42
+ else
43
+ node.updated(nil, process_children(node))
44
+ end
45
+ end
46
+ alias on_subsequence on_sequence
47
+
48
+ private
49
+
50
+ def n(type, children = [], properties = {})
51
+ NodePattern::Node.new(type, children, properties)
52
+ end
53
+
54
+ def process_children(node, range = 0..-1)
55
+ node.children[range].map do |child|
56
+ child.is_a?(::AST::Node) ? process(child) : child
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end