rubocop 1.31.1 → 1.33.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 +2 -2
- data/config/default.yml +68 -16
- data/config/obsoletion.yml +23 -1
- data/lib/rubocop/cache_config.rb +29 -0
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +2 -2
- data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
- data/lib/rubocop/cli.rb +1 -0
- data/lib/rubocop/config.rb +1 -1
- data/lib/rubocop/config_finder.rb +68 -0
- data/lib/rubocop/config_loader.rb +5 -45
- data/lib/rubocop/config_loader_resolver.rb +1 -1
- data/lib/rubocop/config_obsoletion/changed_parameter.rb +5 -0
- data/lib/rubocop/config_obsoletion/parameter_rule.rb +4 -0
- data/lib/rubocop/config_obsoletion.rb +7 -2
- data/lib/rubocop/cop/base.rb +1 -1
- data/lib/rubocop/cop/generator.rb +4 -0
- data/lib/rubocop/cop/internal_affairs/useless_restrict_on_send.rb +60 -0
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/block_end_newline.rb +32 -5
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -1
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +4 -3
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +3 -3
- data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +57 -13
- data/lib/rubocop/cop/layout/line_length.rb +2 -0
- data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +4 -1
- data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +45 -0
- data/lib/rubocop/cop/lint/ambiguous_block_association.rb +26 -6
- data/lib/rubocop/cop/lint/debugger.rb +11 -1
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +10 -4
- data/lib/rubocop/cop/lint/empty_conditional_body.rb +60 -1
- data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +60 -25
- data/lib/rubocop/cop/lint/number_conversion.rb +28 -6
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +7 -0
- data/lib/rubocop/cop/lint/require_range_parentheses.rb +57 -0
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +35 -1
- data/lib/rubocop/cop/metrics/abc_size.rb +3 -1
- data/lib/rubocop/cop/metrics/block_length.rb +6 -6
- data/lib/rubocop/cop/metrics/method_length.rb +8 -7
- data/lib/rubocop/cop/mixin/allowed_methods.rb +15 -1
- data/lib/rubocop/cop/mixin/allowed_pattern.rb +9 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +4 -0
- data/lib/rubocop/cop/mixin/comments_help.rb +5 -1
- data/lib/rubocop/cop/mixin/def_node.rb +2 -7
- data/lib/rubocop/cop/mixin/method_complexity.rb +4 -9
- data/lib/rubocop/cop/mixin/multiline_element_indentation.rb +6 -13
- data/lib/rubocop/cop/mixin/percent_array.rb +60 -1
- data/lib/rubocop/cop/naming/predicate_name.rb +30 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +26 -7
- data/lib/rubocop/cop/style/class_and_module_children.rb +4 -4
- data/lib/rubocop/cop/style/class_equality_comparison.rb +50 -3
- data/lib/rubocop/cop/style/empty_else.rb +37 -0
- data/lib/rubocop/cop/style/empty_heredoc.rb +73 -0
- data/lib/rubocop/cop/style/fetch_env_var.rb +10 -177
- data/lib/rubocop/cop/style/format_string_token.rb +25 -6
- data/lib/rubocop/cop/style/hash_except.rb +0 -4
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +2 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +5 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -7
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +19 -2
- data/lib/rubocop/cop/style/module_function.rb +2 -2
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +9 -0
- data/lib/rubocop/cop/style/numeric_predicate.rb +43 -9
- data/lib/rubocop/cop/style/redundant_condition.rb +19 -4
- data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -1
- data/lib/rubocop/cop/style/redundant_sort.rb +21 -6
- data/lib/rubocop/cop/style/semicolon.rb +27 -3
- data/lib/rubocop/cop/style/symbol_array.rb +2 -3
- data/lib/rubocop/cop/style/symbol_proc.rb +35 -7
- data/lib/rubocop/cop/style/trivial_accessors.rb +3 -0
- data/lib/rubocop/cop/style/word_array.rb +2 -3
- data/lib/rubocop/options.rb +3 -6
- data/lib/rubocop/rake_task.rb +5 -1
- data/lib/rubocop/result_cache.rb +22 -20
- data/lib/rubocop/rspec/shared_contexts.rb +14 -14
- data/lib/rubocop/rspec/support.rb +14 -0
- data/lib/rubocop/runner.rb +4 -0
- data/lib/rubocop/server/cache.rb +33 -1
- data/lib/rubocop/server/cli.rb +19 -2
- data/lib/rubocop/server/client_command/base.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +4 -2
- metadata +12 -7
- data/lib/rubocop/cop/mixin/ignored_methods.rb +0 -52
@@ -44,13 +44,26 @@ module RuboCop
|
|
44
44
|
no_acceptable_style! if brackets_required
|
45
45
|
|
46
46
|
bracketed_array = build_bracketed_array(node)
|
47
|
-
message =
|
47
|
+
message = build_message_for_bracketed_array(bracketed_array)
|
48
48
|
|
49
49
|
add_offense(node, message: message) do |corrector|
|
50
50
|
corrector.replace(node, bracketed_array)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
# @param [String] preferred_array_code
|
55
|
+
# @return [String]
|
56
|
+
def build_message_for_bracketed_array(preferred_array_code)
|
57
|
+
format(
|
58
|
+
self.class::ARRAY_MSG,
|
59
|
+
prefer: if preferred_array_code.include?("\n")
|
60
|
+
'an array literal `[...]`'
|
61
|
+
else
|
62
|
+
"`#{preferred_array_code}`"
|
63
|
+
end
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
54
67
|
def check_bracketed_array(node, literal_prefix)
|
55
68
|
return if allowed_bracket_array?(node)
|
56
69
|
|
@@ -63,6 +76,52 @@ module RuboCop
|
|
63
76
|
percent_literal_corrector.correct(corrector, node, literal_prefix)
|
64
77
|
end
|
65
78
|
end
|
79
|
+
|
80
|
+
# @param [RuboCop::AST::ArrayNode] node
|
81
|
+
# @param [Array<String>] elements
|
82
|
+
# @return [String]
|
83
|
+
def build_bracketed_array_with_appropriate_whitespace(elements:, node:)
|
84
|
+
[
|
85
|
+
'[',
|
86
|
+
whitespace_leading(node),
|
87
|
+
elements.join(",#{whitespace_between(node)}"),
|
88
|
+
whitespace_trailing(node),
|
89
|
+
']'
|
90
|
+
].join
|
91
|
+
end
|
92
|
+
|
93
|
+
# Provides whitespace between elements for building a bracketed array.
|
94
|
+
# %w[ a b c ]
|
95
|
+
# ^^^
|
96
|
+
# @param [RuboCop::AST::ArrayNode] node
|
97
|
+
# @return [String]
|
98
|
+
def whitespace_between(node)
|
99
|
+
if node.children.length >= 2
|
100
|
+
node.source[
|
101
|
+
node.children[0].loc.expression.end_pos...node.children[1].loc.expression.begin_pos
|
102
|
+
]
|
103
|
+
else
|
104
|
+
' '
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Provides leading whitespace for building a bracketed array.
|
109
|
+
# %w[ a b c ]
|
110
|
+
# ^^
|
111
|
+
# @param [RuboCop::AST::ArrayNode] node
|
112
|
+
# @return [String]
|
113
|
+
def whitespace_leading(node)
|
114
|
+
node.source[node.loc.begin.end_pos...node.children[0].loc.expression.begin_pos]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Provides trailing whitespace for building a bracketed array.
|
118
|
+
# %w[ a b c ]
|
119
|
+
# ^^^^
|
120
|
+
# @param [RuboCop::AST::ArrayNode] node
|
121
|
+
# @return [String]
|
122
|
+
def whitespace_trailing(node)
|
123
|
+
node.source[node.children[-1].loc.expression.end_pos...node.loc.end.begin_pos]
|
124
|
+
end
|
66
125
|
end
|
67
126
|
end
|
68
127
|
end
|
@@ -3,7 +3,30 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Naming
|
6
|
-
#
|
6
|
+
# Checks that predicate methods names end with a question mark and
|
7
|
+
# do not start with a forbidden prefix.
|
8
|
+
#
|
9
|
+
# A method is determined to be a predicate method if its name starts
|
10
|
+
# with one of the prefixes defined in the `NamePrefix` configuration.
|
11
|
+
# You can change what prefixes are considered by changing this option.
|
12
|
+
# Any method name that starts with one of these prefixes is required by
|
13
|
+
# the cop to end with a `?`. Other methods can be allowed by adding to
|
14
|
+
# the `AllowedMethods` configuration.
|
15
|
+
#
|
16
|
+
# NOTE: The `is_a?` method is allowed by default.
|
17
|
+
#
|
18
|
+
# If `ForbiddenPrefixes` is set, methods that start with the configured
|
19
|
+
# prefixes will not be allowed and will be removed by autocorrection.
|
20
|
+
#
|
21
|
+
# In other words, if `ForbiddenPrefixes` is empty, a method named `is_foo`
|
22
|
+
# will register an offense only due to the lack of question mark (and will be
|
23
|
+
# autocorrected to `is_foo?`). If `ForbiddenPrefixes` contains `is_`,
|
24
|
+
# `is_foo` will register an offense both because the ? is missing and because of
|
25
|
+
# the `is_` prefix, and will be corrected to `foo?`.
|
26
|
+
#
|
27
|
+
# NOTE: `ForbiddenPrefixes` is only applied to prefixes in `NamePrefix`;
|
28
|
+
# a prefix in the former but not the latter will not be considered by
|
29
|
+
# this cop.
|
7
30
|
#
|
8
31
|
# @example
|
9
32
|
# # bad
|
@@ -27,6 +50,12 @@ module RuboCop
|
|
27
50
|
# # good
|
28
51
|
# def value?
|
29
52
|
# end
|
53
|
+
#
|
54
|
+
# @example AllowedMethods: ['is_a?'] (default)
|
55
|
+
# # good
|
56
|
+
# def is_a?(value)
|
57
|
+
# end
|
58
|
+
#
|
30
59
|
class PredicateName < Base
|
31
60
|
include AllowedMethods
|
32
61
|
|
@@ -10,7 +10,7 @@ module RuboCop
|
|
10
10
|
# Methods that can be either procedural or functional and cannot be
|
11
11
|
# categorised from their usage alone is ignored.
|
12
12
|
# `lambda`, `proc`, and `it` are their defaults.
|
13
|
-
# Additional methods can be added to the `
|
13
|
+
# Additional methods can be added to the `AllowedMethods`.
|
14
14
|
#
|
15
15
|
# @example EnforcedStyle: line_count_based (default)
|
16
16
|
# # bad - single line block
|
@@ -66,7 +66,7 @@ module RuboCop
|
|
66
66
|
# x
|
67
67
|
# }.inspect
|
68
68
|
#
|
69
|
-
# # The AllowBracesOnProceduralOneLiners option is
|
69
|
+
# # The AllowBracesOnProceduralOneLiners option is allowed unless the
|
70
70
|
# # EnforcedStyle is set to `semantic`. If so:
|
71
71
|
#
|
72
72
|
# # If the AllowBracesOnProceduralOneLiners option is unspecified, or
|
@@ -116,7 +116,7 @@ module RuboCop
|
|
116
116
|
#
|
117
117
|
# # Methods listed in the BracesRequiredMethods list, such as 'sig'
|
118
118
|
# # in this example, will require `{...}` braces. This option takes
|
119
|
-
# # precedence over all other configurations except
|
119
|
+
# # precedence over all other configurations except AllowedMethods.
|
120
120
|
#
|
121
121
|
# # bad
|
122
122
|
# sig do
|
@@ -138,7 +138,7 @@ module RuboCop
|
|
138
138
|
# puts foo
|
139
139
|
# end
|
140
140
|
#
|
141
|
-
# @example
|
141
|
+
# @example AllowedMethods: ['lambda', 'proc', 'it' ] (default)
|
142
142
|
#
|
143
143
|
# # good
|
144
144
|
# foo = lambda do |x|
|
@@ -149,9 +149,26 @@ module RuboCop
|
|
149
149
|
# x * 100
|
150
150
|
# end
|
151
151
|
#
|
152
|
+
# @example AllowedPatterns: [] (default)
|
153
|
+
#
|
154
|
+
# # bad
|
155
|
+
# things.map { |thing|
|
156
|
+
# something = thing.some_method
|
157
|
+
# process(something)
|
158
|
+
# }
|
159
|
+
#
|
160
|
+
# @example AllowedPatterns: [/map/]
|
161
|
+
#
|
162
|
+
# # good
|
163
|
+
# things.map { |thing|
|
164
|
+
# something = thing.some_method
|
165
|
+
# process(something)
|
166
|
+
# }
|
167
|
+
#
|
152
168
|
class BlockDelimiters < Base
|
153
169
|
include ConfigurableEnforcedStyle
|
154
|
-
include
|
170
|
+
include AllowedMethods
|
171
|
+
include AllowedPattern
|
155
172
|
include RangeHelp
|
156
173
|
extend AutoCorrector
|
157
174
|
|
@@ -330,12 +347,14 @@ module RuboCop
|
|
330
347
|
end
|
331
348
|
|
332
349
|
def special_method?(method_name)
|
333
|
-
|
350
|
+
allowed_method?(method_name) ||
|
351
|
+
matches_allowed_pattern?(method_name) ||
|
352
|
+
braces_required_method?(method_name)
|
334
353
|
end
|
335
354
|
|
336
355
|
def special_method_proper_block_style?(node)
|
337
356
|
method_name = node.method_name
|
338
|
-
return true if
|
357
|
+
return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)
|
339
358
|
return node.braces? if braces_required_method?(method_name)
|
340
359
|
end
|
341
360
|
|
@@ -117,10 +117,10 @@ module RuboCop
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def remove_end(corrector, body)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
120
|
+
remove_begin_pos = body.loc.end.begin_pos - leading_spaces(body).size
|
121
|
+
adjustment = processed_source.raw_source[remove_begin_pos] == ';' ? 0 : 1
|
122
|
+
range = range_between(remove_begin_pos, body.loc.end.end_pos + adjustment)
|
123
|
+
|
124
124
|
corrector.remove(range)
|
125
125
|
end
|
126
126
|
|
@@ -5,6 +5,8 @@ module RuboCop
|
|
5
5
|
module Style
|
6
6
|
# Enforces the use of `Object#instance_of?` instead of class comparison
|
7
7
|
# for equality.
|
8
|
+
# `==`, `equal?`, and `eql?` methods are allowed by default.
|
9
|
+
# These are customizable with `AllowedMethods` option.
|
8
10
|
#
|
9
11
|
# @example
|
10
12
|
# # bad
|
@@ -16,9 +18,50 @@ module RuboCop
|
|
16
18
|
# # good
|
17
19
|
# var.instance_of?(Date)
|
18
20
|
#
|
21
|
+
# @example AllowedMethods: [] (default)
|
22
|
+
# # good
|
23
|
+
# var.instance_of?(Date)
|
24
|
+
#
|
25
|
+
# # bad
|
26
|
+
# var.class == Date
|
27
|
+
# var.class.equal?(Date)
|
28
|
+
# var.class.eql?(Date)
|
29
|
+
# var.class.name == 'Date'
|
30
|
+
#
|
31
|
+
# @example AllowedMethods: [`==`]
|
32
|
+
# # good
|
33
|
+
# var.instance_of?(Date)
|
34
|
+
# var.class == Date
|
35
|
+
# var.class.name == 'Date'
|
36
|
+
#
|
37
|
+
# # bad
|
38
|
+
# var.class.equal?(Date)
|
39
|
+
# var.class.eql?(Date)
|
40
|
+
#
|
41
|
+
# @example AllowedPatterns: [] (default)
|
42
|
+
# # good
|
43
|
+
# var.instance_of?(Date)
|
44
|
+
#
|
45
|
+
# # bad
|
46
|
+
# var.class == Date
|
47
|
+
# var.class.equal?(Date)
|
48
|
+
# var.class.eql?(Date)
|
49
|
+
# var.class.name == 'Date'
|
50
|
+
#
|
51
|
+
# @example AllowedPatterns: [`/eq/`]
|
52
|
+
# # good
|
53
|
+
# var.instance_of?(Date)
|
54
|
+
# var.class.equal?(Date)
|
55
|
+
# var.class.eql?(Date)
|
56
|
+
#
|
57
|
+
# # bad
|
58
|
+
# var.class == Date
|
59
|
+
# var.class.name == 'Date'
|
60
|
+
#
|
19
61
|
class ClassEqualityComparison < Base
|
20
62
|
include RangeHelp
|
21
|
-
include
|
63
|
+
include AllowedMethods
|
64
|
+
include AllowedPattern
|
22
65
|
extend AutoCorrector
|
23
66
|
|
24
67
|
MSG = 'Use `instance_of?(%<class_name>s)` instead of comparing classes.'
|
@@ -34,7 +77,9 @@ module RuboCop
|
|
34
77
|
|
35
78
|
def on_send(node)
|
36
79
|
def_node = node.each_ancestor(:def, :defs).first
|
37
|
-
return if def_node &&
|
80
|
+
return if def_node &&
|
81
|
+
(allowed_method?(def_node.method_name) ||
|
82
|
+
matches_allowed_pattern?(def_node.method_name))
|
38
83
|
|
39
84
|
class_comparison_candidate?(node) do |receiver_node, class_node|
|
40
85
|
range = offense_range(receiver_node, node)
|
@@ -52,7 +97,9 @@ module RuboCop
|
|
52
97
|
if node.children.first.method?(:name)
|
53
98
|
return class_node.receiver.source if class_node.receiver
|
54
99
|
|
55
|
-
class_node.source.delete('"').delete("'")
|
100
|
+
value = class_node.source.delete('"').delete("'")
|
101
|
+
value.prepend('::') if class_node.each_ancestor(:class, :module).any?
|
102
|
+
value
|
56
103
|
else
|
57
104
|
class_node.source
|
58
105
|
end
|
@@ -89,6 +89,41 @@ module RuboCop
|
|
89
89
|
# if condition
|
90
90
|
# statement
|
91
91
|
# end
|
92
|
+
#
|
93
|
+
# @example AllowComments: false (default)
|
94
|
+
#
|
95
|
+
# # bad
|
96
|
+
# if condition
|
97
|
+
# statement
|
98
|
+
# else
|
99
|
+
# # something comment
|
100
|
+
# nil
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# # bad
|
104
|
+
# if condition
|
105
|
+
# statement
|
106
|
+
# else
|
107
|
+
# # something comment
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# @example AllowComments: true
|
111
|
+
#
|
112
|
+
# # good
|
113
|
+
# if condition
|
114
|
+
# statement
|
115
|
+
# else
|
116
|
+
# # something comment
|
117
|
+
# nil
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# # good
|
121
|
+
# if condition
|
122
|
+
# statement
|
123
|
+
# else
|
124
|
+
# # something comment
|
125
|
+
# end
|
126
|
+
#
|
92
127
|
class EmptyElse < Base
|
93
128
|
include OnNormalIfUnless
|
94
129
|
include ConfigurableEnforcedStyle
|
@@ -108,6 +143,8 @@ module RuboCop
|
|
108
143
|
private
|
109
144
|
|
110
145
|
def check(node)
|
146
|
+
return if cop_config['AllowComments'] && comment_in_else?(node.loc)
|
147
|
+
|
111
148
|
empty_check(node) if empty_style?
|
112
149
|
nil_check(node) if nil_style?
|
113
150
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for using empty heredoc to reduce redundancy.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# <<~EOS
|
12
|
+
# EOS
|
13
|
+
#
|
14
|
+
# <<-EOS
|
15
|
+
# EOS
|
16
|
+
#
|
17
|
+
# <<EOS
|
18
|
+
# EOS
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# ''
|
22
|
+
#
|
23
|
+
# # bad
|
24
|
+
# do_something(<<~EOS)
|
25
|
+
# EOS
|
26
|
+
#
|
27
|
+
# do_something(<<-EOS)
|
28
|
+
# EOS
|
29
|
+
#
|
30
|
+
# do_something(<<EOS)
|
31
|
+
# EOS
|
32
|
+
#
|
33
|
+
# # good
|
34
|
+
# do_something('')
|
35
|
+
#
|
36
|
+
class EmptyHeredoc < Base
|
37
|
+
include Heredoc
|
38
|
+
include RangeHelp
|
39
|
+
extend AutoCorrector
|
40
|
+
|
41
|
+
MSG = 'Use an empty string literal instead of heredoc.'
|
42
|
+
|
43
|
+
def on_heredoc(node)
|
44
|
+
heredoc_body = node.loc.heredoc_body
|
45
|
+
|
46
|
+
return unless heredoc_body.source.empty?
|
47
|
+
|
48
|
+
add_offense(node) do |corrector|
|
49
|
+
heredoc_end = node.loc.heredoc_end
|
50
|
+
|
51
|
+
corrector.replace(node, preferred_string_literal)
|
52
|
+
corrector.remove(range_by_whole_lines(heredoc_body, include_final_newline: true))
|
53
|
+
corrector.remove(range_by_whole_lines(heredoc_end, include_final_newline: true))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def preferred_string_literal
|
60
|
+
enforce_double_quotes? ? '""' : "''"
|
61
|
+
end
|
62
|
+
|
63
|
+
def enforce_double_quotes?
|
64
|
+
string_literals_config['EnforcedStyle'] == 'double_quotes'
|
65
|
+
end
|
66
|
+
|
67
|
+
def string_literals_config
|
68
|
+
config.for_cop('Style/StringLiterals')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -9,32 +9,15 @@ 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
|
-
#
|
15
12
|
# @example
|
16
13
|
# # bad
|
17
14
|
# ENV['X']
|
18
|
-
# ENV['X'] || 'string literal'
|
19
|
-
# ENV['X'] || some_method
|
20
15
|
# x = ENV['X']
|
21
16
|
#
|
22
|
-
# ENV['X'] || y.map do |a|
|
23
|
-
# puts a * 2
|
24
|
-
# end
|
25
|
-
#
|
26
17
|
# # good
|
27
18
|
# ENV.fetch('X')
|
28
|
-
# ENV.fetch('X', 'string literal')
|
29
|
-
# ENV.fetch('X') { some_method }
|
30
19
|
# x = ENV.fetch('X')
|
31
20
|
#
|
32
|
-
# ENV.fetch('X') do
|
33
|
-
# y.map do |a|
|
34
|
-
# puts a * 2
|
35
|
-
# end
|
36
|
-
# end
|
37
|
-
#
|
38
21
|
# # also good
|
39
22
|
# !ENV['X']
|
40
23
|
# ENV['X'].some_method # (e.g. `.nil?`)
|
@@ -42,48 +25,20 @@ module RuboCop
|
|
42
25
|
class FetchEnvVar < Base
|
43
26
|
extend AutoCorrector
|
44
27
|
|
45
|
-
|
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
|
28
|
+
MSG = 'Use `ENV.fetch(%<key>s)` or `ENV.fetch(%<key>s, nil)` instead of `ENV[%<key>s]`.'
|
51
29
|
|
52
30
|
# @!method env_with_bracket?(node)
|
53
31
|
def_node_matcher :env_with_bracket?, <<~PATTERN
|
54
32
|
(send (const nil? :ENV) :[] $_)
|
55
33
|
PATTERN
|
56
34
|
|
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 offensive_nodes(node)
|
68
|
-
def_node_search :offensive_nodes, <<~PATTERN
|
69
|
-
[#env_with_bracket? #offensive?]
|
70
|
-
PATTERN
|
71
|
-
|
72
35
|
def on_send(node)
|
73
36
|
env_with_bracket?(node) do |name_node|
|
74
37
|
break unless offensive?(node)
|
75
38
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if default_to_rhs?(target_node)
|
81
|
-
default_rhs(target_node, target_name_node)
|
82
|
-
else
|
83
|
-
default_nil(target_node, target_name_node)
|
84
|
-
end
|
85
|
-
else
|
86
|
-
default_nil(node, name_node)
|
39
|
+
message = format(MSG, key: name_node.source)
|
40
|
+
add_offense(node, message: message) do |corrector|
|
41
|
+
corrector.replace(node, new_code(name_node))
|
87
42
|
end
|
88
43
|
end
|
89
44
|
end
|
@@ -130,17 +85,6 @@ module RuboCop
|
|
130
85
|
!(allowed_var?(node) || allowable_use?(node))
|
131
86
|
end
|
132
87
|
|
133
|
-
def or_chain_root(node)
|
134
|
-
while operand_of_or?(ancestor_or ||= node.parent)
|
135
|
-
ancestor_or = ancestor_or.parent
|
136
|
-
end
|
137
|
-
ancestor_or
|
138
|
-
end
|
139
|
-
|
140
|
-
def default_to_rhs?(node)
|
141
|
-
operand_of_or?(node) && !right_end_of_or_chains?(node) && rhs_can_be_default_value?(node)
|
142
|
-
end
|
143
|
-
|
144
88
|
# Check if the node is a receiver and receives a message with dot syntax.
|
145
89
|
def message_chained_with_dot?(node)
|
146
90
|
return false if node.root?
|
@@ -157,8 +101,9 @@ module RuboCop
|
|
157
101
|
# it simply checks whether the variable is set.
|
158
102
|
# - Receiving a message with dot syntax, e.g. `ENV['X'].nil?`.
|
159
103
|
# - `ENV['key']` assigned by logical AND/OR assignment.
|
104
|
+
# - `ENV['key']` is the LHS of a `||`.
|
160
105
|
def allowable_use?(node)
|
161
|
-
used_as_flag?(node) || message_chained_with_dot?(node) || assigned?(node)
|
106
|
+
used_as_flag?(node) || message_chained_with_dot?(node) || assigned?(node) || or_lhs?(node)
|
162
107
|
end
|
163
108
|
|
164
109
|
# The following are allowed cases:
|
@@ -172,127 +117,15 @@ module RuboCop
|
|
172
117
|
node == lhs
|
173
118
|
end
|
174
119
|
|
175
|
-
def
|
176
|
-
return false unless
|
177
|
-
|
178
|
-
node.parent.lhs == node
|
179
|
-
end
|
180
|
-
|
181
|
-
def right_end_of_or_chains?(node)
|
182
|
-
!(left_end_of_or_chains?(node) || node.parent&.parent&.or_type?)
|
183
|
-
end
|
184
|
-
|
185
|
-
def conterpart_rhs_of(node)
|
186
|
-
left_end_of_or_chains?(node) ? node.parent.rhs : node.parent.parent.rhs
|
187
|
-
end
|
188
|
-
|
189
|
-
def rhs_can_be_default_value?(node)
|
190
|
-
!rhs_is_block_control?(node)
|
191
|
-
end
|
120
|
+
def or_lhs?(node)
|
121
|
+
return false unless (parent = node.parent)&.or_type?
|
192
122
|
|
193
|
-
|
194
|
-
block_control?(conterpart_rhs_of(node))
|
123
|
+
parent.lhs == node || parent.parent&.or_type?
|
195
124
|
end
|
196
125
|
|
197
|
-
def
|
126
|
+
def new_code(name_node)
|
198
127
|
"ENV.fetch(#{name_node.source}, nil)"
|
199
128
|
end
|
200
|
-
|
201
|
-
def new_code_default_rhs_single_line(node, name_node)
|
202
|
-
parent = node.parent
|
203
|
-
if parent.rhs.basic_literal?
|
204
|
-
"ENV.fetch(#{name_node.source}, #{parent.rhs.source})"
|
205
|
-
else
|
206
|
-
"ENV.fetch(#{name_node.source}) { #{parent.rhs.source} }"
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def new_code_default_rhs_multiline(node, name_node)
|
211
|
-
env_indent = indent(node.parent)
|
212
|
-
default = node.parent.rhs.source.split("\n").map do |line|
|
213
|
-
"#{env_indent}#{line}"
|
214
|
-
end.join("\n")
|
215
|
-
<<~NEW_CODE.chomp
|
216
|
-
ENV.fetch(#{name_node.source}) do
|
217
|
-
#{configured_indentation}#{default}
|
218
|
-
#{env_indent}end
|
219
|
-
NEW_CODE
|
220
|
-
end
|
221
|
-
|
222
|
-
def new_code_default_rhs(node, name_node)
|
223
|
-
if node.parent.rhs.single_line?
|
224
|
-
new_code_default_rhs_single_line(node, name_node)
|
225
|
-
else
|
226
|
-
new_code_default_rhs_multiline(node, name_node)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def default_rhs(node, name_node)
|
231
|
-
if left_end_of_or_chains?(node)
|
232
|
-
default_rhs_in_same_or(node, name_node)
|
233
|
-
else
|
234
|
-
default_rhs_in_outer_or(node, name_node)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
# Adds an offense and sets `nil` to the default value of `ENV.fetch`.
|
239
|
-
# `ENV['X']` --> `ENV.fetch('X', nil)`
|
240
|
-
def default_nil(node, name_node)
|
241
|
-
message = format(MSG_DEFAULT_NIL, key: name_node.source)
|
242
|
-
|
243
|
-
add_offense(node, message: message) do |corrector|
|
244
|
-
corrector.replace(node, new_code_default_nil(name_node))
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# Adds an offense and makes the RHS the default value of `ENV.fetch`.
|
249
|
-
# `ENV['X'] || y` --> `ENV.fetch('X') { y }`
|
250
|
-
def default_rhs_in_same_or(node, name_node)
|
251
|
-
template = message_template_for(node.parent.rhs)
|
252
|
-
message = format(template,
|
253
|
-
key: name_node.source,
|
254
|
-
default: first_line_of(node.parent.rhs.source))
|
255
|
-
|
256
|
-
add_offense(node, message: message) do |corrector|
|
257
|
-
corrector.replace(node.parent, new_code_default_rhs(node, name_node))
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
# Adds an offense and makes the RHS the default value of `ENV.fetch`.
|
262
|
-
# `z || ENV['X'] || y` --> `z || ENV.fetch('X') { y }`
|
263
|
-
def default_rhs_in_outer_or(node, name_node)
|
264
|
-
parent = node.parent
|
265
|
-
grand_parent = parent.parent
|
266
|
-
|
267
|
-
template = message_template_for(grand_parent.rhs)
|
268
|
-
message = format(template,
|
269
|
-
key: name_node.source,
|
270
|
-
default: first_line_of(grand_parent.rhs.source))
|
271
|
-
|
272
|
-
add_offense(node, message: message) do |corrector|
|
273
|
-
lhs_code = parent.lhs.source
|
274
|
-
rhs_code = new_code_default_rhs(parent, name_node)
|
275
|
-
corrector.replace(grand_parent, "#{lhs_code} || #{rhs_code}")
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
def message_template_for(rhs)
|
280
|
-
if rhs.multiline?
|
281
|
-
MSG_DEFAULT_RHS_MULTILINE_BLOCK
|
282
|
-
elsif rhs.basic_literal?
|
283
|
-
MSG_DEFAULT_RHS_SECOND_ARG_OF_FETCH
|
284
|
-
else
|
285
|
-
MSG_DEFAULT_RHS_SINGLE_LINE_BLOCK
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
def configured_indentation
|
290
|
-
' ' * (config.for_cop('Layout/IndentationWidth')['Width'] || 2)
|
291
|
-
end
|
292
|
-
|
293
|
-
def first_line_of(source)
|
294
|
-
source.split("\n").first
|
295
|
-
end
|
296
129
|
end
|
297
130
|
end
|
298
131
|
end
|