rubocop 1.6.1 → 1.9.1
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/LICENSE.txt +1 -1
- data/README.md +4 -3
- data/config/default.yml +145 -19
- data/lib/rubocop.rb +16 -1
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +5 -4
- data/lib/rubocop/comment_config.rb +6 -6
- data/lib/rubocop/config.rb +13 -7
- data/lib/rubocop/config_loader.rb +11 -14
- data/lib/rubocop/config_loader_resolver.rb +21 -4
- data/lib/rubocop/config_obsoletion.rb +5 -3
- data/lib/rubocop/config_store.rb +12 -1
- data/lib/rubocop/cop/base.rb +2 -1
- data/lib/rubocop/cop/exclude_limit.rb +26 -0
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +3 -2
- data/lib/rubocop/cop/generator.rb +1 -3
- data/lib/rubocop/cop/internal_affairs.rb +6 -1
- data/lib/rubocop/cop/internal_affairs/empty_line_between_expect_offense_and_correction.rb +68 -0
- data/lib/rubocop/cop/internal_affairs/example_description.rb +89 -0
- data/lib/rubocop/cop/internal_affairs/redundant_described_class_as_subject.rb +61 -0
- data/lib/rubocop/cop/internal_affairs/redundant_let_rubocop_config_new.rb +64 -0
- data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +145 -0
- data/lib/rubocop/cop/layout/class_structure.rb +7 -2
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +56 -20
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +16 -2
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +14 -0
- data/lib/rubocop/cop/layout/line_length.rb +2 -1
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -2
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -10
- data/lib/rubocop/cop/layout/space_around_equals_in_parameter_default.rb +1 -0
- data/lib/rubocop/cop/layout/space_before_block_braces.rb +2 -0
- data/lib/rubocop/cop/layout/space_before_brackets.rb +67 -0
- data/lib/rubocop/cop/layout/space_inside_block_braces.rb +13 -10
- data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +2 -2
- data/lib/rubocop/cop/lint/ambiguous_assignment.rb +59 -0
- data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +7 -2
- data/lib/rubocop/cop/lint/deprecated_constants.rb +80 -0
- data/lib/rubocop/cop/lint/duplicate_branch.rb +64 -2
- data/lib/rubocop/cop/lint/lambda_without_literal_block.rb +44 -0
- data/lib/rubocop/cop/lint/non_deterministic_require_order.rb +10 -6
- data/lib/rubocop/cop/lint/number_conversion.rb +41 -6
- data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +47 -0
- data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +39 -0
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +2 -1
- data/lib/rubocop/cop/lint/redundant_dir_glob_sort.rb +50 -0
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +50 -17
- data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -11
- data/lib/rubocop/cop/lint/symbol_conversion.rb +103 -0
- data/lib/rubocop/cop/lint/triple_quotes.rb +71 -0
- data/lib/rubocop/cop/lint/unreachable_loop.rb +17 -0
- data/lib/rubocop/cop/message_annotator.rb +4 -1
- data/lib/rubocop/cop/metrics/block_nesting.rb +2 -2
- data/lib/rubocop/cop/metrics/parameter_lists.rb +5 -2
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/mixin/allowed_identifiers.rb +18 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +5 -0
- data/lib/rubocop/cop/mixin/code_length.rb +3 -1
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -11
- data/lib/rubocop/cop/mixin/configurable_max.rb +1 -0
- data/lib/rubocop/cop/mixin/first_element_line_break.rb +1 -1
- data/lib/rubocop/cop/mixin/method_complexity.rb +3 -1
- data/lib/rubocop/cop/mixin/uncommunicative_name.rb +5 -1
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +59 -5
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +38 -5
- data/lib/rubocop/cop/naming/variable_name.rb +2 -0
- data/lib/rubocop/cop/naming/variable_number.rb +2 -9
- data/lib/rubocop/cop/registry.rb +10 -0
- data/lib/rubocop/cop/severity.rb +3 -3
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +3 -1
- data/lib/rubocop/cop/style/ascii_comments.rb +1 -1
- data/lib/rubocop/cop/style/collection_methods.rb +14 -1
- data/lib/rubocop/cop/style/commented_keyword.rb +22 -5
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +49 -9
- data/lib/rubocop/cop/style/empty_literal.rb +6 -2
- data/lib/rubocop/cop/style/endless_method.rb +102 -0
- data/lib/rubocop/cop/style/eval_with_location.rb +63 -34
- data/lib/rubocop/cop/style/explicit_block_argument.rb +10 -0
- data/lib/rubocop/cop/style/float_division.rb +3 -0
- data/lib/rubocop/cop/style/for.rb +2 -0
- data/lib/rubocop/cop/style/format_string_token.rb +18 -2
- data/lib/rubocop/cop/style/hash_except.rb +95 -0
- data/lib/rubocop/cop/style/hash_like_case.rb +2 -1
- data/lib/rubocop/cop/style/if_inside_else.rb +22 -10
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +120 -0
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -2
- data/lib/rubocop/cop/style/lambda_call.rb +2 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +7 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +16 -6
- data/lib/rubocop/cop/style/method_def_parentheses.rb +7 -0
- data/lib/rubocop/cop/style/multiline_method_signature.rb +26 -1
- data/lib/rubocop/cop/style/multiline_when_then.rb +3 -1
- data/lib/rubocop/cop/style/mutable_constant.rb +13 -3
- data/lib/rubocop/cop/style/nested_parenthesized_calls.rb +4 -0
- data/lib/rubocop/cop/style/nil_comparison.rb +3 -0
- data/lib/rubocop/cop/style/non_nil_check.rb +23 -13
- data/lib/rubocop/cop/style/numeric_literals.rb +6 -9
- data/lib/rubocop/cop/style/numeric_predicate.rb +1 -1
- data/lib/rubocop/cop/style/raise_args.rb +5 -2
- data/lib/rubocop/cop/style/redundant_argument.rb +7 -1
- data/lib/rubocop/cop/style/redundant_freeze.rb +8 -4
- data/lib/rubocop/cop/style/redundant_return.rb +1 -1
- data/lib/rubocop/cop/style/single_line_methods.rb +36 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +29 -5
- data/lib/rubocop/cop/style/string_concatenation.rb +1 -1
- data/lib/rubocop/cop/style/symbol_proc.rb +5 -4
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/while_until_modifier.rb +2 -4
- data/lib/rubocop/cop/util.rb +3 -1
- data/lib/rubocop/formatter/git_hub_actions_formatter.rb +1 -0
- data/lib/rubocop/formatter/simple_text_formatter.rb +2 -1
- data/lib/rubocop/magic_comment.rb +30 -1
- data/lib/rubocop/options.rb +10 -10
- data/lib/rubocop/rspec/cop_helper.rb +0 -4
- data/lib/rubocop/rspec/expect_offense.rb +37 -22
- data/lib/rubocop/runner.rb +17 -1
- data/lib/rubocop/target_finder.rb +4 -2
- data/lib/rubocop/target_ruby.rb +47 -11
- data/lib/rubocop/util.rb +16 -0
- data/lib/rubocop/version.rb +8 -2
- metadata +27 -7
@@ -3,9 +3,14 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Style
|
6
|
-
# This cop
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# This cop ensures that eval methods (`eval`, `instance_eval`, `class_eval`
|
7
|
+
# and `module_eval`) are given filename and line number values (`__FILE__`
|
8
|
+
# and `__LINE__`). This data is used to ensure that any errors raised
|
9
|
+
# within the evaluated code will be given the correct identification
|
10
|
+
# in a backtrace.
|
11
|
+
#
|
12
|
+
# The cop also checks that the line number given relative to `__LINE__` is
|
13
|
+
# correct.
|
9
14
|
#
|
10
15
|
# @example
|
11
16
|
# # bad
|
@@ -32,27 +37,17 @@ module RuboCop
|
|
32
37
|
# end
|
33
38
|
# RUBY
|
34
39
|
class EvalWithLocation < Base
|
35
|
-
MSG = 'Pass `__FILE__` and `__LINE__` to
|
36
|
-
|
37
|
-
|
38
|
-
'
|
40
|
+
MSG = 'Pass `__FILE__` and `__LINE__` to `%<method_name>s`.'
|
41
|
+
MSG_EVAL = 'Pass a binding, `__FILE__` and `__LINE__` to `eval`.'
|
42
|
+
MSG_INCORRECT_FILE = 'Incorrect file for `%<method_name>s`; ' \
|
43
|
+
'use `%<expected>s` instead of `%<actual>s`.'
|
44
|
+
MSG_INCORRECT_LINE = 'Incorrect line number for `%<method_name>s`; ' \
|
45
|
+
'use `%<expected>s` instead of `%<actual>s`.'
|
39
46
|
|
40
47
|
RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
|
41
48
|
|
42
|
-
def_node_matcher :
|
43
|
-
{
|
44
|
-
(send nil? :eval ${str dstr})
|
45
|
-
(send nil? :eval ${str dstr} _)
|
46
|
-
(send nil? :eval ${str dstr} _ #special_file_keyword?)
|
47
|
-
(send nil? :eval ${str dstr} _ #special_file_keyword? _)
|
48
|
-
|
49
|
-
(send _ {:class_eval :module_eval :instance_eval}
|
50
|
-
${str dstr})
|
51
|
-
(send _ {:class_eval :module_eval :instance_eval}
|
52
|
-
${str dstr} #special_file_keyword?)
|
53
|
-
(send _ {:class_eval :module_eval :instance_eval}
|
54
|
-
${str dstr} #special_file_keyword? _)
|
55
|
-
}
|
49
|
+
def_node_matcher :valid_eval_receiver?, <<~PATTERN
|
50
|
+
{ nil? (const {nil? cbase} :Kernel) }
|
56
51
|
PATTERN
|
57
52
|
|
58
53
|
def_node_matcher :line_with_offset?, <<~PATTERN
|
@@ -63,17 +58,31 @@ module RuboCop
|
|
63
58
|
PATTERN
|
64
59
|
|
65
60
|
def on_send(node)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
61
|
+
# Classes should not redefine eval, but in case one does, it shouldn't
|
62
|
+
# register an offense. Only `eval` without a receiver and `Kernel.eval`
|
63
|
+
# are considered.
|
64
|
+
return if node.method?(:eval) && !valid_eval_receiver?(node.receiver)
|
65
|
+
|
66
|
+
code = node.arguments.first
|
67
|
+
return unless code && (code.str_type? || code.dstr_type?)
|
68
|
+
|
69
|
+
file, line = file_and_line(node)
|
70
|
+
|
71
|
+
if line
|
72
|
+
check_file(node, file)
|
73
|
+
check_line(node, code)
|
74
|
+
else
|
75
|
+
register_offense(node)
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
79
|
private
|
76
80
|
|
81
|
+
def register_offense(node)
|
82
|
+
msg = node.method?(:eval) ? MSG_EVAL : format(MSG, method_name: node.method_name)
|
83
|
+
add_offense(node, message: msg)
|
84
|
+
end
|
85
|
+
|
77
86
|
def special_file_keyword?(node)
|
78
87
|
node.str_type? &&
|
79
88
|
node.source == '__FILE__'
|
@@ -84,6 +93,11 @@ module RuboCop
|
|
84
93
|
node.source == '__LINE__'
|
85
94
|
end
|
86
95
|
|
96
|
+
def file_and_line(node)
|
97
|
+
base = node.method?(:eval) ? 2 : 1
|
98
|
+
[node.arguments[base], node.arguments[base + 1]]
|
99
|
+
end
|
100
|
+
|
87
101
|
# FIXME: It's a Style/ConditionalAssignment's false positive.
|
88
102
|
# rubocop:disable Style/ConditionalAssignment
|
89
103
|
def with_lineno?(node)
|
@@ -95,17 +109,32 @@ module RuboCop
|
|
95
109
|
end
|
96
110
|
# rubocop:enable Style/ConditionalAssignment
|
97
111
|
|
98
|
-
def message_incorrect_line(actual, sign, line_diff)
|
112
|
+
def message_incorrect_line(method_name, actual, sign, line_diff)
|
99
113
|
expected =
|
100
114
|
if line_diff.zero?
|
101
115
|
'__LINE__'
|
102
116
|
else
|
103
117
|
"__LINE__ #{sign} #{line_diff}"
|
104
118
|
end
|
105
|
-
|
119
|
+
|
120
|
+
format(MSG_INCORRECT_LINE,
|
121
|
+
method_name: method_name,
|
122
|
+
actual: actual.source,
|
123
|
+
expected: expected)
|
124
|
+
end
|
125
|
+
|
126
|
+
def check_file(node, file_node)
|
127
|
+
return true if special_file_keyword?(file_node)
|
128
|
+
|
129
|
+
message = format(MSG_INCORRECT_FILE,
|
130
|
+
method_name: node.method_name,
|
131
|
+
expected: '__FILE__',
|
132
|
+
actual: file_node.source)
|
133
|
+
|
134
|
+
add_offense(file_node, message: message)
|
106
135
|
end
|
107
136
|
|
108
|
-
def
|
137
|
+
def check_line(node, code)
|
109
138
|
line_node = node.arguments.last
|
110
139
|
lineno_range = line_node.loc.expression
|
111
140
|
line_diff = string_first_line(code) - lineno_range.first_line
|
@@ -124,22 +153,22 @@ module RuboCop
|
|
124
153
|
end
|
125
154
|
end
|
126
155
|
|
127
|
-
def add_offense_for_same_line(
|
156
|
+
def add_offense_for_same_line(node, line_node)
|
128
157
|
return if special_line_keyword?(line_node)
|
129
158
|
|
130
159
|
add_offense(
|
131
160
|
line_node.loc.expression,
|
132
|
-
message: message_incorrect_line(line_node, nil, 0)
|
161
|
+
message: message_incorrect_line(node.method_name, line_node, nil, 0)
|
133
162
|
)
|
134
163
|
end
|
135
164
|
|
136
|
-
def add_offense_for_different_line(
|
165
|
+
def add_offense_for_different_line(node, line_node, line_diff)
|
137
166
|
sign = line_diff.positive? ? :+ : :-
|
138
167
|
return if line_with_offset?(line_node, sign, line_diff.abs)
|
139
168
|
|
140
169
|
add_offense(
|
141
170
|
line_node.loc.expression,
|
142
|
-
message: message_incorrect_line(line_node, sign, line_diff.abs)
|
171
|
+
message: message_incorrect_line(node.method_name, line_node, sign, line_diff.abs)
|
143
172
|
)
|
144
173
|
end
|
145
174
|
end
|
@@ -6,6 +6,9 @@ module RuboCop
|
|
6
6
|
# This cop enforces the use of explicit block argument to avoid writing
|
7
7
|
# block literal that just passes its arguments to another block.
|
8
8
|
#
|
9
|
+
# NOTE: This cop only registers an offense if the block args match the
|
10
|
+
# yield args exactly.
|
11
|
+
#
|
9
12
|
# @example
|
10
13
|
# # bad
|
11
14
|
# def with_tmp_dir
|
@@ -75,7 +78,14 @@ module RuboCop
|
|
75
78
|
private
|
76
79
|
|
77
80
|
def yielding_arguments?(block_args, yield_args)
|
81
|
+
yield_args = yield_args.dup.fill(
|
82
|
+
nil,
|
83
|
+
yield_args.length, block_args.length - yield_args.length
|
84
|
+
)
|
85
|
+
|
78
86
|
yield_args.zip(block_args).all? do |yield_arg, block_arg|
|
87
|
+
next false unless yield_arg && block_arg
|
88
|
+
|
79
89
|
block_arg && yield_arg.children.first == block_arg.children.first
|
80
90
|
end
|
81
91
|
end
|
@@ -7,6 +7,9 @@ module RuboCop
|
|
7
7
|
# It is recommended to either always use `fdiv` or coerce one side only.
|
8
8
|
# This cop also provides other options for code consistency.
|
9
9
|
#
|
10
|
+
# This cop is marked as unsafe, because if operand variable is a string object
|
11
|
+
# then `.to_f` will be removed and an error will occur.
|
12
|
+
#
|
10
13
|
# @example EnforcedStyle: single_coerce (default)
|
11
14
|
# # bad
|
12
15
|
# a.to_f / b.to_f
|
@@ -51,6 +51,7 @@ module RuboCop
|
|
51
51
|
if style == :each
|
52
52
|
add_offense(node, message: PREFER_EACH) do |corrector|
|
53
53
|
ForToEachCorrector.new(node).call(corrector)
|
54
|
+
opposite_style_detected
|
54
55
|
end
|
55
56
|
else
|
56
57
|
correct_style_detected
|
@@ -63,6 +64,7 @@ module RuboCop
|
|
63
64
|
if style == :for
|
64
65
|
add_offense(node, message: PREFER_FOR) do |corrector|
|
65
66
|
EachToForCorrector.new(node).call(corrector)
|
67
|
+
opposite_style_detected
|
66
68
|
end
|
67
69
|
else
|
68
70
|
correct_style_detected
|
@@ -11,6 +11,8 @@ module RuboCop
|
|
11
11
|
# The reason is that _unannotated_ format is very similar
|
12
12
|
# to encoded URLs or Date/Time formatting strings.
|
13
13
|
#
|
14
|
+
# This cop can be customized ignored methods with `IgnoredMethods`.
|
15
|
+
#
|
14
16
|
# @example EnforcedStyle: annotated (default)
|
15
17
|
#
|
16
18
|
# # bad
|
@@ -58,12 +60,18 @@ module RuboCop
|
|
58
60
|
#
|
59
61
|
# # good
|
60
62
|
# format('%06d', 10)
|
63
|
+
#
|
64
|
+
# @example IgnoredMethods: [redirect]
|
65
|
+
#
|
66
|
+
# # good
|
67
|
+
# redirect('foo/%{bar_id}')
|
68
|
+
#
|
61
69
|
class FormatStringToken < Base
|
62
70
|
include ConfigurableEnforcedStyle
|
71
|
+
include IgnoredMethods
|
63
72
|
|
64
73
|
def on_str(node)
|
65
|
-
return
|
66
|
-
return if node.each_ancestor(:xstr, :regexp).any?
|
74
|
+
return if format_string_token?(node) || use_ignored_method?(node)
|
67
75
|
|
68
76
|
detections = collect_detections(node)
|
69
77
|
return if detections.empty?
|
@@ -88,6 +96,14 @@ module RuboCop
|
|
88
96
|
}
|
89
97
|
PATTERN
|
90
98
|
|
99
|
+
def format_string_token?(node)
|
100
|
+
!node.value.include?('%') || node.each_ancestor(:xstr, :regexp).any?
|
101
|
+
end
|
102
|
+
|
103
|
+
def use_ignored_method?(node)
|
104
|
+
(parent = node.parent) && parent.send_type? && ignored_method?(parent.method_name)
|
105
|
+
end
|
106
|
+
|
91
107
|
def unannotated_format?(node, detected_style)
|
92
108
|
detected_style == :unannotated && !format_string_in_typical_context?(node)
|
93
109
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop checks for usages of `Hash#reject`, `Hash#select`, and `Hash#filter` methods
|
7
|
+
# that can be replaced with `Hash#except` method.
|
8
|
+
#
|
9
|
+
# This cop should only be enabled on Ruby version 3.0 or higher.
|
10
|
+
# (`Hash#except` was added in Ruby 3.0.)
|
11
|
+
#
|
12
|
+
# For safe detection, it is limited to commonly used string and symbol comparisons
|
13
|
+
# when used `==`.
|
14
|
+
# And do not check `Hash#delete_if` and `Hash#keep_if` to change receiver object.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
#
|
18
|
+
# # bad
|
19
|
+
# {foo: 1, bar: 2, baz: 3}.reject {|k, v| k == :bar }
|
20
|
+
# {foo: 1, bar: 2, baz: 3}.select {|k, v| k != :bar }
|
21
|
+
# {foo: 1, bar: 2, baz: 3}.filter {|k, v| k != :bar }
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# {foo: 1, bar: 2, baz: 3}.except(:bar)
|
25
|
+
#
|
26
|
+
class HashExcept < Base
|
27
|
+
include RangeHelp
|
28
|
+
extend TargetRubyVersion
|
29
|
+
extend AutoCorrector
|
30
|
+
|
31
|
+
minimum_target_ruby_version 3.0
|
32
|
+
|
33
|
+
MSG = 'Use `%<prefer>s` instead.'
|
34
|
+
RESTRICT_ON_SEND = %i[reject select filter].freeze
|
35
|
+
|
36
|
+
def_node_matcher :bad_method?, <<~PATTERN
|
37
|
+
(block
|
38
|
+
(send _ _)
|
39
|
+
(args
|
40
|
+
(arg _)
|
41
|
+
(arg _))
|
42
|
+
(send
|
43
|
+
_ {:== :!= :eql?} _))
|
44
|
+
PATTERN
|
45
|
+
|
46
|
+
def on_send(node)
|
47
|
+
block = node.parent
|
48
|
+
return unless bad_method?(block) && semantically_except_method?(node, block)
|
49
|
+
|
50
|
+
except_key = except_key(block)
|
51
|
+
return unless safe_to_register_offense?(block, except_key)
|
52
|
+
|
53
|
+
range = offense_range(node)
|
54
|
+
preferred_method = "except(#{except_key.source})"
|
55
|
+
|
56
|
+
add_offense(range, message: format(MSG, prefer: preferred_method)) do |corrector|
|
57
|
+
corrector.replace(range, preferred_method)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def semantically_except_method?(send, block)
|
64
|
+
body = block.body
|
65
|
+
|
66
|
+
case send.method_name
|
67
|
+
when :reject
|
68
|
+
body.method?('==') || body.method?('eql?')
|
69
|
+
when :select, :filter
|
70
|
+
body.method?('!=')
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def safe_to_register_offense?(block, except_key)
|
77
|
+
return true if block.body.method?('eql?')
|
78
|
+
|
79
|
+
except_key.sym_type? || except_key.str_type?
|
80
|
+
end
|
81
|
+
|
82
|
+
def except_key(node)
|
83
|
+
key_argument = node.argument_list.first
|
84
|
+
lhs, _method_name, rhs = *node.body
|
85
|
+
|
86
|
+
[lhs, rhs].find { |operand| operand.source != key_argument.source }
|
87
|
+
end
|
88
|
+
|
89
|
+
def offense_range(node)
|
90
|
+
range_between(node.loc.selector.begin_pos, node.parent.loc.end.end_pos)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -68,7 +68,8 @@ module RuboCop
|
|
68
68
|
length = cop_config['MinBranchesCount'] || 3
|
69
69
|
return length if length.is_a?(Integer) && length.positive?
|
70
70
|
|
71
|
-
|
71
|
+
warn Rainbow('`MinBranchesCount` needs to be a positive integer!').red
|
72
|
+
exit!
|
72
73
|
end
|
73
74
|
end
|
74
75
|
end
|
@@ -82,15 +82,14 @@ module RuboCop
|
|
82
82
|
def autocorrect(corrector, node)
|
83
83
|
if node.modifier_form?
|
84
84
|
correct_to_elsif_from_modifier_form(corrector, node)
|
85
|
-
end_range = node.parent.loc.end
|
86
85
|
else
|
87
86
|
correct_to_elsif_from_if_inside_else_form(corrector, node, node.condition)
|
88
|
-
end_range = node.loc.end
|
89
87
|
end
|
90
|
-
corrector.remove(range_by_whole_lines(
|
91
|
-
|
92
|
-
|
93
|
-
)
|
88
|
+
corrector.remove(range_by_whole_lines(find_end_range(node), include_final_newline: true))
|
89
|
+
return unless (if_branch = node.if_branch)
|
90
|
+
|
91
|
+
range = range_by_whole_lines(if_branch.source_range, include_final_newline: true)
|
92
|
+
corrector.remove(range)
|
94
93
|
end
|
95
94
|
|
96
95
|
def correct_to_elsif_from_modifier_form(corrector, node)
|
@@ -103,13 +102,26 @@ module RuboCop
|
|
103
102
|
|
104
103
|
def correct_to_elsif_from_if_inside_else_form(corrector, node, condition)
|
105
104
|
corrector.replace(node.parent.loc.else, "elsif #{condition.source}")
|
106
|
-
if_condition_range =
|
107
|
-
|
108
|
-
|
109
|
-
|
105
|
+
if_condition_range = if_condition_range(node, condition)
|
106
|
+
if (if_branch = node.if_branch)
|
107
|
+
corrector.replace(if_condition_range, if_branch.source)
|
108
|
+
else
|
109
|
+
corrector.remove(range_by_whole_lines(if_condition_range, include_final_newline: true))
|
110
|
+
end
|
110
111
|
corrector.remove(condition)
|
111
112
|
end
|
112
113
|
|
114
|
+
def find_end_range(node)
|
115
|
+
end_range = node.loc.end
|
116
|
+
return end_range if end_range
|
117
|
+
|
118
|
+
find_end_range(node.parent)
|
119
|
+
end
|
120
|
+
|
121
|
+
def if_condition_range(node, condition)
|
122
|
+
range_between(node.loc.keyword.begin_pos, condition.source_range.end_pos)
|
123
|
+
end
|
124
|
+
|
113
125
|
def allow_if_modifier_in_else_branch?(else_branch)
|
114
126
|
allow_if_modifier? && else_branch&.modifier_form?
|
115
127
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop checks for redundant `if` with boolean literal branches.
|
7
|
+
# It checks only conditions to return boolean value (`true` or `false`) for safe detection.
|
8
|
+
# The conditions to be checked are comparison methods, predicate methods, and double negative.
|
9
|
+
# However, auto-correction is unsafe because there is no guarantee that all predicate methods
|
10
|
+
# will return boolean value. Those methods can be allowed with `AllowedMethods` config.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# if foo == bar
|
15
|
+
# true
|
16
|
+
# else
|
17
|
+
# false
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # bad
|
21
|
+
# foo == bar ? true : false
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# foo == bar
|
25
|
+
#
|
26
|
+
# @example AllowedMethods: ['nonzero?']
|
27
|
+
# # good
|
28
|
+
# num.nonzero? ? true : false
|
29
|
+
#
|
30
|
+
class IfWithBooleanLiteralBranches < Base
|
31
|
+
include AllowedMethods
|
32
|
+
extend AutoCorrector
|
33
|
+
|
34
|
+
MSG = 'Remove redundant %<keyword>s with boolean literal branches.'
|
35
|
+
MSG_FOR_ELSIF = 'Use `else` instead of redundant `elsif` with boolean literal branches.'
|
36
|
+
|
37
|
+
def_node_matcher :if_with_boolean_literal_branches?, <<~PATTERN
|
38
|
+
(if #return_boolean_value? {(true) (false) | (false) (true)})
|
39
|
+
PATTERN
|
40
|
+
def_node_matcher :double_negative?, '(send (send _ :!) :!)'
|
41
|
+
|
42
|
+
def on_if(node)
|
43
|
+
return unless if_with_boolean_literal_branches?(node)
|
44
|
+
|
45
|
+
condition = node.condition
|
46
|
+
range, keyword = offense_range_with_keyword(node, condition)
|
47
|
+
|
48
|
+
add_offense(range, message: message(node, keyword)) do |corrector|
|
49
|
+
replacement = replacement_condition(node, condition)
|
50
|
+
|
51
|
+
if node.elsif?
|
52
|
+
corrector.insert_before(node, "else\n")
|
53
|
+
corrector.replace(node, "#{indent(node.if_branch)}#{replacement}")
|
54
|
+
else
|
55
|
+
corrector.replace(node, replacement)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def offense_range_with_keyword(node, condition)
|
63
|
+
if node.ternary?
|
64
|
+
range = condition.source_range.end.join(node.source_range.end)
|
65
|
+
|
66
|
+
[range, 'ternary operator']
|
67
|
+
else
|
68
|
+
keyword = node.loc.keyword
|
69
|
+
|
70
|
+
[keyword, "`#{keyword.source}`"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def message(node, keyword)
|
75
|
+
message_template = node.elsif? ? MSG_FOR_ELSIF : MSG
|
76
|
+
|
77
|
+
format(message_template, keyword: keyword)
|
78
|
+
end
|
79
|
+
|
80
|
+
def return_boolean_value?(condition)
|
81
|
+
if condition.begin_type?
|
82
|
+
return_boolean_value?(condition.children.first)
|
83
|
+
elsif condition.or_type?
|
84
|
+
return_boolean_value?(condition.lhs) && return_boolean_value?(condition.rhs)
|
85
|
+
elsif condition.and_type?
|
86
|
+
return_boolean_value?(condition.rhs)
|
87
|
+
else
|
88
|
+
assume_boolean_value?(condition)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def assume_boolean_value?(condition)
|
93
|
+
return false unless condition.send_type?
|
94
|
+
return false if allowed_method?(condition.method_name)
|
95
|
+
|
96
|
+
condition.comparison_method? || condition.predicate_method? || double_negative?(condition)
|
97
|
+
end
|
98
|
+
|
99
|
+
def replacement_condition(node, condition)
|
100
|
+
bang = '!' if opposite_condition?(node)
|
101
|
+
|
102
|
+
if bang && require_parentheses?(condition)
|
103
|
+
"#{bang}(#{condition.source})"
|
104
|
+
else
|
105
|
+
"#{bang}#{condition.source}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def opposite_condition?(node)
|
110
|
+
!node.unless? && node.if_branch.false_type? || node.unless? && node.if_branch.true_type?
|
111
|
+
end
|
112
|
+
|
113
|
+
def require_parentheses?(condition)
|
114
|
+
condition.and_type? || condition.or_type? ||
|
115
|
+
condition.send_type? && condition.comparison_method?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|