rubocop 1.68.0 → 1.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +41 -6
- data/lib/rubocop/cop/base.rb +1 -1
- data/lib/rubocop/cop/bundler/gem_filename.rb +0 -1
- data/lib/rubocop/cop/bundler/insecure_protocol_source.rb +0 -1
- data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +1 -1
- data/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +1 -2
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +0 -2
- data/lib/rubocop/cop/internal_affairs/location_line_equality_comparison.rb +2 -4
- data/lib/rubocop/cop/internal_affairs/numblock_handler.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/operator_keyword.rb +46 -0
- data/lib/rubocop/cop/internal_affairs/style_detected_api_use.rb +0 -2
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/array_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/begin_end_alignment.rb +0 -1
- data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +2 -3
- data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +3 -4
- data/lib/rubocop/cop/layout/empty_lines_around_method_body.rb +3 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +7 -7
- data/lib/rubocop/cop/layout/leading_comment_space.rb +15 -0
- data/lib/rubocop/cop/layout/line_length.rb +118 -4
- data/lib/rubocop/cop/layout/multiline_method_call_brace_layout.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_method_definition_brace_layout.rb +1 -1
- data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +2 -3
- data/lib/rubocop/cop/layout/parameter_alignment.rb +3 -4
- data/lib/rubocop/cop/layout/redundant_line_break.rb +3 -35
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +3 -2
- data/lib/rubocop/cop/layout/space_around_keyword.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_operators.rb +16 -17
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +6 -0
- data/lib/rubocop/cop/layout/space_inside_hash_literal_braces.rb +4 -0
- data/lib/rubocop/cop/layout/space_inside_string_interpolation.rb +0 -1
- data/lib/rubocop/cop/lint/binary_operator_with_identical_operands.rb +11 -12
- data/lib/rubocop/cop/lint/circular_argument_reference.rb +2 -0
- data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +1 -1
- data/lib/rubocop/cop/lint/empty_ensure.rb +1 -1
- data/lib/rubocop/cop/lint/empty_file.rb +0 -2
- data/lib/rubocop/cop/lint/ensure_return.rb +1 -1
- data/lib/rubocop/cop/lint/float_comparison.rb +14 -6
- data/lib/rubocop/cop/lint/float_out_of_range.rb +1 -3
- data/lib/rubocop/cop/lint/hash_new_with_keyword_arguments_as_default.rb +55 -0
- data/lib/rubocop/cop/lint/interpolation_check.rb +9 -0
- data/lib/rubocop/cop/lint/it_without_arguments_in_block.rb +3 -0
- data/lib/rubocop/cop/lint/literal_assignment_in_condition.rb +1 -1
- data/lib/rubocop/cop/lint/mixed_case_range.rb +2 -5
- data/lib/rubocop/cop/lint/nested_method_definition.rb +1 -1
- data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +2 -2
- data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +1 -1
- data/lib/rubocop/cop/lint/number_conversion.rb +0 -1
- data/lib/rubocop/cop/lint/numbered_parameter_assignment.rb +1 -2
- data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +106 -0
- data/lib/rubocop/cop/lint/or_assignment_to_constant.rb +1 -2
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +1 -1
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +12 -7
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +8 -7
- data/lib/rubocop/cop/lint/regexp_as_condition.rb +0 -1
- data/lib/rubocop/cop/lint/rescue_type.rb +3 -7
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -0
- data/lib/rubocop/cop/lint/self_assignment.rb +8 -10
- data/lib/rubocop/cop/lint/shadowed_exception.rb +1 -1
- data/lib/rubocop/cop/lint/unused_method_argument.rb +18 -2
- data/lib/rubocop/cop/lint/useless_defined.rb +55 -0
- data/lib/rubocop/cop/lint/useless_rescue.rb +1 -1
- data/lib/rubocop/cop/lint/useless_setter_call.rb +14 -25
- data/lib/rubocop/cop/lint/void.rb +3 -2
- data/lib/rubocop/cop/metrics/class_length.rb +7 -7
- data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -2
- data/lib/rubocop/cop/mixin/check_assignment.rb +4 -12
- data/lib/rubocop/cop/mixin/check_single_line_suitability.rb +49 -0
- data/lib/rubocop/cop/mixin/dig_help.rb +27 -0
- data/lib/rubocop/cop/mixin/multiline_expression_indentation.rb +5 -9
- data/lib/rubocop/cop/mixin/range_help.rb +0 -1
- data/lib/rubocop/cop/mixin/target_ruby_version.rb +17 -1
- data/lib/rubocop/cop/naming/constant_name.rb +6 -7
- data/lib/rubocop/cop/naming/file_name.rb +0 -2
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +11 -12
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +3 -11
- data/lib/rubocop/cop/naming/variable_name.rb +3 -4
- data/lib/rubocop/cop/naming/variable_number.rb +2 -3
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +53 -24
- data/lib/rubocop/cop/style/ambiguous_endless_method_definition.rb +1 -1
- data/lib/rubocop/cop/style/array_intersect.rb +5 -4
- data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +1 -1
- data/lib/rubocop/cop/style/case_like_if.rb +8 -11
- data/lib/rubocop/cop/style/commented_keyword.rb +11 -1
- data/lib/rubocop/cop/style/conditional_assignment.rb +19 -21
- data/lib/rubocop/cop/style/constant_visibility.rb +3 -12
- data/lib/rubocop/cop/style/dig_chain.rb +90 -0
- data/lib/rubocop/cop/style/file_null.rb +73 -0
- data/lib/rubocop/cop/style/file_touch.rb +75 -0
- data/lib/rubocop/cop/style/for.rb +0 -1
- data/lib/rubocop/cop/style/global_vars.rb +1 -3
- data/lib/rubocop/cop/style/guard_clause.rb +1 -1
- data/lib/rubocop/cop/style/hash_conversion.rb +1 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +0 -1
- data/lib/rubocop/cop/style/if_with_boolean_literal_branches.rb +1 -2
- data/lib/rubocop/cop/style/if_with_semicolon.rb +14 -5
- data/lib/rubocop/cop/style/inverse_methods.rb +0 -1
- data/lib/rubocop/cop/style/keyword_arguments_merging.rb +2 -2
- data/lib/rubocop/cop/style/lambda_call.rb +0 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +7 -11
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +33 -3
- data/lib/rubocop/cop/style/multiline_memoization.rb +1 -1
- data/lib/rubocop/cop/style/mutable_constant.rb +4 -5
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +6 -4
- data/lib/rubocop/cop/style/nested_ternary_operator.rb +5 -4
- data/lib/rubocop/cop/style/not.rb +1 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +25 -4
- data/lib/rubocop/cop/style/operator_method_call.rb +5 -6
- data/lib/rubocop/cop/style/or_assignment.rb +3 -6
- data/lib/rubocop/cop/style/parallel_assignment.rb +8 -13
- data/lib/rubocop/cop/style/raise_args.rb +1 -1
- data/lib/rubocop/cop/style/redundant_assignment.rb +1 -1
- data/lib/rubocop/cop/style/redundant_condition.rb +36 -21
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +7 -6
- data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -0
- data/lib/rubocop/cop/style/redundant_return.rb +2 -2
- data/lib/rubocop/cop/style/redundant_self.rb +7 -14
- data/lib/rubocop/cop/style/redundant_self_assignment.rb +7 -5
- data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +4 -4
- data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
- data/lib/rubocop/cop/style/rescue_modifier.rb +2 -3
- data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/style/select_by_regexp.rb +1 -1
- data/lib/rubocop/cop/style/self_assignment.rb +11 -17
- data/lib/rubocop/cop/style/signal_exception.rb +2 -3
- data/lib/rubocop/cop/style/single_argument_dig.rb +9 -5
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +13 -3
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -3
- data/lib/rubocop/cop/style/special_global_vars.rb +1 -1
- data/lib/rubocop/cop/style/string_concatenation.rb +0 -1
- data/lib/rubocop/cop/style/swap_values.rb +4 -15
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +4 -4
- data/lib/rubocop/cop/style/variable_interpolation.rb +1 -2
- data/lib/rubocop/cop/variable_force.rb +4 -10
- data/lib/rubocop/cops_documentation_generator.rb +9 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +8 -0
- metadata +17 -8
@@ -33,24 +33,20 @@ module RuboCop
|
|
33
33
|
branch.begin_type? ? Array(branch).last : branch
|
34
34
|
end
|
35
35
|
|
36
|
-
# rubocop:disable Metrics/AbcSize
|
37
36
|
def lhs(node)
|
38
37
|
case node.type
|
39
38
|
when :send
|
40
39
|
lhs_for_send(node)
|
41
|
-
when :op_asgn
|
42
|
-
"#{node.
|
43
|
-
when :and_asgn, :or_asgn
|
44
|
-
"#{node.children[0].source} #{node.loc.operator.source} "
|
40
|
+
when :op_asgn, :and_asgn, :or_asgn
|
41
|
+
"#{node.assignment_node.source} #{node.operator}= "
|
45
42
|
when :casgn
|
46
43
|
lhs_for_casgn(node)
|
47
44
|
when *ConditionalAssignment::VARIABLE_ASSIGNMENT_TYPES
|
48
|
-
"#{node.
|
45
|
+
"#{node.name} = "
|
49
46
|
else
|
50
47
|
node.source
|
51
48
|
end
|
52
49
|
end
|
53
|
-
# rubocop:enable Metrics/AbcSize
|
54
50
|
|
55
51
|
def indent(cop, source)
|
56
52
|
conf = cop.config.for_cop(END_ALIGNMENT)
|
@@ -94,11 +90,12 @@ module RuboCop
|
|
94
90
|
end
|
95
91
|
|
96
92
|
def lhs_for_casgn(node)
|
97
|
-
|
98
|
-
|
99
|
-
|
93
|
+
if node.namespace.nil?
|
94
|
+
"#{node.name} = "
|
95
|
+
elsif node.namespace.cbase_type?
|
96
|
+
"::#{node.name} = "
|
100
97
|
else
|
101
|
-
"#{namespace.
|
98
|
+
"#{node.namespace.const_name}::#{node.name} = "
|
102
99
|
end
|
103
100
|
end
|
104
101
|
|
@@ -210,7 +207,6 @@ module RuboCop
|
|
210
207
|
class ConditionalAssignment < Base
|
211
208
|
include ConditionalAssignmentHelper
|
212
209
|
include ConfigurableEnforcedStyle
|
213
|
-
include IgnoredNode
|
214
210
|
extend AutoCorrector
|
215
211
|
|
216
212
|
MSG = 'Use the return of the conditional for variable assignment and comparison.'
|
@@ -317,12 +313,14 @@ module RuboCop
|
|
317
313
|
end
|
318
314
|
|
319
315
|
def assignment_node(node)
|
320
|
-
|
316
|
+
assignment = node.send_type? ? node.last_argument : node.expression
|
321
317
|
|
322
318
|
# ignore pseudo-assignments without rhs in for nodes
|
323
319
|
return if node.parent&.for_type?
|
324
320
|
|
325
|
-
|
321
|
+
if assignment.begin_type? && assignment.children.one?
|
322
|
+
assignment = assignment.children.first
|
323
|
+
end
|
326
324
|
|
327
325
|
assignment
|
328
326
|
end
|
@@ -338,7 +336,7 @@ module RuboCop
|
|
338
336
|
end
|
339
337
|
|
340
338
|
def move_assignment_inside_condition(corrector, node)
|
341
|
-
|
339
|
+
condition = node.send_type? ? node.last_argument : node.expression
|
342
340
|
|
343
341
|
if ternary_condition?(condition)
|
344
342
|
TernaryCorrector.move_assignment_inside_condition(corrector, node)
|
@@ -459,7 +457,7 @@ module RuboCop
|
|
459
457
|
end
|
460
458
|
|
461
459
|
def assignment(node)
|
462
|
-
|
460
|
+
condition = node.send_type? ? node.last_argument : node.expression
|
463
461
|
|
464
462
|
node.source_range.begin.join(condition.source_range.begin)
|
465
463
|
end
|
@@ -506,7 +504,7 @@ module RuboCop
|
|
506
504
|
end
|
507
505
|
|
508
506
|
def move_assignment_inside_condition(corrector, node)
|
509
|
-
|
507
|
+
rhs = node.send_type? ? node.last_argument : node.expression
|
510
508
|
if_branch, else_branch = extract_branches(node)
|
511
509
|
assignment = assignment(node)
|
512
510
|
|
@@ -537,8 +535,8 @@ module RuboCop
|
|
537
535
|
end
|
538
536
|
|
539
537
|
def extract_branches(node)
|
540
|
-
|
541
|
-
condition
|
538
|
+
rhs = node.send_type? ? node.last_argument : node.expression
|
539
|
+
condition = rhs.children.first if rhs.begin_type? && rhs.children.one?
|
542
540
|
_condition, if_branch, else_branch = *(condition || rhs)
|
543
541
|
|
544
542
|
[if_branch, else_branch]
|
@@ -567,7 +565,7 @@ module RuboCop
|
|
567
565
|
|
568
566
|
def move_assignment_inside_condition(corrector, node)
|
569
567
|
column = node.source_range.column
|
570
|
-
|
568
|
+
condition = node.send_type? ? node.last_argument : node.expression
|
571
569
|
assignment = assignment(node)
|
572
570
|
|
573
571
|
corrector.remove(assignment)
|
@@ -618,7 +616,7 @@ module RuboCop
|
|
618
616
|
|
619
617
|
def move_assignment_inside_condition(corrector, node)
|
620
618
|
column = node.source_range.column
|
621
|
-
|
619
|
+
condition = node.send_type? ? node.last_argument : node.expression
|
622
620
|
assignment = assignment(node)
|
623
621
|
|
624
622
|
corrector.remove(assignment)
|
@@ -53,8 +53,7 @@ module RuboCop
|
|
53
53
|
return if visibility_declaration?(node)
|
54
54
|
return if ignore_modules? && module?(node)
|
55
55
|
|
56
|
-
message
|
57
|
-
add_offense(node, message: message)
|
56
|
+
add_offense(node, message: format(MSG, constant_name: node.name))
|
58
57
|
end
|
59
58
|
|
60
59
|
private
|
@@ -64,13 +63,7 @@ module RuboCop
|
|
64
63
|
end
|
65
64
|
|
66
65
|
def module?(node)
|
67
|
-
node.
|
68
|
-
end
|
69
|
-
|
70
|
-
def message(node)
|
71
|
-
_namespace, constant_name, _value = *node
|
72
|
-
|
73
|
-
format(MSG, constant_name: constant_name)
|
66
|
+
node.expression.class_constructor?
|
74
67
|
end
|
75
68
|
|
76
69
|
def class_or_module_scope?(node)
|
@@ -85,10 +78,8 @@ module RuboCop
|
|
85
78
|
end
|
86
79
|
|
87
80
|
def visibility_declaration?(node)
|
88
|
-
_namespace, constant_name, _value = *node
|
89
|
-
|
90
81
|
node.parent.each_child_node(:send).any? do |child|
|
91
|
-
visibility_declaration_for?(child,
|
82
|
+
visibility_declaration_for?(child, node.name)
|
92
83
|
end
|
93
84
|
end
|
94
85
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Check for chained `dig` calls that can be collapsed into a single `dig`.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# This cop is unsafe because it cannot be guaranteed that the receiver
|
10
|
+
# is an `Enumerable` or does not have a nonstandard implementation
|
11
|
+
# of `dig`.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # bad
|
15
|
+
# x.dig(:foo).dig(:bar).dig(:baz)
|
16
|
+
# x.dig(:foo, :bar).dig(:baz)
|
17
|
+
# x.dig(:foo, :bar)&.dig(:baz)
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# x.dig(:foo, :bar, :baz)
|
21
|
+
#
|
22
|
+
# # good - `dig`s cannot be combined
|
23
|
+
# x.dig(:foo).bar.dig(:baz)
|
24
|
+
#
|
25
|
+
class DigChain < Base
|
26
|
+
extend AutoCorrector
|
27
|
+
include RangeHelp
|
28
|
+
include CommentsHelp
|
29
|
+
include DigHelp
|
30
|
+
|
31
|
+
MSG = 'Use `%<replacement>s` instead of chaining.'
|
32
|
+
RESTRICT_ON_SEND = %i[dig].freeze
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
return if ignored_node?(node)
|
36
|
+
return unless node.loc.dot
|
37
|
+
return unless dig?(node)
|
38
|
+
|
39
|
+
range, arguments = inspect_chain(node)
|
40
|
+
return if invalid_arguments?(arguments)
|
41
|
+
return unless range
|
42
|
+
|
43
|
+
register_offense(node, range, arguments)
|
44
|
+
end
|
45
|
+
alias on_csend on_send
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Walk up the method chain while the receiver is `dig` with arguments.
|
50
|
+
def inspect_chain(node)
|
51
|
+
arguments = node.arguments.dup
|
52
|
+
end_pos = node.source_range.end_pos
|
53
|
+
|
54
|
+
while dig?((node = node.receiver))
|
55
|
+
begin_pos = node.loc.dot ? node.loc.dot.begin_pos + 1 : 0
|
56
|
+
arguments.unshift(*node.arguments)
|
57
|
+
ignore_node(node)
|
58
|
+
end
|
59
|
+
|
60
|
+
return unless begin_pos
|
61
|
+
|
62
|
+
[range_between(begin_pos, end_pos), arguments]
|
63
|
+
end
|
64
|
+
|
65
|
+
def invalid_arguments?(arguments)
|
66
|
+
# If any of the arguments are arguments forwarding (`...`), it can only be the
|
67
|
+
# first argument, or else the resulting code will have a syntax error.
|
68
|
+
|
69
|
+
return false unless arguments&.any?
|
70
|
+
|
71
|
+
forwarded_args_index = arguments.index(&:forwarded_args_type?)
|
72
|
+
forwarded_args_index && forwarded_args_index < (arguments.size - 1)
|
73
|
+
end
|
74
|
+
|
75
|
+
def register_offense(node, range, arguments)
|
76
|
+
arguments = arguments.map(&:source).join(', ')
|
77
|
+
replacement = "dig(#{arguments})"
|
78
|
+
|
79
|
+
add_offense(range, message: format(MSG, replacement: replacement)) do |corrector|
|
80
|
+
corrector.replace(range, replacement)
|
81
|
+
|
82
|
+
comments_in_range(node).reverse_each do |comment|
|
83
|
+
corrector.insert_before(node, "#{comment.source}\n")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Use `File::NULL` instead of hardcoding the null device (`/dev/null` on Unix-like
|
7
|
+
# OSes, `NUL` or `NUL:` on Windows), so that code is platform independent.
|
8
|
+
# Only looks for full string matches, substrings within a longer string are not
|
9
|
+
# considered.
|
10
|
+
#
|
11
|
+
# NOTE: Uses inside arrays and hashes are ignored.
|
12
|
+
#
|
13
|
+
# @safety
|
14
|
+
# It is possible for a string value to be changed if code is being run
|
15
|
+
# on multiple platforms and was previously hardcoded to a specific null device.
|
16
|
+
#
|
17
|
+
# For example, the following string will change on Windows when changed to
|
18
|
+
# `File::NULL`:
|
19
|
+
#
|
20
|
+
# [source,ruby]
|
21
|
+
# ----
|
22
|
+
# path = "/dev/null"
|
23
|
+
# ----
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# # bad
|
27
|
+
# '/dev/null'
|
28
|
+
# 'NUL'
|
29
|
+
# 'NUL:'
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# File::NULL
|
33
|
+
#
|
34
|
+
# # ok - inside an array
|
35
|
+
# null_devices = %w[/dev/null nul]
|
36
|
+
#
|
37
|
+
# # ok - inside a hash
|
38
|
+
# { unix: "/dev/null", windows: "nul" }
|
39
|
+
class FileNull < Base
|
40
|
+
extend AutoCorrector
|
41
|
+
|
42
|
+
REGEXP = %r{\A(/dev/null|NUL:?)\z}i.freeze
|
43
|
+
MSG = 'Use `File::NULL` instead of `%<source>s`.'
|
44
|
+
|
45
|
+
def on_str(node)
|
46
|
+
value = node.value
|
47
|
+
|
48
|
+
return if invalid_string?(value)
|
49
|
+
return if acceptable?(node)
|
50
|
+
return unless REGEXP.match?(value)
|
51
|
+
|
52
|
+
add_offense(node, message: format(MSG, source: value)) do |corrector|
|
53
|
+
corrector.replace(node, 'File::NULL')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def invalid_string?(value)
|
60
|
+
value.empty? || !value.valid_encoding?
|
61
|
+
end
|
62
|
+
|
63
|
+
def acceptable?(node)
|
64
|
+
# Using a hardcoded null device is acceptable when inside an array or
|
65
|
+
# inside a hash to ensure behavior doesn't change.
|
66
|
+
return false unless node.parent
|
67
|
+
|
68
|
+
node.parent.type?(:array, :pair)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for usage of `File.open` in append mode with empty block.
|
7
|
+
#
|
8
|
+
# Such a usage only creates a new file, but it doesn't update
|
9
|
+
# timestamps for an existing file, which might have been the intention.
|
10
|
+
#
|
11
|
+
# For example, for an existing file `foo.txt`:
|
12
|
+
#
|
13
|
+
# ruby -e "puts File.mtime('foo.txt')"
|
14
|
+
# # 2024-11-26 12:17:23 +0100
|
15
|
+
#
|
16
|
+
# ruby -e "File.open('foo.txt', 'a') {}"
|
17
|
+
#
|
18
|
+
# ruby -e "puts File.mtime('foo.txt')"
|
19
|
+
# # 2024-11-26 12:17:23 +0100 -> unchanged
|
20
|
+
#
|
21
|
+
# If the intention was to update timestamps, `FileUtils.touch('foo.txt')`
|
22
|
+
# should be used instead.
|
23
|
+
#
|
24
|
+
# @safety
|
25
|
+
# Autocorrection is unsafe for this cop because unlike `File.open`,
|
26
|
+
# `FileUtils.touch` updates an existing file's timestamps.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# # bad
|
30
|
+
# File.open(filename, 'a') {}
|
31
|
+
# File.open(filename, 'a+') {}
|
32
|
+
#
|
33
|
+
# # good
|
34
|
+
# FileUtils.touch(filename)
|
35
|
+
#
|
36
|
+
class FileTouch < Base
|
37
|
+
extend AutoCorrector
|
38
|
+
|
39
|
+
MSG = 'Use `FileUtils.touch(%<argument>s)` instead of `File.open` in ' \
|
40
|
+
'append mode with empty block.'
|
41
|
+
|
42
|
+
RESTRICT_ON_SEND = %i[open].freeze
|
43
|
+
|
44
|
+
APPEND_FILE_MODES = %w[a a+ ab a+b at a+t].to_set.freeze
|
45
|
+
|
46
|
+
# @!method file_open?(node)
|
47
|
+
def_node_matcher :file_open?, <<~PATTERN
|
48
|
+
(send
|
49
|
+
(const {nil? cbase} :File) :open
|
50
|
+
$(...)
|
51
|
+
(str %APPEND_FILE_MODES))
|
52
|
+
PATTERN
|
53
|
+
|
54
|
+
def on_send(node)
|
55
|
+
filename = file_open?(node)
|
56
|
+
parent = node.parent
|
57
|
+
|
58
|
+
return unless filename
|
59
|
+
return unless parent && empty_block?(parent)
|
60
|
+
|
61
|
+
message = format(MSG, argument: filename.source)
|
62
|
+
add_offense(parent, message: message) do |corrector|
|
63
|
+
corrector.replace(parent, "FileUtils.touch(#{filename.source})")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def empty_block?(node)
|
70
|
+
node.block_type? && !node.body
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -111,8 +111,7 @@ module RuboCop
|
|
111
111
|
end
|
112
112
|
|
113
113
|
def requires_parens?(node)
|
114
|
-
(node.call_type? && node.arguments.any? && !node.parenthesized?) ||
|
115
|
-
node.or_type? || node.and_type?
|
114
|
+
(node.call_type? && node.arguments.any? && !node.parenthesized?) || node.operator_keyword?
|
116
115
|
end
|
117
116
|
|
118
117
|
def multi_argument(node)
|
@@ -155,8 +155,7 @@ module RuboCop
|
|
155
155
|
end
|
156
156
|
|
157
157
|
def require_parentheses?(condition)
|
158
|
-
condition.
|
159
|
-
(condition.send_type? && condition.comparison_method?)
|
158
|
+
condition.operator_keyword? || (condition.send_type? && condition.comparison_method?)
|
160
159
|
end
|
161
160
|
end
|
162
161
|
end
|
@@ -23,6 +23,7 @@ module RuboCop
|
|
23
23
|
|
24
24
|
def on_normal_if_unless(node)
|
25
25
|
return if node.parent&.if_type?
|
26
|
+
return if part_of_ignored_node?(node)
|
26
27
|
|
27
28
|
beginning = node.loc.begin
|
28
29
|
return unless beginning&.is?(';')
|
@@ -32,13 +33,14 @@ module RuboCop
|
|
32
33
|
add_offense(node, message: message) do |corrector|
|
33
34
|
autocorrect(corrector, node)
|
34
35
|
end
|
36
|
+
|
37
|
+
ignore_node(node)
|
35
38
|
end
|
36
39
|
|
37
40
|
private
|
38
41
|
|
39
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
40
42
|
def message(node)
|
41
|
-
template = if node
|
43
|
+
template = if require_newline?(node)
|
42
44
|
MSG_NEWLINE
|
43
45
|
elsif node.else_branch&.if_type? || node.else_branch&.begin_type? ||
|
44
46
|
use_block_in_branches?(node)
|
@@ -49,20 +51,27 @@ module RuboCop
|
|
49
51
|
|
50
52
|
format(template, expr: node.condition.source)
|
51
53
|
end
|
52
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
53
54
|
|
54
55
|
def autocorrect(corrector, node)
|
55
|
-
if
|
56
|
+
if require_newline?(node) || use_block_in_branches?(node)
|
56
57
|
corrector.replace(node.loc.begin, "\n")
|
57
58
|
else
|
58
59
|
corrector.replace(node, replacement(node))
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
63
|
+
def require_newline?(node)
|
64
|
+
node.branches.compact.any?(&:begin_type?) || use_return_with_argument?(node)
|
65
|
+
end
|
66
|
+
|
62
67
|
def use_block_in_branches?(node)
|
63
68
|
node.branches.compact.any? { |branch| branch.block_type? || branch.numblock_type? }
|
64
69
|
end
|
65
70
|
|
71
|
+
def use_return_with_argument?(node)
|
72
|
+
node.if_branch&.return_type? && node.if_branch&.arguments&.any?
|
73
|
+
end
|
74
|
+
|
66
75
|
def replacement(node)
|
67
76
|
return correct_elsif(node) if node.else_branch&.if_type?
|
68
77
|
|
@@ -111,7 +120,7 @@ module RuboCop
|
|
111
120
|
end
|
112
121
|
|
113
122
|
def require_argument_parentheses?(node)
|
114
|
-
return false
|
123
|
+
return false if !node.call_type? || node.arithmetic_operation?
|
115
124
|
|
116
125
|
!node.parenthesized? && node.arguments.any? && !node.method?(:[]) && !node.method?(:[]=)
|
117
126
|
end
|
@@ -6,8 +6,8 @@ module RuboCop
|
|
6
6
|
# When passing an existing hash as keyword arguments, provide additional arguments
|
7
7
|
# directly rather than using `merge`.
|
8
8
|
#
|
9
|
-
# Providing arguments directly is more performant
|
10
|
-
# also leads to
|
9
|
+
# Providing arguments directly is more performant than using `merge`, and
|
10
|
+
# also leads to shorter and simpler code.
|
11
11
|
#
|
12
12
|
# @example
|
13
13
|
# # bad
|
@@ -71,9 +71,11 @@ module RuboCop
|
|
71
71
|
return false if node.receiver
|
72
72
|
|
73
73
|
any_assignment?(node) do |asgn_node|
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
if asgn_node.masgn_type?
|
75
|
+
variable_in_mass_assignment?(node.method_name, asgn_node)
|
76
|
+
else
|
77
|
+
asgn_node.loc.name.source == node.method_name.to_s
|
78
|
+
end
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
@@ -98,20 +100,14 @@ module RuboCop
|
|
98
100
|
# `obj.method ||= value` parses as (or-asgn (send ...) ...)
|
99
101
|
# which IS an `asgn_node`. Similarly, `obj.method += value` parses
|
100
102
|
# as (op-asgn (send ...) ...), which is also an `asgn_node`.
|
101
|
-
if asgn_node.shorthand_asgn?
|
102
|
-
asgn_node, _value = *asgn_node
|
103
|
-
next if asgn_node.send_type?
|
104
|
-
end
|
103
|
+
next if asgn_node.shorthand_asgn? && asgn_node.lhs.send_type?
|
105
104
|
|
106
105
|
yield asgn_node
|
107
106
|
end
|
108
107
|
end
|
109
108
|
|
110
109
|
def variable_in_mass_assignment?(variable_name, node)
|
111
|
-
|
112
|
-
var_nodes = *mlhs_node
|
113
|
-
|
114
|
-
var_nodes.any? { |n| n.to_a.first == variable_name }
|
110
|
+
node.assignments.any? { |n| n.name == variable_name }
|
115
111
|
end
|
116
112
|
|
117
113
|
def offense_range(node)
|
@@ -6,19 +6,49 @@ module RuboCop
|
|
6
6
|
# Checks for the presence of `method_missing` without also
|
7
7
|
# defining `respond_to_missing?`.
|
8
8
|
#
|
9
|
+
# Not defining `respond_to_missing?` will cause metaprogramming
|
10
|
+
# methods like `respond_to?` to behave unexpectedly:
|
11
|
+
#
|
12
|
+
# [source,ruby]
|
13
|
+
# ----
|
14
|
+
# class StringDelegator
|
15
|
+
# def initialize(string)
|
16
|
+
# @string = string
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def method_missing(name, *args)
|
20
|
+
# @string.send(name, *args)
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# delegator = StringDelegator.new("foo")
|
25
|
+
# # Claims to not respond to `upcase`.
|
26
|
+
# delegator.respond_to?(:upcase) # => false
|
27
|
+
# # But you can call it.
|
28
|
+
# delegator.upcase # => FOO
|
29
|
+
# ----
|
30
|
+
#
|
9
31
|
# @example
|
10
32
|
# # bad
|
11
33
|
# def method_missing(name, *args)
|
12
|
-
#
|
34
|
+
# if @delegate.respond_to?(name)
|
35
|
+
# @delegate.send(name, *args)
|
36
|
+
# else
|
37
|
+
# super
|
38
|
+
# end
|
13
39
|
# end
|
14
40
|
#
|
15
41
|
# # good
|
16
42
|
# def respond_to_missing?(name, include_private)
|
17
|
-
#
|
43
|
+
# @delegate.respond_to?(name) || super
|
18
44
|
# end
|
19
45
|
#
|
20
46
|
# def method_missing(name, *args)
|
21
|
-
#
|
47
|
+
# if @delegate.respond_to?(name)
|
48
|
+
# @delegate.send(name, *args)
|
49
|
+
# else
|
50
|
+
# super
|
51
|
+
# end
|
22
52
|
# end
|
23
53
|
#
|
24
54
|
class MissingRespondToMissing < Base
|