rubocop 1.88.2 → 1.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/default.yml +67 -11
- data/lib/rubocop/comment_config.rb +35 -19
- data/lib/rubocop/config.rb +53 -0
- data/lib/rubocop/config_validator.rb +38 -1
- data/lib/rubocop/cop/badge.rb +8 -0
- data/lib/rubocop/cop/base.rb +6 -4
- data/lib/rubocop/cop/bundler.rb +19 -0
- data/lib/rubocop/cop/corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
- data/lib/rubocop/cop/gemspec.rb +22 -0
- data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
- data/lib/rubocop/cop/generator.rb +4 -0
- data/lib/rubocop/cop/layout/class_structure.rb +8 -2
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
- data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
- data/lib/rubocop/cop/layout/line_length.rb +85 -12
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
- data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +33 -34
- data/lib/rubocop/cop/layout.rb +114 -0
- data/lib/rubocop/cop/lazy_loader.rb +41 -0
- data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
- data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
- data/lib/rubocop/cop/lint/constant_reassignment.rb +1 -10
- data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
- data/lib/rubocop/cop/lint/deprecated_reference.rb +185 -0
- data/lib/rubocop/cop/lint/duplicate_methods.rb +202 -22
- data/lib/rubocop/cop/lint/inherit_exception.rb +53 -7
- data/lib/rubocop/cop/lint/literal_as_condition.rb +5 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +1 -1
- data/lib/rubocop/cop/lint/missing_super.rb +31 -1
- data/lib/rubocop/cop/lint/name_typo.rb +223 -0
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +0 -4
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +7 -0
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +37 -1
- data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
- data/lib/rubocop/cop/lint/void.rb +17 -6
- data/lib/rubocop/cop/lint.rb +176 -0
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics.rb +31 -0
- data/lib/rubocop/cop/migration/department_name.rb +0 -4
- data/lib/rubocop/cop/migration.rb +13 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
- data/lib/rubocop/cop/mixin/endless_method_rewriter.rb +13 -5
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
- data/lib/rubocop/cop/mixin/project_index_help.rb +140 -0
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
- data/lib/rubocop/cop/mixin/statement_modifier.rb +42 -6
- data/lib/rubocop/cop/mixin.rb +78 -77
- data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -0
- data/lib/rubocop/cop/naming/predicate_prefix.rb +33 -5
- data/lib/rubocop/cop/naming/variable_number.rb +3 -1
- data/lib/rubocop/cop/naming.rb +31 -0
- data/lib/rubocop/cop/offense.rb +10 -6
- data/lib/rubocop/cop/registry.rb +153 -43
- data/lib/rubocop/cop/security.rb +19 -0
- data/lib/rubocop/cop/style/array_intersect.rb +24 -5
- data/lib/rubocop/cop/style/class_and_module_children.rb +78 -18
- data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
- data/lib/rubocop/cop/style/copyright.rb +6 -1
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +41 -3
- data/lib/rubocop/cop/style/documentation.rb +47 -0
- data/lib/rubocop/cop/style/endless_method.rb +0 -10
- data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -48
- data/lib/rubocop/cop/style/lambda.rb +4 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
- data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
- data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
- data/lib/rubocop/cop/style/negative_array_index.rb +3 -1
- data/lib/rubocop/cop/style/redundant_assignment.rb +11 -0
- data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
- data/lib/rubocop/cop/style/redundant_file_extension_in_require.rb +9 -0
- data/lib/rubocop/cop/style/redundant_format.rb +12 -3
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
- data/lib/rubocop/cop/style/redundant_parentheses.rb +63 -63
- data/lib/rubocop/cop/style/safe_navigation.rb +10 -0
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +20 -3
- data/lib/rubocop/cop/style/static_class.rb +21 -0
- data/lib/rubocop/cop/style.rb +310 -0
- data/lib/rubocop/cop/team.rb +48 -3
- data/lib/rubocop/cop/variable_force/variable.rb +18 -6
- data/lib/rubocop/cops_documentation_generator.rb +1 -1
- data/lib/rubocop/directive_comment.rb +2 -0
- data/lib/rubocop/feature_loader.rb +0 -6
- data/lib/rubocop/lsp/routes.rb +41 -3
- data/lib/rubocop/lsp/runtime.rb +4 -0
- data/lib/rubocop/lsp/server.rb +4 -0
- data/lib/rubocop/lsp/stdin_runner.rb +20 -0
- data/lib/rubocop/pending_cops_reporter.rb +1 -6
- data/lib/rubocop/project_index_loader.rb +16 -0
- data/lib/rubocop/result_cache.rb +6 -4
- data/lib/rubocop/rspec/shared_contexts.rb +14 -2
- data/lib/rubocop/runner.rb +45 -20
- data/lib/rubocop/server/cache.rb +10 -5
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +17 -626
- metadata +18 -3
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
class Generator
|
|
6
|
+
# A class that injects a `register_cop` directive into the cop's department module
|
|
7
|
+
# so that the new cop is registered for lazy loading.
|
|
8
|
+
# The directive is injected in alphabetical order among the existing directives without
|
|
9
|
+
# reordering them, since the registration order defines the cop execution order.
|
|
10
|
+
# When the department module file does not exist yet, it is created, and a require for
|
|
11
|
+
# it is injected into the root RuboCop file.
|
|
12
|
+
#
|
|
13
|
+
# Anything unexpected in the department module, such as a commented-out or otherwise
|
|
14
|
+
# unparsable `register_cop` line, raises an error instead of silently producing
|
|
15
|
+
# a broken registration.
|
|
16
|
+
class RegistrationInjector
|
|
17
|
+
EXTEND_PATTERN = /\A(?<indent>\s*)extend LazyLoader\n\z/.freeze
|
|
18
|
+
ENTRY_PATTERN = %r{
|
|
19
|
+
\A(?<indent>\s*)register_cop\s
|
|
20
|
+
:(?<constant>[A-Za-z0-9]+),\s
|
|
21
|
+
"\#\{__dir__\}/(?<path>[a-z0-9_/]+)"\n\z
|
|
22
|
+
}x.freeze
|
|
23
|
+
DEPARTMENT_REQUIRE_PATTERN = %r{\Arequire_relative 'rubocop/cop/[a-z0-9_/]+'\n\z}.freeze
|
|
24
|
+
|
|
25
|
+
def initialize(source_path:, badge:, root_file_path: 'lib/rubocop.rb', output: $stdout)
|
|
26
|
+
@source_path = Pathname(source_path)
|
|
27
|
+
@badge = badge
|
|
28
|
+
@root_file_path = root_file_path
|
|
29
|
+
@department_file_path = Pathname("#{@source_path.dirname}.rb")
|
|
30
|
+
@output = output
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def inject
|
|
34
|
+
if File.exist?(department_file_path)
|
|
35
|
+
inject_directive
|
|
36
|
+
else
|
|
37
|
+
create_department_file
|
|
38
|
+
inject_department_require
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :source_path, :badge, :root_file_path, :department_file_path, :output
|
|
45
|
+
|
|
46
|
+
def inject_directive
|
|
47
|
+
lines = File.readlines(department_file_path)
|
|
48
|
+
entries = parse_entries(lines)
|
|
49
|
+
return if entries.any? { |entry| entry[:path] == cop_path }
|
|
50
|
+
|
|
51
|
+
line, indent = insertion_point(lines, entries)
|
|
52
|
+
lines.insert(line, "#{indent}#{directive}\n")
|
|
53
|
+
|
|
54
|
+
write_department_file(lines)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create_department_file
|
|
58
|
+
File.write(department_file_path, department_module_source)
|
|
59
|
+
|
|
60
|
+
output.puts "[create] #{department_file_path}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def inject_department_require
|
|
64
|
+
require_path = department_file_path.sub_ext('').to_s.delete_prefix('lib/')
|
|
65
|
+
require_directive = "require_relative '#{require_path}'\n"
|
|
66
|
+
|
|
67
|
+
lines = File.readlines(root_file_path)
|
|
68
|
+
return if lines.include?(require_directive)
|
|
69
|
+
|
|
70
|
+
lines.insert(department_require_index(lines, require_directive), require_directive)
|
|
71
|
+
File.write(root_file_path, lines.join)
|
|
72
|
+
|
|
73
|
+
output.puts "[modify] #{root_file_path} - `#{require_directive.strip}` was injected."
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def write_department_file(lines)
|
|
77
|
+
File.write(department_file_path, lines.join)
|
|
78
|
+
|
|
79
|
+
output.puts "[modify] #{department_file_path} - `#{directive}` was injected."
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Array<Hash>] the `register_cop` entries with their line numbers
|
|
83
|
+
def parse_entries(lines)
|
|
84
|
+
lines.filter_map.with_index do |line, index|
|
|
85
|
+
match = ENTRY_PATTERN.match(line)
|
|
86
|
+
|
|
87
|
+
if match
|
|
88
|
+
{ line: index, indent: match[:indent], constant: match[:constant],
|
|
89
|
+
path: match[:path] }
|
|
90
|
+
elsif line.include?('register_cop')
|
|
91
|
+
raise Error, "unexpected `register_cop` line in #{department_file_path}:" \
|
|
92
|
+
"#{index + 1}; fix it or register the cop manually: #{line}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The directive path is relative to the department module file's directory,
|
|
98
|
+
# which `__dir__` resolves to at load time.
|
|
99
|
+
def cop_path
|
|
100
|
+
source_path.relative_path_from(department_file_path.dirname).to_s.delete_suffix('.rb')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def insertion_point(lines, entries)
|
|
104
|
+
if entries.empty?
|
|
105
|
+
first_directive_position(lines)
|
|
106
|
+
else
|
|
107
|
+
successor = entries.find { |entry| entry[:path] > cop_path }
|
|
108
|
+
entry = successor || entries.last
|
|
109
|
+
|
|
110
|
+
[entry[:line] + (successor ? 0 : 1), entry[:indent]]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def directive
|
|
115
|
+
"register_cop :#{badge.cop_name}, \"\#{__dir__}/#{cop_path}\""
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The department requires form the contiguous block right before
|
|
119
|
+
# the `rubocop/cop/team` require in the root RuboCop file.
|
|
120
|
+
def department_require_index(lines, require_directive)
|
|
121
|
+
anchor = lines.index("require_relative 'rubocop/cop/team'\n")
|
|
122
|
+
unless anchor
|
|
123
|
+
raise Error, "could not find the department require block in #{root_file_path}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
block_end = anchor - 1
|
|
127
|
+
block_end -= 1 while lines[block_end] == "\n"
|
|
128
|
+
block_start = block_end
|
|
129
|
+
block_start -= 1 while lines[block_start - 1]&.match?(DEPARTMENT_REQUIRE_PATTERN)
|
|
130
|
+
|
|
131
|
+
(block_start..block_end).find do |index|
|
|
132
|
+
lines[index] > require_directive
|
|
133
|
+
end || (block_end + 1)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def first_directive_position(lines)
|
|
137
|
+
extend_lines = lines.filter_map.with_index do |line, index|
|
|
138
|
+
[index, EXTEND_PATTERN.match(line)[:indent]] if EXTEND_PATTERN.match?(line)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
unless extend_lines.size == 1
|
|
142
|
+
raise Error, "expected `extend LazyLoader` in #{department_file_path}; " \
|
|
143
|
+
'add it or register the cop manually'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
index, indent = extend_lines.first
|
|
147
|
+
blank_after_extend = lines[index + 1] == "\n"
|
|
148
|
+
|
|
149
|
+
[index + (blank_after_extend ? 2 : 1), indent]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def department_module_source
|
|
153
|
+
modules = %w[RuboCop Cop] + badge.department.to_s.split('/')
|
|
154
|
+
body_indent = ' ' * modules.size
|
|
155
|
+
|
|
156
|
+
lines = ['# frozen_string_literal: true', '']
|
|
157
|
+
lines.concat(module_opening_lines(modules))
|
|
158
|
+
lines << "#{body_indent}extend LazyLoader"
|
|
159
|
+
lines << ''
|
|
160
|
+
lines << "#{body_indent}#{directive}"
|
|
161
|
+
lines.concat(module_closing_lines(modules))
|
|
162
|
+
|
|
163
|
+
"#{lines.join("\n")}\n"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def module_opening_lines(modules)
|
|
167
|
+
modules.flat_map.with_index do |mod, nesting|
|
|
168
|
+
indent = ' ' * nesting
|
|
169
|
+
lines = []
|
|
170
|
+
|
|
171
|
+
if mod == modules.last
|
|
172
|
+
lines << "#{indent}# Cops for the `#{badge.department}` department. " \
|
|
173
|
+
"The department's cops are"
|
|
174
|
+
lines << "#{indent}# registered for lazy loading and their files are " \
|
|
175
|
+
'loaded on demand.'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
lines << "#{indent}module #{mod}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def module_closing_lines(modules)
|
|
183
|
+
(modules.size - 1).downto(0).map { |nesting| "#{' ' * nesting}end" }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -136,6 +136,10 @@ module RuboCop
|
|
|
136
136
|
RequireFileInjector.new(source_path: source_path, root_file_path: root_file_path).inject
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
+
def inject_registration
|
|
140
|
+
RegistrationInjector.new(source_path: source_path, badge: badge).inject
|
|
141
|
+
end
|
|
142
|
+
|
|
139
143
|
def inject_config(config_file_path: 'config/default.yml',
|
|
140
144
|
version_added: '<<next>>')
|
|
141
145
|
injector =
|
|
@@ -251,7 +251,7 @@ module RuboCop
|
|
|
251
251
|
key = category || name
|
|
252
252
|
visibility_key =
|
|
253
253
|
if node.def_modifier?
|
|
254
|
-
"#{name}_methods"
|
|
254
|
+
name.end_with?('_class_method') ? "#{name}s" : "#{name}_methods"
|
|
255
255
|
else
|
|
256
256
|
"#{node_visibility(node)}_#{key}"
|
|
257
257
|
end
|
|
@@ -283,7 +283,7 @@ module RuboCop
|
|
|
283
283
|
# Exploding such a node into its children would yield non-node values (e.g. a
|
|
284
284
|
# method-name `Symbol` from a `csend`) and crash later checks.
|
|
285
285
|
if class_def.type?(:begin, :kwbegin)
|
|
286
|
-
class_def
|
|
286
|
+
flatten_class_elements(class_def)
|
|
287
287
|
else
|
|
288
288
|
[class_def]
|
|
289
289
|
end
|
|
@@ -296,6 +296,12 @@ module RuboCop
|
|
|
296
296
|
private_constant?(node)
|
|
297
297
|
end
|
|
298
298
|
|
|
299
|
+
def flatten_class_elements(node)
|
|
300
|
+
node.children.compact.flat_map do |child|
|
|
301
|
+
child.kwbegin_type? ? flatten_class_elements(child) : [child]
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
299
305
|
def ignore_for_autocorrect?(node, sibling)
|
|
300
306
|
classification = classify(node)
|
|
301
307
|
sibling_class = classify(sibling)
|
|
@@ -172,7 +172,7 @@ module RuboCop
|
|
|
172
172
|
'Indent the right bracket the same as the first position ' \
|
|
173
173
|
'after the preceding left parenthesis.'
|
|
174
174
|
when :parent_hash_key
|
|
175
|
-
'Indent the right bracket the same as the parent hash key.'
|
|
175
|
+
'Indent the right bracket the same as the parent hash key.'
|
|
176
176
|
else
|
|
177
177
|
'Indent the right bracket the same as the start of the line ' \
|
|
178
178
|
'where the left bracket is.'
|
|
@@ -206,7 +206,9 @@ module RuboCop
|
|
|
206
206
|
def autocorrect(corrector, node)
|
|
207
207
|
return unless node
|
|
208
208
|
|
|
209
|
-
AlignmentCorrector.correct(
|
|
209
|
+
AlignmentCorrector.correct(
|
|
210
|
+
corrector, processed_source, node, @column_delta, tab_indentation: true
|
|
211
|
+
)
|
|
210
212
|
end
|
|
211
213
|
|
|
212
214
|
# Returns a range at the first non-space column of the line the opening parenthesis is on,
|
|
@@ -98,6 +98,24 @@ module RuboCop
|
|
|
98
98
|
#
|
|
99
99
|
# name = 'John' #: String
|
|
100
100
|
#
|
|
101
|
+
# @example AllowYARDCommentBlockSeparator: false (default)
|
|
102
|
+
#
|
|
103
|
+
# # bad
|
|
104
|
+
#
|
|
105
|
+
# # Copyright (c) Example Corp
|
|
106
|
+
# #-
|
|
107
|
+
# class Client
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
# @example AllowYARDCommentBlockSeparator: true
|
|
111
|
+
#
|
|
112
|
+
# # good
|
|
113
|
+
#
|
|
114
|
+
# # Copyright (c) Example Corp
|
|
115
|
+
# #-
|
|
116
|
+
# class Client
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
101
119
|
class LeadingCommentSpace < Base
|
|
102
120
|
include RangeHelp
|
|
103
121
|
extend AutoCorrector
|
|
@@ -113,6 +131,7 @@ module RuboCop
|
|
|
113
131
|
next if gemfile_ruby_comment?(comment)
|
|
114
132
|
next if rbs_inline_annotation?(comment)
|
|
115
133
|
next if steep_annotation?(comment)
|
|
134
|
+
next if yard_comment_block_separator?(comment)
|
|
116
135
|
|
|
117
136
|
add_offense(comment) do |corrector|
|
|
118
137
|
expr = comment.source_range
|
|
@@ -197,6 +216,14 @@ module RuboCop
|
|
|
197
216
|
def steep_annotation?(comment)
|
|
198
217
|
allow_steep_annotation? && comment.text.start_with?(/#[$:]/)
|
|
199
218
|
end
|
|
219
|
+
|
|
220
|
+
def allow_yard_comment_block_separator?
|
|
221
|
+
cop_config['AllowYARDCommentBlockSeparator']
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def yard_comment_block_separator?(comment)
|
|
225
|
+
allow_yard_comment_block_separator? && comment.text.match?(/\A#-\s*\z/)
|
|
226
|
+
end
|
|
200
227
|
end
|
|
201
228
|
end
|
|
202
229
|
end
|
|
@@ -65,6 +65,7 @@ module RuboCop
|
|
|
65
65
|
include AllowedPattern
|
|
66
66
|
include RangeHelp
|
|
67
67
|
include LineLengthHelp
|
|
68
|
+
include EndlessMethodRewriter
|
|
68
69
|
extend AutoCorrector
|
|
69
70
|
|
|
70
71
|
exclude_limit 'Max'
|
|
@@ -92,8 +93,15 @@ module RuboCop
|
|
|
92
93
|
alias on_hash on_potential_breakable_node
|
|
93
94
|
alias on_send on_potential_breakable_node
|
|
94
95
|
alias on_csend on_potential_breakable_node
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
def on_def(node)
|
|
98
|
+
if node.endless?
|
|
99
|
+
track_endless_method(node)
|
|
100
|
+
else
|
|
101
|
+
check_for_breakable_node(node)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
alias on_defs on_def
|
|
97
105
|
|
|
98
106
|
def on_new_investigation
|
|
99
107
|
return unless processed_source.raw_source.include?(';')
|
|
@@ -111,6 +119,70 @@ module RuboCop
|
|
|
111
119
|
|
|
112
120
|
attr_accessor :breakable_range
|
|
113
121
|
|
|
122
|
+
def track_endless_method(node)
|
|
123
|
+
line_index = node.first_line - processed_source.buffer.first_line
|
|
124
|
+
endless_methods_by_line[line_index] = node
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def handle_endless_method_line(line, line_index)
|
|
128
|
+
if require_endless_methods?
|
|
129
|
+
return register_required_endless_method_offense(line, line_index)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
register_endless_method_offense(line, line_index)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def require_endless_methods?
|
|
136
|
+
config.cop_enabled?('Style/EndlessMethod') &&
|
|
137
|
+
config.for_cop('Style/EndlessMethod')['EnforcedStyle'] == 'require_always'
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def register_endless_method_offense(line, line_index)
|
|
141
|
+
message = format(MSG, length: line_length(line), max: max)
|
|
142
|
+
loc = excess_range(nil, line, line_index)
|
|
143
|
+
|
|
144
|
+
add_offense(loc, message: message) do |corrector|
|
|
145
|
+
self.max = line_length(line)
|
|
146
|
+
|
|
147
|
+
correct_to_multiline(corrector, endless_methods_by_line[line_index])
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def register_required_endless_method_offense(line, line_index)
|
|
152
|
+
node = endless_methods_by_line[line_index]
|
|
153
|
+
loc = excess_range(nil, line, line_index)
|
|
154
|
+
|
|
155
|
+
add_offense(loc, message: format(MSG, length: line_length(line), max: max)) do |corrector|
|
|
156
|
+
self.max = line_length(line)
|
|
157
|
+
|
|
158
|
+
if correctable_endless_method_block?(node)
|
|
159
|
+
correct_endless_method_block_to_multiline(corrector, node)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def correctable_endless_method_block?(node)
|
|
165
|
+
block_node = node.body
|
|
166
|
+
|
|
167
|
+
block_node&.type?(:any_block) &&
|
|
168
|
+
block_node.braces? &&
|
|
169
|
+
block_node.single_line? &&
|
|
170
|
+
block_node.body &&
|
|
171
|
+
!receiver_contains_heredoc?(block_node)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def correct_endless_method_block_to_multiline(corrector, node)
|
|
175
|
+
block_node = node.body
|
|
176
|
+
block_arguments = block_node.arguments? ? " #{block_node.arguments.source}" : ''
|
|
177
|
+
replacement = [
|
|
178
|
+
"#{block_node.send_node.source} do#{block_arguments}",
|
|
179
|
+
"#{indent(node, offset: 2)}#{block_node.body.source}",
|
|
180
|
+
"#{indent(node)}end"
|
|
181
|
+
].join("\n")
|
|
182
|
+
|
|
183
|
+
corrector.replace(block_node, replacement)
|
|
184
|
+
end
|
|
185
|
+
|
|
114
186
|
def check_for_breakable_node(node)
|
|
115
187
|
breakable_node = extract_breakable_node(node, max)
|
|
116
188
|
return if breakable_node.nil?
|
|
@@ -237,6 +309,10 @@ module RuboCop
|
|
|
237
309
|
@breakable_range_by_line_index ||= {}
|
|
238
310
|
end
|
|
239
311
|
|
|
312
|
+
def endless_methods_by_line
|
|
313
|
+
@endless_methods_by_line ||= {}
|
|
314
|
+
end
|
|
315
|
+
|
|
240
316
|
def breakable_string_delimiters
|
|
241
317
|
@breakable_string_delimiters ||= {}
|
|
242
318
|
end
|
|
@@ -252,10 +328,15 @@ module RuboCop
|
|
|
252
328
|
[max - indentation_difference(line), 0].max
|
|
253
329
|
end
|
|
254
330
|
|
|
255
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
331
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
256
332
|
def check_line(line, line_index)
|
|
257
333
|
return if line_length(line) <= max
|
|
258
334
|
return if allowed_line?(line, line_index)
|
|
335
|
+
|
|
336
|
+
if endless_methods_by_line.key?(line_index)
|
|
337
|
+
return handle_endless_method_line(line, line_index)
|
|
338
|
+
end
|
|
339
|
+
|
|
259
340
|
if allow_rbs_inline_annotation? && rbs_inline_annotation_on_source_line?(line_index)
|
|
260
341
|
return
|
|
261
342
|
end
|
|
@@ -267,7 +348,7 @@ module RuboCop
|
|
|
267
348
|
|
|
268
349
|
register_offense(excess_range(nil, line, line_index), line, line_index)
|
|
269
350
|
end
|
|
270
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
351
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
271
352
|
|
|
272
353
|
def allowed_line?(line, line_index)
|
|
273
354
|
matches_allowed_pattern?(line) ||
|
|
@@ -313,10 +394,6 @@ module RuboCop
|
|
|
313
394
|
end
|
|
314
395
|
alias max_line_length max
|
|
315
396
|
|
|
316
|
-
def allow_heredoc?
|
|
317
|
-
allowed_heredoc
|
|
318
|
-
end
|
|
319
|
-
|
|
320
397
|
def allowed_heredoc
|
|
321
398
|
cop_config['AllowHeredoc']
|
|
322
399
|
end
|
|
@@ -344,10 +421,6 @@ module RuboCop
|
|
|
344
421
|
end
|
|
345
422
|
end
|
|
346
423
|
|
|
347
|
-
def line_in_heredoc?(line_number)
|
|
348
|
-
heredocs.any? { |range, _delimiter| range.cover?(line_number) }
|
|
349
|
-
end
|
|
350
|
-
|
|
351
424
|
def receiver_contains_heredoc?(node)
|
|
352
425
|
return false unless (receiver = node.receiver)
|
|
353
426
|
return true if receiver.any_str_type? && receiver.heredoc?
|
|
@@ -410,7 +410,7 @@ module RuboCop
|
|
|
410
410
|
|
|
411
411
|
def find_continuation_node(node)
|
|
412
412
|
receiver = node.receiver
|
|
413
|
-
return receiver
|
|
413
|
+
return leftmost_call_on_same_line(receiver) if single_line_block_receiver?(receiver)
|
|
414
414
|
return unless receiver.call_type? && receiver.loc.dot
|
|
415
415
|
return receiver if receiver.receiver.begin_type? && node.block_node.single_line?
|
|
416
416
|
return unless receiver.loc.dot.line > receiver.receiver.last_line
|
|
@@ -422,9 +422,19 @@ module RuboCop
|
|
|
422
422
|
receiver.single_line? && receiver.any_block_type?
|
|
423
423
|
end
|
|
424
424
|
|
|
425
|
+
def leftmost_call_on_same_line(node)
|
|
426
|
+
current = node.any_block_type? ? node.send_node : node
|
|
427
|
+
while current.receiver&.call_type? &&
|
|
428
|
+
current.receiver.loc?(:dot) &&
|
|
429
|
+
current.receiver.loc.dot.line == current.loc.dot.line
|
|
430
|
+
current = current.receiver
|
|
431
|
+
end
|
|
432
|
+
current
|
|
433
|
+
end
|
|
434
|
+
|
|
425
435
|
def handle_descendant_block(node)
|
|
426
436
|
receiver = node.receiver
|
|
427
|
-
return receiver
|
|
437
|
+
return leftmost_call_on_same_line(receiver) if single_line_block_receiver?(receiver)
|
|
428
438
|
|
|
429
439
|
block_node = node.each_descendant(:any_block).first
|
|
430
440
|
return unless block_node&.multiline?
|
|
@@ -49,6 +49,7 @@ module RuboCop
|
|
|
49
49
|
class RedundantLineBreak < Base
|
|
50
50
|
include CheckAssignment
|
|
51
51
|
include CheckSingleLineSuitability
|
|
52
|
+
include ReparsedEquivalence
|
|
52
53
|
extend AutoCorrector
|
|
53
54
|
|
|
54
55
|
MSG = 'Redundant line break detected.'
|
|
@@ -82,12 +83,88 @@ module RuboCop
|
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
def register_offense(node)
|
|
86
|
+
# The exact single-line correction is verified to parse equivalently
|
|
87
|
+
# before the offense is registered, so a join that would change how
|
|
88
|
+
# the code parses is never reported or offered.
|
|
89
|
+
return if verified_by_reparse([node], oversized: :verify).empty?
|
|
90
|
+
|
|
85
91
|
add_offense(node) do |corrector|
|
|
86
92
|
corrector.replace(node, to_single_line(node.source).strip)
|
|
87
93
|
end
|
|
88
94
|
ignore_node(node)
|
|
89
95
|
end
|
|
90
96
|
|
|
97
|
+
def apply_reparse_correction(corrector, node)
|
|
98
|
+
corrector.replace(node, to_single_line(node.source).strip)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Joining lines shifts the value of any later `__LINE__`, exactly as
|
|
102
|
+
# any other line-removing correction does; neutralize it on both sides
|
|
103
|
+
# with an identifier, which stays valid in every position the keyword
|
|
104
|
+
# can appear in.
|
|
105
|
+
def preprocess_reparsed_source(source)
|
|
106
|
+
source.gsub(/\b__LINE__\b/, '__LINE0__')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def normalize_reparsed_ast(node)
|
|
110
|
+
fold_string_concatenation(node)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Joining lines merges split string literals (`"a" \<newline> "b"` into
|
|
114
|
+
# `"ab"`) and turns mixed-quote pairs into `+` concatenation, which
|
|
115
|
+
# changes the tree but not the resulting string. Normalize all string
|
|
116
|
+
# concatenation to a canonical form before comparing: nested
|
|
117
|
+
# concatenations are flattened and adjacent literal parts merged.
|
|
118
|
+
def fold_string_concatenation(node)
|
|
119
|
+
return node unless node.is_a?(::Parser::AST::Node)
|
|
120
|
+
|
|
121
|
+
children = node.children.map { |child| fold_string_concatenation(child) }
|
|
122
|
+
node = node.updated(nil, children)
|
|
123
|
+
|
|
124
|
+
parts = string_concatenation_parts(node)
|
|
125
|
+
return node unless parts
|
|
126
|
+
|
|
127
|
+
merged = merge_string_parts(parts)
|
|
128
|
+
if merged.one? && merged.first.str_type?
|
|
129
|
+
node.updated(:str, merged.first.children)
|
|
130
|
+
else
|
|
131
|
+
node.updated(:dstr, merged)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def string_concatenation_parts(node)
|
|
136
|
+
case node.type
|
|
137
|
+
when :dstr
|
|
138
|
+
node.children
|
|
139
|
+
when :send
|
|
140
|
+
parts = [node.receiver, *node.arguments]
|
|
141
|
+
parts if node.method?(:+) && parts.all? { |part| stringish?(part) }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def merge_string_parts(parts)
|
|
146
|
+
flattened = parts.flat_map do |part|
|
|
147
|
+
part.is_a?(::Parser::AST::Node) && part.dstr_type? ? part.children : [part]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
flattened.chunk_while { |left, right| plain_string?(left) && plain_string?(right) }
|
|
151
|
+
.map { |chunk| merge_plain_strings(chunk) }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def merge_plain_strings(chunk)
|
|
155
|
+
return chunk.first if chunk.one?
|
|
156
|
+
|
|
157
|
+
chunk.first.updated(:str, [chunk.map { |part| part.children.first }.join])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def stringish?(node)
|
|
161
|
+
node.is_a?(::Parser::AST::Node) && %i[str dstr].include?(node.type)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def plain_string?(node)
|
|
165
|
+
node.is_a?(::Parser::AST::Node) && node.str_type? && node.children.one?
|
|
166
|
+
end
|
|
167
|
+
|
|
91
168
|
def offense?(node)
|
|
92
169
|
return false unless node.multiline? && suitable_as_single_line?(node)
|
|
93
170
|
return require_backslash?(node) if node.operator_keyword?
|