rubocop 1.91.0 → 1.92.1

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 (263) 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_loader.rb +6 -3
  14. data/lib/rubocop/config_loader_resolver.rb +37 -12
  15. data/lib/rubocop/config_obsoletion/removed_cop.rb +1 -1
  16. data/lib/rubocop/config_validator.rb +11 -0
  17. data/lib/rubocop/cop/autocorrect_logic.rb +10 -5
  18. data/lib/rubocop/cop/badge.rb +1 -1
  19. data/lib/rubocop/cop/base.rb +42 -21
  20. data/lib/rubocop/cop/bundler/gem_comment.rb +0 -1
  21. data/lib/rubocop/cop/bundler/ordered_gems.rb +0 -8
  22. data/lib/rubocop/cop/cop_documentation.rb +81 -0
  23. data/lib/rubocop/cop/cop_explanation.rb +140 -0
  24. data/lib/rubocop/cop/correctors/alignment_corrector.rb +13 -1
  25. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +37 -7
  26. data/lib/rubocop/cop/correctors/empty_line_corrector.rb +1 -1
  27. data/lib/rubocop/cop/correctors/lambda_literal_to_method_corrector.rb +25 -4
  28. data/lib/rubocop/cop/correctors/multiline_literal_brace_corrector.rb +34 -0
  29. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +45 -8
  30. data/lib/rubocop/cop/documentation.rb +1 -1
  31. data/lib/rubocop/cop/gemspec/dependency_version.rb +0 -6
  32. data/lib/rubocop/cop/gemspec/ordered_dependencies.rb +0 -6
  33. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +2 -5
  34. data/lib/rubocop/cop/internal_affairs/node_type_group.rb +0 -7
  35. data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +1 -7
  36. data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +1 -7
  37. data/lib/rubocop/cop/layout/argument_alignment.rb +5 -1
  38. data/lib/rubocop/cop/layout/block_alignment.rb +27 -0
  39. data/lib/rubocop/cop/layout/case_indentation.rb +25 -9
  40. data/lib/rubocop/cop/layout/class_structure.rb +29 -7
  41. data/lib/rubocop/cop/layout/def_end_alignment.rb +4 -0
  42. data/lib/rubocop/cop/layout/dot_position.rb +25 -15
  43. data/lib/rubocop/cop/layout/empty_line_after_multiline_condition.rb +21 -8
  44. data/lib/rubocop/cop/layout/empty_lines.rb +5 -2
  45. data/lib/rubocop/cop/layout/empty_lines_around_attribute_accessor.rb +1 -0
  46. data/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +9 -3
  47. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +23 -1
  48. data/lib/rubocop/cop/layout/end_alignment.rb +19 -17
  49. data/lib/rubocop/cop/layout/extra_spacing.rb +4 -0
  50. data/lib/rubocop/cop/layout/first_argument_indentation.rb +27 -1
  51. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +15 -0
  52. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +16 -8
  53. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +6 -0
  54. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +4 -0
  55. data/lib/rubocop/cop/layout/hash_alignment.rb +16 -12
  56. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +7 -3
  57. data/lib/rubocop/cop/layout/heredoc_indentation.rb +15 -0
  58. data/lib/rubocop/cop/layout/indentation_consistency.rb +22 -5
  59. data/lib/rubocop/cop/layout/indentation_style.rb +4 -1
  60. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  61. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +8 -1
  62. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +25 -9
  63. data/lib/rubocop/cop/layout/line_length.rb +153 -21
  64. data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +12 -0
  65. data/lib/rubocop/cop/layout/multiline_block_layout.rb +8 -1
  66. data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +9 -0
  67. data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -0
  68. data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +30 -5
  69. data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +4 -0
  70. data/lib/rubocop/cop/layout/redundant_line_break.rb +22 -1
  71. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +15 -2
  72. data/lib/rubocop/cop/layout/single_line_block_chain.rb +10 -2
  73. data/lib/rubocop/cop/layout/space_after_not.rb +5 -0
  74. data/lib/rubocop/cop/layout/space_around_block_parameters.rb +6 -11
  75. data/lib/rubocop/cop/layout/space_around_keyword.rb +10 -0
  76. data/lib/rubocop/cop/layout/space_before_block_braces.rb +4 -1
  77. data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +15 -2
  78. data/lib/rubocop/cop/layout/space_inside_block_braces.rb +31 -8
  79. data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +2 -12
  80. data/lib/rubocop/cop/lint/ambiguous_operator.rb +1 -1
  81. data/lib/rubocop/cop/lint/ambiguous_operator_precedence.rb +2 -1
  82. data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -1
  83. data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
  84. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +4 -0
  85. data/lib/rubocop/cop/lint/literal_as_condition.rb +15 -0
  86. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +63 -19
  87. data/lib/rubocop/cop/lint/loop.rb +24 -2
  88. data/lib/rubocop/cop/lint/misplaced_magic_comment.rb +71 -11
  89. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +18 -3
  90. data/lib/rubocop/cop/lint/number_conversion.rb +3 -1
  91. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +12 -1
  92. data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +13 -2
  93. data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +47 -1
  94. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -0
  95. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +18 -1
  96. data/lib/rubocop/cop/lint/useless_assignment.rb +14 -7
  97. data/lib/rubocop/cop/lint/useless_setter_call.rb +7 -4
  98. data/lib/rubocop/cop/lint/void.rb +8 -1
  99. data/lib/rubocop/cop/metrics/class_length.rb +4 -2
  100. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -4
  101. data/lib/rubocop/cop/metrics/module_length.rb +4 -2
  102. data/lib/rubocop/cop/mixin/alignment.rb +4 -0
  103. data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -19
  104. data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +8 -2
  105. data/lib/rubocop/cop/mixin/code_length.rb +10 -2
  106. data/lib/rubocop/cop/mixin/empty_lines_around_body.rb +12 -1
  107. data/lib/rubocop/cop/mixin/endless_method_body_help.rb +105 -0
  108. data/lib/rubocop/cop/mixin/forbidden_identifiers.rb +2 -2
  109. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +24 -5
  110. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +26 -2
  111. data/lib/rubocop/cop/mixin/min_branches_count.rb +3 -1
  112. data/lib/rubocop/cop/mixin/multiline_element_line_breaks.rb +11 -1
  113. data/lib/rubocop/cop/mixin/multiline_literal_brace_layout.rb +9 -0
  114. data/lib/rubocop/cop/mixin/ordered_gem_node.rb +1 -1
  115. data/lib/rubocop/cop/mixin/pattern_matching_help.rb +35 -0
  116. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +17 -2
  117. data/lib/rubocop/cop/mixin/project_index_help.rb +15 -0
  118. data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +13 -3
  119. data/lib/rubocop/cop/mixin/statement_modifier.rb +25 -4
  120. data/lib/rubocop/cop/mixin/visibility_help.rb +2 -1
  121. data/lib/rubocop/cop/mixin.rb +2 -0
  122. data/lib/rubocop/cop/naming/accessor_method_name.rb +0 -16
  123. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  124. data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +9 -2
  125. data/lib/rubocop/cop/naming/heredoc_delimiter_naming.rb +5 -3
  126. data/lib/rubocop/cop/naming/predicate_prefix.rb +7 -22
  127. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  128. data/lib/rubocop/cop/offense.rb +59 -3
  129. data/lib/rubocop/cop/registry.rb +1 -1
  130. data/lib/rubocop/cop/security/json_load.rb +16 -4
  131. data/lib/rubocop/cop/style/access_modifier_declarations.rb +37 -2
  132. data/lib/rubocop/cop/style/alias.rb +18 -5
  133. data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +4 -3
  134. data/lib/rubocop/cop/style/and_or.rb +6 -2
  135. data/lib/rubocop/cop/style/arguments_forwarding.rb +32 -3
  136. data/lib/rubocop/cop/style/attr.rb +3 -4
  137. data/lib/rubocop/cop/style/bitwise_predicate.rb +6 -4
  138. data/lib/rubocop/cop/style/block_delimiters.rb +35 -3
  139. data/lib/rubocop/cop/style/case_like_if.rb +6 -3
  140. data/lib/rubocop/cop/style/class_and_module_children.rb +56 -16
  141. data/lib/rubocop/cop/style/class_methods_definitions.rb +15 -0
  142. data/lib/rubocop/cop/style/collection_querying.rb +79 -46
  143. data/lib/rubocop/cop/style/combinable_loops.rb +89 -11
  144. data/lib/rubocop/cop/style/concat_array_literals.rb +58 -7
  145. data/lib/rubocop/cop/style/conditional_assignment.rb +4 -0
  146. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +65 -9
  147. data/lib/rubocop/cop/style/double_negation.rb +11 -0
  148. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  149. data/lib/rubocop/cop/style/empty_method.rb +3 -1
  150. data/lib/rubocop/cop/style/empty_string_inside_interpolation.rb +53 -11
  151. data/lib/rubocop/cop/style/endless_method.rb +71 -6
  152. data/lib/rubocop/cop/style/exact_regexp_match.rb +45 -8
  153. data/lib/rubocop/cop/style/explicit_block_argument.rb +50 -10
  154. data/lib/rubocop/cop/style/float_division.rb +22 -2
  155. data/lib/rubocop/cop/style/for.rb +53 -2
  156. data/lib/rubocop/cop/style/format_string.rb +28 -6
  157. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +2 -1
  158. data/lib/rubocop/cop/style/guard_clause.rb +31 -22
  159. data/lib/rubocop/cop/style/hash_as_last_array_item.rb +4 -0
  160. data/lib/rubocop/cop/style/hash_conversion.rb +28 -8
  161. data/lib/rubocop/cop/style/hash_lookup_method.rb +32 -9
  162. data/lib/rubocop/cop/style/hash_syntax.rb +16 -3
  163. data/lib/rubocop/cop/style/identical_conditional_branches.rb +22 -3
  164. data/lib/rubocop/cop/style/if_inside_else.rb +21 -1
  165. data/lib/rubocop/cop/style/if_unless_modifier.rb +32 -26
  166. data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +4 -0
  167. data/lib/rubocop/cop/style/if_with_semicolon.rb +47 -4
  168. data/lib/rubocop/cop/style/inverse_methods.rb +24 -2
  169. data/lib/rubocop/cop/style/it_block_parameter.rb +94 -4
  170. data/lib/rubocop/cop/style/keyword_parameters_order.rb +3 -1
  171. data/lib/rubocop/cop/style/lambda.rb +34 -1
  172. data/lib/rubocop/cop/style/lambda_call.rb +7 -2
  173. data/lib/rubocop/cop/style/map_into_array.rb +1 -2
  174. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +80 -13
  175. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +25 -2
  176. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +26 -3
  177. data/lib/rubocop/cop/style/method_def_parentheses.rb +29 -3
  178. data/lib/rubocop/cop/style/missing_else.rb +7 -1
  179. data/lib/rubocop/cop/style/mixin_grouping.rb +12 -3
  180. data/lib/rubocop/cop/style/multiline_if_modifier.rb +45 -5
  181. data/lib/rubocop/cop/style/multiline_in_pattern_then.rb +7 -0
  182. data/lib/rubocop/cop/style/multiline_method_signature.rb +2 -5
  183. data/lib/rubocop/cop/style/multiline_ternary_operator.rb +7 -3
  184. data/lib/rubocop/cop/style/multiline_when_then.rb +7 -0
  185. data/lib/rubocop/cop/style/mutable_constant.rb +52 -7
  186. data/lib/rubocop/cop/style/negated_if_else_condition.rb +2 -1
  187. data/lib/rubocop/cop/style/negative_array_index.rb +9 -1
  188. data/lib/rubocop/cop/style/nested_modifier.rb +27 -8
  189. data/lib/rubocop/cop/style/nested_ternary_operator.rb +26 -8
  190. data/lib/rubocop/cop/style/next.rb +24 -10
  191. data/lib/rubocop/cop/style/nil_comparison.rb +16 -4
  192. data/lib/rubocop/cop/style/non_nil_check.rb +1 -1
  193. data/lib/rubocop/cop/style/not.rb +10 -5
  194. data/lib/rubocop/cop/style/numeric_predicate.rb +21 -5
  195. data/lib/rubocop/cop/style/one_line_conditional.rb +13 -2
  196. data/lib/rubocop/cop/style/parentheses_around_condition.rb +1 -1
  197. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +14 -3
  198. data/lib/rubocop/cop/style/raise_args.rb +19 -9
  199. data/lib/rubocop/cop/style/reduce_to_hash.rb +17 -1
  200. data/lib/rubocop/cop/style/redundant_begin.rb +22 -4
  201. data/lib/rubocop/cop/style/redundant_condition.rb +30 -13
  202. data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +6 -0
  203. data/lib/rubocop/cop/style/redundant_exception.rb +15 -3
  204. data/lib/rubocop/cop/style/redundant_fetch_block.rb +9 -1
  205. data/lib/rubocop/cop/style/redundant_freeze.rb +11 -3
  206. data/lib/rubocop/cop/style/redundant_interpolation_unfreeze.rb +24 -1
  207. data/lib/rubocop/cop/style/redundant_parentheses.rb +12 -1
  208. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +7 -0
  209. data/lib/rubocop/cop/style/redundant_regexp_constructor.rb +20 -0
  210. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +4 -0
  211. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +28 -0
  212. data/lib/rubocop/cop/style/redundant_sort.rb +38 -4
  213. data/lib/rubocop/cop/style/regexp_literal.rb +75 -26
  214. data/lib/rubocop/cop/style/require_order.rb +25 -8
  215. data/lib/rubocop/cop/style/rescue_modifier.rb +11 -9
  216. data/lib/rubocop/cop/style/return_nil.rb +17 -2
  217. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +11 -1
  218. data/lib/rubocop/cop/style/sample.rb +9 -8
  219. data/lib/rubocop/cop/style/self_assignment.rb +12 -15
  220. data/lib/rubocop/cop/style/semicolon.rb +16 -5
  221. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +4 -0
  222. data/lib/rubocop/cop/style/single_line_methods.rb +21 -4
  223. data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +29 -5
  224. data/lib/rubocop/cop/style/string_concatenation.rb +9 -3
  225. data/lib/rubocop/cop/style/struct_inheritance.rb +6 -0
  226. data/lib/rubocop/cop/style/super_arguments.rb +3 -0
  227. data/lib/rubocop/cop/style/symbol_proc.rb +24 -6
  228. data/lib/rubocop/cop/style/ternary_parentheses.rb +23 -1
  229. data/lib/rubocop/cop/style/trailing_underscore_variable.rb +23 -19
  230. data/lib/rubocop/cop/style/while_until_modifier.rb +1 -1
  231. data/lib/rubocop/cop/style/yoda_condition.rb +24 -9
  232. data/lib/rubocop/cop/style/yoda_expression.rb +10 -1
  233. data/lib/rubocop/cop/team.rb +10 -5
  234. data/lib/rubocop/cop/variable_force/assignment.rb +1 -1
  235. data/lib/rubocop/cop/variable_force/variable.rb +14 -1
  236. data/lib/rubocop/formatter/base_formatter.rb +22 -0
  237. data/lib/rubocop/formatter/formatter_set.rb +4 -0
  238. data/lib/rubocop/formatter/json_formatter.rb +71 -1
  239. data/lib/rubocop/formatter/offense_count_formatter.rb +3 -1
  240. data/lib/rubocop/formatter/pacman_formatter.rb +3 -1
  241. data/lib/rubocop/formatter/sarif_formatter.rb +12 -7
  242. data/lib/rubocop/formatter/worst_offenders_formatter.rb +3 -1
  243. data/lib/rubocop/lsp/routes.rb +23 -23
  244. data/lib/rubocop/lsp/runtime.rb +47 -11
  245. data/lib/rubocop/lsp/stdin_runner.rb +11 -5
  246. data/lib/rubocop/lsp.rb +5 -2
  247. data/lib/rubocop/mcp/autocorrection_request.rb +52 -0
  248. data/lib/rubocop/mcp/explain_tool.rb +68 -0
  249. data/lib/rubocop/mcp/scope.rb +65 -0
  250. data/lib/rubocop/mcp/server.rb +184 -76
  251. data/lib/rubocop/mcp/source_files.rb +35 -0
  252. data/lib/rubocop/offense_limit.rb +30 -0
  253. data/lib/rubocop/options.rb +45 -10
  254. data/lib/rubocop/plugin/configuration_integrator.rb +12 -5
  255. data/lib/rubocop/remote_config.rb +4 -1
  256. data/lib/rubocop/result_cache.rb +20 -13
  257. data/lib/rubocop/runner.rb +109 -41
  258. data/lib/rubocop/server/cache.rb +6 -6
  259. data/lib/rubocop/target_ruby.rb +21 -9
  260. data/lib/rubocop/util.rb +69 -0
  261. data/lib/rubocop/version.rb +1 -1
  262. data/lib/rubocop.rb +5 -0
  263. metadata +16 -5
