rubocop 1.91.0 → 1.92.2

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 (278) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +4 -1
  4. data/lib/rubocop/cached_data.rb +47 -7
  5. data/lib/rubocop/changed_files.rb +35 -13
  6. data/lib/rubocop/cli/command/cop_names.rb +57 -0
  7. data/lib/rubocop/cli/command/execute_runner.rb +2 -2
  8. data/lib/rubocop/cli/command/explain.rb +34 -0
  9. data/lib/rubocop/cli/command/show_cops.rb +6 -0
  10. data/lib/rubocop/cli/command/show_docs_url.rb +6 -4
  11. data/lib/rubocop/cli.rb +3 -2
  12. data/lib/rubocop/comment_config.rb +17 -8
  13. data/lib/rubocop/config.rb +0 -4
  14. data/lib/rubocop/config_loader.rb +6 -3
  15. data/lib/rubocop/config_loader_resolver.rb +37 -12
  16. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  17. data/lib/rubocop/config_obsoletion.rb +9 -0
  18. data/lib/rubocop/config_validator.rb +11 -0
  19. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  20. data/lib/rubocop/cop/badge.rb +1 -1
  21. data/lib/rubocop/cop/base.rb +42 -21
  22. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  23. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  24. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  25. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  26. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  27. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  28. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  29. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  30. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  31. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  32. data/lib/rubocop/cop/documentation.rb +1 -1
  33. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  34. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  35. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  36. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  37. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  38. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  39. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  40. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  41. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  42. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  43. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  44. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  45. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  46. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  47. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  48. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  49. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  50. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  51. data/lib/rubocop/cop/layout/end_alignment.rb +21 -25
  52. data/lib/rubocop/cop/layout/extra_spacing.rb +21 -6
  53. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  54. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  55. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  56. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  57. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  58. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  59. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  60. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  61. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  62. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  63. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  64. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  65. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  66. data/lib/rubocop/cop/layout/line_length.rb +176 -26
  67. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  68. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  69. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  70. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  71. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  72. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  73. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  74. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +16 -3
  75. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  76. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  77. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  78. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  79. data/lib/rubocop/cop/layout/space_around_operators.rb +31 -4
  80. data/lib/rubocop/cop/layout/space_before_block_braces.rb +16 -1
  81. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  82. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +32 -9
  83. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +29 -11
  84. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  85. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  86. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  87. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  88. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  89. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  90. data/lib/rubocop/cop/lint/interpolation_check.rb +20 -17
  91. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  92. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  93. data/lib/rubocop/cop/lint/loop.rb +24 -2
  94. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  95. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  96. data/lib/rubocop/cop/lint/nested_percent_literal.rb +3 -0
  97. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  98. data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +22 -1
  99. data/lib/rubocop/cop/lint/percent_string_array.rb +75 -9
  100. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +39 -9
  101. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  102. data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +11 -4
  103. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  104. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  105. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  106. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  107. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  108. data/lib/rubocop/cop/lint/void.rb +8 -1
  109. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  110. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  111. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  112. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  113. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  114. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  115. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  116. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  117. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  118. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  119. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  120. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +41 -6
  121. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  122. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  123. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  124. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  125. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  126. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  127. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  128. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  129. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  130. data/lib/rubocop/cop/mixin/statement_modifier.rb +39 -5
  131. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  132. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  133. data/lib/rubocop/cop/mixin.rb +2 -0
  134. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  135. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  136. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  137. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  138. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  139. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  140. data/lib/rubocop/cop/offense.rb +59 -3
  141. data/lib/rubocop/cop/registry.rb +1 -1
  142. data/lib/rubocop/cop/security/json_load.rb +16 -4
  143. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  144. data/lib/rubocop/cop/style/alias.rb +18 -5
  145. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  146. data/lib/rubocop/cop/style/and_or.rb +37 -10
  147. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  148. data/lib/rubocop/cop/style/attr.rb +3 -4
  149. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  150. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  151. data/lib/rubocop/cop/style/case_equality.rb +14 -3
  152. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  153. data/lib/rubocop/cop/style/class_and_module_children.rb +57 -16
  154. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  155. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  156. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  157. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  158. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  159. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  160. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  161. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  162. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  163. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  164. data/lib/rubocop/cop/style/endless_method.rb +104 -11
  165. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  166. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  167. data/lib/rubocop/cop/style/float_division.rb +37 -2
  168. data/lib/rubocop/cop/style/for.rb +53 -2
  169. data/lib/rubocop/cop/style/format_string.rb +28 -6
  170. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  171. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  172. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  173. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  174. data/lib/rubocop/cop/style/hash_lookup_method.rb +42 -9
  175. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  176. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  177. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  178. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  179. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  180. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  181. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  182. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  183. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  184. data/lib/rubocop/cop/style/lambda.rb +34 -1
  185. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  186. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  187. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  188. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +26 -2
  189. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  190. data/lib/rubocop/cop/style/method_def_parentheses.rb +38 -4
  191. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  192. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  193. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  194. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  195. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  196. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  197. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  198. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  199. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  200. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  201. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  202. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  203. data/lib/rubocop/cop/style/next.rb +24 -10
  204. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  205. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  206. data/lib/rubocop/cop/style/not.rb +10 -5
  207. data/lib/rubocop/cop/style/numeric_predicate.rb +28 -5
  208. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  209. data/lib/rubocop/cop/style/parentheses_around_condition.rb +19 -1
  210. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  211. data/lib/rubocop/cop/style/quoted_symbols.rb +5 -7
  212. data/lib/rubocop/cop/style/raise_args.rb +80 -11
  213. data/lib/rubocop/cop/style/reduce_to_hash.rb +47 -33
  214. data/lib/rubocop/cop/style/redundant_begin.rb +45 -5
  215. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  216. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  217. data/lib/rubocop/cop/style/redundant_exception.rb +27 -11
  218. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  219. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  220. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  221. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  222. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  223. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  224. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  225. data/lib/rubocop/cop/style/redundant_self.rb +9 -1
  226. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  227. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  228. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  229. data/lib/rubocop/cop/style/require_order.rb +25 -8
  230. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  231. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  232. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  233. data/lib/rubocop/cop/style/sample.rb +9 -8
  234. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  235. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  236. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  237. data/lib/rubocop/cop/style/single_line_methods.rb +42 -4
  238. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  239. data/lib/rubocop/cop/style/string_concatenation.rb +21 -4
  240. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  241. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  242. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  243. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  244. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  245. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  246. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  247. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  248. data/lib/rubocop/cop/team.rb +10 -5
  249. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  250. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  251. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  252. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  253. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  254. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  255. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  256. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  257. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  258. data/lib/rubocop/lsp/routes.rb +23 -23
  259. data/lib/rubocop/lsp/runtime.rb +47 -11
  260. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  261. data/lib/rubocop/lsp.rb +5 -2
  262. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  263. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  264. data/lib/rubocop/mcp/scope.rb +65 -0
  265. data/lib/rubocop/mcp/server.rb +184 -76
  266. data/lib/rubocop/mcp/source_files.rb +35 -0
  267. data/lib/rubocop/offense_limit.rb +30 -0
  268. data/lib/rubocop/options.rb +45 -10
  269. data/lib/rubocop/plugin/configuration_integrator.rb +36 -8
  270. data/lib/rubocop/remote_config.rb +4 -1
  271. data/lib/rubocop/result_cache.rb +20 -13
  272. data/lib/rubocop/runner.rb +110 -42
  273. data/lib/rubocop/server/cache.rb +6 -6
  274. data/lib/rubocop/target_ruby.rb +21 -9
  275. data/lib/rubocop/util.rb +69 -0
  276. data/lib/rubocop/version.rb +1 -1
  277. data/lib/rubocop.rb +5 -0
  278. metadata +16 -5
