rubocop 1.17.0 → 1.18.4
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +70 -29
- data/lib/rubocop.rb +2 -0
- data/lib/rubocop/cli/command/suggest_extensions.rb +3 -3
- data/lib/rubocop/config_loader.rb +1 -1
- data/lib/rubocop/config_loader_resolver.rb +1 -1
- data/lib/rubocop/config_validator.rb +23 -10
- data/lib/rubocop/cop/base.rb +2 -2
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
- data/lib/rubocop/cop/bundler/gem_version.rb +38 -4
- data/lib/rubocop/cop/corrector.rb +4 -4
- data/lib/rubocop/cop/generator.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -1
- data/lib/rubocop/cop/layout/argument_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/array_alignment.rb +2 -2
- data/lib/rubocop/cop/layout/block_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/class_structure.rb +5 -1
- data/lib/rubocop/cop/layout/closing_parenthesis_indentation.rb +7 -1
- data/lib/rubocop/cop/layout/comment_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/end_alignment.rb +8 -1
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -2
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +2 -2
- data/lib/rubocop/cop/layout/first_parameter_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/hash_alignment.rb +25 -24
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_style.rb +2 -2
- data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +122 -0
- data/lib/rubocop/cop/layout/multiline_array_brace_layout.rb +6 -6
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +2 -2
- data/lib/rubocop/cop/layout/multiline_hash_brace_layout.rb +6 -6
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +6 -6
- data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +6 -6
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +3 -3
- data/lib/rubocop/cop/layout/parameter_alignment.rb +2 -2
- data/lib/rubocop/cop/layout/space_around_operators.rb +5 -1
- data/lib/rubocop/cop/lint/duplicate_branch.rb +2 -1
- data/lib/rubocop/cop/lint/nested_percent_literal.rb +1 -1
- data/lib/rubocop/cop/lint/percent_string_array.rb +1 -1
- data/lib/rubocop/cop/lint/percent_symbol_array.rb +1 -1
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/unused_block_argument.rb +1 -1
- data/lib/rubocop/cop/lint/useless_assignment.rb +1 -1
- data/lib/rubocop/cop/lint/useless_times.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +12 -3
- data/lib/rubocop/cop/mixin/hash_transform_method.rb +6 -1
- data/lib/rubocop/cop/naming/inclusive_language.rb +249 -0
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -2
- data/lib/rubocop/cop/style/block_delimiters.rb +15 -0
- data/lib/rubocop/cop/style/class_and_module_children.rb +14 -0
- data/lib/rubocop/cop/style/comment_annotation.rb +50 -6
- data/lib/rubocop/cop/style/double_cop_disable_directive.rb +1 -7
- data/lib/rubocop/cop/style/eval_with_location.rb +1 -1
- data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +8 -2
- data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +1 -1
- data/lib/rubocop/cop/style/mutable_constant.rb +6 -8
- data/lib/rubocop/cop/style/percent_literal_delimiters.rb +1 -1
- data/lib/rubocop/cop/style/quoted_symbols.rb +2 -2
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
- data/lib/rubocop/cop/style/regexp_literal.rb +3 -2
- data/lib/rubocop/cop/style/single_line_methods.rb +25 -15
- data/lib/rubocop/cop/style/special_global_vars.rb +3 -3
- data/lib/rubocop/cop/style/string_concatenation.rb +32 -5
- data/lib/rubocop/cop/style/string_literals.rb +2 -2
- data/lib/rubocop/cop/style/swap_values.rb +1 -1
- data/lib/rubocop/cop/style/unpack_first.rb +1 -1
- data/lib/rubocop/cop/variable_force/variable_table.rb +1 -1
- data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -1
- data/lib/rubocop/options.rb +4 -4
- data/lib/rubocop/rspec/cop_helper.rb +1 -1
- data/lib/rubocop/rspec/expect_offense.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- metadata +11 -9
@@ -147,9 +147,9 @@ module RuboCop
|
|
147
147
|
include ConfigurableEnforcedStyle
|
148
148
|
|
149
149
|
MSG = 'Memoized variable `%<var>s` does not match ' \
|
150
|
-
|
150
|
+
'method name `%<method>s`. Use `@%<suggested_var>s` instead.'
|
151
151
|
UNDERSCORE_REQUIRED = 'Memoized variable `%<var>s` does not start ' \
|
152
|
-
|
152
|
+
'with `_`. Use `@%<suggested_var>s` instead.'
|
153
153
|
DYNAMIC_DEFINE_METHODS = %i[define_method define_singleton_method].to_set.freeze
|
154
154
|
|
155
155
|
# @!method method_definition?(node)
|
@@ -135,6 +135,7 @@ module RuboCop
|
|
135
135
|
class BlockDelimiters < Base
|
136
136
|
include ConfigurableEnforcedStyle
|
137
137
|
include IgnoredMethods
|
138
|
+
include RangeHelp
|
138
139
|
extend AutoCorrector
|
139
140
|
|
140
141
|
ALWAYS_BRACES_MESSAGE = 'Prefer `{...}` over `do...end` for blocks.'
|
@@ -231,6 +232,11 @@ module RuboCop
|
|
231
232
|
corrector.insert_before(e, ' ') unless whitespace_before?(e)
|
232
233
|
corrector.insert_after(b, ' ') unless whitespace_after?(b)
|
233
234
|
corrector.replace(b, 'do')
|
235
|
+
|
236
|
+
if (comment = processed_source.comment_at_line(e.line))
|
237
|
+
move_comment_before_block(corrector, comment, loc.node, e)
|
238
|
+
end
|
239
|
+
|
234
240
|
corrector.replace(e, 'end')
|
235
241
|
end
|
236
242
|
|
@@ -252,6 +258,15 @@ module RuboCop
|
|
252
258
|
/\s/.match?(range.source_buffer.source[range.begin_pos + length, 1])
|
253
259
|
end
|
254
260
|
|
261
|
+
def move_comment_before_block(corrector, comment, block_node, closing_brace)
|
262
|
+
range = range_between(closing_brace.end_pos, comment.loc.expression.end_pos)
|
263
|
+
|
264
|
+
corrector.remove(range_with_surrounding_space(range: range, side: :right))
|
265
|
+
corrector.insert_after(closing_brace, "\n")
|
266
|
+
|
267
|
+
corrector.insert_before(block_node, "#{comment.text}\n")
|
268
|
+
end
|
269
|
+
|
255
270
|
def get_blocks(node, &block)
|
256
271
|
case node.type
|
257
272
|
when :block
|
@@ -84,6 +84,7 @@ module RuboCop
|
|
84
84
|
def compact_definition(corrector, node)
|
85
85
|
compact_node(corrector, node)
|
86
86
|
remove_end(corrector, node.body)
|
87
|
+
unindent(corrector, node)
|
87
88
|
end
|
88
89
|
|
89
90
|
def compact_node(corrector, node)
|
@@ -114,6 +115,19 @@ module RuboCop
|
|
114
115
|
corrector.remove(range)
|
115
116
|
end
|
116
117
|
|
118
|
+
def configured_indentation_width
|
119
|
+
config.for_badge(Layout::IndentationWidth.badge).fetch('Width', 2)
|
120
|
+
end
|
121
|
+
|
122
|
+
def unindent(corrector, node)
|
123
|
+
return if node.body.children.last.nil?
|
124
|
+
|
125
|
+
column_delta = configured_indentation_width - leading_spaces(node.body.children.last).size
|
126
|
+
return if column_delta.zero?
|
127
|
+
|
128
|
+
AlignmentCorrector.correct(corrector, processed_source, node, column_delta)
|
129
|
+
end
|
130
|
+
|
117
131
|
def leading_spaces(node)
|
118
132
|
node.source_range.source_line[/\A\s*/]
|
119
133
|
end
|
@@ -12,7 +12,7 @@ module RuboCop
|
|
12
12
|
# incorrect registering of keywords (eg. `review`) inside a paragraph as an
|
13
13
|
# annotation.
|
14
14
|
#
|
15
|
-
# @example
|
15
|
+
# @example RequireColon: true (default)
|
16
16
|
# # bad
|
17
17
|
# # TODO make better
|
18
18
|
#
|
@@ -36,14 +36,36 @@ module RuboCop
|
|
36
36
|
#
|
37
37
|
# # good
|
38
38
|
# # OPTIMIZE: does not work
|
39
|
+
#
|
40
|
+
# @example RequireColon: false
|
41
|
+
# # bad
|
42
|
+
# # TODO: make better
|
43
|
+
#
|
44
|
+
# # good
|
45
|
+
# # TODO make better
|
46
|
+
#
|
47
|
+
# # bad
|
48
|
+
# # fixme does not work
|
49
|
+
#
|
50
|
+
# # good
|
51
|
+
# # FIXME does not work
|
52
|
+
#
|
53
|
+
# # bad
|
54
|
+
# # Optimize does not work
|
55
|
+
#
|
56
|
+
# # good
|
57
|
+
# # OPTIMIZE does not work
|
39
58
|
class CommentAnnotation < Base
|
40
59
|
include AnnotationComment
|
41
60
|
include RangeHelp
|
42
61
|
extend AutoCorrector
|
43
62
|
|
44
|
-
|
45
|
-
|
46
|
-
|
63
|
+
MSG_COLON_STYLE = 'Annotation keywords like `%<keyword>s` should be all ' \
|
64
|
+
'upper case, followed by a colon, and a space, ' \
|
65
|
+
'then a note describing the problem.'
|
66
|
+
MSG_SPACE_STYLE = 'Annotation keywords like `%<keyword>s` should be all ' \
|
67
|
+
'upper case, followed by a space, ' \
|
68
|
+
'then a note describing the problem.'
|
47
69
|
MISSING_NOTE = 'Annotation comment, with keyword `%<keyword>s`, is missing a note.'
|
48
70
|
|
49
71
|
def on_new_investigation
|
@@ -63,13 +85,15 @@ module RuboCop
|
|
63
85
|
private
|
64
86
|
|
65
87
|
def register_offense(range, note, first_word)
|
88
|
+
message = requires_colon? ? MSG_COLON_STYLE : MSG_SPACE_STYLE
|
89
|
+
|
66
90
|
add_offense(
|
67
91
|
range,
|
68
|
-
message: format(note ?
|
92
|
+
message: format(note ? message : MISSING_NOTE, keyword: first_word)
|
69
93
|
) do |corrector|
|
70
94
|
next if note.nil?
|
71
95
|
|
72
|
-
corrector
|
96
|
+
correct_offense(corrector, range, first_word)
|
73
97
|
end
|
74
98
|
end
|
75
99
|
|
@@ -92,8 +116,28 @@ module RuboCop
|
|
92
116
|
end
|
93
117
|
|
94
118
|
def correct_annotation?(first_word, colon, space, note)
|
119
|
+
return correct_colon_annotation?(first_word, colon, space, note) if requires_colon?
|
120
|
+
|
121
|
+
correct_space_annotation?(first_word, colon, space, note)
|
122
|
+
end
|
123
|
+
|
124
|
+
def correct_colon_annotation?(first_word, colon, space, note)
|
95
125
|
keyword?(first_word) && (colon && space && note || !colon && !note)
|
96
126
|
end
|
127
|
+
|
128
|
+
def correct_space_annotation?(first_word, colon, space, note)
|
129
|
+
keyword?(first_word) && (!colon && space && note || !colon && !note)
|
130
|
+
end
|
131
|
+
|
132
|
+
def correct_offense(corrector, range, first_word)
|
133
|
+
return corrector.replace(range, "#{first_word.upcase}: ") if requires_colon?
|
134
|
+
|
135
|
+
corrector.replace(range, "#{first_word.upcase} ")
|
136
|
+
end
|
137
|
+
|
138
|
+
def requires_colon?
|
139
|
+
cop_config['RequireColon']
|
140
|
+
end
|
97
141
|
end
|
98
142
|
end
|
99
143
|
end
|
@@ -36,13 +36,7 @@ module RuboCop
|
|
36
36
|
next unless comment.text.scan(/# rubocop:(?:disable|todo)/).size > 1
|
37
37
|
|
38
38
|
add_offense(comment) do |corrector|
|
39
|
-
|
40
|
-
'# rubocop:disable'
|
41
|
-
else
|
42
|
-
'# rubocop:todo'
|
43
|
-
end
|
44
|
-
|
45
|
-
corrector.replace(comment, comment.text[/#{prefix} \S+/])
|
39
|
+
corrector.replace(comment, comment.text.gsub(%r{ # rubocop:(disable|todo)}, ','))
|
46
40
|
end
|
47
41
|
end
|
48
42
|
end
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
# RUBY
|
44
44
|
#
|
45
45
|
# This cop works only when a string literal is given as a code string.
|
46
|
-
# No
|
46
|
+
# No offense is reported if a string variable is given as below:
|
47
47
|
#
|
48
48
|
# @example
|
49
49
|
# # not checked
|
@@ -5,11 +5,17 @@ module RuboCop
|
|
5
5
|
module Style
|
6
6
|
# This cop is designed to help you transition from mutable string literals
|
7
7
|
# to frozen string literals.
|
8
|
-
# It will add the
|
9
|
-
# files to enable frozen string literals. Frozen string literals may be
|
8
|
+
# It will add the `# frozen_string_literal: true` magic comment to the top
|
9
|
+
# of files to enable frozen string literals. Frozen string literals may be
|
10
10
|
# default in future Ruby. The comment will be added below a shebang and
|
11
11
|
# encoding comment.
|
12
12
|
#
|
13
|
+
# Note that the cop will ignore files where the comment exists but is set
|
14
|
+
# to `false` instead of `true`.
|
15
|
+
#
|
16
|
+
# To require a blank line after this comment, please see
|
17
|
+
# `Layout/EmptyLineAfterMagicComment` cop.
|
18
|
+
#
|
13
19
|
# @example EnforcedStyle: always (default)
|
14
20
|
# # The `always` style will always add the frozen string literal comment
|
15
21
|
# # to a file, regardless of the Ruby version or if `freeze` or `<<` are
|
@@ -44,7 +44,7 @@ module RuboCop
|
|
44
44
|
extend AutoCorrector
|
45
45
|
|
46
46
|
MSG = 'Avoid comparing a variable with multiple items ' \
|
47
|
-
|
47
|
+
'in a conditional, use `Array#include?` instead.'
|
48
48
|
|
49
49
|
def on_new_investigation
|
50
50
|
@last_comparison = nil
|
@@ -61,13 +61,12 @@ module RuboCop
|
|
61
61
|
|
62
62
|
def on_casgn(node)
|
63
63
|
_scope, _const_name, value = *node
|
64
|
-
|
65
|
-
|
64
|
+
if value.nil? # This is only the case for `CONST += ...` or similarg66
|
65
|
+
parent = node.parent
|
66
|
+
return unless parent.or_asgn_type? # We only care about `CONST ||= ...`
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
return unless lhs&.casgn_type?
|
68
|
+
value = parent.children.last
|
69
|
+
end
|
71
70
|
|
72
71
|
on_assignment(value)
|
73
72
|
end
|
@@ -118,14 +117,13 @@ module RuboCop
|
|
118
117
|
end
|
119
118
|
|
120
119
|
def mutable_literal?(value)
|
121
|
-
return false if value.nil?
|
122
120
|
return false if frozen_regexp_or_range_literals?(value)
|
123
121
|
|
124
122
|
value.mutable_literal?
|
125
123
|
end
|
126
124
|
|
127
125
|
def immutable_literal?(node)
|
128
|
-
|
126
|
+
frozen_regexp_or_range_literals?(node) || node.immutable_literal?
|
129
127
|
end
|
130
128
|
|
131
129
|
def frozen_string_literal?(node)
|
@@ -36,9 +36,9 @@ module RuboCop
|
|
36
36
|
extend AutoCorrector
|
37
37
|
|
38
38
|
MSG_SINGLE = "Prefer single-quoted symbols when you don't need string interpolation " \
|
39
|
-
|
39
|
+
'or special symbols.'
|
40
40
|
MSG_DOUBLE = 'Prefer double-quoted symbols unless you need single quotes to ' \
|
41
|
-
|
41
|
+
'avoid extra backslashes for escaping.'
|
42
42
|
|
43
43
|
def on_sym(node)
|
44
44
|
return unless quoted?(node)
|
@@ -32,7 +32,7 @@ module RuboCop
|
|
32
32
|
|
33
33
|
REQUIRES_ESCAPE_OUTSIDE_CHAR_CLASS_CHARS = '.*+?{}()|$'.chars.freeze
|
34
34
|
MSG_REDUNDANT_CHARACTER_CLASS = 'Redundant single-element character class, ' \
|
35
|
-
|
35
|
+
'`%<char_class>s` can be replaced with `%<element>s`.'
|
36
36
|
|
37
37
|
def on_regexp(node)
|
38
38
|
each_redundant_character_class(node) do |loc|
|
@@ -117,7 +117,7 @@ module RuboCop
|
|
117
117
|
def allowed_percent_r_literal?(node)
|
118
118
|
style == :slashes && contains_disallowed_slash?(node) ||
|
119
119
|
style == :percent_r ||
|
120
|
-
allowed_mixed_percent_r?(node) ||
|
120
|
+
allowed_mixed_percent_r?(node) || allowed_omit_parentheses_with_percent_r_literal?(node)
|
121
121
|
end
|
122
122
|
|
123
123
|
def allowed_mixed_percent_r?(node)
|
@@ -149,8 +149,9 @@ module RuboCop
|
|
149
149
|
config.for_cop('Style/PercentLiteralDelimiters') ['PreferredDelimiters']['%r'].chars
|
150
150
|
end
|
151
151
|
|
152
|
-
def
|
152
|
+
def allowed_omit_parentheses_with_percent_r_literal?(node)
|
153
153
|
return false unless node.parent&.call_type?
|
154
|
+
return true if node.content.start_with?(' ')
|
154
155
|
|
155
156
|
enforced_style = config.for_cop('Style/MethodCallWithArgsParentheses')['EnforcedStyle']
|
156
157
|
|
@@ -36,6 +36,7 @@ module RuboCop
|
|
36
36
|
extend AutoCorrector
|
37
37
|
|
38
38
|
MSG = 'Avoid single-line method definitions.'
|
39
|
+
NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES = %i[return break next].freeze
|
39
40
|
|
40
41
|
def on_def(node)
|
41
42
|
return unless node.single_line?
|
@@ -62,29 +63,24 @@ module RuboCop
|
|
62
63
|
|
63
64
|
def correct_to_endless?(body_node)
|
64
65
|
return false if target_ruby_version < 3.0
|
65
|
-
|
66
|
-
endless_method_config = config.for_cop('Style/EndlessMethod')
|
67
|
-
|
68
|
-
return false unless endless_method_config['Enabled']
|
69
|
-
return false if endless_method_config['EnforcedStyle'] == 'disallow'
|
66
|
+
return false if disallow_endless_method_style?
|
70
67
|
return false unless body_node
|
71
|
-
return false if body_node.parent.assignment_method?
|
68
|
+
return false if body_node.parent.assignment_method? ||
|
69
|
+
NOT_SUPPORTED_ENDLESS_METHOD_BODY_TYPES.include?(body_node.type)
|
72
70
|
|
73
71
|
!(body_node.begin_type? || body_node.kwbegin_type?)
|
74
72
|
end
|
75
73
|
|
76
74
|
def correct_to_multiline(corrector, node)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
if (body = node.body) && body.begin_type? && body.parenthesized_call?
|
76
|
+
break_line_before(corrector, node, body)
|
77
|
+
else
|
78
|
+
each_part(body) do |part|
|
79
|
+
break_line_before(corrector, node, part)
|
80
|
+
end
|
82
81
|
end
|
83
82
|
|
84
|
-
|
85
|
-
range: node.loc.end, node: node, corrector: corrector,
|
86
|
-
indent_steps: 0, configured_width: configured_indentation_width
|
87
|
-
)
|
83
|
+
break_line_before(corrector, node, node.loc.end, indent_steps: 0)
|
88
84
|
|
89
85
|
move_comment(node, corrector)
|
90
86
|
end
|
@@ -98,6 +94,13 @@ module RuboCop
|
|
98
94
|
corrector.replace(node, replacement)
|
99
95
|
end
|
100
96
|
|
97
|
+
def break_line_before(corrector, node, range, indent_steps: 1)
|
98
|
+
LineBreakCorrector.break_line_before(
|
99
|
+
range: range, node: node, corrector: corrector,
|
100
|
+
configured_width: configured_indentation_width, indent_steps: indent_steps
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
101
104
|
def each_part(body)
|
102
105
|
return unless body
|
103
106
|
|
@@ -129,6 +132,13 @@ module RuboCop
|
|
129
132
|
def require_parentheses?(method_body)
|
130
133
|
method_body.send_type? && !method_body.arguments.empty? && !method_body.comparison_method?
|
131
134
|
end
|
135
|
+
|
136
|
+
def disallow_endless_method_style?
|
137
|
+
endless_method_config = config.for_cop('Style/EndlessMethod')
|
138
|
+
return false unless endless_method_config['Enabled']
|
139
|
+
|
140
|
+
endless_method_config['EnforcedStyle'] == 'disallow'
|
141
|
+
end
|
132
142
|
end
|
133
143
|
end
|
134
144
|
end
|
@@ -53,10 +53,10 @@ module RuboCop
|
|
53
53
|
extend AutoCorrector
|
54
54
|
|
55
55
|
MSG_BOTH = 'Prefer `%<prefer>s` from the stdlib \'English\' ' \
|
56
|
-
|
57
|
-
|
56
|
+
'module (don\'t forget to require it) or `%<regular>s` over ' \
|
57
|
+
'`%<global>s`.'
|
58
58
|
MSG_ENGLISH = 'Prefer `%<prefer>s` from the stdlib \'English\' ' \
|
59
|
-
|
59
|
+
'module (don\'t forget to require it) over `%<global>s`.'
|
60
60
|
MSG_REGULAR = 'Prefer `%<prefer>s` over `%<global>s`.'
|
61
61
|
|
62
62
|
ENGLISH_VARS = { # rubocop:disable Style/MutableConstant
|
@@ -15,18 +15,37 @@ module RuboCop
|
|
15
15
|
# lines, this cop does not register an offense; instead,
|
16
16
|
# `Style/LineEndConcatenation` will pick up the offense if enabled.
|
17
17
|
#
|
18
|
-
#
|
18
|
+
# Two modes are supported:
|
19
|
+
# 1. `aggressive` style checks and corrects all occurrences of `+` where
|
20
|
+
# either the left or right side of `+` is a string literal.
|
21
|
+
# 2. `conservative` style on the other hand, checks and corrects only if
|
22
|
+
# left side (receiver of `+` method call) is a string literal.
|
23
|
+
# This is useful when the receiver is some expression that returns string like `Pathname`
|
24
|
+
# instead of a string literal.
|
25
|
+
#
|
26
|
+
# @example Mode: aggressive (default)
|
19
27
|
# # bad
|
20
28
|
# email_with_name = user.name + ' <' + user.email + '>'
|
29
|
+
# Pathname.new('/') + 'test'
|
21
30
|
#
|
22
31
|
# # good
|
23
32
|
# email_with_name = "#{user.name} <#{user.email}>"
|
24
33
|
# email_with_name = format('%s <%s>', user.name, user.email)
|
34
|
+
# "#{Pathname.new('/')}test"
|
25
35
|
#
|
26
36
|
# # accepted, line-end concatenation
|
27
37
|
# name = 'First' +
|
28
38
|
# 'Last'
|
29
39
|
#
|
40
|
+
# @example Mode: conservative
|
41
|
+
# # bad
|
42
|
+
# 'Hello' + user.name
|
43
|
+
#
|
44
|
+
# # good
|
45
|
+
# "Hello #{user.name}"
|
46
|
+
# user.name + '!!'
|
47
|
+
# Pathname.new('/') + 'test'
|
48
|
+
#
|
30
49
|
class StringConcatenation < Base
|
31
50
|
include Util
|
32
51
|
include RangeHelp
|
@@ -52,10 +71,15 @@ module RuboCop
|
|
52
71
|
return if line_end_concatenation?(node)
|
53
72
|
|
54
73
|
topmost_plus_node = find_topmost_plus_node(node)
|
74
|
+
parts = collect_parts(topmost_plus_node)
|
75
|
+
return unless parts[0..-2].any? { |receiver_node| offensive_for_mode?(receiver_node) }
|
55
76
|
|
56
|
-
parts
|
57
|
-
|
77
|
+
register_offense(topmost_plus_node, parts)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
58
81
|
|
82
|
+
def register_offense(topmost_plus_node, parts)
|
59
83
|
add_offense(topmost_plus_node) do |corrector|
|
60
84
|
correctable_parts = parts.none? { |part| uncorrectable?(part) }
|
61
85
|
if correctable_parts && !corrected_ancestor?(topmost_plus_node)
|
@@ -67,7 +91,10 @@ module RuboCop
|
|
67
91
|
end
|
68
92
|
end
|
69
93
|
|
70
|
-
|
94
|
+
def offensive_for_mode?(receiver_node)
|
95
|
+
mode = cop_config['Mode'].to_sym
|
96
|
+
mode == :aggressive || mode == :conservative && receiver_node.str_type?
|
97
|
+
end
|
71
98
|
|
72
99
|
def line_end_concatenation?(node)
|
73
100
|
# If the concatenation happens at the end of the line,
|
@@ -87,7 +114,7 @@ module RuboCop
|
|
87
114
|
current
|
88
115
|
end
|
89
116
|
|
90
|
-
def collect_parts(node, parts)
|
117
|
+
def collect_parts(node, parts = [])
|
91
118
|
return unless node
|
92
119
|
|
93
120
|
if plus_node?(node)
|