@@ -156,16 +156,22 @@ module RuboCop
156
156
  if first_pair
157
157
  return if same_line?(first_pair, left_brace)
158
158
 
159
- if separator_style?(first_pair)
160
- check_based_on_longest_key(hash_node, left_brace, left_parenthesis)
161
- else
162
- check_first(first_pair, left_brace, left_parenthesis, 0)
163
- end
159
+ check_first_pair(hash_node, first_pair, left_brace, left_parenthesis)
164
160
  end
165
161
 
166
162
  check_right_brace(hash_node.loc.end, first_pair, left_brace, left_parenthesis)
167
163
  end
168
164
 
165
+ def check_first_pair(hash_node, first_pair, left_brace, left_parenthesis)
166
+ return unless begins_its_line?(first_pair.source_range)
167
+
168
+ if separator_style?(first_pair)
169
+ check_based_on_longest_key(hash_node, left_brace, left_parenthesis)
170
+ else
171
+ check_first(first_pair, left_brace, left_parenthesis, 0)
172
+ end
173
+ end
174
+
169
175
  def check_right_brace(right_brace, first_pair, left_brace, left_parenthesis)
170
176
  # if the right brace is on the same line as the last value, accept
171
177
  return if /\S/.match?(right_brace.source_line[0...right_brace.column])