@@ -45,6 +45,8 @@ module RuboCop
45
45
  MSG_TRAILING_CONDITIONAL = 'Do not use trailing conditionals in string interpolation.'
46
46
  MSG_TERNARY = 'Do not return empty strings in string interpolation.'
47
47
 
48
+ INTERPOLATED_TYPES = %i[dstr dsym regexp xstr].freeze
49
+
48
50
  def on_interpolation(node)
49
51
  node.each_child_node(:if) do |child_node|
50
52
  if style == :trailing_conditional
@@ -63,28 +65,54 @@ module RuboCop
63
65
  return if child_node.modifier_form?
64
66
 
65
67
  if empty_if_outcome?(child_node)
66
- ternary_style_autocorrect(child_node, child_node.else_branch.source, 'unless')
68
+ ternary_style_autocorrect(child_node, child_node.else_branch, 'unless')
67
69
  end
68
70
 
69
71
  return unless empty_else_outcome?(child_node)
70
72
 
71
- ternary_style_autocorrect(child_node, child_node.if_branch.source, 'if')
73
+ ternary_style_autocorrect(child_node, child_node.if_branch, 'if')
72
74
  end
73
75
 
74
76
  def ternary_correction(node, child_node)
75
77
  return unless child_node.modifier_form?
76
78
 
