rubocop 1.28.2 → 1.29.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 +3 -3
- data/config/default.yml +21 -6
- data/lib/rubocop/cop/badge.rb +1 -1
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
- data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
- data/lib/rubocop/cop/gemspec/dependency_version.rb +156 -0
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -6
- data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/method_name_end_with.rb +80 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/comment_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +1 -1
- data/lib/rubocop/cop/layout/space_inside_reference_brackets.rb +1 -1
- data/lib/rubocop/cop/layout/trailing_empty_lines.rb +1 -1
- data/lib/rubocop/cop/lint/ambiguous_range.rb +2 -2
- data/lib/rubocop/cop/lint/erb_new_arguments.rb +0 -3
- data/lib/rubocop/cop/lint/loop.rb +1 -1
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +1 -1
- data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -1
- data/lib/rubocop/cop/lint/parentheses_as_grouped_expression.rb +1 -1
- data/lib/rubocop/cop/lint/raise_exception.rb +1 -1
- data/lib/rubocop/cop/lint/return_in_void_context.rb +5 -17
- data/lib/rubocop/cop/lint/useless_times.rb +1 -1
- data/lib/rubocop/cop/mixin/duplication.rb +1 -1
- data/lib/rubocop/cop/mixin/preferred_delimiters.rb +2 -2
- data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
- data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/naming/file_name.rb +1 -1
- data/lib/rubocop/cop/naming/predicate_name.rb +2 -2
- data/lib/rubocop/cop/naming/variable_name.rb +9 -0
- data/lib/rubocop/cop/naming/variable_number.rb +10 -0
- data/lib/rubocop/cop/security/yaml_load.rb +1 -1
- data/lib/rubocop/cop/style/alias.rb +3 -3
- data/lib/rubocop/cop/style/and_or.rb +1 -1
- data/lib/rubocop/cop/style/bisected_attr_accessor/macro.rb +1 -1
- data/lib/rubocop/cop/style/case_like_if.rb +1 -1
- data/lib/rubocop/cop/style/character_literal.rb +1 -1
- data/lib/rubocop/cop/style/collection_compact.rb +3 -3
- data/lib/rubocop/cop/style/date_time.rb +1 -1
- data/lib/rubocop/cop/style/double_negation.rb +28 -2
- data/lib/rubocop/cop/style/empty_case_condition.rb +1 -1
- data/lib/rubocop/cop/style/empty_literal.rb +1 -1
- data/lib/rubocop/cop/style/env_home.rb +56 -0
- data/lib/rubocop/cop/style/fetch_env_var.rb +238 -11
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -2
- data/lib/rubocop/cop/style/map_to_hash.rb +0 -3
- data/lib/rubocop/cop/style/mixin_grouping.rb +1 -1
- data/lib/rubocop/cop/style/multiline_ternary_operator.rb +5 -1
- data/lib/rubocop/cop/style/next.rb +1 -1
- data/lib/rubocop/cop/style/optional_arguments.rb +1 -1
- data/lib/rubocop/cop/style/optional_boolean_parameter.rb +1 -1
- data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
- data/lib/rubocop/cop/style/raise_args.rb +1 -1
- data/lib/rubocop/cop/style/redundant_condition.rb +77 -7
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +1 -1
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
- data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
- data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/style/slicing_with_range.rb +0 -3
- data/lib/rubocop/cop/style/string_chars.rb +1 -1
- data/lib/rubocop/cop/style/trivial_accessors.rb +7 -8
- data/lib/rubocop/cops_documentation_generator.rb +1 -1
- data/lib/rubocop/formatter/formatter_set.rb +1 -0
- data/lib/rubocop/formatter/html_formatter.rb +2 -9
- data/lib/rubocop/formatter/markdown_formatter.rb +76 -0
- data/lib/rubocop/magic_comment.rb +4 -3
- data/lib/rubocop/options.rb +4 -3
- data/lib/rubocop/result_cache.rb +1 -1
- data/lib/rubocop/rspec/cop_helper.rb +1 -1
- data/lib/rubocop/rspec/parallel_formatter.rb +1 -1
- data/lib/rubocop/rspec/shared_contexts.rb +1 -5
- data/lib/rubocop/runner.rb +1 -1
- data/lib/rubocop/string_interpreter.rb +4 -4
- data/lib/rubocop/target_ruby.rb +8 -2
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +3 -1
- metadata +17 -8
- data/lib/rubocop/cop/lint/useless_else_without_rescue.rb +0 -45
@@ -70,7 +70,7 @@ module RuboCop
|
|
70
70
|
!(method_name.start_with?(prefix) && # cheap check to avoid allocating Regexp
|
71
71
|
method_name.match?(/^#{prefix}[^0-9]/)) ||
|
72
72
|
method_name == expected_name(method_name, prefix) ||
|
73
|
-
method_name.end_with?('=') ||
|
73
|
+
method_name.end_with?('=') || # rubocop:todo InternalAffairs/MethodNameEndWith
|
74
74
|
allowed_method?(method_name)
|
75
75
|
end
|
76
76
|
|
@@ -80,7 +80,7 @@ module RuboCop
|
|
80
80
|
else
|
81
81
|
method_name.dup
|
82
82
|
end
|
83
|
-
new_name << '?' unless method_name.end_with?('?')
|
83
|
+
new_name << '?' unless method_name.end_with?('?') # rubocop:todo InternalAffairs/MethodNameEndWith
|
84
84
|
new_name
|
85
85
|
end
|
86
86
|
|
@@ -19,12 +19,21 @@ module RuboCop
|
|
19
19
|
#
|
20
20
|
# # good
|
21
21
|
# fooBar = 1
|
22
|
+
#
|
23
|
+
# @example AllowedPatterns: ['_v\d+\z']
|
24
|
+
# # good
|
25
|
+
# :release_v1
|
22
26
|
class VariableName < Base
|
23
27
|
include AllowedIdentifiers
|
24
28
|
include ConfigurableNaming
|
29
|
+
include AllowedPattern
|
25
30
|
|
26
31
|
MSG = 'Use %<style>s for variable names.'
|
27
32
|
|
33
|
+
def valid_name?(node, name, given_style = style)
|
34
|
+
super || matches_allowed_pattern?(name)
|
35
|
+
end
|
36
|
+
|
28
37
|
def on_lvasgn(node)
|
29
38
|
name, = *node
|
30
39
|
return unless name
|
@@ -96,12 +96,21 @@ module RuboCop
|
|
96
96
|
# # good
|
97
97
|
# expect(Open3).to receive(:capture3)
|
98
98
|
#
|
99
|
+
# @example AllowedPatterns: ['_v\d+\z']
|
100
|
+
# # good
|
101
|
+
# :some_sym_v1
|
102
|
+
#
|
99
103
|
class VariableNumber < Base
|
100
104
|
include AllowedIdentifiers
|
101
105
|
include ConfigurableNumbering
|
106
|
+
include AllowedPattern
|
102
107
|
|
103
108
|
MSG = 'Use %<style>s for %<identifier_type>s numbers.'
|
104
109
|
|
110
|
+
def valid_name?(node, name, given_style = style)
|
111
|
+
super || matches_allowed_pattern?(name)
|
112
|
+
end
|
113
|
+
|
105
114
|
def on_arg(node)
|
106
115
|
@node = node
|
107
116
|
name, = *node
|
@@ -112,6 +121,7 @@ module RuboCop
|
|
112
121
|
alias on_lvasgn on_arg
|
113
122
|
alias on_ivasgn on_arg
|
114
123
|
alias on_cvasgn on_arg
|
124
|
+
alias on_gvasgn on_arg
|
115
125
|
|
116
126
|
def on_def(node)
|
117
127
|
@node = node
|
@@ -10,7 +10,7 @@ module RuboCop
|
|
10
10
|
# NOTE: Ruby 3.1+ (Psych 4) uses `Psych.load` as `Psych.safe_load` by default.
|
11
11
|
#
|
12
12
|
# @safety
|
13
|
-
# The
|
13
|
+
# The behavior of the code might change depending on what was
|
14
14
|
# in the YAML payload, since `YAML.safe_load` is more restrictive.
|
15
15
|
#
|
16
16
|
# @example
|
@@ -76,7 +76,7 @@ module RuboCop
|
|
76
76
|
|
77
77
|
def add_offense_for_args(node, &block)
|
78
78
|
existing_args = node.children.map(&:source).join(' ')
|
79
|
-
preferred_args = node.children.map { |a| a.source[1
|
79
|
+
preferred_args = node.children.map { |a| a.source[1..] }.join(' ')
|
80
80
|
arg_ranges = node.children.map(&:source_range)
|
81
81
|
msg = format(MSG_SYMBOL_ARGS, prefer: preferred_args, current: existing_args)
|
82
82
|
add_offense(arg_ranges.reduce(&:join), message: msg, &block)
|
@@ -134,8 +134,8 @@ module RuboCop
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def correct_alias_with_symbol_args(corrector, node)
|
137
|
-
corrector.replace(node.new_identifier, node.new_identifier.source[1
|
138
|
-
corrector.replace(node.old_identifier, node.old_identifier.source[1
|
137
|
+
corrector.replace(node.new_identifier, node.new_identifier.source[1..])
|
138
|
+
corrector.replace(node.old_identifier, node.old_identifier.source[1..])
|
139
139
|
end
|
140
140
|
|
141
141
|
# @!method identifier(node)
|
@@ -10,7 +10,7 @@ module RuboCop
|
|
10
10
|
# @safety
|
11
11
|
# Auto-correction is unsafe because there is a different operator precedence
|
12
12
|
# between logical operators (`&&` and `||`) and semantic operators (`and` and `or`),
|
13
|
-
# and that might change the
|
13
|
+
# and that might change the behavior.
|
14
14
|
#
|
15
15
|
# @example EnforcedStyle: always
|
16
16
|
# # bad
|
@@ -9,7 +9,7 @@ module RuboCop
|
|
9
9
|
# @safety
|
10
10
|
# This cop is unsafe. `case` statements use `===` for equality,
|
11
11
|
# so if the original conditional used a different equality operator, the
|
12
|
-
#
|
12
|
+
# behavior may be different.
|
13
13
|
#
|
14
14
|
# @example
|
15
15
|
# # bad
|
@@ -70,7 +70,7 @@ module RuboCop
|
|
70
70
|
def on_send(node)
|
71
71
|
return unless (range = offense_range(node))
|
72
72
|
|
73
|
-
good = good_method_name(node
|
73
|
+
good = good_method_name(node)
|
74
74
|
message = format(MSG, good: good, bad: range.source)
|
75
75
|
|
76
76
|
add_offense(range, message: message) { |corrector| corrector.replace(range, good) }
|
@@ -94,8 +94,8 @@ module RuboCop
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
def good_method_name(
|
98
|
-
if
|
97
|
+
def good_method_name(node)
|
98
|
+
if node.bang_method?
|
99
99
|
'compact!'
|
100
100
|
else
|
101
101
|
'compact'
|
@@ -11,7 +11,7 @@ module RuboCop
|
|
11
11
|
#
|
12
12
|
# @safety
|
13
13
|
# Autocorrection is not safe, because `DateTime` and `Time` do not have
|
14
|
-
# exactly the same
|
14
|
+
# exactly the same behavior, although in most cases the autocorrection
|
15
15
|
# will be fine.
|
16
16
|
#
|
17
17
|
# @example
|
@@ -37,11 +37,27 @@ module RuboCop
|
|
37
37
|
# !!return_value
|
38
38
|
# end
|
39
39
|
#
|
40
|
+
# define_method :foo? do
|
41
|
+
# !!return_value
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# define_singleton_method :foo? do
|
45
|
+
# !!return_value
|
46
|
+
# end
|
47
|
+
#
|
40
48
|
# @example EnforcedStyle: forbidden
|
41
49
|
# # bad
|
42
50
|
# def foo?
|
43
51
|
# !!return_value
|
44
52
|
# end
|
53
|
+
#
|
54
|
+
# define_method :foo? do
|
55
|
+
# !!return_value
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# define_singleton_method :foo? do
|
59
|
+
# !!return_value
|
60
|
+
# end
|
45
61
|
class DoubleNegation < Base
|
46
62
|
include ConfigurableEnforcedStyle
|
47
63
|
extend AutoCorrector
|
@@ -73,22 +89,32 @@ module RuboCop
|
|
73
89
|
return false unless (def_node = find_def_node_from_ascendant(node))
|
74
90
|
|
75
91
|
conditional_node = find_conditional_node_from_ascendant(node)
|
76
|
-
last_child = find_last_child(def_node.body)
|
92
|
+
last_child = find_last_child(def_node.send_type? ? def_node : def_node.body)
|
77
93
|
|
78
94
|
if conditional_node
|
79
95
|
double_negative_condition_return_value?(node, last_child, conditional_node)
|
80
96
|
else
|
81
|
-
last_child.last_line
|
97
|
+
last_child.last_line <= node.last_line
|
82
98
|
end
|
83
99
|
end
|
84
100
|
|
85
101
|
def find_def_node_from_ascendant(node)
|
86
102
|
return unless (parent = node.parent)
|
87
103
|
return parent if parent.def_type? || parent.defs_type?
|
104
|
+
return node.parent.child_nodes.first if define_mehod?(parent)
|
88
105
|
|
89
106
|
find_def_node_from_ascendant(node.parent)
|
90
107
|
end
|
91
108
|
|
109
|
+
def define_mehod?(node)
|
110
|
+
return false unless node.block_type?
|
111
|
+
|
112
|
+
child = node.child_nodes.first
|
113
|
+
return false unless child.send_type?
|
114
|
+
|
115
|
+
child.method?(:define_method) || child.method?(:define_singleton_method)
|
116
|
+
end
|
117
|
+
|
92
118
|
def find_conditional_node_from_ascendant(node)
|
93
119
|
return unless (parent = node.parent)
|
94
120
|
return parent if parent.conditional?
|
@@ -119,7 +119,7 @@ module RuboCop
|
|
119
119
|
# because the braces are interpreted as a block. We will have
|
120
120
|
# to rewrite the arguments to wrap them in parenthesis.
|
121
121
|
args = node.parent.arguments
|
122
|
-
"(#{args[1
|
122
|
+
"(#{args[1..].map(&:source).unshift('{}').join(', ')})"
|
123
123
|
else
|
124
124
|
'{}'
|
125
125
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop checks for consistent usage of `ENV['HOME']`. If `nil` is used as
|
7
|
+
# the second argument of `ENV.fetch`, it is treated as a bad case like `ENV[]`.
|
8
|
+
#
|
9
|
+
# @safety
|
10
|
+
# The cop is unsafe because the result when `nil` is assigned to `ENV['HOME']` changes:
|
11
|
+
#
|
12
|
+
# [source,ruby]
|
13
|
+
# ----
|
14
|
+
# ENV['HOME'] = nil
|
15
|
+
# ENV['HOME'] # => nil
|
16
|
+
# Dir.home # => '/home/foo'
|
17
|
+
# ----
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
#
|
21
|
+
# # bad
|
22
|
+
# ENV['HOME']
|
23
|
+
# ENV.fetch('HOME', nil)
|
24
|
+
#
|
25
|
+
# # good
|
26
|
+
# Dir.home
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# ENV.fetch('HOME', default)
|
30
|
+
#
|
31
|
+
class EnvHome < Base
|
32
|
+
extend AutoCorrector
|
33
|
+
|
34
|
+
MSG = 'Use `Dir.home` instead.'
|
35
|
+
RESTRICT_ON_SEND = %i[[] fetch].freeze
|
36
|
+
|
37
|
+
# @!method env_home?(node)
|
38
|
+
def_node_matcher :env_home?, <<~PATTERN
|
39
|
+
(send
|
40
|
+
(const {cbase nil?} :ENV) {:[] :fetch}
|
41
|
+
(str "HOME")
|
42
|
+
...)
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
def on_send(node)
|
46
|
+
return unless env_home?(node)
|
47
|
+
return if node.arguments.count == 2 && !node.arguments[1].nil_type?
|
48
|
+
|
49
|
+
add_offense(node) do |corrector|
|
50
|
+
corrector.replace(node, 'Dir.home')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -9,17 +9,32 @@ module RuboCop
|
|
9
9
|
# On the other hand, `ENV.fetch` raises KeyError or returns the explicitly
|
10
10
|
# specified default value.
|
11
11
|
#
|
12
|
+
# When an `ENV[]` is the LHS of `||`, the autocorrect makes the RHS
|
13
|
+
# the default value of `ENV.fetch`.
|
14
|
+
#
|
12
15
|
# @example
|
13
16
|
# # bad
|
14
17
|
# ENV['X']
|
15
|
-
# ENV['X'] ||
|
18
|
+
# ENV['X'] || 'string literal'
|
19
|
+
# ENV['X'] || some_method
|
16
20
|
# x = ENV['X']
|
17
21
|
#
|
22
|
+
# ENV['X'] || y.map do |a|
|
23
|
+
# puts a * 2
|
24
|
+
# end
|
25
|
+
#
|
18
26
|
# # good
|
19
27
|
# ENV.fetch('X')
|
20
|
-
# ENV.fetch('X',
|
28
|
+
# ENV.fetch('X', 'string literal')
|
29
|
+
# ENV.fetch('X') { some_method }
|
21
30
|
# x = ENV.fetch('X')
|
22
31
|
#
|
32
|
+
# ENV.fetch('X') do
|
33
|
+
# y.map do |a|
|
34
|
+
# puts a * 2
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
23
38
|
# # also good
|
24
39
|
# !ENV['X']
|
25
40
|
# ENV['X'].some_method # (e.g. `.nil?`)
|
@@ -27,41 +42,82 @@ module RuboCop
|
|
27
42
|
class FetchEnvVar < Base
|
28
43
|
extend AutoCorrector
|
29
44
|
|
30
|
-
|
45
|
+
# rubocop:disable Layout/LineLength
|
46
|
+
MSG_DEFAULT_NIL = 'Use `ENV.fetch(%<key>s)` or `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
|
47
|
+
MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH = 'Use `ENV.fetch(%<key>s, %<default>s)` instead of `ENV[%<key>s] || %<default>s`.'
|
48
|
+
MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK = 'Use `ENV.fetch(%<key>s) { %<default>s }` instead of `ENV[%<key>s] || %<default>s`.'
|
49
|
+
MSG_DEFAULT_RHS_MULTILINE_BLOCK = 'Use `ENV.fetch(%<key>s)` with a block containing `%<default>s ...`'
|
50
|
+
# rubocop:enable Layout/LineLength
|
31
51
|
|
32
52
|
# @!method env_with_bracket?(node)
|
33
53
|
def_node_matcher :env_with_bracket?, <<~PATTERN
|
34
54
|
(send (const nil? :ENV) :[] $_)
|
35
55
|
PATTERN
|
36
56
|
|
57
|
+
# @!method operand_of_or?(node)
|
58
|
+
def_node_matcher :operand_of_or?, <<~PATTERN
|
59
|
+
(^or ...)
|
60
|
+
PATTERN
|
61
|
+
|
62
|
+
# @!method block_control?(node)
|
63
|
+
def_node_matcher :block_control?, <<~PATTERN
|
64
|
+
({next | break | retry | redo})
|
65
|
+
PATTERN
|
66
|
+
|
67
|
+
# @!method env_with_bracket_in_descendants?(node)
|
68
|
+
def_node_matcher :env_with_bracket_in_descendants?, <<~PATTERN
|
69
|
+
`(send (const nil? :ENV) :[] $_)
|
70
|
+
PATTERN
|
71
|
+
|
37
72
|
def on_send(node)
|
38
73
|
env_with_bracket?(node) do |expression|
|
39
|
-
break
|
40
|
-
|
74
|
+
break unless offensive?(node)
|
75
|
+
|
76
|
+
if operand_of_or?(node)
|
77
|
+
target_node = lookahead_target_node(node)
|
78
|
+
target_expr = env_with_bracket?(target_node)
|
41
79
|
|
42
|
-
|
43
|
-
|
80
|
+
if default_to_rhs?(target_node)
|
81
|
+
default_rhs(target_node, target_expr)
|
82
|
+
else
|
83
|
+
default_nil(target_node, target_expr)
|
84
|
+
end
|
85
|
+
else
|
86
|
+
default_nil(node, expression)
|
44
87
|
end
|
45
88
|
end
|
46
89
|
end
|
47
90
|
|
48
91
|
private
|
49
92
|
|
50
|
-
def allowed_var?(
|
51
|
-
|
93
|
+
def allowed_var?(node)
|
94
|
+
env_key_node = node.children.last
|
95
|
+
env_key_node.str_type? && cop_config['AllowedVars'].include?(env_key_node.value)
|
52
96
|
end
|
53
97
|
|
54
98
|
def used_as_flag?(node)
|
55
99
|
return false if node.root?
|
100
|
+
return true if node.parent.if_type?
|
56
101
|
|
57
|
-
node.parent.
|
102
|
+
node.parent.send_type? && (node.parent.prefix_bang? || node.parent.comparison_method?)
|
103
|
+
end
|
104
|
+
|
105
|
+
def offensive?(node)
|
106
|
+
!(allowed_var?(node) || allowable_use?(node))
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_to_rhs?(node)
|
110
|
+
operand_of_or?(node) && !right_end_of_or_chains?(node) && rhs_can_be_default_value?(node)
|
58
111
|
end
|
59
112
|
|
60
113
|
# Check if the node is a receiver and receives a message with dot syntax.
|
61
114
|
def message_chained_with_dot?(node)
|
62
115
|
return false if node.root?
|
63
116
|
|
64
|
-
|
117
|
+
parent = node.parent
|
118
|
+
return false if !parent.call_type? || parent.children.first != node
|
119
|
+
|
120
|
+
parent.dot? || parent.safe_navigation?
|
65
121
|
end
|
66
122
|
|
67
123
|
# The following are allowed cases:
|
@@ -84,6 +140,177 @@ module RuboCop
|
|
84
140
|
lhs, _method, _rhs = *parent
|
85
141
|
node == lhs
|
86
142
|
end
|
143
|
+
|
144
|
+
def left_end_of_or_chains?(node)
|
145
|
+
return false unless operand_of_or?(node)
|
146
|
+
|
147
|
+
node.parent.lhs == node
|
148
|
+
end
|
149
|
+
|
150
|
+
def right_end_of_or_chains?(node)
|
151
|
+
!(left_end_of_or_chains?(node) || node.parent&.parent&.or_type?)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the node and expression of the rightmost `ENV[]` in `||` chains.
|
155
|
+
# e.g.,
|
156
|
+
# `ENV['X'] || y || z || ENV['A'] || b`
|
157
|
+
# ^^^^^^^^ Matches this one
|
158
|
+
def rightmost_offense_in_or_chains(base_node)
|
159
|
+
or_nodes = [base_node.parent]
|
160
|
+
|
161
|
+
while (grand_parent = or_nodes.last&.parent)&.or_type?
|
162
|
+
or_nodes << grand_parent
|
163
|
+
end
|
164
|
+
|
165
|
+
# Finds the rightmost `ENV[]` in `||` chains.
|
166
|
+
or_node = or_nodes.reverse.find do |n|
|
167
|
+
env_with_bracket?(n.rhs)
|
168
|
+
end
|
169
|
+
|
170
|
+
or_node ? or_node.rhs : base_node
|
171
|
+
end
|
172
|
+
|
173
|
+
def no_env_with_bracket_in_descendants?(node)
|
174
|
+
!env_with_bracket_in_descendants?(node)
|
175
|
+
end
|
176
|
+
|
177
|
+
def conterpart_rhs_of(node)
|
178
|
+
left_end_of_or_chains?(node) ? node.parent.rhs : node.parent.parent.rhs
|
179
|
+
end
|
180
|
+
|
181
|
+
# Looks ahead to the `ENV[]` that must be corrected first, avoiding a cross correction.
|
182
|
+
# ```
|
183
|
+
# ENV['X'] || y.map do |a|
|
184
|
+
# a.map do |b|
|
185
|
+
# ENV['Z'] + b
|
186
|
+
# ^^^^^^^^ This must be corrected first.
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
# ```
|
190
|
+
def lookahead_target_node(base_node)
|
191
|
+
return base_node unless operand_of_or?(base_node)
|
192
|
+
|
193
|
+
candidate_node = rightmost_offense_in_or_chains(base_node)
|
194
|
+
return candidate_node if right_end_of_or_chains?(candidate_node)
|
195
|
+
|
196
|
+
counterpart_rhs = conterpart_rhs_of(candidate_node)
|
197
|
+
return candidate_node if no_env_with_bracket_in_descendants?(counterpart_rhs)
|
198
|
+
|
199
|
+
new_base_node = counterpart_rhs.each_descendant.find do |d|
|
200
|
+
env_with_bracket?(d) && offensive?(d)
|
201
|
+
end
|
202
|
+
return candidate_node unless new_base_node
|
203
|
+
|
204
|
+
lookahead_target_node(new_base_node)
|
205
|
+
end
|
206
|
+
|
207
|
+
def rhs_can_be_default_value?(node)
|
208
|
+
!rhs_is_block_control?(node)
|
209
|
+
end
|
210
|
+
|
211
|
+
def rhs_is_block_control?(node)
|
212
|
+
block_control?(conterpart_rhs_of(node))
|
213
|
+
end
|
214
|
+
|
215
|
+
def new_code_default_nil(expression)
|
216
|
+
"ENV.fetch(#{expression.source}, nil)"
|
217
|
+
end
|
218
|
+
|
219
|
+
def new_code_default_rhs_single_line(node, expression)
|
220
|
+
parent = node.parent
|
221
|
+
if parent.rhs.basic_literal?
|
222
|
+
"ENV.fetch(#{expression.source}, #{parent.rhs.source})"
|
223
|
+
else
|
224
|
+
"ENV.fetch(#{expression.source}) { #{parent.rhs.source} }"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def new_code_default_rhs_multiline(node, expression)
|
229
|
+
env_indent = indent(node.parent)
|
230
|
+
default = node.parent.rhs.source.split("\n").map do |line|
|
231
|
+
"#{env_indent}#{line}"
|
232
|
+
end.join("\n")
|
233
|
+
<<~NEW_CODE.chomp
|
234
|
+
ENV.fetch(#{expression.source}) do
|
235
|
+
#{configured_indentation}#{default}
|
236
|
+
#{env_indent}end
|
237
|
+
NEW_CODE
|
238
|
+
end
|
239
|
+
|
240
|
+
def new_code_default_rhs(node, expression)
|
241
|
+
if node.parent.rhs.single_line?
|
242
|
+
new_code_default_rhs_single_line(node, expression)
|
243
|
+
else
|
244
|
+
new_code_default_rhs_multiline(node, expression)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def default_rhs(node, expression)
|
249
|
+
if left_end_of_or_chains?(node)
|
250
|
+
default_rhs_in_same_or(node, expression)
|
251
|
+
else
|
252
|
+
default_rhs_in_outer_or(node, expression)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Adds an offense and sets `nil` to the default value of `ENV.fetch`.
|
257
|
+
# `ENV['X']` --> `ENV.fetch('X', nil)`
|
258
|
+
def default_nil(node, expression)
|
259
|
+
message = format(MSG_DEFAULT_NIL, key: expression.source)
|
260
|
+
|
261
|
+
add_offense(node, message: message) do |corrector|
|
262
|
+
corrector.replace(node, new_code_default_nil(expression))
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Adds an offense and makes the RHS the default value of `ENV.fetch`.
|
267
|
+
# `ENV['X'] || y` --> `ENV.fetch('X') { y }`
|
268
|
+
def default_rhs_in_same_or(node, expression)
|
269
|
+
template = message_template_for(node.parent.rhs)
|
270
|
+
message = format(template,
|
271
|
+
key: expression.source,
|
272
|
+
default: first_line_of(node.parent.rhs.source))
|
273
|
+
|
274
|
+
add_offense(node, message: message) do |corrector|
|
275
|
+
corrector.replace(node.parent, new_code_default_rhs(node, expression))
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Adds an offense and makes the RHS the default value of `ENV.fetch`.
|
280
|
+
# `z || ENV['X'] || y` --> `z || ENV.fetch('X') { y }`
|
281
|
+
def default_rhs_in_outer_or(node, expression)
|
282
|
+
parent = node.parent
|
283
|
+
grand_parent = parent.parent
|
284
|
+
|
285
|
+
template = message_template_for(grand_parent.rhs)
|
286
|
+
message = format(template,
|
287
|
+
key: expression.source,
|
288
|
+
default: first_line_of(grand_parent.rhs.source))
|
289
|
+
|
290
|
+
add_offense(node, message: message) do |corrector|
|
291
|
+
lhs_code = parent.lhs.source
|
292
|
+
rhs_code = new_code_default_rhs(parent, expression)
|
293
|
+
corrector.replace(grand_parent, "#{lhs_code} || #{rhs_code}")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def message_template_for(rhs)
|
298
|
+
if rhs.multiline?
|
299
|
+
MSG_DEFAULT_RHS_MULTILINE_BLOCK
|
300
|
+
elsif rhs.basic_literal?
|
301
|
+
MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH
|
302
|
+
else
|
303
|
+
MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def configured_indentation
|
308
|
+
' ' * (config.for_cop('Layout/IndentationWidth')['Width'] || 2)
|
309
|
+
end
|
310
|
+
|
311
|
+
def first_line_of(source)
|
312
|
+
source.split("\n").first
|
313
|
+
end
|
87
314
|
end
|
88
315
|
end
|
89
316
|
end
|
@@ -13,7 +13,7 @@ module RuboCop
|
|
13
13
|
#
|
14
14
|
# @safety
|
15
15
|
# Auto-correction is unsafe because changing the order of method invocations
|
16
|
-
# may change the
|
16
|
+
# may change the behavior of the code. For example:
|
17
17
|
#
|
18
18
|
# [source,ruby]
|
19
19
|
# ----
|
@@ -27,7 +27,7 @@ module RuboCop
|
|
27
27
|
# ----
|
28
28
|
#
|
29
29
|
# In this example, `method_that_relies_on_global_state` will be moved before
|
30
|
-
# `method_that_modifies_global_state`, which changes the
|
30
|
+
# `method_that_modifies_global_state`, which changes the behavior of the program.
|
31
31
|
#
|
32
32
|
# @example
|
33
33
|
# # bad
|
@@ -29,11 +29,8 @@ module RuboCop
|
|
29
29
|
#
|
30
30
|
class MapToHash < Base
|
31
31
|
extend AutoCorrector
|
32
|
-
extend TargetRubyVersion
|
33
32
|
include RangeHelp
|
34
33
|
|
35
|
-
minimum_target_ruby_version 2.6
|
36
|
-
|
37
34
|
MSG = 'Pass a block to `to_h` instead of calling `%<method>s.to_h`.'
|
38
35
|
RESTRICT_ON_SEND = %i[to_h].freeze
|
39
36
|
|
@@ -119,7 +119,7 @@ module RuboCop
|
|
119
119
|
arguments = node.arguments.reverse
|
120
120
|
mixins = ["#{node.method_name} #{arguments.first.source}"]
|
121
121
|
|
122
|
-
arguments[1
|
122
|
+
arguments[1..].inject(mixins) do |replacement, arg|
|
123
123
|
replacement << "#{indent(node)}#{node.method_name} #{arg.source}"
|
124
124
|
end.join("\n")
|
125
125
|
end
|
@@ -73,7 +73,11 @@ module RuboCop
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def enforce_single_line_ternary_operator?(node)
|
76
|
-
SINGLE_LINE_TYPES.include?(node.parent.type)
|
76
|
+
SINGLE_LINE_TYPES.include?(node.parent.type) && !use_assignment_method?(node.parent)
|
77
|
+
end
|
78
|
+
|
79
|
+
def use_assignment_method?(node)
|
80
|
+
node.send_type? && node.assignment_method?
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|