@@ -187,9 +193,11 @@ module RuboCop
187
193
  end
188
194
 
189
195
  def check_based_on_longest_key(hash_node, left_brace, left_parenthesis)
190
- key_lengths = hash_node.keys.map { |key| key.source_range.length }
191
- check_first(hash_node.pairs.first, left_brace, left_parenthesis,
192
- key_lengths.max - key_lengths.first)
196
+ first_key = hash_node.keys.first
197
+ key_lengths = hash_node.keys.select(&:single_line?).map { |key| key.source_range.length }
198
+ offset = first_key.single_line? ? key_lengths.max - first_key.source_range.length : 0
199
+
200
+ check_first(hash_node.pairs.first, left_brace, left_parenthesis, offset)
193
201
  end
194
202
 
195
203
  # Returns the description of what the correct indentation is based on.
@@ -75,6 +75,12 @@ module RuboCop
75
75
 
76
76
  MSG = 'Add a line break before the first argument of a multi-line method argument list.'
77
77
 
78
+ # A line break inserted after `(` while `Style/MethodCallWithArgsParentheses` removes
79
+ # the parentheses in the same pass leaves the method name alone on its line.
80
+ def self.autocorrect_incompatible_with
81
+ [Style::MethodCallWithArgsParentheses]
82
+ end
83
+
78
84
  def on_send(node)