77
- ternary_component = if child_node.unless?
78
- "'' : #{child_node.if_branch.source}"
79
- else
80
- "#{child_node.if_branch.source} : ''"
81
- end
82
-
83
79
  add_offense(node, message: MSG_TRAILING_CONDITIONAL) do |corrector|
84
- corrector.replace(node, "\#{#{child_node.condition.source} ? #{ternary_component}}")
80
+ next if nested_trailing_conditional?(child_node)
81
+
82
+ autocorrect_to_ternary(corrector, child_node)
83
+ end
84
+ end
85
+
86
+ def nested_trailing_conditional?(if_node)
87
+ if_node.each_descendant(:if).any? do |node|
88
+ node.modifier_form? && node.parent.parent&.type?(*INTERPOLATED_TYPES)
85
89
  end
86
90
  end
87
91
 
92
+ def autocorrect_to_ternary(corrector, if_node)
93
+ condition = if_node.condition.source
94
+ branch = ternary_branch_source(if_node.if_branch)
95
+ outcomes = if_node.unless? ? "'' : #{branch}" : "#{branch} : ''"
96
+
97
+ corrector.replace(if_node, "#{condition} ? #{outcomes}")
98
+ end
99
+
100
+ def ternary_branch_source(branch)
101
+ return branch.source unless command_call?(branch)
102
+
103
+ arguments = branch.first_argument.source_range.join(branch.last_argument.source_range)
104
+ method = branch.source_range.begin.join(arguments.begin).source.rstrip
105
+
106
+ "#{method}(#{arguments.source})"
107
+ end
108
+
109
+ def command_call?(node)
110
+ return false unless node.type?(:call, :super, :yield) && node.arguments?
111
+ return false if node.call_type? && (node.operator_method? || node.setter_method?)
112
+
113
+ !node.parenthesized?
114
+ end
115
+
88
116
  def empty_if_outcome?(node)
89
117
  empty_branch_outcome?(node.if_branch)
90
118
  end
@@ -99,9 +127,23 @@ module RuboCop
99
127
  branch.nil_type? || (branch.str_type? && branch.value.empty?)
100
128
  end
101
129
 
102
- def ternary_style_autocorrect(node, outcome, condition)
130
+ def ternary_style_autocorrect(node, outcome, keyword)
103
131
  add_offense(node, message: MSG_TERNARY) do |corrector|
104
- corrector.replace(node, "#{outcome} #{condition} #{node.condition.source}")
132
+ next if empty_string_conditional_in_condition?(node)
133
+
134
+ head = node.source_range.begin.join(outcome.source_range.begin)
135
+ tail = outcome.source_range.end.join(node.source_range.end)
136
+
137
+ corrector.remove(head)
138
+ corrector.replace(tail, " #{keyword} #{node.condition.source}")
139
+ end
140
+ end
141
+
142
+ def empty_string_conditional_in_condition?(if_node)
143
+ if_node.condition.each_descendant(:if).any? do |node|
144
+ next false if node.modifier_form? || !node.parent.parent&.type?(*INTERPOLATED_TYPES)
145
+
146
+ empty_if_outcome?(node) || empty_else_outcome?(node)
105
147
  end
106
148
  end
107
149
  end
@@ -131,7 +131,9 @@ module RuboCop
131
131
  #
132
132
  class EndlessMethod < Base
133
133
  include ConfigurableEnforcedStyle
134
+ include LineLengthHelp
134
135
  include EndlessMethodRewriter
136
+ include EndlessMethodBodyHelp
135
137
  extend TargetRubyVersion
136
138
  extend AutoCorrector
137
139
 
@@ -143,6 +145,25 @@ module RuboCop
143
145
  MSG_REQUIRE_SINGLE = 'Use endless method definitions for single line methods.'
144
146
  MSG_REQUIRE_ALWAYS = 'Use endless method definitions.'
145
147
 
