rubocop 1.69.2 → 1.70.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/config/default.yml +19 -2
- data/lib/rubocop/cli/command/execute_runner.rb +3 -3
- data/lib/rubocop/config.rb +13 -4
- data/lib/rubocop/config_loader.rb +4 -0
- data/lib/rubocop/config_loader_resolver.rb +14 -3
- data/lib/rubocop/config_validator.rb +18 -8
- data/lib/rubocop/cop/base.rb +6 -0
- data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
- data/lib/rubocop/cop/internal_affairs/cop_enabled.rb +85 -0
- data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +4 -3
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/argument_alignment.rb +1 -7
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
- data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -7
- data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -7
- data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -6
- data/lib/rubocop/cop/layout/hash_alignment.rb +6 -4
- data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -0
- data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
- data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/line_length.rb +1 -0
- data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +24 -0
- data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
- data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -1
- data/lib/rubocop/cop/layout/space_around_operators.rb +3 -3
- data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -3
- data/lib/rubocop/cop/lint/constant_reassignment.rb +152 -0
- data/lib/rubocop/cop/lint/duplicate_set_element.rb +20 -7
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +11 -3
- data/lib/rubocop/cop/lint/nested_method_definition.rb +5 -1
- data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +4 -3
- data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +6 -14
- data/lib/rubocop/cop/lint/shared_mutable_default.rb +65 -0
- data/lib/rubocop/cop/lint/syntax.rb +4 -1
- data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -4
- data/lib/rubocop/cop/lint/void.rb +3 -2
- data/lib/rubocop/cop/metrics/method_length.rb +8 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -7
- data/lib/rubocop/cop/mixin/comments_help.rb +2 -0
- data/lib/rubocop/cop/mixin/dig_help.rb +1 -1
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +26 -16
- data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
- data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -1
- data/lib/rubocop/cop/style/and_or.rb +1 -1
- data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -4
- data/lib/rubocop/cop/style/class_and_module_children.rb +5 -2
- data/lib/rubocop/cop/style/each_for_simple_loop.rb +3 -6
- data/lib/rubocop/cop/style/empty_else.rb +4 -2
- data/lib/rubocop/cop/style/empty_literal.rb +1 -1
- data/lib/rubocop/cop/style/empty_method.rb +1 -1
- data/lib/rubocop/cop/style/exact_regexp_match.rb +2 -9
- data/lib/rubocop/cop/style/exponential_notation.rb +1 -1
- data/lib/rubocop/cop/style/float_division.rb +8 -4
- data/lib/rubocop/cop/style/hash_except.rb +54 -67
- data/lib/rubocop/cop/style/hash_syntax.rb +5 -2
- data/lib/rubocop/cop/style/it_assignment.rb +36 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +11 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -0
- data/lib/rubocop/cop/style/missing_else.rb +2 -0
- data/lib/rubocop/cop/style/multiple_comparison.rb +26 -20
- data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
- data/lib/rubocop/cop/style/object_then.rb +13 -15
- data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
- data/lib/rubocop/cop/style/raise_args.rb +5 -3
- data/lib/rubocop/cop/style/random_with_offset.rb +3 -3
- data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +2 -1
- data/lib/rubocop/cop/style/redundant_initialize.rb +12 -3
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +7 -3
- data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -4
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +3 -0
- data/lib/rubocop/cop/style/redundant_self_assignment.rb +6 -5
- data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/style/send_with_literal_method_name.rb +2 -1
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
- data/lib/rubocop/cop/style/single_line_methods.rb +2 -3
- data/lib/rubocop/cop/style/slicing_with_range.rb +40 -11
- data/lib/rubocop/cop/style/super_arguments.rb +63 -15
- data/lib/rubocop/cop/style/yoda_condition.rb +8 -4
- data/lib/rubocop/cop/style/yoda_expression.rb +1 -0
- data/lib/rubocop/cop/util.rb +9 -2
- data/lib/rubocop/formatter/formatter_set.rb +1 -1
- data/lib/rubocop/lsp/diagnostic.rb +189 -0
- data/lib/rubocop/lsp/logger.rb +2 -2
- data/lib/rubocop/lsp/routes.rb +7 -23
- data/lib/rubocop/lsp/runtime.rb +15 -49
- data/lib/rubocop/lsp/stdin_runner.rb +83 -0
- data/lib/rubocop/path_util.rb +11 -8
- data/lib/rubocop/rspec/shared_contexts.rb +4 -1
- data/lib/rubocop/runner.rb +5 -6
- data/lib/rubocop/target_ruby.rb +15 -0
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +3 -0
- data/lib/ruby_lsp/rubocop/addon.rb +78 -0
- data/lib/ruby_lsp/rubocop/wraps_built_in_lsp_runtime.rb +50 -0
- metadata +12 -4
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
|
49
49
|
def on_new_investigation
|
50
50
|
processed_source.lines.each_with_index do |line, index|
|
51
|
-
next unless line.
|
51
|
+
next unless line.match?(/[[:blank:]]\z/)
|
52
52
|
|
53
53
|
process_line(line, index + 1)
|
54
54
|
end
|
@@ -84,7 +84,7 @@ module RuboCop
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def whitespace_is_indentation?(range, level)
|
87
|
-
range.source[/[
|
87
|
+
range.source[/[[:blank:]]+/].length <= level
|
88
88
|
end
|
89
89
|
|
90
90
|
def whitespace_only?(range)
|
@@ -123,7 +123,9 @@ module RuboCop
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def offense_range(lineno, line)
|
126
|
-
source_range(
|
126
|
+
source_range(
|
127
|
+
processed_source.buffer, lineno, (line.sub(/[[:blank:]]+\z/, '').length)...(line.length)
|
128
|
+
)
|
127
129
|
end
|
128
130
|
end
|
129
131
|
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# Checks for constant reassignments.
|
7
|
+
#
|
8
|
+
# Emulates Ruby's runtime warning "already initialized constant X"
|
9
|
+
# when a constant is reassigned in the same file and namespace using the
|
10
|
+
# `NAME = value` syntax.
|
11
|
+
#
|
12
|
+
# The cop cannot catch all offenses, like, for example, when a constant
|
13
|
+
# is reassigned in another file, or when using metaprogramming (`Module#const_set`).
|
14
|
+
#
|
15
|
+
# The cop only takes into account constants assigned in a "simple" way: directly
|
16
|
+
# inside class/module definition, or within another constant. Other type of assignments
|
17
|
+
# (e.g., inside a conditional) are disregarded.
|
18
|
+
#
|
19
|
+
# The cop also tracks constant removal using `Module#remove_const` with symbol
|
20
|
+
# or string argument.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# # bad
|
24
|
+
# X = :foo
|
25
|
+
# X = :bar
|
26
|
+
#
|
27
|
+
# # bad
|
28
|
+
# class A
|
29
|
+
# X = :foo
|
30
|
+
# X = :bar
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# # bad
|
34
|
+
# module A
|
35
|
+
# X = :foo
|
36
|
+
# X = :bar
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# # good - keep only one assignment
|
40
|
+
# X = :bar
|
41
|
+
#
|
42
|
+
# class A
|
43
|
+
# X = :bar
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# module A
|
47
|
+
# X = :bar
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # good - use OR assignment
|
51
|
+
# X = :foo
|
52
|
+
# X ||= :bar
|
53
|
+
#
|
54
|
+
# # good - use conditional assignment
|
55
|
+
# X = :foo
|
56
|
+
# X = :bar unless defined?(X)
|
57
|
+
#
|
58
|
+
# # good - remove the assigned constant first
|
59
|
+
# class A
|
60
|
+
# X = :foo
|
61
|
+
# remove_const :X
|
62
|
+
# X = :bar
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
class ConstantReassignment < Base
|
66
|
+
MSG = 'Constant `%<constant>s` is already assigned in this namespace.'
|
67
|
+
|
68
|
+
RESTRICT_ON_SEND = %i[remove_const].freeze
|
69
|
+
|
70
|
+
# @!method remove_constant(node)
|
71
|
+
def_node_matcher :remove_constant, <<~PATTERN
|
72
|
+
(send _ :remove_const
|
73
|
+
({sym str} $_))
|
74
|
+
PATTERN
|
75
|
+
|
76
|
+
def on_casgn(node)
|
77
|
+
return unless fixed_constant_path?(node)
|
78
|
+
return unless simple_assignment?(node)
|
79
|
+
return if constant_names.add?(fully_qualified_constant_name(node))
|
80
|
+
|
81
|
+
add_offense(node, message: format(MSG, constant: node.name))
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_send(node)
|
85
|
+
constant = remove_constant(node)
|
86
|
+
|
87
|
+
return unless constant
|
88
|
+
|
89
|
+
namespaces = ancestor_namespaces(node)
|
90
|
+
|
91
|
+
return if namespaces.none?
|
92
|
+
|
93
|
+
constant_names.delete(fully_qualified_name_for(namespaces, constant))
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def fixed_constant_path?(node)
|
99
|
+
node.each_path.all? { |path| path.cbase_type? || path.const_type? || path.self_type? }
|
100
|
+
end
|
101
|
+
|
102
|
+
def simple_assignment?(node)
|
103
|
+
node.ancestors.all? do |ancestor|
|
104
|
+
return true if namespace_definition?(ancestor)
|
105
|
+
|
106
|
+
ancestor.begin_type? || ancestor.literal? || ancestor.casgn_type? ||
|
107
|
+
freeze_method?(ancestor)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def namespace_definition?(node)
|
112
|
+
node.module_type? || node.class_type?
|
113
|
+
end
|
114
|
+
|
115
|
+
def freeze_method?(node)
|
116
|
+
node.send_type? && node.method?(:freeze)
|
117
|
+
end
|
118
|
+
|
119
|
+
def fully_qualified_constant_name(node)
|
120
|
+
if node.absolute?
|
121
|
+
namespace = node.namespace.const_type? ? node.namespace.source : nil
|
122
|
+
|
123
|
+
"#{namespace}::#{node.name}"
|
124
|
+
else
|
125
|
+
constant_namespaces = ancestor_namespaces(node) + constant_namespaces(node)
|
126
|
+
|
127
|
+
fully_qualified_name_for(constant_namespaces, node.name)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def fully_qualified_name_for(namespaces, constant)
|
132
|
+
['', *namespaces, constant].join('::')
|
133
|
+
end
|
134
|
+
|
135
|
+
def constant_namespaces(node)
|
136
|
+
node.each_path.select(&:const_type?).map(&:short_name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def ancestor_namespaces(node)
|
140
|
+
node
|
141
|
+
.each_ancestor(:class, :module)
|
142
|
+
.map { |ancestor| ancestor.identifier.short_name }
|
143
|
+
.reverse
|
144
|
+
end
|
145
|
+
|
146
|
+
def constant_names
|
147
|
+
@constant_names ||= Set.new
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Lint
|
6
|
-
# Checks for duplicate literal, constant, or variable elements in Set.
|
6
|
+
# Checks for duplicate literal, constant, or variable elements in Set and SortedSet.
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
#
|
@@ -25,17 +25,28 @@ module RuboCop
|
|
25
25
|
# # good
|
26
26
|
# [:foo, :bar].to_set
|
27
27
|
#
|
28
|
+
# # bad
|
29
|
+
# SortedSet[:foo, :bar, :foo]
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# SortedSet[:foo, :bar]
|
33
|
+
#
|
34
|
+
# # bad
|
35
|
+
# SortedSet.new([:foo, :bar, :foo])
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# SortedSet.new([:foo, :bar])
|
28
39
|
class DuplicateSetElement < Base
|
29
40
|
extend AutoCorrector
|
30
41
|
|
31
|
-
MSG = 'Remove the duplicate element in
|
42
|
+
MSG = 'Remove the duplicate element in %<class_name>s.'
|
32
43
|
RESTRICT_ON_SEND = %i[\[\] new to_set].freeze
|
33
44
|
|
34
45
|
# @!method set_init_elements(node)
|
35
46
|
def_node_matcher :set_init_elements, <<~PATTERN
|
36
47
|
{
|
37
|
-
(send (const {nil? cbase} :Set) :[] $...)
|
38
|
-
(send (const {nil? cbase} :Set) :new (array $...))
|
48
|
+
(send (const {nil? cbase} {:Set :SortedSet}) :[] $...)
|
49
|
+
(send (const {nil? cbase} {:Set :SortedSet}) :new (array $...))
|
39
50
|
(call (array $...) :to_set)
|
40
51
|
}
|
41
52
|
PATTERN
|
@@ -51,7 +62,7 @@ module RuboCop
|
|
51
62
|
next if !set_element.literal? && !set_element.const_type? && !set_element.variable?
|
52
63
|
|
53
64
|
if seen_elements.include?(set_element)
|
54
|
-
register_offense(set_element, set_elements[index - 1])
|
65
|
+
register_offense(set_element, set_elements[index - 1], node)
|
55
66
|
else
|
56
67
|
seen_elements << set_element
|
57
68
|
end
|
@@ -61,8 +72,10 @@ module RuboCop
|
|
61
72
|
|
62
73
|
private
|
63
74
|
|
64
|
-
def register_offense(current_element, prev_element)
|
65
|
-
|
75
|
+
def register_offense(current_element, prev_element, node)
|
76
|
+
class_name = node.receiver.const_type? ? node.receiver.const_name : 'Set'
|
77
|
+
|
78
|
+
add_offense(current_element, message: format(MSG, class_name: class_name)) do |corrector|
|
66
79
|
range = prev_element.source_range.end.join(current_element.source_range.end)
|
67
80
|
|
68
81
|
corrector.remove(range)
|
@@ -21,6 +21,7 @@ module RuboCop
|
|
21
21
|
MSG = 'Literal interpolation detected.'
|
22
22
|
COMPOSITE = %i[array hash pair irange erange].freeze
|
23
23
|
|
24
|
+
# rubocop:disable Metrics/AbcSize
|
24
25
|
def on_interpolation(begin_node)
|
25
26
|
final_node = begin_node.children.last
|
26
27
|
return unless offending?(final_node)
|
@@ -35,11 +36,18 @@ module RuboCop
|
|
35
36
|
return if in_array_percent_literal?(begin_node) && /\s|\A\z/.match?(expanded_value)
|
36
37
|
|
37
38
|
add_offense(final_node) do |corrector|
|
38
|
-
|
39
|
+
next if final_node.dstr_type? # nested, fixed in next iteration
|
39
40
|
|
40
|
-
|
41
|
+
replacement = if final_node.str_type? && !final_node.value.valid_encoding?
|
42
|
+
final_node.source.delete_prefix('"').delete_suffix('"')
|
43
|
+
else
|
44
|
+
expanded_value
|
45
|
+
end
|
46
|
+
|
47
|
+
corrector.replace(final_node.parent, replacement)
|
41
48
|
end
|
42
49
|
end
|
50
|
+
# rubocop:enable Metrics/AbcSize
|
43
51
|
|
44
52
|
private
|
45
53
|
|
@@ -168,7 +176,7 @@ module RuboCop
|
|
168
176
|
end
|
169
177
|
|
170
178
|
def space_literal?(node)
|
171
|
-
node.str_type? && node.value.blank?
|
179
|
+
node.str_type? && node.value.valid_encoding? && node.value.blank?
|
172
180
|
end
|
173
181
|
|
174
182
|
def ends_heredoc_line?(node)
|
@@ -96,7 +96,7 @@ module RuboCop
|
|
96
96
|
|
97
97
|
def on_def(node)
|
98
98
|
subject, = *node # rubocop:disable InternalAffairs/NodeDestructuring
|
99
|
-
return if node.defs_type? && subject
|
99
|
+
return if node.defs_type? && allowed_subject_type?(subject)
|
100
100
|
|
101
101
|
def_ancestor = node.each_ancestor(:def, :defs).first
|
102
102
|
return unless def_ancestor
|
@@ -117,6 +117,10 @@ module RuboCop
|
|
117
117
|
child.class_constructor? || allowed_method_name?(child)
|
118
118
|
end
|
119
119
|
|
120
|
+
def allowed_subject_type?(subject)
|
121
|
+
subject.variable? || subject.const_type? || subject.call_type?
|
122
|
+
end
|
123
|
+
|
120
124
|
def allowed_method_name?(node)
|
121
125
|
name = node.method_name
|
122
126
|
|
@@ -59,12 +59,12 @@ module RuboCop
|
|
59
59
|
|
60
60
|
# @!method send_exist_node(node)
|
61
61
|
def_node_search :send_exist_node, <<~PATTERN
|
62
|
-
$(send (const nil? {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
|
62
|
+
$(send (const {cbase nil?} {:FileTest :File :Dir :Shell}) {:exist? :exists?} ...)
|
63
63
|
PATTERN
|
64
64
|
|
65
65
|
# @!method receiver_and_method_name(node)
|
66
66
|
def_node_matcher :receiver_and_method_name, <<~PATTERN
|
67
|
-
(send (const nil? $_) $_ ...)
|
67
|
+
(send (const {cbase nil?} $_) $_ ...)
|
68
68
|
PATTERN
|
69
69
|
|
70
70
|
# @!method force?(node)
|
@@ -78,7 +78,7 @@ module RuboCop
|
|
78
78
|
PATTERN
|
79
79
|
|
80
80
|
def on_send(node)
|
81
|
-
return unless node.receiver
|
81
|
+
return unless node.receiver&.const_type?
|
82
82
|
return unless if_node_child?(node)
|
83
83
|
return if explicit_not_force?(node)
|
84
84
|
return unless (exist_node = send_exist_node(node.parent).first)
|
@@ -116,6 +116,7 @@ module RuboCop
|
|
116
116
|
|
117
117
|
def message_remove_file_exist_check(node)
|
118
118
|
receiver, method_name = receiver_and_method_name(node)
|
119
|
+
|
119
120
|
format(MSG_REMOVE_FILE_EXIST_CHECK, receiver: receiver, method_name: method_name)
|
120
121
|
end
|
121
122
|
|
@@ -4,30 +4,27 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Lint
|
6
6
|
# Certain numeric operations have a constant result, usually 0 or 1.
|
7
|
-
#
|
8
|
-
# Additionally, a variable modulo 0 or itself will always return 0.
|
7
|
+
# Multiplying a number by 0 will always return 0.
|
9
8
|
# Dividing a number by itself or raising it to the power of 0 will always return 1.
|
10
9
|
# As such, they can be replaced with that result.
|
11
10
|
# These are probably leftover from debugging, or are mistakes.
|
12
11
|
# Other numeric operations that are similarly leftover from debugging or mistakes
|
13
12
|
# are handled by Lint/UselessNumericOperation.
|
14
13
|
#
|
14
|
+
# NOTE: This cop doesn't detect offenses for the `-` and `%` operator because it
|
15
|
+
# can't determine the type of `x`. If `x` is an Array or String, it doesn't perform
|
16
|
+
# a numeric operation.
|
17
|
+
#
|
15
18
|
# @example
|
16
19
|
#
|
17
20
|
# # bad
|
18
|
-
# x - x
|
19
21
|
# x * 0
|
20
|
-
# x % 1
|
21
|
-
# x % x
|
22
22
|
#
|
23
23
|
# # good
|
24
24
|
# 0
|
25
25
|
#
|
26
26
|
# # bad
|
27
|
-
# x -= x
|
28
27
|
# x *= 0
|
29
|
-
# x %= 1
|
30
|
-
# x %= x
|
31
28
|
#
|
32
29
|
# # good
|
33
30
|
# x = 0
|
@@ -49,7 +46,7 @@ module RuboCop
|
|
49
46
|
class NumericOperationWithConstantResult < Base
|
50
47
|
extend AutoCorrector
|
51
48
|
MSG = 'Numeric operation with a constant result detected.'
|
52
|
-
RESTRICT_ON_SEND = %i[
|
49
|
+
RESTRICT_ON_SEND = %i[* / **].freeze
|
53
50
|
|
54
51
|
# @!method operation_with_constant_result?(node)
|
55
52
|
def_node_matcher :operation_with_constant_result?,
|
@@ -85,21 +82,16 @@ module RuboCop
|
|
85
82
|
|
86
83
|
private
|
87
84
|
|
88
|
-
# rubocop :disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
89
85
|
def constant_result?(variable, operation, number)
|
90
86
|
if number.to_s == '0'
|
91
87
|
return 0 if operation == :*
|
92
88
|
return 1 if operation == :**
|
93
|
-
elsif number.to_s == '1'
|
94
|
-
return 0 if operation == :%
|
95
89
|
elsif number == variable
|
96
|
-
return 0 if %i[- %].include?(operation)
|
97
90
|
return 1 if operation == :/
|
98
91
|
end
|
99
92
|
# If we weren't able to find any matches, return false so we can bail out.
|
100
93
|
false
|
101
94
|
end
|
102
|
-
# rubocop :enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
103
95
|
end
|
104
96
|
end
|
105
97
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# Checks for Hash creation with a mutable default value.
|
7
|
+
# Creating a Hash in such a way will share the default value
|
8
|
+
# across all keys, causing unexpected behavior when modifying it.
|
9
|
+
#
|
10
|
+
# For example, when the Hash was created with an Array as the argument,
|
11
|
+
# calling `hash[:foo] << 'bar'` will also change the value of all
|
12
|
+
# other keys that have not been explicitly assigned to.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# # bad
|
16
|
+
# Hash.new([])
|
17
|
+
# Hash.new({})
|
18
|
+
# Hash.new(Array.new)
|
19
|
+
# Hash.new(Hash.new)
|
20
|
+
#
|
21
|
+
# # okay -- In rare cases that intentionally have this behavior,
|
22
|
+
# # without disabling the cop, you can set the default explicitly.
|
23
|
+
# h = Hash.new
|
24
|
+
# h.default = []
|
25
|
+
# h[:a] << 1
|
26
|
+
# h[:b] << 2
|
27
|
+
# h # => {:a => [1, 2], :b => [1, 2]}
|
28
|
+
#
|
29
|
+
# # okay -- beware this will discard mutations and only remember assignments
|
30
|
+
# Hash.new { Array.new }
|
31
|
+
# Hash.new { Hash.new }
|
32
|
+
# Hash.new { {} }
|
33
|
+
# Hash.new { [] }
|
34
|
+
#
|
35
|
+
# # good - frozen solution will raise an error when mutation attempted
|
36
|
+
# Hash.new([].freeze)
|
37
|
+
# Hash.new({}.freeze)
|
38
|
+
#
|
39
|
+
# # good - using a proc will create a new object for each key
|
40
|
+
# h = Hash.new
|
41
|
+
# h.default_proc = ->(h, k) { [] }
|
42
|
+
# h.default_proc = ->(h, k) { {} }
|
43
|
+
#
|
44
|
+
# # good - using a block will create a new object for each key
|
45
|
+
# Hash.new { |h, k| h[k] = [] }
|
46
|
+
# Hash.new { |h, k| h[k] = {} }
|
47
|
+
class SharedMutableDefault < Base
|
48
|
+
MSG = 'Do not create a Hash with a mutable default value ' \
|
49
|
+
'as the default value can accidentally be changed.'
|
50
|
+
RESTRICT_ON_SEND = %i[new].freeze
|
51
|
+
|
52
|
+
# @!method hash_initialized_with_mutable_shared_object?(node)
|
53
|
+
def_node_matcher :hash_initialized_with_mutable_shared_object?, <<~PATTERN
|
54
|
+
(send (const {nil? cbase} :Hash) :new {array hash (send (const {nil? cbase} {:Array :Hash}) :new)})
|
55
|
+
PATTERN
|
56
|
+
|
57
|
+
def on_send(node)
|
58
|
+
return unless hash_initialized_with_mutable_shared_object?(node)
|
59
|
+
|
60
|
+
add_offense(node)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -6,9 +6,12 @@ module RuboCop
|
|
6
6
|
# Repacks Parser's diagnostics/errors
|
7
7
|
# into RuboCop's offenses.
|
8
8
|
class Syntax < Base
|
9
|
+
LEVELS = %i[error fatal].freeze
|
10
|
+
|
9
11
|
def on_other_file
|
10
12
|
add_offense_from_error(processed_source.parser_error) if processed_source.parser_error
|
11
|
-
processed_source.diagnostics.
|
13
|
+
syntax_errors = processed_source.diagnostics.select { |d| LEVELS.include?(d.level) }
|
14
|
+
syntax_errors.each do |diagnostic|
|
12
15
|
add_offense_from_diagnostic(diagnostic, processed_source.ruby_version)
|
13
16
|
end
|
14
17
|
super
|
@@ -55,12 +55,9 @@ module RuboCop
|
|
55
55
|
return if node.each_descendant(:dstr).any?
|
56
56
|
|
57
57
|
regexp_constructor(node) do |text|
|
58
|
-
|
58
|
+
parse_regexp(text.value)&.each_expression do |expr|
|
59
59
|
detect_offenses(text, expr)
|
60
60
|
end
|
61
|
-
rescue Regexp::Parser::Error
|
62
|
-
# Upon encountering an invalid regular expression,
|
63
|
-
# we aim to proceed and identify any remaining potential offenses.
|
64
61
|
end
|
65
62
|
end
|
66
63
|
|
@@ -113,7 +113,8 @@ module RuboCop
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def check_expression(expr)
|
116
|
-
expr = expr.body if expr.if_type?
|
116
|
+
expr = expr.body if expr.if_type?
|
117
|
+
return unless expr
|
117
118
|
|
118
119
|
check_literal(expr)
|
119
120
|
check_var(expr)
|
@@ -229,7 +230,7 @@ module RuboCop
|
|
229
230
|
end
|
230
231
|
|
231
232
|
def autocorrect_void_expression(corrector, node)
|
232
|
-
return if node.parent.if_type?
|
233
|
+
return if node.parent.if_type?
|
233
234
|
|
234
235
|
corrector.remove(range_with_surrounding_space(range: node.source_range, side: :left))
|
235
236
|
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
LABEL = 'Method'
|
49
49
|
|
50
50
|
def on_def(node)
|
51
|
-
return if
|
51
|
+
return if allowed?(node.method_name)
|
52
52
|
|
53
53
|
check_code_length(node)
|
54
54
|
end
|
@@ -57,6 +57,9 @@ module RuboCop
|
|
57
57
|
def on_block(node)
|
58
58
|
return unless node.method?(:define_method)
|
59
59
|
|
60
|
+
method_name = node.send_node.first_argument
|
61
|
+
return if method_name.basic_literal? && allowed?(method_name.value)
|
62
|
+
|
60
63
|
check_code_length(node)
|
61
64
|
end
|
62
65
|
alias on_numblock on_block
|
@@ -66,6 +69,10 @@ module RuboCop
|
|
66
69
|
def cop_label
|
67
70
|
LABEL
|
68
71
|
end
|
72
|
+
|
73
|
+
def allowed?(method_name)
|
74
|
+
allowed_method?(method_name) || matches_allowed_pattern?(method_name)
|
75
|
+
end
|
69
76
|
end
|
70
77
|
end
|
71
78
|
end
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
# (Note: Passes may not happen exactly in this sequence.)
|
44
44
|
module CheckLineBreakable
|
45
45
|
def extract_breakable_node(node, max)
|
46
|
-
if node.
|
46
|
+
if node.call_type?
|
47
47
|
return if chained_to_heredoc?(node)
|
48
48
|
|
49
49
|
args = process_args(node.arguments)
|
@@ -74,9 +74,9 @@ module RuboCop
|
|
74
74
|
def extract_first_element_over_column_limit(node, elements, max)
|
75
75
|
line = node.first_line
|
76
76
|
|
77
|
-
# If a `send` node is not parenthesized, don't move the first element, because it
|
77
|
+
# If a `send` or `csend` node is not parenthesized, don't move the first element, because it
|
78
78
|
# can result in changed behavior or a syntax error.
|
79
|
-
if node.
|
79
|
+
if node.call_type? && !node.parenthesized? && !first_argument_is_heredoc?(node)
|
80
80
|
elements = elements.drop(1)
|
81
81
|
end
|
82
82
|
|
@@ -98,10 +98,10 @@ module RuboCop
|
|
98
98
|
end
|
99
99
|
|
100
100
|
# @api private
|
101
|
-
# If a send node contains a heredoc argument, splitting cannot happen
|
101
|
+
# If a `send` or `csend` node contains a heredoc argument, splitting cannot happen
|
102
102
|
# after the heredoc or else it will cause a syntax error.
|
103
103
|
def shift_elements_for_heredoc_arg(node, elements, index)
|
104
|
-
return index unless node.
|
104
|
+
return index unless node.call_type? || node.array_type?
|
105
105
|
|
106
106
|
heredoc_index = elements.index { |arg| arg.respond_to?(:heredoc?) && arg.heredoc? }
|
107
107
|
return index unless heredoc_index
|
@@ -156,7 +156,7 @@ module RuboCop
|
|
156
156
|
|
157
157
|
if ancestor.hash_type? || ancestor.array_type?
|
158
158
|
elements = ancestor.children
|
159
|
-
elsif ancestor.
|
159
|
+
elsif ancestor.call_type?
|
160
160
|
elements = process_args(ancestor.arguments)
|
161
161
|
else
|
162
162
|
next
|
@@ -176,7 +176,7 @@ module RuboCop
|
|
176
176
|
return children_could_be_broken_up?(ancestor.children)
|
177
177
|
end
|
178
178
|
|
179
|
-
next unless ancestor.
|
179
|
+
next unless ancestor.call_type?
|
180
180
|
|
181
181
|
args = process_args(ancestor.arguments)
|
182
182
|
return children_could_be_broken_up?(args) if breakable_collection?(ancestor, args)
|
@@ -73,6 +73,8 @@ module RuboCop
|
|
73
73
|
node.else_branch.loc.line
|
74
74
|
elsif node.elsif?
|
75
75
|
node.each_ancestor(:if).find(&:if?).loc.end.line
|
76
|
+
elsif node.if? && node.parent && parentheses?(node.parent)
|
77
|
+
node.parent.loc.end.line
|
76
78
|
end
|
77
79
|
elsif node.block_type? || node.numblock_type?
|
78
80
|
node.loc.end.line
|