79
85
  return if allowed_method?(node.method_name)
80
86
 
@@ -59,6 +59,10 @@ module RuboCop
59
59
 
60
60
  MSG = 'Add a line break before the first parameter of a multi-line method parameter list.'
61
61
 
62
+ def self.autocorrect_incompatible_with
63
+ [Style::MethodDefParentheses]
64
+ end
65
+
62
66
  def on_def(node)
63
67
  check_method_line_break(node, node.arguments, ignore_last: ignore_last_element?)
64
68
  end
@@ -215,7 +215,7 @@ module RuboCop
215
215
  check_pairs(node)
216
216
  end
217
217
 
218
- attr_accessor :offenses_by, :column_deltas
218
+ attr_accessor :offenses_by, :column_deltas, :unalignable_styles
219
219
 
220
220
  private
221
221
 
@@ -240,6 +240,7 @@ module RuboCop
240
240
  def reset!
241
241
  self.offenses_by = {}
242
242
  self.column_deltas = Hash.new { |hash, key| hash[key] = {} }
243
+ self.unalignable_styles = []
243
244
  end
244
245
 
245
246
  def check_pairs(node)
@@ -265,7 +266,9 @@ module RuboCop
265
266
  kwsplat_offenses = offenses_by.delete(KeywordSplatAlignment)
266
267
  register_offenses_with_format(kwsplat_offenses, KeywordSplatAlignment)
267
268
 
268
- format, offenses = offenses_by.min_by { |_, v| v.length }
269
+ candidates = offenses_by.reject { |style, _| unalignable_styles.include?(style) }
270
+ candidates = offenses_by if candidates.empty?
271
+ format, offenses = candidates.min_by { |_, v| v.length }
269
272
  register_offenses_with_format(offenses, format)
270
273
  end
271
274
 
@@ -283,10 +286,19 @@ module RuboCop
283
286
  offenses_by[alignment.class] ||= []
284
287
  return if good_alignment? delta
285
288
 
289
+ if key_out_of_line?(delta, node)
290
+ unalignable_styles << alignment.class
291
+ return
292
+ end
293
+
286
294
  column_deltas[alignment.class][node] = delta
287
295
  offenses_by[alignment.class].push(node)
288
296
  end
289
297
 
298
+ def key_out_of_line?(delta, node)
299
+ (delta[:key] || 0) < -node.source_range.column
300
+ end
301
+
290
302
  def ignore_hash_argument?(node)
291
303
  case cop_config['EnforcedLastArgumentHashStyle']
292
304
  when 'always_inspect' then false
@@ -325,18 +337,10 @@ module RuboCop
325
337
  node.loc.operator)
326
338
  else
327
339
  delta_value = delta[:key] || 0
328
- correct_no_value(corrector, delta_value, node.source_range)
340
+ adjust(corrector, delta_value, node.source_range)
329
341
  end
330
342
  end
331
343
 
332
- def correct_no_value(corrector, key_delta, key)
333
- adjust(corrector, clamped_key_delta(key_delta, key), key)
334
- end
335
-
336
- def clamped_key_delta(key_delta, key)
337
- [key_delta, -key.column].max
338
- end
339
-
340
344
  def correct_key_value(corrector, delta, key, value, separator)