148
+ # `Style/MethodDefParentheses` adds the same parentheses a parameter list needs in
149
+ # an endless definition, so letting both correct in the same pass would double them.
150
+ # `Style/GuardClause` turns a single `if` body into several statements,
151
+ # which an endless definition cannot hold.
152
+ # `Style/RaiseArgs` would drop the parentheses of a `raise` that becomes
153
+ # the body in the same pass, which a method passed as an argument needs.
154
+ # `Style/IfUnlessModifier` and `Style/WhileUntilModifier` would turn the body into
155
+ # a modifier form, which then applies to the whole endless definition.
156
+ def self.autocorrect_incompatible_with
157
+ [
158
+ Style::GuardClause,
159
+ Style::IfUnlessModifier,
160
+ Style::MethodCallWithArgsParentheses,
161
+ Style::MethodDefParentheses,
162
+ Style::RaiseArgs,
163
+ Style::WhileUntilModifier
164
+ ]
165
+ end
166
+
146
167
  def on_def(node)
147
168
  return if node.assignment_method? || use_heredoc?(node)
148
169
 
@@ -177,9 +198,7 @@ module RuboCop
177
198
  add_offense(node, message: MSG_MULTI_LINE) do |corrector|
178
199
  correct_to_multiline(corrector, node)
179
200
  end
180
- elsif !node.endless? && can_be_made_endless?(node) && single_line_when_made_endless?(node)
181
- return if too_long_when_made_endless?(node)
182
-
201
+ elsif convertible_to_endless?(node) && single_line_when_made_endless?(node)
183
202
  add_offense(node, message: MSG_REQUIRE_SINGLE) do |corrector|
184
203
  correct_to_endless(corrector, node)
185
204
  end
@@ -187,14 +206,18 @@ module RuboCop
187
206
  end
188
207
 
189
208
  def handle_require_always_style(node)
190
- return if node.endless? || !can_be_made_endless?(node)
191
- return if too_long_when_made_endless?(node)
209
+ return unless convertible_to_endless?(node)
192
210
 
193
211
  add_offense(node, message: MSG_REQUIRE_ALWAYS) do |corrector|
194
212
  correct_to_endless(corrector, node)
195
213
  end
196
214
  end
197
215
 
216
+ def convertible_to_endless?(node)
217
+ !node.endless? && can_be_made_endless?(node) &&
218
+ !too_long_when_made_endless?(node) && !ambiguous_when_made_endless?(node)
219
+ end
220
+
198
221
  def handle_disallow_style(node)
199
222
  return unless node.endless?
200
223
 
@@ -213,12 +236,26 @@ module RuboCop
213
236
  return false unless (body = node.body)
214
237
  return true if body.any_str_type? && body.heredoc?
215
238
 
216
- body.each_descendant(:str).any?(&:heredoc?)
239
+ body.each_descendant(:any_str).any?(&:heredoc?)
217
240
  end
218
241
 
219
242
  def correct_to_endless(corrector, node)
220
- corrector.replace(signature_to_body_range(node), ' = ')
221
- corrector.remove(node.body.source_range.end.join(node.loc.end.end))
243
+ if unparenthesized_arguments?(node)
244
+ corrector.replace(name_to_arguments_range(node), '(')
245
+ corrector.replace(signature_to_body_range(node), ') = ')
246
+ else
247
+ corrector.replace(signature_to_body_range(node), ' = ')
248
+ end
249
+
250
+ corrector.remove(body_to_end_range(node))
251
+ end
252
+
253
+ def unparenthesized_arguments?(node)
254
+ node.arguments.any? && !node.arguments.loc.begin
255
+ end
256
+
257
+ def name_to_arguments_range(node)
258
+ node.loc.name.end.join(node.arguments.source_range.begin)
222
259
  end
223
260
 
224
261
  def signature_to_body_range(node)
@@ -227,6 +264,10 @@ module RuboCop
227
264
  signature_end.join(node.body.source_range.begin)
228
265
  end
229
266
 
267
+ def body_to_end_range(node)
268
+ node.body.source_range.end.join(node.loc.end.end)
269
+ end
270
+
230
271
  def endless_replacement(node)
231
272
  <<~RUBY.strip
232
273
  def #{receiver(node)}#{node.method_name}#{arguments(node)} = #{node.body.source}
@@ -238,21 +279,73 @@ module RuboCop
238
279
  end
239
280
 
240
281
  def arguments(node, missing = '')
