rubocop 1.25.1 → 1.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +29 -6
- data/lib/rubocop/cli.rb +1 -1
- data/lib/rubocop/config_obsoletion/extracted_cop.rb +3 -1
- data/lib/rubocop/cop/autocorrect_logic.rb +4 -0
- data/lib/rubocop/cop/badge.rb +7 -1
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -5
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +1 -5
- data/lib/rubocop/cop/gemspec/require_mfa.rb +4 -3
- data/lib/rubocop/cop/generator.rb +2 -7
- data/lib/rubocop/cop/internal_affairs/redundant_context_config_parameter.rb +46 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/case_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +1 -2
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +7 -8
- data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -4
- data/lib/rubocop/cop/lint/ambiguous_operator.rb +6 -6
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +3 -1
- data/lib/rubocop/cop/lint/empty_in_pattern.rb +3 -1
- data/lib/rubocop/cop/lint/empty_when.rb +3 -1
- data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +11 -4
- data/lib/rubocop/cop/lint/inherit_exception.rb +19 -28
- data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +8 -1
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +3 -2
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +5 -0
- data/lib/rubocop/cop/lint/refinement_import_methods.rb +51 -0
- data/lib/rubocop/cop/lint/shadowing_outer_local_variable.rb +10 -0
- data/lib/rubocop/cop/lint/symbol_conversion.rb +3 -2
- data/lib/rubocop/cop/lint/syntax.rb +1 -2
- data/lib/rubocop/cop/lint/unused_method_argument.rb +1 -1
- data/lib/rubocop/cop/lint/useless_times.rb +13 -9
- data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -2
- data/lib/rubocop/cop/mixin/comments_help.rb +22 -2
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +2 -3
- data/lib/rubocop/cop/mixin/line_length_help.rb +17 -6
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +4 -3
- data/lib/rubocop/cop/mixin/surrounding_space.rb +4 -2
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/security/yaml_load.rb +9 -3
- data/lib/rubocop/cop/style/def_with_parentheses.rb +16 -11
- data/lib/rubocop/cop/style/double_negation.rb +32 -1
- data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
- data/lib/rubocop/cop/style/file_write.rb +12 -0
- data/lib/rubocop/cop/style/for.rb +4 -0
- data/lib/rubocop/cop/style/lambda_call.rb +12 -20
- data/lib/rubocop/cop/style/nested_file_dirname.rb +66 -0
- data/lib/rubocop/cop/style/optional_boolean_parameter.rb +3 -2
- data/lib/rubocop/cop/style/raise_args.rb +5 -2
- data/lib/rubocop/cop/style/redundant_capital_w.rb +1 -2
- data/lib/rubocop/cop/style/redundant_initialize.rb +119 -0
- data/lib/rubocop/cop/style/safe_navigation.rb +12 -7
- data/lib/rubocop/cop/style/select_by_regexp.rb +6 -1
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +50 -12
- data/lib/rubocop/cop/style/string_concatenation.rb +7 -1
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -2
- data/lib/rubocop/cop/style/trailing_comma_in_array_literal.rb +1 -1
- data/lib/rubocop/cop/style/trailing_comma_in_hash_literal.rb +1 -1
- data/lib/rubocop/cop/style/trailing_method_end_statement.rb +1 -4
- data/lib/rubocop/cop/style/unless_else.rb +4 -0
- data/lib/rubocop/cop/variable_force.rb +1 -5
- data/lib/rubocop/cops_documentation_generator.rb +2 -2
- data/lib/rubocop/formatter/disabled_config_formatter.rb +2 -2
- data/lib/rubocop/formatter/offense_count_formatter.rb +6 -2
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
- data/lib/rubocop/options.rb +8 -2
- data/lib/rubocop/result_cache.rb +9 -1
- data/lib/rubocop/rspec/shared_contexts.rb +4 -0
- data/lib/rubocop/runner.rb +1 -1
- data/lib/rubocop/target_ruby.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +3 -0
- metadata +9 -5
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# This cop checks if `include` or `prepend` is called in `refine` block.
|
7
|
+
# These methods are deprecated and should be replaced with `Refinement#import_methods`.
|
8
|
+
#
|
9
|
+
# It emulates deprecation warnings in Ruby 3.1.
|
10
|
+
#
|
11
|
+
# @safety
|
12
|
+
# This cop's autocorrection is unsafe because `include M` will affect the included class
|
13
|
+
# if any changes are made to module `M`.
|
14
|
+
# On the other hand, `import_methods M` uses a snapshot of method definitions,
|
15
|
+
# thus it will not be affected if module `M` changes.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# refine Foo do
|
21
|
+
# include Bar
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # bad
|
25
|
+
# refine Foo do
|
26
|
+
# prepend Bar
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# refine Foo do
|
31
|
+
# import_methods Bar
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class RefinementImportMethods < Base
|
35
|
+
extend TargetRubyVersion
|
36
|
+
|
37
|
+
MSG = 'Use `import_methods` instead of `%<current>s` because it is deprecated in Ruby 3.1.'
|
38
|
+
RESTRICT_ON_SEND = %i[include prepend].freeze
|
39
|
+
|
40
|
+
minimum_target_ruby_version 3.1
|
41
|
+
|
42
|
+
def on_send(node)
|
43
|
+
return if node.receiver
|
44
|
+
return unless node.parent.block_type? && node.parent.method?(:refine)
|
45
|
+
|
46
|
+
add_offense(node.loc.selector, message: format(MSG, current: node.method_name))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -57,10 +57,20 @@ module RuboCop
|
|
57
57
|
|
58
58
|
outer_local_variable = variable_table.find_variable(variable.name)
|
59
59
|
return unless outer_local_variable
|
60
|
+
return if same_conditions_node_different_branch?(variable, outer_local_variable)
|
60
61
|
|
61
62
|
message = format(MSG, variable: variable.name)
|
62
63
|
add_offense(variable.declaration_node, message: message)
|
63
64
|
end
|
65
|
+
|
66
|
+
def same_conditions_node_different_branch?(variable, outer_local_variable)
|
67
|
+
variable_node = variable.scope.node.parent
|
68
|
+
return false unless variable_node.conditional?
|
69
|
+
|
70
|
+
outer_local_variable_node = outer_local_variable.scope.node
|
71
|
+
|
72
|
+
outer_local_variable_node.conditional? && variable_node == outer_local_variable_node
|
73
|
+
end
|
64
74
|
end
|
65
75
|
end
|
66
76
|
end
|
@@ -147,13 +147,14 @@ module RuboCop
|
|
147
147
|
# will be ignored.
|
148
148
|
return unless node.value.to_s.match?(/\A[a-z0-9_]/i)
|
149
149
|
|
150
|
-
correction = node.value.inspect
|
150
|
+
correction = node.value.inspect
|
151
|
+
correction = correction.delete_prefix(':') if node.parent.colon?
|
151
152
|
return if properly_quoted?(node.source, correction)
|
152
153
|
|
153
154
|
register_offense(
|
154
155
|
node,
|
155
156
|
correction: correction,
|
156
|
-
message: format(MSG, correction: "#{correction}:")
|
157
|
+
message: format(MSG, correction: node.parent.colon? ? "#{correction}:" : correction)
|
157
158
|
)
|
158
159
|
end
|
159
160
|
|
@@ -9,8 +9,7 @@ module RuboCop
|
|
9
9
|
def on_other_file
|
10
10
|
add_offense_from_error(processed_source.parser_error) if processed_source.parser_error
|
11
11
|
processed_source.diagnostics.each do |diagnostic|
|
12
|
-
add_offense_from_diagnostic(diagnostic,
|
13
|
-
processed_source.ruby_version)
|
12
|
+
add_offense_from_diagnostic(diagnostic, processed_source.ruby_version)
|
14
13
|
end
|
15
14
|
super
|
16
15
|
end
|
@@ -64,7 +64,7 @@ module RuboCop
|
|
64
64
|
|
65
65
|
# @!method not_implemented?(node)
|
66
66
|
def_node_matcher :not_implemented?, <<~PATTERN
|
67
|
-
{(send nil? :raise (const {nil? cbase} :NotImplementedError))
|
67
|
+
{(send nil? :raise (const {nil? cbase} :NotImplementedError) ...)
|
68
68
|
(send nil? :fail ...)}
|
69
69
|
PATTERN
|
70
70
|
|
@@ -51,20 +51,24 @@ module RuboCop
|
|
51
51
|
node = node.block_node if node.block_literal?
|
52
52
|
|
53
53
|
add_offense(node, message: format(MSG, count: count)) do |corrector|
|
54
|
-
next
|
55
|
-
|
56
|
-
|
57
|
-
remove_node(corrector, node)
|
58
|
-
elsif !proc_name.empty?
|
59
|
-
autocorrect_block_pass(corrector, node, proc_name)
|
60
|
-
else
|
61
|
-
autocorrect_block(corrector, node)
|
62
|
-
end
|
54
|
+
next if !own_line?(node) || node.parent&.send_type?
|
55
|
+
|
56
|
+
autocorrect(corrector, count, node, proc_name)
|
63
57
|
end
|
64
58
|
end
|
65
59
|
|
66
60
|
private
|
67
61
|
|
62
|
+
def autocorrect(corrector, count, node, proc_name)
|
63
|
+
if never_process?(count, node)
|
64
|
+
remove_node(corrector, node)
|
65
|
+
elsif !proc_name.empty?
|
66
|
+
autocorrect_block_pass(corrector, node, proc_name)
|
67
|
+
else
|
68
|
+
autocorrect_block(corrector, node)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
68
72
|
def never_process?(count, node)
|
69
73
|
count < 1 || (node.block_type? && node.body.nil?)
|
70
74
|
end
|
@@ -101,8 +101,7 @@ module RuboCop
|
|
101
101
|
children = node.masgn_type? ? node.children[0].children : node.children
|
102
102
|
|
103
103
|
will_be_miscounted = children.count do |child|
|
104
|
-
child.respond_to?(:setter_method?) &&
|
105
|
-
!child.setter_method?
|
104
|
+
child.respond_to?(:setter_method?) && !child.setter_method?
|
106
105
|
end
|
107
106
|
@assignment += will_be_miscounted
|
108
107
|
|
@@ -4,8 +4,6 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
# Help methods for working with nodes containing comments.
|
6
6
|
module CommentsHelp
|
7
|
-
include VisibilityHelp
|
8
|
-
|
9
7
|
def source_range_with_comment(node)
|
10
8
|
begin_pos = begin_pos_with_comment(node)
|
11
9
|
end_pos = end_position_for(node)
|
@@ -13,6 +11,13 @@ module RuboCop
|
|
13
11
|
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
14
12
|
end
|
15
13
|
|
14
|
+
def contains_comments?(node)
|
15
|
+
start_line = node.source_range.line
|
16
|
+
end_line = find_end_line(node)
|
17
|
+
|
18
|
+
processed_source.each_comment_in_lines(start_line...end_line).any?
|
19
|
+
end
|
20
|
+
|
16
21
|
private
|
17
22
|
|
18
23
|
def end_position_for(node)
|
@@ -37,6 +42,21 @@ module RuboCop
|
|
37
42
|
def buffer
|
38
43
|
processed_source.buffer
|
39
44
|
end
|
45
|
+
|
46
|
+
# Returns the end line of a node, which might be a comment and not part of the AST
|
47
|
+
# End line is considered either the line at which another node starts, or
|
48
|
+
# the line at which the parent node ends.
|
49
|
+
def find_end_line(node)
|
50
|
+
if node.if_type? && node.loc.else
|
51
|
+
node.loc.else.line
|
52
|
+
elsif (next_sibling = node.right_sibling)
|
53
|
+
next_sibling.loc.line
|
54
|
+
elsif (parent = node.parent)
|
55
|
+
parent.loc.end.line
|
56
|
+
else
|
57
|
+
node.loc.end.line
|
58
|
+
end
|
59
|
+
end
|
40
60
|
end
|
41
61
|
end
|
42
62
|
end
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def require_hash_value?(hash_key_source, node)
|
44
|
-
return true if require_hash_value_for_around_hash_literal?(node)
|
44
|
+
return true if !node.key.sym_type? || require_hash_value_for_around_hash_literal?(node)
|
45
45
|
|
46
46
|
hash_value = node.value
|
47
47
|
return true unless hash_value.send_type? || hash_value.lvar_type?
|
@@ -65,9 +65,8 @@ module RuboCop
|
|
65
65
|
|
66
66
|
def use_modifier_form_without_parenthesized_method_call?(ancestor)
|
67
67
|
return false if ancestor.respond_to?(:parenthesized?) && ancestor.parenthesized?
|
68
|
-
return false unless (parent = ancestor.parent)
|
69
68
|
|
70
|
-
|
69
|
+
ancestor.ancestors.any? { |node| node.respond_to?(:modifier_form?) && node.modifier_form? }
|
71
70
|
end
|
72
71
|
|
73
72
|
def without_parentheses_call_expr_follows?(ancestor)
|
@@ -39,12 +39,7 @@ module RuboCop
|
|
39
39
|
pos + indentation_difference(line)
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
|
-
# This allows for URIs that are wrapped in quotes or parens to be handled properly
|
44
|
-
# while not allowing additional words to be added after the URL.
|
45
|
-
if (match = line[end_position..line_length(line)]&.match(/^\S+(?=\s|$)/))
|
46
|
-
end_position += match.offset(0).last
|
47
|
-
end
|
42
|
+
end_position = extend_uri_end_position(line, end_position)
|
48
43
|
|
49
44
|
return nil if begin_position < max_line_length && end_position < max_line_length
|
50
45
|
|
@@ -65,6 +60,22 @@ module RuboCop
|
|
65
60
|
(line.index(/[^\t]/) || 0) * (tab_indentation_width - 1)
|
66
61
|
end
|
67
62
|
|
63
|
+
def extend_uri_end_position(line, end_position)
|
64
|
+
# Extend the end position YARD comments with linked URLs of the form {<uri> <title>}
|
65
|
+
if line&.match(/{(\s|\S)*}$/)
|
66
|
+
match = line[end_position..line_length(line)]&.match(/(\s|\S)*}/)
|
67
|
+
end_position += match.offset(0).last
|
68
|
+
end
|
69
|
+
|
70
|
+
# Extend the end position until the start of the next word, if any.
|
71
|
+
# This allows for URIs that are wrapped in quotes or parens to be handled properly
|
72
|
+
# while not allowing additional words to be added after the URL.
|
73
|
+
if (match = line[end_position..line_length(line)]&.match(/^\S+(?=\s|$)/))
|
74
|
+
end_position += match.offset(0).last
|
75
|
+
end
|
76
|
+
end_position
|
77
|
+
end
|
78
|
+
|
68
79
|
def tab_indentation_width
|
69
80
|
config.for_cop('Layout/IndentationStyle')['IndentationWidth'] ||
|
70
81
|
config.for_cop('Layout/IndentationWidth')['Width']
|
@@ -29,7 +29,9 @@ module RuboCop
|
|
29
29
|
# b c { block }. <-- b is indented relative to a
|
30
30
|
# d <-- d is indented relative to a
|
31
31
|
def left_hand_side(lhs)
|
32
|
-
|
32
|
+
while lhs.parent&.send_type? && lhs.parent.loc.dot && !lhs.parent.assignment_method?
|
33
|
+
lhs = lhs.parent
|
34
|
+
end
|
33
35
|
lhs
|
34
36
|
end
|
35
37
|
|
@@ -194,8 +196,7 @@ module RuboCop
|
|
194
196
|
|
195
197
|
def not_for_this_cop?(node)
|
196
198
|
node.ancestors.any? do |ancestor|
|
197
|
-
grouped_expression?(ancestor) ||
|
198
|
-
inside_arg_list_parentheses?(node, ancestor)
|
199
|
+
grouped_expression?(ancestor) || inside_arg_list_parentheses?(node, ancestor)
|
199
200
|
end
|
200
201
|
end
|
201
202
|
|
@@ -44,7 +44,8 @@ module RuboCop
|
|
44
44
|
if extra_space?(left_token, :left) && !start_ok
|
45
45
|
space_offense(node, left_token, :right, message, NO_SPACE_COMMAND)
|
46
46
|
end
|
47
|
-
return if !extra_space?(right_token, :right) || end_ok
|
47
|
+
return if (!extra_space?(right_token, :right) || end_ok) ||
|
48
|
+
(autocorrect_with_disable_uncorrectable? && !start_ok)
|
48
49
|
|
49
50
|
space_offense(node, right_token, :left, message, NO_SPACE_COMMAND)
|
50
51
|
end
|
@@ -58,7 +59,8 @@ module RuboCop
|
|
58
59
|
unless extra_space?(left_token, :left) || start_ok
|
59
60
|
space_offense(node, left_token, :none, message, SPACE_COMMAND)
|
60
61
|
end
|
61
|
-
return if extra_space?(right_token, :right) || end_ok
|
62
|
+
return if (extra_space?(right_token, :right) || end_ok) ||
|
63
|
+
(autocorrect_with_disable_uncorrectable? && !start_ok)
|
62
64
|
|
63
65
|
space_offense(node, right_token, :none, message, SPACE_COMMAND)
|
64
66
|
end
|
@@ -107,7 +107,7 @@ module RuboCop
|
|
107
107
|
def use_block_argument_as_local_variable?(node, last_argument)
|
108
108
|
return if node.body.nil?
|
109
109
|
|
110
|
-
node.body.each_descendant(:lvar).any? do |lvar|
|
110
|
+
node.body.each_descendant(:lvar, :lvasgn).any? do |lvar|
|
111
111
|
!lvar.parent.block_pass_type? && lvar.source == last_argument
|
112
112
|
end
|
113
113
|
end
|
@@ -7,17 +7,21 @@ module RuboCop
|
|
7
7
|
# potential security issues leading to remote code execution when
|
8
8
|
# loading from an untrusted source.
|
9
9
|
#
|
10
|
+
# NOTE: Ruby 3.1+ (Psych 4) uses `Psych.load` as `Psych.safe_load` by default.
|
11
|
+
#
|
10
12
|
# @safety
|
11
13
|
# The behaviour of the code might change depending on what was
|
12
14
|
# in the YAML payload, since `YAML.safe_load` is more restrictive.
|
13
15
|
#
|
14
16
|
# @example
|
15
17
|
# # bad
|
16
|
-
# YAML.load("---
|
18
|
+
# YAML.load("--- !ruby/object:Foo {}") # Psych 3 is unsafe by default
|
17
19
|
#
|
18
20
|
# # good
|
19
|
-
# YAML.safe_load("---
|
20
|
-
# YAML.
|
21
|
+
# YAML.safe_load("--- !ruby/object:Foo {}", [Foo]) # Ruby 2.5 (Psych 3)
|
22
|
+
# YAML.safe_load("--- !ruby/object:Foo {}", permitted_classes: [Foo]) # Ruby 3.0- (Psych 3)
|
23
|
+
# YAML.load("--- !ruby/object:Foo {}", permitted_classes: [Foo]) # Ruby 3.1+ (Psych 4)
|
24
|
+
# YAML.dump(foo)
|
21
25
|
#
|
22
26
|
class YAMLLoad < Base
|
23
27
|
extend AutoCorrector
|
@@ -31,6 +35,8 @@ module RuboCop
|
|
31
35
|
PATTERN
|
32
36
|
|
33
37
|
def on_send(node)
|
38
|
+
return if target_ruby_version >= 3.1
|
39
|
+
|
34
40
|
yaml_load(node) do
|
35
41
|
add_offense(node.loc.selector) do |corrector|
|
36
42
|
corrector.replace(node.loc.selector, 'safe_load')
|
@@ -11,27 +11,33 @@ module RuboCop
|
|
11
11
|
#
|
12
12
|
# # bad
|
13
13
|
# def foo()
|
14
|
-
#
|
14
|
+
# do_something
|
15
15
|
# end
|
16
16
|
#
|
17
17
|
# # good
|
18
18
|
# def foo
|
19
|
-
#
|
19
|
+
# do_something
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
# #
|
23
|
-
# def foo()
|
22
|
+
# # bad
|
23
|
+
# def foo() = do_something
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# def foo = do_something
|
27
|
+
#
|
28
|
+
# # good (without parentheses it's a syntax error)
|
29
|
+
# def foo() do_something end
|
24
30
|
#
|
25
31
|
# @example
|
26
32
|
#
|
27
33
|
# # bad
|
28
34
|
# def Baz.foo()
|
29
|
-
#
|
35
|
+
# do_something
|
30
36
|
# end
|
31
37
|
#
|
32
38
|
# # good
|
33
39
|
# def Baz.foo
|
34
|
-
#
|
40
|
+
# do_something
|
35
41
|
# end
|
36
42
|
class DefWithParentheses < Base
|
37
43
|
extend AutoCorrector
|
@@ -39,12 +45,11 @@ module RuboCop
|
|
39
45
|
MSG = "Omit the parentheses in defs when the method doesn't accept any arguments."
|
40
46
|
|
41
47
|
def on_def(node)
|
42
|
-
return if node.single_line?
|
43
|
-
return unless !node.arguments? && (
|
48
|
+
return if node.single_line? && !node.endless?
|
49
|
+
return unless !node.arguments? && (node_arguments = node.arguments.source_range)
|
44
50
|
|
45
|
-
add_offense(
|
46
|
-
corrector.remove(
|
47
|
-
corrector.remove(node.arguments.loc.end)
|
51
|
+
add_offense(node_arguments) do |corrector|
|
52
|
+
corrector.remove(node_arguments)
|
48
53
|
end
|
49
54
|
end
|
50
55
|
alias on_defs on_def
|
@@ -72,9 +72,14 @@ module RuboCop
|
|
72
72
|
def end_of_method_definition?(node)
|
73
73
|
return false unless (def_node = find_def_node_from_ascendant(node))
|
74
74
|
|
75
|
+
conditional_node = find_conditional_node_from_ascendant(node)
|
75
76
|
last_child = find_last_child(def_node.body)
|
76
77
|
|
77
|
-
|
78
|
+
if conditional_node
|
79
|
+
double_negative_condition_return_value?(node, last_child, conditional_node)
|
80
|
+
else
|
81
|
+
last_child.last_line == node.last_line
|
82
|
+
end
|
78
83
|
end
|
79
84
|
|
80
85
|
def find_def_node_from_ascendant(node)
|
@@ -84,6 +89,13 @@ module RuboCop
|
|
84
89
|
find_def_node_from_ascendant(node.parent)
|
85
90
|
end
|
86
91
|
|
92
|
+
def find_conditional_node_from_ascendant(node)
|
93
|
+
return unless (parent = node.parent)
|
94
|
+
return parent if parent.conditional?
|
95
|
+
|
96
|
+
find_conditional_node_from_ascendant(parent)
|
97
|
+
end
|
98
|
+
|
87
99
|
def find_last_child(node)
|
88
100
|
case node.type
|
89
101
|
when :rescue
|
@@ -94,6 +106,25 @@ module RuboCop
|
|
94
106
|
node.child_nodes.last
|
95
107
|
end
|
96
108
|
end
|
109
|
+
|
110
|
+
def double_negative_condition_return_value?(node, last_child, conditional_node)
|
111
|
+
parent = find_parent_not_enumerable(node)
|
112
|
+
if parent.begin_type?
|
113
|
+
node.loc.line == parent.loc.last_line
|
114
|
+
else
|
115
|
+
last_child.last_line <= conditional_node.last_line
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_parent_not_enumerable(node)
|
120
|
+
return unless (parent = node.parent)
|
121
|
+
|
122
|
+
if parent.pair_type? || parent.hash_type? || parent.array_type?
|
123
|
+
find_parent_not_enumerable(parent)
|
124
|
+
else
|
125
|
+
parent
|
126
|
+
end
|
127
|
+
end
|
97
128
|
end
|
98
129
|
end
|
99
130
|
end
|
@@ -47,8 +47,7 @@ module RuboCop
|
|
47
47
|
branch_bodies = [*case_node.when_branches.map(&:body), case_node.else_branch].compact
|
48
48
|
|
49
49
|
return if branch_bodies.any? do |body|
|
50
|
-
body.return_type? ||
|
51
|
-
body.each_descendant.any?(&:return_type?)
|
50
|
+
body.return_type? || body.each_descendant.any?(&:return_type?)
|
52
51
|
end
|
53
52
|
|
54
53
|
add_offense(case_node.loc.keyword) { |corrector| autocorrect(corrector, case_node) }
|
@@ -5,6 +5,17 @@ module RuboCop
|
|
5
5
|
module Style
|
6
6
|
# Favor `File.(bin)write` convenience methods.
|
7
7
|
#
|
8
|
+
# NOTE: There are different method signatures between `File.write` (class method)
|
9
|
+
# and `File#write` (instance method). The following case will be allowed because
|
10
|
+
# static analysis does not know the contents of the splat argument:
|
11
|
+
#
|
12
|
+
# [source,ruby]
|
13
|
+
# ----
|
14
|
+
# File.open(filename, 'w') do |f|
|
15
|
+
# f.write(*objects)
|
16
|
+
# end
|
17
|
+
# ----
|
18
|
+
#
|
8
19
|
# @example
|
9
20
|
# ## text mode
|
10
21
|
# # bad
|
@@ -85,6 +96,7 @@ module RuboCop
|
|
85
96
|
content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
|
86
97
|
write_arg if block_arg == lvar
|
87
98
|
end
|
99
|
+
return false if content&.splat_type?
|
88
100
|
|
89
101
|
yield(content) if content
|
90
102
|
end
|
@@ -22,45 +22,37 @@ module RuboCop
|
|
22
22
|
include ConfigurableEnforcedStyle
|
23
23
|
extend AutoCorrector
|
24
24
|
|
25
|
+
MSG = 'Prefer the use of `%<prefer>s` over `%<current>s`.'
|
25
26
|
RESTRICT_ON_SEND = %i[call].freeze
|
26
27
|
|
27
28
|
def on_send(node)
|
28
29
|
return unless node.receiver
|
29
30
|
|
30
31
|
if offense?(node)
|
31
|
-
|
32
|
+
prefer = prefer(node)
|
33
|
+
current = node.source
|
34
|
+
|
35
|
+
add_offense(node, message: format(MSG, prefer: prefer, current: current)) do |corrector|
|
32
36
|
opposite_style_detected
|
33
|
-
|
37
|
+
corrector.replace(node, prefer)
|
34
38
|
end
|
35
39
|
else
|
36
40
|
correct_style_detected
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
|
-
def autocorrect(corrector, node)
|
41
|
-
if explicit_style?
|
42
|
-
receiver = node.receiver.source
|
43
|
-
replacement = node.source.sub("#{receiver}.", "#{receiver}.call")
|
44
|
-
|
45
|
-
corrector.replace(node, replacement)
|
46
|
-
else
|
47
|
-
add_parentheses(node, corrector) unless node.parenthesized?
|
48
|
-
corrector.remove(node.loc.selector)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
44
|
private
|
53
45
|
|
54
46
|
def offense?(node)
|
55
47
|
(explicit_style? && node.implicit_call?) || (implicit_style? && !node.implicit_call?)
|
56
48
|
end
|
57
49
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
50
|
+
def prefer(node)
|
51
|
+
receiver = node.receiver.source
|
52
|
+
arguments = node.arguments.map(&:source).join(', ')
|
53
|
+
method = explicit_style? ? "call(#{arguments})" : "(#{arguments})"
|
54
|
+
|
55
|
+
"#{receiver}.#{method}"
|
64
56
|
end
|
65
57
|
|
66
58
|
def implicit_style?
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop checks for nested `File.dirname`.
|
7
|
+
# It replaces nested `File.dirname` with the level argument introduced in Ruby 3.1.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# File.dirname(File.dirname(path))
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# File.dirname(path, 2)
|
16
|
+
#
|
17
|
+
class NestedFileDirname < Base
|
18
|
+
include RangeHelp
|
19
|
+
extend AutoCorrector
|
20
|
+
extend TargetRubyVersion
|
21
|
+
|
22
|
+
MSG = 'Use `dirname(%<path>s, %<level>s)` instead.'
|
23
|
+
RESTRICT_ON_SEND = %i[dirname].freeze
|
24
|
+
|
25
|
+
minimum_target_ruby_version 3.1
|
26
|
+
|
27
|
+
# @!method file_dirname?(node)
|
28
|
+
def_node_matcher :file_dirname?, <<~PATTERN
|
29
|
+
(send
|
30
|
+
(const {cbase nil?} :File) :dirname ...)
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def on_send(node)
|
34
|
+
return if file_dirname?(node.parent) || !file_dirname?(node.first_argument)
|
35
|
+
|
36
|
+
path, level = path_with_dir_level(node, 1)
|
37
|
+
return if level < 2
|
38
|
+
|
39
|
+
message = format(MSG, path: path, level: level)
|
40
|
+
range = offense_range(node)
|
41
|
+
|
42
|
+
add_offense(range, message: message) do |corrector|
|
43
|
+
corrector.replace(range, "dirname(#{path}, #{level})")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def path_with_dir_level(node, level)
|
50
|
+
first_argument = node.first_argument
|
51
|
+
|
52
|
+
if file_dirname?(first_argument)
|
53
|
+
level += 1
|
54
|
+
path_with_dir_level(first_argument, level)
|
55
|
+
else
|
56
|
+
[first_argument.source, level]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def offense_range(node)
|
61
|
+
range_between(node.loc.selector.begin_pos, node.source_range.end_pos)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -54,8 +54,9 @@ module RuboCop
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def format_message(argument)
|
57
|
-
|
58
|
-
|
57
|
+
replacement = "#{argument.name}: #{argument.default_value.source}"
|
58
|
+
|
59
|
+
format(MSG, original: argument.source, replacement: replacement)
|
59
60
|
end
|
60
61
|
end
|
61
62
|
end
|
@@ -107,8 +107,7 @@ module RuboCop
|
|
107
107
|
|
108
108
|
first_arg = node.first_argument
|
109
109
|
|
110
|
-
return
|
111
|
-
return if acceptable_exploded_args?(first_arg.arguments)
|
110
|
+
return if !use_new_method?(first_arg) || acceptable_exploded_args?(first_arg.arguments)
|
112
111
|
|
113
112
|
return if allowed_non_exploded_type?(first_arg)
|
114
113
|
|
@@ -120,6 +119,10 @@ module RuboCop
|
|
120
119
|
end
|
121
120
|
end
|
122
121
|
|
122
|
+
def use_new_method?(first_arg)
|
123
|
+
first_arg.send_type? && first_arg.receiver && first_arg.method?(:new)
|
124
|
+
end
|
125
|
+
|
123
126
|
def acceptable_exploded_args?(args)
|
124
127
|
# Allow code like `raise Ex.new(arg1, arg2)`.
|
125
128
|
return true if args.size > 1
|