341
345
  # We can't use the instance variable inside the lambda. That would
342
346
  # just give each lambda the same reference and they would all get the
@@ -345,7 +349,7 @@ module RuboCop
345
349
  value_delta = delta[:value] || 0
346
350
  key_delta = delta[:key] || 0
347
351
 
348
- adjust(corrector, clamped_key_delta(key_delta, key), key)
352
+ adjust(corrector, key_delta, key)
349
353
  adjust(corrector, separator_delta, separator)
350
354
  adjust(corrector, value_delta, value)
351
355
  end
@@ -57,8 +57,10 @@ module RuboCop
57
57
  MSG = 'Put the closing parenthesis for a method call with a ' \
58
58
  'HEREDOC parameter on the same line as the HEREDOC opening.'
59
59
 
60
+ # `Layout/MultilineMethodCallBraceLayout` can move the same closing parenthesis after
61
+ # the last argument in the same pass, and both insertions are kept.
60
62
  def self.autocorrect_incompatible_with
61
- [Style::TrailingCommaInArguments]
63
+ [Layout::MultilineMethodCallBraceLayout, Style::TrailingCommaInArguments]
62
64
  end
63
65
 
64
66
  def on_send(node)
@@ -117,6 +119,8 @@ module RuboCop
117
119
  previous = heredoc
118
120
  current = previous.parent
119
121
  until send_missing_closing_parens?(current, previous, heredoc)
122
+ return if current&.any_block_type? && !current.send_node.equal?(previous)
123
+
120
124
  previous = current
121
125
  current = current.parent
122
126
  return unless previous && current
@@ -229,8 +233,8 @@ module RuboCop
229
233
  end
230
234
 
231
235
  def find_most_bottom_of_heredoc_end(arguments)
232
- arguments.filter_map do |argument|
233
- argument.loc.heredoc_end.end_pos if argument.loc?(:heredoc_end)
236
+ arguments.flat_map { |argument| argument.each_node(:any_str).to_a }.filter_map do |node|
237
+ node.loc.heredoc_end.end_pos if node.loc?(:heredoc_end)
234
238
  end.max
235
239
  end
236
240
 
@@ -55,6 +55,10 @@ module RuboCop
55
55
  (send _ {:squish :squish!})
56
56
  PATTERN
57
57
 
58
+ def on_new_investigation
59
+ @offending_heredoc_bodies = nil
60
+ end
61
+
58
62
  def on_heredoc(node)
59
63
  body = heredoc_body(node)
60
64
  return if body.strip.empty?
@@ -77,6 +81,9 @@ module RuboCop
77
81
  private
78
82
 
79
83
  def register_offense(node, heredoc_indent_type)
84
+ return if within_offending_heredoc?(node)
85
+
86
+ offending_heredoc_bodies << node.loc.heredoc_body
80
87
  message = message(heredoc_indent_type)
81
88
 
82
89
  add_offense(node.loc.heredoc_body, message: message) do |corrector|
@@ -90,6 +97,14 @@ module RuboCop
90
97
  end
91
98
  end
92
99
 
100
+ def within_offending_heredoc?(node)
101
+ offending_heredoc_bodies.any? { |body| body.contains?(node.source_range) }
102
+ end
103
+
104
+ def offending_heredoc_bodies
105
+ @offending_heredoc_bodies ||= []
106
+ end
107
+
93
108
  def message(heredoc_indent_type)
94
109
  current_indent_type = "<<#{heredoc_indent_type}"
95
110
 
@@ -178,10 +178,24 @@ module RuboCop
178
178
  end
179
179
 
180
180
  def check_normal_style(node)
181
- check_alignment(
182
- node.children.reject { |child| bare_access_modifier?(child) },
183
- base_column_for_normal_style(node)
184
- )
181
+ items = node.children.reject { |child| bare_access_modifier?(child) }
182
+
183
+ check_alignment(items, base_column_for_normal_style(node) || governed_base_column(items))
184
+ end
185
+
186
+ def governed_base_column(items)
187
+ return unless config.cop_enabled?('Layout/IndentationWidth')
188
+ return if items.empty? || indentation_width_enabled_at?(items.first)
189
+
190
+ governed_item = items.find { |item| indentation_width_enabled_at?(item) }
191
+
192
+ display_column(governed_item.source_range) if governed_item
193
+ end
194
+
195
+ def indentation_width_enabled_at?(node)
196
+ comment_config = processed_source.comment_config
197
+
198
+ comment_config.cop_enabled_at_line?('Layout/IndentationWidth', node.first_line)
185
199
  end
186
200
 
187
201
  def check_indented_internal_methods_style(node)
@@ -197,7 +211,10 @@ module RuboCop
197
211
  children_to_check.last << child
198
212
  end
199
213
  end
200
- children_to_check.each { |group| check_alignment(group) }
214
+
215
+ children_to_check.each do |group|
216
+ check_alignment(group, governed_base_column(group))
217
+ end
201
218
  end
202
219
  end
203
220
  end
@@ -6,6 +6,9 @@ module RuboCop
6
6
  # Checks that the indentation method is consistent.
7
7
  # Either tabs only or spaces only are used for indentation.
8
8
  #
9
+ # With `EnforcedStyle: tabs`, spaces that follow the indenting tabs are
10
+ # taken to be alignment rather than indentation, and are left alone.
11
+ #
9
12
  # @example EnforcedStyle: spaces (default)
10
13
  # # bad
11
14
  # # This example uses a tab to indent bar.
@@ -67,7 +70,7 @@ module RuboCop
67
70
  match = if style == :spaces
68
71
  line.match(/\A\s*\t+/)
69
72
  else
