Skip to content

Commit

Permalink
[Fix rubocop#7592] Add cop Layout/LineEndStringConcatenationIndentation
Browse files Browse the repository at this point in the history
The cop Layout/MultilineOperationIndentation doesn't view the
backslash as an operator, which is correct. Therefore, a new cop
is needed to inspect character literals broken up into multiple
lines with backslashes at the ends of the lines.

The new cop deals exclusively with string literals, not any other
constructs where backslashes might be used to "glue" lines
together.
  • Loading branch information
jonas054 committed Jun 20, 2021
1 parent 981a8a2 commit ac1c62f
Show file tree
Hide file tree
Showing 5 changed files with 471 additions and 27 deletions.
1 change: 1 addition & 0 deletions changelog/fix_manually_fix_the_remaining_offense_after.md
@@ -0,0 +1 @@
* [#7592](https://github.com/rubocop/rubocop/pull/7592): Add cop `Layout/LineEndStringConcatenationIndentation`. ([@jonas054][])
68 changes: 41 additions & 27 deletions config/default.yml
Expand Up @@ -270,8 +270,8 @@ Layout/AccessModifierIndentation:
SupportedStyles:
- outdent
- indent
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/ArgumentAlignment:
Expand Down Expand Up @@ -299,8 +299,8 @@ Layout/ArgumentAlignment:
SupportedStyles:
- with_first_argument
- with_fixed_indentation
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/ArrayAlignment:
Expand Down Expand Up @@ -328,8 +328,8 @@ Layout/ArrayAlignment:
SupportedStyles:
- with_first_element
- with_fixed_indentation
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/AssignmentIndentation:
Expand All @@ -339,8 +339,8 @@ Layout/AssignmentIndentation:
Enabled: true
VersionAdded: '0.49'
VersionChanged: '0.77'
# By default, the indentation width from `Layout/IndentationWidth` is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/BeginEndAlignment:
Expand Down Expand Up @@ -387,9 +387,9 @@ Layout/CaseIndentation:
- case
- end
IndentOneStep: false
# By default, the indentation width from `Layout/IndentationWidth` is used.
# But it can be overridden by setting this parameter.
# This only matters if `IndentOneStep` is `true`
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
# This only matters if `IndentOneStep` is `true`.
IndentationWidth: ~

Layout/ClassStructure:
Expand Down Expand Up @@ -666,8 +666,8 @@ Layout/FirstArgumentIndentation:
# Same as `special_for_inner_method_call` except that the special rule only
# applies if the outer method call encloses its arguments in parentheses.
- special_for_inner_method_call_in_parentheses
# By default, the indentation width from `Layout/IndentationWidth` is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/FirstArrayElementIndentation:
Expand All @@ -693,8 +693,8 @@ Layout/FirstArrayElementIndentation:
- special_inside_parentheses
- consistent
- align_brackets
# By default, the indentation width from `Layout/IndentationWidth` is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/FirstArrayElementLineBreak:
Expand Down Expand Up @@ -725,8 +725,8 @@ Layout/FirstHashElementIndentation:
- special_inside_parentheses
- consistent
- align_braces
# By default, the indentation width from `Layout/IndentationWidth` is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/FirstHashElementLineBreak:
Expand Down Expand Up @@ -761,8 +761,8 @@ Layout/FirstParameterIndentation:
SupportedStyles:
- consistent
- align_parentheses
# By default, the indentation width from `Layout/IndentationWidth` is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/HashAlignment:
Expand Down Expand Up @@ -883,8 +883,8 @@ Layout/IndentationStyle:
Enabled: true
VersionAdded: '0.49'
VersionChanged: '0.82'
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
# It is used during auto-correction to determine how many spaces should
# replace each tab.
IndentationWidth: ~
Expand Down Expand Up @@ -923,6 +923,20 @@ Layout/LeadingEmptyLines:
VersionAdded: '0.57'
VersionChanged: '0.77'

Layout/LineEndStringConcatenationIndentation:
Description: >-
Checks the indentation of the next line after a line that
ends with a string literal and a backslash.
Enabled: pending
VersionAdded: '<<next>>'
EnforcedStyle: aligned
SupportedStyles:
- aligned
- indented
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/LineLength:
Description: 'Checks that line length does not exceed the configured limit.'
StyleGuide: '#max-line-length'
Expand Down Expand Up @@ -1053,8 +1067,8 @@ Layout/MultilineMethodCallIndentation:
- aligned
- indented
- indented_relative_to_receiver
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/MultilineMethodDefinitionBraceLayout:
Expand Down Expand Up @@ -1083,8 +1097,8 @@ Layout/MultilineOperationIndentation:
SupportedStyles:
- aligned
- indented
# By default, the indentation width from `Layout/IndentationWidth` is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/ParameterAlignment:
Expand Down Expand Up @@ -1112,8 +1126,8 @@ Layout/ParameterAlignment:
SupportedStyles:
- with_first_parameter
- with_fixed_indentation
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# By default the indentation width from `Layout/IndentationWidth` is used,
# but it can be overridden by setting this parameter.
IndentationWidth: ~

Layout/RedundantLineBreak:
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -211,6 +211,7 @@
require_relative 'rubocop/cop/layout/initial_indentation'
require_relative 'rubocop/cop/layout/leading_comment_space'
require_relative 'rubocop/cop/layout/leading_empty_lines'
require_relative 'rubocop/cop/layout/line_end_string_concatenation_indentation'
require_relative 'rubocop/cop/layout/line_length'
require_relative 'rubocop/cop/layout/multiline_array_brace_layout'
require_relative 'rubocop/cop/layout/multiline_array_line_breaks'
Expand Down
122 changes: 122 additions & 0 deletions lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb
@@ -0,0 +1,122 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Layout
# This cop checks the indentation of the next line after a line that ends with a string
# literal and a backslash.
#
# If `EnforcedStyle: aligned` is set, the concatenated string parts shall be aligned with the
# first part. There are some exceptions, such as implicit return values, where the
# concatenated string parts shall be indented regardless of `EnforcedStyle` configuration.
#
# If `EnforcedStyle: indented` is set, it's the second line that shall be indented one step
# more than the first line. Lines 3 and forward shall be aligned with line 2. Here too there
# are exceptions. Values in a hash literal are always aligned.
#
# @example
# # bad
# def some_method
# 'x' \
# 'y' \
# 'z'
# end
#
# my_hash = {
# first: 'a message' \
# 'in two parts'
# }
#
# # good
# def some_method
# 'x' \
# 'y' \
# 'z'
# end
#
# my_hash = {
# first: 'a message' \
# 'in two parts'
# }
#
# @example EnforcedStyle: aligned (default)
# # bad
# puts 'x' \
# 'y'
#
# # good
# puts 'x' \
# 'y'
#
# @example EnforcedStyle: indented
# # bad
# result = 'x' \
# 'y'
#
# # good
# result = 'x' \
# 'y'
#
class LineEndStringConcatenationIndentation < Base
include ConfigurableEnforcedStyle
include Alignment
extend AutoCorrector

MSG_ALIGN = 'Align parts of a string concatenated with backslash.'
MSG_INDENT = 'Indent the first part of a string concatenated with backslash.'
PARENT_TYPES_FOR_INDENTED = [nil, :block, :begin, :def, :defs, :if].freeze

def on_dstr(node)
return unless strings_concatenated_with_backslash?(node)

children = node.children
if style == :aligned && !always_indented?(node) || always_aligned?(node)
check_aligned(children, 1)
else
check_indented(children)
check_aligned(children, 2)
end
end

def autocorrect(corrector, node)
AlignmentCorrector.correct(corrector, processed_source, node, @column_delta)
end

private

def strings_concatenated_with_backslash?(dstr_node)
!dstr_node.heredoc? &&
dstr_node.children.length > 1 &&
dstr_node.children.all? { |c| c.str_type? || c.dstr_type? }
end

def always_indented?(dstr_node)
PARENT_TYPES_FOR_INDENTED.include?(dstr_node.parent&.type)
end

def always_aligned?(dstr_node)
dstr_node.parent&.pair_type?
end

def check_aligned(children, start_index)
base_column = children[start_index - 1].loc.column
children[start_index..-1].each do |child|
@column_delta = base_column - child.loc.column
add_offense_and_correction(child, MSG_ALIGN) if @column_delta != 0
base_column = child.loc.column
end
end

def check_indented(children)
base_column = children[0].source_range.source_line =~ /\S/
@column_delta = base_column + configured_indentation_width - children[1].loc.column
add_offense_and_correction(children[1], MSG_INDENT) if @column_delta != 0
end

def add_offense_and_correction(node, message)
add_offense(node, message: message) { |corrector| autocorrect(corrector, node) }
end
end
end
end
end

0 comments on commit ac1c62f

Please sign in to comment.