241
- node.arguments.any? ? node.arguments.source : missing
282
+ return missing unless node.arguments.any?
283
+ return parenthesized_arguments(node) if unparenthesized_arguments?(node)
284
+
285
+ node.arguments.source
286
+ end
287
+
288
+ def parenthesized_arguments(node)
289
+ padding = space_inside_parens? ? ' ' : ''
290
+
291
+ "(#{padding}#{node.arguments.source}#{padding})"
292
+ end
293
+
294
+ def space_inside_parens?
295
+ return false unless config.cop_enabled?('Layout/SpaceInsideParens')
296
+
297
+ config.for_cop('Layout/SpaceInsideParens')['EnforcedStyle'] != 'no_space'
242
298
  end
243
299
 
244
300
  def can_be_made_endless?(node)
245
- node.body && !node.body.type?(:begin, :kwbegin, :rescue, :ensure)
301
+ return false unless node.body
302
+ return false if comment_in_discarded_range?(node)
303
+
304
+ endless_method_body?(node.body)
246
305
  end
247
306
 
248
307
  def single_line_when_made_endless?(node)
249
308
  !endless_replacement(node).include?("\n")
250
309
  end
251
310
 
311
+ def ambiguous_when_made_endless?(node)
312
+ (operation = ambiguous_operation(node)) && ambiguous_enclosing_operation?(operation)
313
+ end
314
+
315
+ # @!method ambiguous_operation(node)
316
+ def_node_matcher :ambiguous_operation, <<~PATTERN
317
+ ^${
318
+ (if _ <any_def _>)
319
+ ({and or} any_def _)
320
+ ({while until} _ any_def)
321
+ }
322
+ PATTERN
323
+
324
+ def ambiguous_enclosing_operation?(operation)
325
+ operation.operator_keyword? || operation.modifier_form?
326
+ end
327
+
252
328
  def too_long_when_made_endless?(node)
253
329
  return false unless config.cop_enabled?('Layout/LineLength')
254
330
 
255
- endless_replacement(node).length + node.loc.column > max_line_length
331
+ line_length(line_when_made_endless(node)) > max_line_length
332
+ end
333
+
334
+ def comment_in_discarded_range?(node)
335
+ discarded_ranges = [signature_to_body_range(node), body_to_end_range(node)]
336
+
337
+ processed_source.each_comment_in_lines(node.first_line..node.last_line).any? do |comment|
338
+ discarded_ranges.any? { |range| range.contains?(comment.source_range) }
339
+ end
340
+ end
341
+
342
+ def line_when_made_endless(node)
343
+ keyword_element = node.loc.keyword
344
+ code_before = keyword_element.source_line[0...keyword_element.column]
345
+ end_element = node.loc.end
346
+ code_after = end_element.source_line[end_element.last_column..]
347
+
348
+ "#{code_before}#{endless_replacement(node)}#{code_after}"
256
349
  end
257
350
  end
258
351
  end
@@ -5,11 +5,15 @@ module RuboCop
5
5
  module Style
6
6
  # Checks for exact regexp match inside `Regexp` literals.
7
7
  #
8
+ # `=~` returns an index and `match` returns `MatchData`, so they are only
9
+ # autocorrected when their value is used as a condition. The same goes for
10
+ # safe navigation calls, which return `nil` for a `nil` receiver.
11
+ #
8
12
  # @example
9
13
  #
10
14
  # # bad
11
15
  # string =~ /\Astring\z/
12
- # string === /\Astring\z/
16
+ # /\Astring\z/ === string
13
17
  # string.match(/\Astring\z/)
14
18
  # string.match?(/\Astring\z/)
15
19
  #
@@ -27,14 +31,15 @@ module RuboCop
27
31
 
28
32
  MSG = 'Use `%<prefer>s`.'
29
33
  RESTRICT_ON_SEND = %i[=~ === !~ match match?].freeze
34
+ BOOLEAN_METHODS = %i[=== !~ match?].to_set.freeze
30
35
 
36
+ # `String#===` is plain equality, so `===` only matches with the regexp as its receiver.
31
37
  # @!method exact_regexp_match(node)
32
38
  def_node_matcher :exact_regexp_match, <<~PATTERN
33
- (call
34
- _ {:=~ :=== :!~ :match :match?}
35
- (regexp
36
- (str $_)
37
- (regopt)))
39
+ {
40
+ (call _ {:=~ :!~ :match :match?} (regexp (str $_) (regopt)))
41
+ (send (regexp (str $_) (regopt)) :=== _)
42
+ }
38
43
  PATTERN
39
44
 
40
45
  def on_send(node)
@@ -44,16 +49,48 @@ module RuboCop
44
49
  return unless exact_match_pattern?(parsed_regexp)