70
- line.match(/\A\s* +/)
73
+ line.match(/\A +/)
71
74
  end
72
75
  return unless match
73
76
 
@@ -82,7 +82,7 @@ module RuboCop
82
82
 
83
83
  # @!method access_modifier?(node)
84
84
  def_node_matcher :access_modifier?, <<~PATTERN
85
- [(send ...) access_modifier?]
85
+ [(send ...) bare_access_modifier?]
86
86
  PATTERN
87
87
 
88
88
  def on_rescue(node)
@@ -250,7 +250,7 @@ module RuboCop
250
250
 
251
251
  def check_members_for_normal_style(base, members)
252
252
  members.first.children.each do |member|
253
- next if member.send_type? && member.access_modifier?
253
+ next if member.send_type? && member.bare_access_modifier?
254
254
 
255
255
  check_indentation(base, member)
256
256
  end
@@ -111,6 +111,12 @@ module RuboCop
111
111
  comments.map(&:source_range)
112
112
  end
113
113
 
114
+ def output_record_separator_ranges(ast)
115
+ return [] if ast.nil?
116
+
117
+ ast.each_node(:gvar).select { |gvar| gvar.source == '$\\' }.map(&:source_range)
118
+ end
119
+
114
120
  def last_line(processed_source)
115
121
  last_token = processed_source.tokens.last
116
122
 
@@ -123,7 +129,8 @@ module RuboCop
123
129
 
124
130
  def ignored_ranges
125
131
  @ignored_ranges ||= ignored_literal_ranges(processed_source.ast) +
126
- comment_ranges(processed_source.comments)
132
+ comment_ranges(processed_source.comments) +
133
+ output_record_separator_ranges(processed_source.ast)
127
134
  end
128
135
 
129
136
  def ignored_parent?(node)
@@ -87,10 +87,10 @@ module RuboCop
87
87
  return if children.empty?
88
88
 
89
89
  if style == :aligned && !always_indented?(node)
90
- check_aligned(children, 1)
90
+ check_aligned(children, 1, children[0].loc.column)
91
91
  else
92
92
  check_indented(children)
93
- check_aligned(children, 2)
93
+ check_aligned(children, 2, indented_column(children))
94
94
  end
95
95
  end
96
96
 
@@ -110,21 +110,37 @@ module RuboCop
110
110
  PARENT_TYPES_FOR_INDENTED.include?(dstr_node.parent&.type)
111
111
  end
112
112
 
113
- def check_aligned(children, start_index)
113
+ def check_aligned(children, start_index, target_column)
114
114
  base_column = children[start_index - 1].loc.column
115
- children[start_index..].each do |child|
116
- @column_delta = base_column - child.loc.column
117
- add_offense_and_correction(child, MSG_ALIGN) if @column_delta != 0
118
- base_column = child.loc.column
115
+ same_column_runs(children[start_index..]).each do |run|
116
+ column = run.first.loc.column
117
+ if column != base_column && column != target_column
118
+ register_misaligned_run(run, target_column - column)
119
+ end
120
+ base_column = column
121
+ end
122
+ end
123
+
124
+ def same_column_runs(parts)
125
+ parts.chunk_while { |a, b| a.loc.column == b.loc.column }
126
+ end
127
+
128
+ def register_misaligned_run(run, column_delta)
129
+ @column_delta = column_delta
130
+ add_offense(run.first, message: MSG_ALIGN) do |corrector|
131
+ run.each { |child| autocorrect(corrector, child) }
119
132
  end
120
133
  end
121
134
 
122
135
  def check_indented(children)
123
- @column_delta = base_column(children[0]) + configured_indentation_width -
124
- children[1].loc.column
136
+ @column_delta = indented_column(children) - children[1].loc.column
125
137
  add_offense_and_correction(children[1], MSG_INDENT) if @column_delta != 0
126
138
  end
127
139
 
140
+ def indented_column(children)
141
+ base_column(children[0]) + configured_indentation_width
142
+ end
143
+
128
144
  def base_column(child)
129
145
  grandparent = child.parent.parent
130
146
  if grandparent&.pair_type?
@@ -72,6 +72,22 @@ module RuboCop
72
72
 
73
73
  MSG = 'Line is too long. [%<length>d/%<max>d]'
74
74
 
