rubocop 0.73.0 → 0.74.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/README.md +1 -1
- data/bin/console +1 -0
- data/config/default.yml +2 -1
- data/lib/rubocop.rb +3 -0
- data/lib/rubocop/ast/node.rb +1 -7
- data/lib/rubocop/config.rb +17 -537
- data/lib/rubocop/config_obsoletion.rb +201 -0
- data/lib/rubocop/config_validator.rb +239 -0
- data/lib/rubocop/cop/layout/extra_spacing.rb +14 -53
- data/lib/rubocop/cop/layout/indentation_width.rb +19 -5
- data/lib/rubocop/cop/layout/space_around_operators.rb +42 -23
- data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +22 -40
- data/lib/rubocop/cop/lint/debugger.rb +0 -2
- data/lib/rubocop/cop/lint/empty_interpolation.rb +4 -4
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +56 -0
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +7 -8
- data/lib/rubocop/cop/lint/string_conversion_in_interpolation.rb +6 -6
- data/lib/rubocop/cop/lint/unneeded_splat_expansion.rb +6 -1
- data/lib/rubocop/cop/metrics/line_length.rb +6 -0
- data/lib/rubocop/cop/mixin/documentation_comment.rb +0 -2
- data/lib/rubocop/cop/mixin/interpolation.rb +27 -0
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +87 -0
- data/lib/rubocop/cop/mixin/surrounding_space.rb +7 -5
- data/lib/rubocop/cop/style/commented_keyword.rb +8 -28
- data/lib/rubocop/cop/style/conditional_assignment.rb +1 -3
- data/lib/rubocop/cop/style/constant_visibility.rb +13 -2
- data/lib/rubocop/cop/style/guard_clause.rb +39 -10
- data/lib/rubocop/cop/style/lambda.rb +0 -2
- data/lib/rubocop/cop/style/trailing_method_end_statement.rb +4 -6
- data/lib/rubocop/cop/style/variable_interpolation.rb +6 -16
- data/lib/rubocop/path_util.rb +1 -1
- data/lib/rubocop/processed_source.rb +4 -0
- data/lib/rubocop/rspec/expect_offense.rb +4 -1
- data/lib/rubocop/version.rb +1 -1
- metadata +5 -2
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for working with string interpolations.
|
6
|
+
#
|
7
|
+
# @abstract Subclasses are expected to implement {#on_interpolation}.
|
8
|
+
module Interpolation
|
9
|
+
def on_dstr(node)
|
10
|
+
on_node_with_interpolations(node)
|
11
|
+
end
|
12
|
+
|
13
|
+
alias on_xstr on_dstr
|
14
|
+
alias on_dsym on_dstr
|
15
|
+
alias on_regexp on_dstr
|
16
|
+
|
17
|
+
def on_node_with_interpolations(node)
|
18
|
+
node.each_child_node(:begin) do |begin_node|
|
19
|
+
on_interpolation(begin_node)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!method on_interpolation(begin_node)
|
24
|
+
# Inspect the `:begin` node of an interpolation
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -19,6 +19,18 @@ module RuboCop
|
|
19
19
|
aligned_with_adjacent_line?(range, method(:aligned_operator?))
|
20
20
|
end
|
21
21
|
|
22
|
+
def aligned_with_preceding_assignment(token)
|
23
|
+
preceding_line_range = token.line.downto(1)
|
24
|
+
|
25
|
+
aligned_with_assignment(token, preceding_line_range)
|
26
|
+
end
|
27
|
+
|
28
|
+
def aligned_with_subsequent_assignment(token)
|
29
|
+
subsequent_line_range = token.line.upto(processed_source.lines.length)
|
30
|
+
|
31
|
+
aligned_with_assignment(token, subsequent_line_range)
|
32
|
+
end
|
33
|
+
|
22
34
|
def aligned_with_adjacent_line?(range, predicate)
|
23
35
|
# minus 2 because node.loc.line is zero-based
|
24
36
|
pre = (range.line - 2).downto(0)
|
@@ -89,6 +101,81 @@ module RuboCop
|
|
89
101
|
def aligned_identical?(range, line)
|
90
102
|
range.source == line[range.column, range.size]
|
91
103
|
end
|
104
|
+
|
105
|
+
def aligned_with_assignment(token, line_range)
|
106
|
+
token_line_indent = processed_source
|
107
|
+
.line_indentation(token.line)
|
108
|
+
assignment_lines = relevant_assignment_lines(line_range)
|
109
|
+
relevant_line_number = assignment_lines[1]
|
110
|
+
|
111
|
+
return :none unless relevant_line_number
|
112
|
+
|
113
|
+
relevant_indent = processed_source
|
114
|
+
.line_indentation(relevant_line_number)
|
115
|
+
|
116
|
+
return :none if relevant_indent < token_line_indent
|
117
|
+
|
118
|
+
assignment_line = processed_source.lines[relevant_line_number - 1]
|
119
|
+
|
120
|
+
return :none unless assignment_line
|
121
|
+
|
122
|
+
aligned_assignment?(token.pos, assignment_line) ? :yes : :no
|
123
|
+
end
|
124
|
+
|
125
|
+
def assignment_lines
|
126
|
+
@assignment_lines ||= assignment_tokens.map(&:line)
|
127
|
+
end
|
128
|
+
|
129
|
+
def assignment_tokens
|
130
|
+
@assignment_tokens ||= begin
|
131
|
+
tokens = processed_source.tokens.select(&:equal_sign?)
|
132
|
+
|
133
|
+
# we don't want to operate on equals signs which are part of an
|
134
|
+
# optarg in a method definition
|
135
|
+
# e.g.: def method(optarg = default_val); end
|
136
|
+
tokens = remove_optarg_equals(tokens, processed_source)
|
137
|
+
|
138
|
+
# Only attempt to align the first = on each line
|
139
|
+
Set.new(tokens.uniq(&:line))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
144
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
|
145
|
+
def relevant_assignment_lines(line_range)
|
146
|
+
result = []
|
147
|
+
original_line_indent = processed_source
|
148
|
+
.line_indentation(line_range.first)
|
149
|
+
relevant_line_indent_at_level = true
|
150
|
+
|
151
|
+
line_range.each do |line_number|
|
152
|
+
current_line_indent = processed_source.line_indentation(line_number)
|
153
|
+
blank_line = processed_source.lines[line_number - 1].blank?
|
154
|
+
|
155
|
+
if (current_line_indent < original_line_indent && !blank_line) ||
|
156
|
+
(relevant_line_indent_at_level && blank_line)
|
157
|
+
break
|
158
|
+
end
|
159
|
+
|
160
|
+
result << line_number if assignment_lines.include?(line_number) &&
|
161
|
+
current_line_indent == original_line_indent
|
162
|
+
|
163
|
+
unless blank_line
|
164
|
+
relevant_line_indent_at_level = \
|
165
|
+
current_line_indent == original_line_indent
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
result
|
170
|
+
end
|
171
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
172
|
+
# rubocop:enable Metrics/PerceivedComplexity, Metrics/MethodLength
|
173
|
+
|
174
|
+
def remove_optarg_equals(asgn_tokens, processed_source)
|
175
|
+
optargs = processed_source.ast.each_node(:optarg)
|
176
|
+
optarg_eql = optargs.map { |o| o.loc.operator.begin_pos }.to_set
|
177
|
+
asgn_tokens.reject { |t| optarg_eql.include?(t.begin_pos) }
|
178
|
+
end
|
92
179
|
end
|
93
180
|
end
|
94
181
|
end
|
@@ -9,6 +9,8 @@ module RuboCop
|
|
9
9
|
NO_SPACE_COMMAND = 'Do not use'
|
10
10
|
SPACE_COMMAND = 'Use'
|
11
11
|
|
12
|
+
SINGLE_SPACE_REGEXP = /[ \t]/.freeze
|
13
|
+
|
12
14
|
private
|
13
15
|
|
14
16
|
def side_space_range(range:, side:)
|
@@ -18,11 +20,11 @@ module RuboCop
|
|
18
20
|
begin_pos = range.begin_pos
|
19
21
|
end_pos = range.end_pos
|
20
22
|
if side == :left
|
23
|
+
end_pos = begin_pos
|
21
24
|
begin_pos = reposition(src, begin_pos, -1)
|
22
|
-
end_pos -= 1
|
23
25
|
end
|
24
26
|
if side == :right
|
25
|
-
begin_pos
|
27
|
+
begin_pos = end_pos
|
26
28
|
end_pos = reposition(src, end_pos, 1)
|
27
29
|
end
|
28
30
|
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
@@ -85,15 +87,15 @@ module RuboCop
|
|
85
87
|
return false unless token
|
86
88
|
|
87
89
|
if side == :left
|
88
|
-
String(token.space_after?)
|
90
|
+
String(token.space_after?) =~ SINGLE_SPACE_REGEXP
|
89
91
|
else
|
90
|
-
String(token.space_before?)
|
92
|
+
String(token.space_before?) =~ SINGLE_SPACE_REGEXP
|
91
93
|
end
|
92
94
|
end
|
93
95
|
|
94
96
|
def reposition(src, pos, step)
|
95
97
|
offset = step == -1 ? -1 : 0
|
96
|
-
pos += step while src[pos + offset] =~
|
98
|
+
pos += step while src[pos + offset] =~ SINGLE_SPACE_REGEXP
|
97
99
|
pos.negative? ? 0 : pos
|
98
100
|
end
|
99
101
|
|
@@ -33,26 +33,12 @@ module RuboCop
|
|
33
33
|
# y
|
34
34
|
# end
|
35
35
|
class CommentedKeyword < Cop
|
36
|
-
include RangeHelp
|
37
|
-
|
38
36
|
MSG = 'Do not place comments on the same line as the ' \
|
39
37
|
'`%<keyword>s` keyword.'
|
40
38
|
|
41
39
|
def investigate(processed_source)
|
42
|
-
heredoc_lines = extract_heredoc_lines(processed_source.ast)
|
43
|
-
|
44
40
|
processed_source.each_comment do |comment|
|
45
|
-
|
46
|
-
line_position = location.line
|
47
|
-
line = processed_source.lines[line_position - 1]
|
48
|
-
next if heredoc_lines.any? { |r| r.include?(line_position) }
|
49
|
-
next unless offensive?(line)
|
50
|
-
|
51
|
-
range = source_range(processed_source.buffer,
|
52
|
-
line_position,
|
53
|
-
(location.column)...(location.last_column))
|
54
|
-
|
55
|
-
add_offense(range, location: range)
|
41
|
+
add_offense(comment) if offensive?(comment)
|
56
42
|
end
|
57
43
|
end
|
58
44
|
|
@@ -61,25 +47,19 @@ module RuboCop
|
|
61
47
|
KEYWORDS = %w[begin class def end module].freeze
|
62
48
|
ALLOWED_COMMENTS = %w[:nodoc: :yields: rubocop:disable].freeze
|
63
49
|
|
64
|
-
def offensive?(
|
65
|
-
line = line
|
66
|
-
KEYWORDS.any? { |word| line =~
|
50
|
+
def offensive?(comment)
|
51
|
+
line = line(comment)
|
52
|
+
KEYWORDS.any? { |word| line =~ /^\s*#{word}\s/ } &&
|
67
53
|
ALLOWED_COMMENTS.none? { |c| line =~ /#\s*#{c}/ }
|
68
54
|
end
|
69
55
|
|
70
|
-
def message(
|
71
|
-
|
72
|
-
keyword = /^\s*(\S+).*#/.match(line)[1]
|
56
|
+
def message(comment)
|
57
|
+
keyword = line(comment).match(/(\S+).*#/)[1]
|
73
58
|
format(MSG, keyword: keyword)
|
74
59
|
end
|
75
60
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
ast.each_node(:str, :dstr, :xstr).select(&:heredoc?).map do |node|
|
80
|
-
body = node.location.heredoc_body
|
81
|
-
(body.first_line...body.last_line)
|
82
|
-
end
|
61
|
+
def line(comment)
|
62
|
+
comment.location.expression.source_line
|
83
63
|
end
|
84
64
|
end
|
85
65
|
end
|
@@ -68,7 +68,7 @@ module RuboCop
|
|
68
68
|
private
|
69
69
|
|
70
70
|
def expand_elsif(node, elsif_branches = [])
|
71
|
-
return [] if node.nil? || !node.if_type?
|
71
|
+
return [] if node.nil? || !node.if_type? || !node.elsif?
|
72
72
|
|
73
73
|
elsif_branches << node.if_branch
|
74
74
|
|
@@ -219,8 +219,6 @@ module RuboCop
|
|
219
219
|
SINGLE_LINE_CONDITIONS_ONLY = 'SingleLineConditionsOnly'
|
220
220
|
WIDTH = 'Width'
|
221
221
|
|
222
|
-
def_node_matcher :condition?, '{if case}'
|
223
|
-
|
224
222
|
# The shovel operator `<<` does not have its own type. It is a `send`
|
225
223
|
# type.
|
226
224
|
def_node_matcher :assignment_type?, <<~PATTERN
|
@@ -46,7 +46,14 @@ module RuboCop
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def class_or_module_scope?(node)
|
49
|
-
|
49
|
+
return false unless node.parent
|
50
|
+
|
51
|
+
case node.parent.type
|
52
|
+
when :begin
|
53
|
+
class_or_module_scope?(node.parent)
|
54
|
+
when :class, :module
|
55
|
+
true
|
56
|
+
end
|
50
57
|
end
|
51
58
|
|
52
59
|
def visibility_declaration?(node)
|
@@ -58,8 +65,12 @@ module RuboCop
|
|
58
65
|
end
|
59
66
|
|
60
67
|
def_node_matcher :visibility_declaration_for?, <<~PATTERN
|
61
|
-
(send nil? {:public_constant :private_constant} ({sym str} %1))
|
68
|
+
(send nil? {:public_constant :private_constant} ({sym str} #match_name?(%1)))
|
62
69
|
PATTERN
|
70
|
+
|
71
|
+
def match_name?(name, constant_name)
|
72
|
+
name.to_sym == constant_name.to_sym
|
73
|
+
end
|
63
74
|
end
|
64
75
|
end
|
65
76
|
end
|
@@ -37,9 +37,10 @@ module RuboCop
|
|
37
37
|
# ok
|
38
38
|
class GuardClause < Cop
|
39
39
|
include MinBodyLength
|
40
|
+
include StatementModifier
|
40
41
|
|
41
|
-
MSG = 'Use a guard clause instead of wrapping the
|
42
|
-
'conditional expression.'
|
42
|
+
MSG = 'Use a guard clause (`%<example>s`) instead of wrapping the ' \
|
43
|
+
'code inside a conditional expression.'
|
43
44
|
|
44
45
|
def on_def(node)
|
45
46
|
body = node.body
|
@@ -55,9 +56,19 @@ module RuboCop
|
|
55
56
|
alias on_defs on_def
|
56
57
|
|
57
58
|
def on_if(node)
|
58
|
-
return if accepted_form?(node)
|
59
|
+
return if accepted_form?(node)
|
59
60
|
|
60
|
-
|
61
|
+
guard_clause_in_if = node.if_branch&.guard_clause?
|
62
|
+
guard_clause_in_else = node.else_branch&.guard_clause?
|
63
|
+
guard_clause = guard_clause_in_if || guard_clause_in_else
|
64
|
+
return unless guard_clause
|
65
|
+
|
66
|
+
kw = if guard_clause_in_if
|
67
|
+
node.loc.keyword.source
|
68
|
+
else
|
69
|
+
opposite_keyword(node)
|
70
|
+
end
|
71
|
+
register_offense(node, guard_clause.source, kw)
|
61
72
|
end
|
62
73
|
|
63
74
|
private
|
@@ -65,7 +76,30 @@ module RuboCop
|
|
65
76
|
def check_ending_if(node)
|
66
77
|
return if accepted_form?(node, true) || !min_body_length?(node)
|
67
78
|
|
68
|
-
|
79
|
+
register_offense(node, 'return', opposite_keyword(node))
|
80
|
+
end
|
81
|
+
|
82
|
+
def opposite_keyword(node)
|
83
|
+
node.if? ? 'unless' : 'if'
|
84
|
+
end
|
85
|
+
|
86
|
+
def register_offense(node, scope_exiting_keyword, conditional_keyword)
|
87
|
+
condition, = node.node_parts
|
88
|
+
example = [scope_exiting_keyword,
|
89
|
+
conditional_keyword,
|
90
|
+
condition.source].join(' ')
|
91
|
+
if too_long_for_single_line?(node, example)
|
92
|
+
example = "#{conditional_keyword} #{condition.source}; " \
|
93
|
+
"#{scope_exiting_keyword}; end"
|
94
|
+
end
|
95
|
+
add_offense(node,
|
96
|
+
location: :keyword,
|
97
|
+
message: format(MSG, example: example))
|
98
|
+
end
|
99
|
+
|
100
|
+
def too_long_for_single_line?(node, example)
|
101
|
+
max = max_line_length
|
102
|
+
max && node.source_range.column + example.length > max
|
69
103
|
end
|
70
104
|
|
71
105
|
def accepted_form?(node, ending = false)
|
@@ -81,11 +115,6 @@ module RuboCop
|
|
81
115
|
!node.else? || node.elsif?
|
82
116
|
end
|
83
117
|
end
|
84
|
-
|
85
|
-
def contains_guard_clause?(node)
|
86
|
-
node.if_branch&.guard_clause? ||
|
87
|
-
node.else_branch&.guard_clause?
|
88
|
-
end
|
89
118
|
end
|
90
119
|
end
|
91
120
|
end
|
@@ -42,7 +42,7 @@ module RuboCop
|
|
42
42
|
def on_def(node)
|
43
43
|
return unless trailing_end?(node)
|
44
44
|
|
45
|
-
add_offense(node
|
45
|
+
add_offense(node, location: end_token(node).pos)
|
46
46
|
end
|
47
47
|
|
48
48
|
def autocorrect(node)
|
@@ -61,7 +61,7 @@ module RuboCop
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def end_token(node)
|
64
|
-
|
64
|
+
tokens(node).reverse.find(&:end?)
|
65
65
|
end
|
66
66
|
|
67
67
|
def body_and_end_on_same_line?(node)
|
@@ -69,10 +69,8 @@ module RuboCop
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def token_before_end(node)
|
72
|
-
|
73
|
-
|
74
|
-
tokens(node)[i - 1]
|
75
|
-
end
|
72
|
+
i = tokens(node).index(end_token(node))
|
73
|
+
tokens(node)[i - 1]
|
76
74
|
end
|
77
75
|
|
78
76
|
def break_line_before_end(node, corrector)
|
@@ -16,19 +16,15 @@ module RuboCop
|
|
16
16
|
# /check #{$pattern}/
|
17
17
|
# "Let's go to the #{@store}"
|
18
18
|
class VariableInterpolation < Cop
|
19
|
+
include Interpolation
|
20
|
+
|
19
21
|
MSG = 'Replace interpolated variable `%<variable>s` ' \
|
20
22
|
'with expression `#{%<variable>s}`.'
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def on_regexp(node)
|
27
|
-
check_for_interpolation(node)
|
28
|
-
end
|
29
|
-
|
30
|
-
def on_xstr(node)
|
31
|
-
check_for_interpolation(node)
|
24
|
+
def on_node_with_interpolations(node)
|
25
|
+
var_nodes(node.children).each do |var_node|
|
26
|
+
add_offense(var_node)
|
27
|
+
end
|
32
28
|
end
|
33
29
|
|
34
30
|
def autocorrect(node)
|
@@ -39,12 +35,6 @@ module RuboCop
|
|
39
35
|
|
40
36
|
private
|
41
37
|
|
42
|
-
def check_for_interpolation(node)
|
43
|
-
var_nodes(node.children).each do |var_node|
|
44
|
-
add_offense(var_node)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
38
|
def message(node)
|
49
39
|
format(MSG, variable: node.source)
|
50
40
|
end
|