45
50
 
46
51
  string = escape_single_quotes(parsed_regexp[1].text)
47
- prefer = "#{receiver.source} #{new_method(node)} '#{string}'"
52
+ subject = node.method?(:===) ? node.first_argument : receiver
53
+ prefer = "#{subject.source} #{new_method(node)} '#{string}'"
48
54
 
49
55
  add_offense(node, message: format(MSG, prefer: prefer)) do |corrector|
50
- corrector.replace(node, prefer)
56
+ autocorrect(corrector, node, prefer)
51
57
  end
52
58
  end
53
59
  alias on_csend on_send
54
60
 
55
61
  private
56
62
 
63
+ def autocorrect(corrector, node, prefer)
64
+ return unless boolean_result?(node) || truthiness_only?(node)
65
+
66
+ corrector.replace(node, parenthesize?(node) ? "(#{prefer})" : prefer)
67
+ end
68
+
69
+ # `=~` returns an index, `match` returns `MatchData` and `&.` can return `nil`,
70
+ # so replacing them with `==` is only safe where the value is used as a condition.
71
+ def boolean_result?(node)
72
+ node.send_type? && BOOLEAN_METHODS.include?(node.method_name)
73
+ end
74
+
75
+ def truthiness_only?(node)
76
+ return true unless node.value_used?
77
+
78
+ parent = node.parent
79
+ if parent.type?(:begin, :and, :or)
80
+ truthiness_only?(parent)
81
+ elsif parent.type?(:if, :while, :until)
82
+ parent.condition.equal?(node)
83
+ else
84
+ parent.send_type? && parent.method?(:!)
85
+ end
86
+ end
87
+
88
+ def parenthesize?(node)
89
+ return false unless (parent = node.parent)&.call_type?
90
+
91
+ parent.receiver.equal?(node) || (parent.operator_method? && !parent.method?(:[]))
92
+ end
93
+
57
94
  # Escape characters that are special inside a single-quoted string so the
58
95
  # generated literal (e.g. for `/\Afoo'bar\z/`) stays valid Ruby.
59
96
  def escape_single_quotes(text)
@@ -50,8 +50,11 @@ module RuboCop
50
50
  (block $_ (args $...) (yield $...))
51
51
  PATTERN
52
52
 
53
+ # `Style/LambdaCall` rewrites the `call` that the block argument is appended to, and
54
+ # `Style/TrailingCommaInArguments` changes the trailing comma where it is appended,
55
+ # in the same pass.
53
56
  def self.autocorrect_incompatible_with
54
- [Lint::UnusedMethodArgument]
57
+ [Lint::UnusedMethodArgument, Style::LambdaCall, Style::TrailingCommaInArguments]
55
58
  end
56
59
 
57
60
  def initialize(config = nil, options = nil)
@@ -70,10 +73,13 @@ module RuboCop
70
73
  # this is not a valid ruby pattern, but can happen in haml or erb,
71
74
  # so this can cause crashes in haml_lint
72
75
  return unless def_node
76
+ return if argument_forwarding?(def_node)
73
77
 
74
78
  block_name = extract_block_name(def_node)
75
79
 
76
80
  add_offense(block_node) do |corrector|
81
+ next unless can_add_block_argument?(def_node, send_node, block_name)
82
+
77
83
  corrector.remove(block_body_range(block_node, send_node))
78
84
 
79
85
  add_block_argument(send_node, corrector, block_name)
@@ -84,6 +90,11 @@ module RuboCop
84
90
 
85
91
  private
86
92
 
93
+ # A block parameter can't be added next to `...`.
94
+ def argument_forwarding?(def_node)
95
+ def_node.arguments.any?(&:forward_arg_type?)
96
+ end
97
+
87
98
  def extract_block_name(def_node)
88
99
  if def_node.block_argument?
89
100
  def_node.last_argument.name
@@ -92,6 +103,27 @@ module RuboCop
92
103
  end
93
104
  end
94
105
 