75
+ # A trailing backslash, or a trailing unicode, hex, octal, meta or control escape whose
76
+ # payload may be incomplete. An even run of backslashes is complete on its own.
77
+ # Meta and control prefixes chain (`\M-\C-x`, `\c\M-x`), so a whole chain is one escape,
78
+ # and a braced unicode escape may hold several space-separated codepoints.
79
+ ESCAPE_SEQUENCE_TAIL = /
80
+ (?<!\\)(?:\\\\)*
81
+ (
82
+ \\(?:(?:[MC]-|c)\\)*
83
+ (?:u\{[\da-f\x20]*|u[\da-f]{0,4}|x[\da-f]{0,2}|[0-7]{0,3}|[MC]-?|c)?
84
+ )\z
85
+ /xi.freeze
86
+
87
+ def self.autocorrect_incompatible_with
88
+ [Style::MethodCallWithArgsParentheses, Style::MethodDefParentheses]
89
+ end
90
+
75
91
  def on_block(node)
76
92
  check_for_breakable_block(node)
77
93
  end
@@ -106,6 +122,7 @@ module RuboCop
106
122
  def on_new_investigation
107
123
  @breakable_range_by_line_index = {}
108
124
  @breakable_string_delimiters = {}
125
+ @breakable_strings = {}
109
126
  @endless_methods_by_line = {}
110
127
  @heredocs = nil
111
128
 
@@ -191,6 +208,7 @@ module RuboCop
191
208
  def check_for_breakable_node(node)
192
209
  breakable_node = extract_breakable_node(node, max)
193
210
  return if breakable_node.nil?
211
+ return if compact_nested_array_break?(node, breakable_node)
194
212
 
195
213
  line_index = breakable_node.first_line - 1
196
214
  range = breakable_node.source_range
@@ -201,6 +219,20 @@ module RuboCop
201
219
  breakable_range_by_line_index[line_index] = range
202
220
  end
203
221
 
222
+ def compact_nested_array_break?(node, breakable_node)
223
+ return false if !node.array_type? || !node.square_brackets?
224
+ return false unless breakable_node.equal?(node.children.first)
225
+ return false unless breakable_node.source.start_with?('[')
226
+
227
+ compact_array_brackets_enforced?
228
+ end
229
+
230
+ def compact_array_brackets_enforced?
231
+ return false unless config.cop_enabled?('Layout/SpaceInsideArrayLiteralBrackets')
232
+
233
+ config.for_cop('Layout/SpaceInsideArrayLiteralBrackets')['EnforcedStyle'] == 'compact'
234
+ end
235
+
204
236
  def check_for_breakable_semicolons(processed_source)
205
237
  tokens = processed_source.tokens.select { |t| t.type == :tSEMI }
206
238
  tokens.reverse_each do |token|
@@ -214,12 +246,15 @@ module RuboCop
214
246
  return if receiver_contains_heredoc?(block_node)
215
247
 
216
248
  line_index = block_node.loc.line - 1
249
+ return if breakable_string_delimiters[line_index]
250
+
217
251
  range = breakable_block_range(block_node)
218
252
  pos = range.begin_pos + 1
219
253
 
220
254
  breakable_range_by_line_index[line_index] = range_between(pos, pos + 1)
221
255
  end
222
256
 
257
+ # rubocop:disable-next Metrics/AbcSize
223
258
  def check_for_breakable_str(node)
224
259
  line_index = node.loc.line - 1
225
260
  return if breakable_range_by_line_index[line_index]
@@ -230,6 +265,7 @@ module RuboCop
230
265
 
231
266
  breakable_range_by_line_index[line_index] = range_between(pos, pos + 1)
232
267
  breakable_string_delimiters[line_index] = delimiter
268
+ breakable_strings[line_index] = node
233
269
  end
234
270
 
235
271
  def check_for_breakable_dstr(node) # rubocop:disable Metrics/AbcSize
@@ -282,7 +318,33 @@ module RuboCop
282
318
  return if source_range.last_column < max
283
319
  return unless (pos = breakable_string_range(node))
284
320
 
285
- pos.end_pos unless pos.end_pos == source_range.begin_pos
321
+ content_begin_pos = node.loc?(:begin) ? node.loc.begin.end_pos : source_range.begin_pos
322
+ if blank_left_part?(node, content_begin_pos, pos)
323
+ pos = breakable_string_range_at_limit(source_range)
324
+ end
325
+ pos.end_pos if pos && leaves_two_parts?(node, content_begin_pos, pos)
326
+ end
327
+
328
+ def leaves_two_parts?(node, content_begin_pos, pos)
329
+ pos.end_pos > content_begin_pos && !blank_rest?(node, pos.end_pos)
330
+ end
331
+
332
+ def blank_left_part?(node, content_begin_pos, pos)
333
+ return false unless pos.end_pos > content_begin_pos
334
+ return false unless starts_after_quote?(node)
335
+
336
+ blank_between?(content_begin_pos, pos.end_pos)
337
+ end
338
+
339
+ def starts_after_quote?(node)
340
+ return true if node.loc?(:begin)
341
+
342
+ quote = node.parent&.loc&.begin if node.parent&.dstr_type?
343
+ quote ? quote.end_pos == node.source_range.begin_pos : false
344
+ end
345
+
346
+ def blank_between?(begin_pos, end_pos)
347
+ range_between(begin_pos, end_pos).source.strip.empty?
286
348
  end
287
349
 
288
350
  # Locate where to break a string that is too long, ensuring that escape characters
@@ -294,24 +356,57 @@ module RuboCop
294
356
  relevant_substr = largest_possible_string(node)
295
357
 
296
358
  if (space_pos = breakable_space_position(node, relevant_substr))
297
- source_range.resize(space_pos + 1)
298
- elsif (escape_pos = relevant_substr.rindex(/\\(u[\da-f]{0,4}|x[\da-f]{0,2})?\z/))
359
+ # A space can separate codepoints inside a braced unicode escape.
360
+ escape_pos = escape_sequence_start(relevant_substr[0..space_pos])
361
+ source_range.resize(escape_pos || (space_pos + 1))
362
+ elsif (escape_pos = escape_sequence_start(relevant_substr))
299
363
  source_range.resize(escape_pos)
300
364
  else
301
- adjustment = max - source_range.last_column - 3
302
- return if adjustment.abs > source_range.size
303
-
304
- source_range.adjust(end_pos: adjustment)
365
+ breakable_string_range_at_limit(source_range)
305
366
  end
306
367
  end
307
368
 
308
369
  def breakable_space_position(node, substr)
309
370
  limit = string_content_length(node) - 1
310
- space_pos = substr.rindex(/\s/)
311
- space_pos = substr[0, limit].rindex(/\s/) if space_pos && space_pos + 1 > limit
371
+ space_pos = space_before_word(node, substr.length - 1)
372
+ space_pos = space_before_word(node, limit - 1) if space_pos && space_pos + 1 > limit
312
373
  space_pos
313
374
  end
314
375
 
376
+ def space_before_word(node, max_pos)
377
+ return if max_pos.negative?
378
+
379
+ node.source.rindex(/\s(?=\S)/, max_pos)
380
+ end
381
+
382
+ def escape_sequence_start(text)
383
+ text.match(ESCAPE_SEQUENCE_TAIL)&.begin(1)
384
+ end
385
+
386
+ def breakable_string_range_at_limit(source_range)
387
+ adjustment = max - source_range.last_column - 3
388
+ return if adjustment.abs > source_range.size
389
+
390
+ cut = source_range.adjust(end_pos: adjustment)
391
+ cut = cut.resize(size_before_whitespace_run(source_range.source, cut.size))
392
+ escape_pos = escape_sequence_start(cut.source)
393
+
394
+ escape_pos ? source_range.resize(escape_pos) : cut
395
+ end
396
+
397
+ def size_before_whitespace_run(text, size)
398
+ return size if size.zero?
399
+ return size unless text[size]&.match?(/\s/)
400
+
401
+ text.rindex(/\S/, size - 1) || size
402
+ end
403
+
404
+ def blank_rest?(node, break_pos)
405
+ offset = break_pos - node.source_range.begin_pos
406
+
407
+ node.source[offset...string_content_length(node)].strip.empty?
408
+ end
409
+
315
410
  def string_content_length(node)
316
411
  content_end = node.loc?(:end) ? node.loc.end.begin_pos : node.source_range.end_pos
317
412
 
@@ -320,7 +415,18 @@ module RuboCop
320
415
 
321
416
  def breakable_dstr_begin_position(node)
322
417
  source_range = node.source_range
323
- source_range.begin_pos if source_range.column < max && source_range.last_column >= max
418
+ return unless source_range.column < max && source_range.last_column >= max
419
+ return if opens_string?(node)
420
+
421
+ source_range.begin_pos
422
+ end
423
+
424
+ def opens_string?(begin_node)
425
+ quote = begin_node.parent.loc.begin
426
+
427
+ return false unless quote
428
+
429
+ blank_between?(quote.end_pos, begin_node.source_range.begin_pos)
324
430
  end
325
431
 
326
432
  def breakable_range_by_line_index
@@ -335,6 +441,10 @@ module RuboCop
335
441
  @breakable_string_delimiters ||= {}
336
442
  end
337
443
 
444
+ def breakable_strings
445
+ @breakable_strings ||= {}
446
+ end
447
+
338
448
  def heredocs
339
449
  @heredocs ||= extract_heredocs(processed_source.ast)
340
450
  end
@@ -391,10 +501,38 @@ module RuboCop
391
501
  "\n"
392
502
  end
393
503
 
394
- corrector.insert_before(breakable_range, insertion) unless breakable_range.nil?
504
+ break_ranges(line_index).each do |range|
505
+ corrector.insert_before(range, insertion)
506
+ end
395
507
  end
396
508
  end
397
509
 
510
+ def break_ranges(line_index)
511
+ return [] if breakable_range.nil?
512
+ return [breakable_range] unless (node = breakable_strings[line_index])&.loc?(:end)
513
+
514
+ string_break_ranges(node, breakable_string_delimiters[line_index])
515
+ end
516
+
517
+ def string_break_ranges(node, delimiter)
518
+ ranges = [breakable_range]
519
+ pos = breakable_range.begin_pos
520
+
521
+ while (pos = next_string_break_position(node, pos, delimiter))
522
+ ranges << range_between(pos, pos + 1)
523
+ end
524
+
525
+ ranges
526
+ end
527
+
528
+ def next_string_break_position(node, pos, delimiter)
529
+ rest = parse("#{delimiter}#{node.source_range.with(begin_pos: pos).source}").ast
530
+ return unless rest&.str_type?
531
+ return unless (rest_pos = breakable_string_position(rest))
532
+
533
+ pos + rest_pos - delimiter.length
534
+ end
535
+
398
536
  def excess_range(uri_range, line, line_index)
399
537
  excessive_position = if uri_range && uri_range.begin < max
400
538
  uri_range.end
@@ -511,16 +649,10 @@ module RuboCop
511
649
  # `Max` - end delimiter (quote) - continuation characters (space and slash)
512
650
  max_length = max - 3
513
651
  # Offset by the string's starting column so the broken line actually fits
514
- # within `Max`. When on the same line as its parent, use the column difference;
515
- # otherwise the string is indented on its own line, so subtract that indentation.
516
- # (Without this, an indented string under a multi-line parent never shortens
517
- # below `Max` and the autocorrect loops, inserting empty `"" \` fragments.)
518
- max_length -= if same_line?(node, node.parent)
519
- column_offset_between(node.loc, node.parent.loc)
520
- else
521
- node.loc.column
522
- end
523
- node.source[0...(max_length)]
652
+ # within `Max`, whatever precedes the string on its line.
653
+ max_length -= node.loc.column
654
+
655
+ node.source[0, [max_length, 0].max]
524
656
  end
525
657
  end
526
658
  end
@@ -106,6 +106,18 @@ module RuboCop
106
106
  ALWAYS_SAME_LINE_MESSAGE = 'The closing array brace must be on the ' \
107
107
  'same line as the last array element.'
108
108
 
109
+ # Moving the closing bracket after a hash literal or method call element while
110
+ # `Layout/MultilineHashBraceLayout` or `Layout/MultilineMethodCallBraceLayout` moves
111
+ # that element's closing brace in the same pass places the bracket after whatever
112
+ # follows the moved brace, such as a comment.
113
+ # Moving it while `Style/ConcatArrayLiterals` removes the brackets in the same pass
114
+ # leaves the bracket behind.
115
+ def self.autocorrect_incompatible_with
116
+ [
117
+ Layout::MultilineHashBraceLayout, Layout::MultilineMethodCallBraceLayout, Style::ConcatArrayLiterals
118
+ ]
119
+ end
120
+
109
121
  def on_array(node)
110
122
  check_brace_layout(node)
111
123
  end