rubocop 1.88.1 → 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 +69 -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 +19 -12
- data/lib/rubocop/cop/bundler.rb +19 -0
- data/lib/rubocop/cop/commissioner.rb +13 -11
- data/lib/rubocop/cop/corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +12 -2
- 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/comment_indentation.rb +9 -2
- data/lib/rubocop/cop/layout/else_alignment.rb +1 -14
- 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 +95 -17
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +18 -3
- 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/debugger.rb +13 -1
- 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/numeric_operation_with_constant_result.rb +7 -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/to_json.rb +8 -1
- data/lib/rubocop/cop/lint/unreachable_code.rb +9 -4
- data/lib/rubocop/cop/lint/unused_private_method.rb +157 -0
- data/lib/rubocop/cop/lint/useless_numeric_operation.rb +10 -10
- data/lib/rubocop/cop/lint/useless_ruby2_keywords.rb +2 -0
- data/lib/rubocop/cop/lint/void.rb +19 -8
- 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/allowed_methods.rb +5 -5
- data/lib/rubocop/cop/mixin/allowed_pattern.rb +5 -1
- 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/forbidden_pattern.rb +5 -1
- 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 +43 -7
- 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/arguments_forwarding.rb +1 -1
- 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/collection_compact.rb +1 -1
- data/lib/rubocop/cop/style/conditional_assignment.rb +22 -0
- data/lib/rubocop/cop/style/copyright.rb +6 -1
- data/lib/rubocop/cop/style/def_with_parentheses.rb +6 -2
- 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/documentation_method.rb +5 -1
- data/lib/rubocop/cop/style/endless_method.rb +0 -10
- data/lib/rubocop/cop/style/hash_conversion.rb +14 -6
- data/lib/rubocop/cop/style/hash_lookup_method.rb +2 -6
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +15 -4
- data/lib/rubocop/cop/style/if_unless_modifier.rb +11 -52
- data/lib/rubocop/cop/style/invertible_unless_condition.rb +25 -5
- data/lib/rubocop/cop/style/lambda.rb +4 -0
- data/lib/rubocop/cop/style/lambda_call.rb +11 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +20 -3
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +5 -3
- data/lib/rubocop/cop/style/method_def_parentheses.rb +4 -0
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +40 -7
- data/lib/rubocop/cop/style/mixin_usage.rb +1 -1
- data/lib/rubocop/cop/style/module_function.rb +4 -2
- 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_current_directory_in_path.rb +13 -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_heredoc_delimiter_quotes.rb +6 -1
- 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/struct_inheritance.rb +3 -1
- data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
- data/lib/rubocop/cop/style.rb +310 -0
- data/lib/rubocop/cop/team.rb +66 -7
- 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 +95 -30
- 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
|
@@ -12,20 +12,28 @@ module RuboCop
|
|
|
12
12
|
class << self
|
|
13
13
|
attr_reader :processed_source
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# `tab_indentation` may only be set by callers whose `column_delta` represents
|
|
16
|
+
# whole indentation levels (e.g. `Layout/IndentationWidth`).
|
|
17
|
+
# Alignment to an arbitrary column cannot be expressed with tabs, so those callers
|
|
18
|
+
# leave tab correction disabled to avoid producing a never-converging correction.
|
|
19
|
+
def correct(corrector, processed_source, node, column_delta, tab_indentation: false)
|
|
16
20
|
return unless node
|
|
17
21
|
|
|
18
22
|
@processed_source = processed_source
|
|
19
|
-
# Disable autocorrection for tabs as it requires special handling
|
|
20
|
-
return if using_tabs?
|
|
21
23
|
|
|
22
24
|
expr = node.respond_to?(:loc) ? node.source_range : node
|
|
23
25
|
return if block_comment_within?(expr)
|
|
24
26
|
|
|
25
27
|
taboo_ranges = inside_string_ranges(node)
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
if using_tabs?
|
|
30
|
+
return unless tab_indentation
|
|
31
|
+
|
|
32
|
+
correct_tab_indentation(corrector, expr, column_delta, taboo_ranges)
|
|
33
|
+
else
|
|
34
|
+
each_line(expr) do |line_begin_pos|
|
|
35
|
+
autocorrect_line(corrector, line_begin_pos, expr, column_delta, taboo_ranges)
|
|
36
|
+
end
|
|
29
37
|
end
|
|
30
38
|
end
|
|
31
39
|
|
|
@@ -58,6 +66,48 @@ module RuboCop
|
|
|
58
66
|
end
|
|
59
67
|
end
|
|
60
68
|
|
|
69
|
+
# Tab indentation is corrected by rewriting each line's leading whitespace to
|
|
70
|
+
# the target number of tabs. Working in whole tabs rather than applying a column delta
|
|
71
|
+
# keeps the result idempotent, which avoids the infinite loops that delta-based correction
|
|
72
|
+
# caused for tabs.
|
|
73
|
+
def correct_tab_indentation(corrector, expr, column_delta, taboo_ranges)
|
|
74
|
+
buffer = expr.source_buffer
|
|
75
|
+
|
|
76
|
+
each_line(expr) do |line_begin_pos|
|
|
77
|
+
line_range = buffer.line_range(buffer.line_for_position(line_begin_pos))
|
|
78
|
+
correct_tab_line(corrector, line_range, column_delta, taboo_ranges)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def correct_tab_line(corrector, line_range, column_delta, taboo_ranges)
|
|
83
|
+
leading = line_range.source[/\A[ \t]*/]
|
|
84
|
+
return if leading.length == line_range.source.length # blank line
|
|
85
|
+
|
|
86
|
+
leading_range = range_between(line_range.begin_pos, line_range.begin_pos + leading.length)
|
|
87
|
+
return if taboo_ranges.any? { |t| within?(leading_range, t) }
|
|
88
|
+
|
|
89
|
+
target = target_tab_indentation(leading, column_delta)
|
|
90
|
+
corrector.replace(leading_range, target) unless leading == target
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def target_tab_indentation(leading, column_delta)
|
|
94
|
+
width = configured_indentation_width
|
|
95
|
+
return leading unless width.positive?
|
|
96
|
+
|
|
97
|
+
visual_width = leading.chars.sum { |char| char == "\t" ? width : 1 }
|
|
98
|
+
|
|
99
|
+
"\t" * ([visual_width + column_delta, 0].max / width)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The number of columns a single tab spans. This must match the width
|
|
103
|
+
# `Layout/IndentationWidth` uses to compute `column_delta`, otherwise
|
|
104
|
+
# the division below would not land on a tab boundary. `Layout/IndentationStyle`
|
|
105
|
+
# has its own `IndentationWidth`, but that only governs how that cop replaces tabs
|
|
106
|
+
# with spaces, so it must not be consulted here.
|
|
107
|
+
def configured_indentation_width
|
|
108
|
+
processed_source.config.for_cop('Layout/IndentationWidth')['Width'] || 2
|
|
109
|
+
end
|
|
110
|
+
|
|
61
111
|
def inside_string_ranges(node)
|
|
62
112
|
return [] unless node.is_a?(Parser::AST::Node)
|
|
63
113
|
|
|
@@ -83,18 +83,28 @@ module RuboCop
|
|
|
83
83
|
def duplicated_assignment_method_nodes
|
|
84
84
|
assignment_method_declarations(processed_source.ast)
|
|
85
85
|
.select(&:assignment_method?)
|
|
86
|
-
.group_by(
|
|
86
|
+
.group_by { |node| [enclosing_specification(node), node.method_name] }
|
|
87
87
|
.values
|
|
88
88
|
.select { |nodes| nodes.size > 1 }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def duplicated_indexed_assignment_method_nodes
|
|
92
92
|
indexed_assignment_method_declarations(processed_source.ast)
|
|
93
|
-
.group_by { |node|
|
|
93
|
+
.group_by { |node| indexed_assignment_key(node) }
|
|
94
94
|
.values
|
|
95
95
|
.select { |nodes| nodes.size > 1 }
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
def indexed_assignment_key(node)
|
|
99
|
+
[enclosing_specification(node), node.children.first.method_name, node.first_argument]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Assignments in separate `Gem::Specification.new` blocks are not duplicates of
|
|
103
|
+
# one another, so the enclosing specification is part of the grouping key.
|
|
104
|
+
def enclosing_specification(node)
|
|
105
|
+
node.each_ancestor(:block).find { |block| gem_specification?(block) }
|
|
106
|
+
end
|
|
107
|
+
|
|
98
108
|
def register_offense(node, assignment, line_of_first_occurrence)
|
|
99
109
|
line_range = node.loc.column...node.loc.last_column
|
|
100
110
|
offense_location = source_range(processed_source.buffer, node.first_line, line_range)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
# Cops for the `Gemspec` department. The department's cops are registered for lazy loading
|
|
6
|
+
# and their files are loaded on demand.
|
|
7
|
+
module Gemspec
|
|
8
|
+
extend LazyLoader
|
|
9
|
+
|
|
10
|
+
register_cop :AddRuntimeDependency, "#{__dir__}/gemspec/add_runtime_dependency"
|
|
11
|
+
register_cop :AttributeAssignment, "#{__dir__}/gemspec/attribute_assignment"
|
|
12
|
+
register_cop :DependencyVersion, "#{__dir__}/gemspec/dependency_version"
|
|
13
|
+
register_cop :DeprecatedAttributeAssignment, "#{__dir__}/gemspec/deprecated_attribute_assignment"
|
|
14
|
+
register_cop :DevelopmentDependencies, "#{__dir__}/gemspec/development_dependencies"
|
|
15
|
+
register_cop :DuplicatedAssignment, "#{__dir__}/gemspec/duplicated_assignment"
|
|
16
|
+
register_cop :OrderedDependencies, "#{__dir__}/gemspec/ordered_dependencies"
|
|
17
|
+
register_cop :RequireMFA, "#{__dir__}/gemspec/require_mfa"
|
|
18
|
+
register_cop :RequiredRubyVersion, "#{__dir__}/gemspec/required_ruby_version"
|
|
19
|
+
register_cop :RubyVersionGlobalsUsage, "#{__dir__}/gemspec/ruby_version_globals_usage"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -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)
|
|
@@ -155,8 +155,15 @@ module RuboCop
|
|
|
155
155
|
|
|
156
156
|
def less_indented?(line)
|
|
157
157
|
rule = config.for_cop('Layout/AccessModifierIndentation')['EnforcedStyle'] == 'outdent'
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
/\A\s*(end\b|[)}\]])/.match?(line) || (rule && bare_access_modifier?(line))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Only a bare access modifier (the keyword alone on its line) is outdented by
|
|
162
|
+
# `Layout/AccessModifierIndentation`. An inline modifier such as `private def foo`
|
|
163
|
+
# keeps the regular method indentation, so a comment above it must not be pushed
|
|
164
|
+
# one level deeper.
|
|
165
|
+
def bare_access_modifier?(line)
|
|
166
|
+
/\A\s*(private|protected|public)\s*(#.*)?\z/.match?(line)
|
|
160
167
|
end
|
|
161
168
|
|
|
162
169
|
def two_alternatives?(line)
|
|
@@ -92,13 +92,7 @@ module RuboCop
|
|
|
92
92
|
case parent.type
|
|
93
93
|
when :def, :defs then base_for_method_definition(parent)
|
|
94
94
|
when :kwbegin then parent.loc.begin
|
|
95
|
-
when :block, :numblock, :itblock
|
|
96
|
-
assignment_node = assignment_node(parent)
|
|
97
|
-
if same_line?(parent, assignment_node)
|
|
98
|
-
assignment_node.source_range
|
|
99
|
-
else
|
|
100
|
-
parent.send_node.source_range
|
|
101
|
-
end
|
|
95
|
+
when :block, :numblock, :itblock then start_line_range(parent)
|
|
102
96
|
else node.loc.keyword
|
|
103
97
|
end
|
|
104
98
|
end
|
|
@@ -143,13 +137,6 @@ module RuboCop
|
|
|
143
137
|
autocorrect(corrector, else_range)
|
|
144
138
|
end
|
|
145
139
|
end
|
|
146
|
-
|
|
147
|
-
def assignment_node(node)
|
|
148
|
-
assignment_node = node.ancestors.first
|
|
149
|
-
return unless assignment_node&.assignment?
|
|
150
|
-
|
|
151
|
-
assignment_node
|
|
152
|
-
end
|
|
153
140
|
end
|
|
154
141
|
end
|
|
155
142
|
end
|
|
@@ -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?
|
|
@@ -420,11 +493,16 @@ module RuboCop
|
|
|
420
493
|
# The maximum allowed length of a string value is:
|
|
421
494
|
# `Max` - end delimiter (quote) - continuation characters (space and slash)
|
|
422
495
|
max_length = max - 3
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
496
|
+
# Offset by the string's starting column so the broken line actually fits
|
|
497
|
+
# within `Max`. When on the same line as its parent, use the column difference;
|
|
498
|
+
# otherwise the string is indented on its own line, so subtract that indentation.
|
|
499
|
+
# (Without this, an indented string under a multi-line parent never shortens
|
|
500
|
+
# below `Max` and the autocorrect loops, inserting empty `"" \` fragments.)
|
|
501
|
+
max_length -= if same_line?(node, node.parent)
|
|
502
|
+
column_offset_between(node.loc, node.parent.loc)
|
|
503
|
+
else
|
|
504
|
+
node.loc.column
|
|
505
|
+
end
|
|
428
506
|
node.source[0...(max_length)]
|
|
429
507
|
end
|
|
430
508
|
end
|