106
+ def can_add_block_argument?(def_node, send_node, block_name)
107
+ return false if block_name_taken?(def_node, block_name)
108
+
109
+ !send_node.zsuper_type? || zsuper_arguments_forwardable?(def_node)
110
+ end
111
+
112
+ def block_name_taken?(def_node, block_name)
113
+ return false if def_node.block_argument?
114
+
115
+ def_node.each_descendant(:argument, :lvasgn).any? { |node| node.name.to_s == block_name }
116
+ end
117
+
118
+ # A destructuring argument can only be passed on as the original value,
119
+ # and anonymous arguments can only be forwarded since Ruby 3.2.
120
+ def zsuper_arguments_forwardable?(def_node)
121
+ def_node.arguments.none? do |arg|
122
+ arg.mlhs_type? ||
123
+ (arg.type?(:restarg, :kwrestarg) && !arg.name && target_ruby_version < 3.2)
124
+ end
125
+ end
126
+
95
127
  def yielding_arguments?(block_args, yield_args)
96
128
  yield_args = yield_args.dup.fill(
97
129
  nil,
@@ -110,6 +142,8 @@ module RuboCop
110
142
  insert_argument(node, corrector, block_name)
111
143
  elsif empty_arguments?(node)
112
144
  corrector.replace(node.arguments, "(&#{block_name})")
145
+ elsif index_call?(node)
146
+ corrector.replace(node.loc.selector, "[&#{block_name}]")
113
147
  elsif call_like?(node)
114
148
  correct_call_node(node, corrector, block_name)
115
149
  else
@@ -122,6 +156,10 @@ module RuboCop
122
156
  node.arguments.is_a?(RuboCop::AST::Node) && node.arguments.loc.begin
123
157
  end
124
158
 
159
+ def index_call?(node)
160
+ node.send_type? && node.method?(:[]) && !node.loc?(:dot)
161
+ end
162
+
125
163
  def call_like?(node)
126
164
  node.type?(:call, :zsuper, :super)
127
165
  end
@@ -141,19 +179,21 @@ module RuboCop
141
179
  else
142
180
  "&#{block_name}"
143
181
  end
144
- corrector.insert_after(node, "(#{new_arguments})")
145
- return unless node.parenthesized?
146
-
147
- args_begin = Util.args_begin(node)
148
- args_end = Util.args_end(node)
149
- range = range_between(args_begin.begin_pos, args_end.end_pos)
150
- corrector.remove(range)
182
+ if node.parenthesized?
183
+ corrector.replace(node.loc.begin.join(node.loc.end), "(#{new_arguments})")
184
+ else
185
+ corrector.insert_after(node, "(#{new_arguments})")
186
+ end
151
187
  end
152
188
 
153
189
  def build_new_arguments_for_zsuper(node)
154
190
  def_node = node.each_ancestor(:any_def).first
155
- def_node.arguments.map do |arg|
156
- arg.optarg_type? ? arg.node_parts[0] : arg.source
191
+ def_node.arguments.filter_map do |arg|
192
+ case arg.type
193
+ when :arg, :optarg then arg.name.to_s
194
+ when :kwarg, :kwoptarg then "#{arg.name}: #{arg.name}"
195
+ when :restarg, :kwrestarg then arg.source
196
+ end
157
197
  end
158
198
  end
159
199
 
@@ -97,6 +97,7 @@ module RuboCop
97
97
 
98
98
  def on_send(node)
99
99
  return unless offense_condition?(node)
100
+ return if comment_before_to_f?(node)
100
101
 
101
102
  add_offense(node) do |corrector|
102
103
  case style
@@ -141,8 +142,42 @@ module RuboCop
141
142
  end
142
143
 
143
144
  def remove_to_f_method(corrector, send_node)
144
- corrector.remove(send_node.loc.dot)
145
- corrector.remove(send_node.loc.selector)
145
+ corrector.remove(send_node.receiver.source_range.end.join(send_node.loc.selector.end))
146
+ end
147
+
148
+ def comment_before_to_f?(node)
149
+ return true if comment_around_fdiv_operator?(node)
150
+
151
+ operands_losing_to_f(node).any? do |operand|
152
+ next false unless to_f_method?(operand)
153
+
154
+ range = operand.receiver.source_range.end.join(operand.loc.selector.begin)
155
+ comment_in_range?(range)
156
+ end
157
+ end
158
+
159
+ def comment_around_fdiv_operator?(node)
160
+ return false unless style == :fdiv
161
+
162
+ range = node.receiver.source_range.end.join(node.first_argument.source_range.begin)
163
+ comment_in_range?(range)
164
+ end
165
+
166
+ def comment_in_range?(range)
167
+ processed_source.each_comment_in_lines(range.line..range.last_line).any? do |comment|
168
+ range.contains?(comment.source_range)
169
+ end
170
+ end
171
+
172
+ def operands_losing_to_f(node)
173
+ case style
174
+ when :left_coerce, :single_coerce
175
+ [node.first_argument]
176
+ when :right_coerce
177
+ [node.receiver]
178
+ else
179
+ [node.receiver, node.first_argument]
180
+ end
146
181
  end
147
182
 
148
183
  def correct_from_slash_to_fdiv(corrector, receiver, argument)
@@ -52,12 +52,23 @@ module RuboCop
52
52
  EACH_LENGTH = 'each'.length
53
53
  PREFER_EACH = 'Prefer `each` over `for`.'
54
54
  PREFER_FOR = 'Prefer `for` over `each`.'
55
+ IMPLICIT_PARAMETERS = %i[_1 _2 _3 _4 _5 _6 _7 _8 _9 it].to_set.freeze
56
+
57
+ # `Style/RedundantBegin` removes the `begin` of a block body in the same pass,
58
+ # leaving a `rescue` or `ensure` clause directly in the `for` body.
59
+ # `Style/ItBlockParameter` replaces block parameter references in the body
60
+ # with `it` in the same pass, while they become `for` loop variables.
61
+ def self.autocorrect_incompatible_with
62
+ [Style::RedundantBegin, Style::ItBlockParameter]
63
+ end
55
64
 
56
65
  def on_for(node)
57
66
  if style == :each
58
67
  add_offense(node, message: PREFER_EACH) do |corrector|
59
- ForToEachCorrector.new(node).call(corrector)
60
68
  opposite_style_detected
69
+ next unless convertible_to_each?(node)
70
+
71
+ ForToEachCorrector.new(node).call(corrector)
61
72
  end
62
73
  else
63
74
  correct_style_detected
@@ -69,7 +80,8 @@ module RuboCop
69
80
 
70
81
  if style == :for
71
82
  return unless node.receiver
72
- return if rescue_or_ensure_body?(node)
83
+ return unless convertible_to_for?(node)
84
+ return if rescue_or_ensure_body?(node) || do_end_block_in_collection?(node)
73
85
 
74
86
  add_offense(node, message: PREFER_FOR) do |corrector|
75
87
  EachToForCorrector.new(node).call(corrector)
@@ -85,13 +97,52 @@ module RuboCop
85
97
 
86
98
  private
87
99
 
100
+ # The loop variable becomes a block parameter, which can only be a local
101
+ # variable, and the body can't refer to an implicit block parameter
102
+ # (`_1` or `it`) once the block has an explicit one.
103
+ def convertible_to_each?(node)
104
+ return false unless node.variable.each_node.all? { |n| n.type?(:lvasgn, :mlhs, :splat) }
105
+ return true unless node.body
106
+
107
+ node.body.each_node(:send, :lvar).none? { |ref| implicit_parameter?(ref, node) }
108
+ end
109
+
110
+ # Inside a numbered or `it` block, `_1` and `it` are local variables
111
+ # rather than method calls.
112
+ def implicit_parameter?(ref, for_node)
113
+ return false unless implicit_parameter_reference?(ref)
114
+
115
+ ref.each_ancestor.take_while { |ancestor| !ancestor.equal?(for_node) }
116
+ .none?(&:any_block_type?)
117
+ end
118
+
119
+ def implicit_parameter_reference?(ref)
120
+ return IMPLICIT_PARAMETERS.include?(ref.name) if ref.lvar_type?
121
+
122
+ IMPLICIT_PARAMETERS.include?(ref.method_name) && !ref.receiver && !ref.arguments?
123
+ end
124
+
88
125
  def suspect_enumerable?(node)
89
126
  node.multiline? && node.method?(:each) && !node.send_node.arguments?
90
127
  end
91
128
 
129
+ def convertible_to_for?(node)
130
+ return false if node.type?(:numblock, :itblock)
131
+ return true unless node.arguments?
132
+
133
+ arguments = node.arguments
134
+ return false if arguments.children.one? && arguments.children.first.restarg_type?
135
+
136
+ arguments.each_descendant.all? { |arg| arg.type?(:arg, :restarg, :mlhs) }
137
+ end
138
+
92
139
  def rescue_or_ensure_body?(node)
93
140
  node.body&.type?(:rescue, :ensure)
94
141
  end
142
+
143
+ def do_end_block_in_collection?(node)
144
+ node.receiver.each_node(:any_block).any?(&:keywords?)
145
+ end
95
146
  end
96
147
  end
97
148
  end