rubocop 0.93.0 → 1.3.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 +36 -16
- data/config/default.yml +207 -77
- data/exe/rubocop +1 -1
- data/lib/rubocop.rb +16 -2
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
- data/lib/rubocop/cli/command/version.rb +1 -1
- data/lib/rubocop/comment_config.rb +1 -1
- data/lib/rubocop/config.rb +4 -0
- data/lib/rubocop/config_loader.rb +26 -8
- data/lib/rubocop/config_loader_resolver.rb +7 -5
- data/lib/rubocop/config_validator.rb +7 -6
- data/lib/rubocop/cop/badge.rb +9 -24
- data/lib/rubocop/cop/base.rb +16 -1
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +26 -6
- data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
- data/lib/rubocop/cop/commissioner.rb +37 -23
- data/lib/rubocop/cop/corrector.rb +3 -1
- data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +1 -1
- data/lib/rubocop/cop/force.rb +1 -1
- data/lib/rubocop/cop/gemspec/duplicated_assignment.rb +3 -3
- data/lib/rubocop/cop/gemspec/required_ruby_version.rb +4 -5
- data/lib/rubocop/cop/gemspec/ruby_version_globals_usage.rb +1 -1
- data/lib/rubocop/cop/generator.rb +1 -1
- data/lib/rubocop/cop/layout/block_alignment.rb +3 -4
- data/lib/rubocop/cop/layout/class_structure.rb +7 -0
- data/lib/rubocop/cop/layout/def_end_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/else_alignment.rb +15 -2
- data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
- data/lib/rubocop/cop/layout/end_alignment.rb +3 -3
- data/lib/rubocop/cop/layout/extra_spacing.rb +1 -2
- data/lib/rubocop/cop/layout/hash_alignment.rb +4 -4
- data/lib/rubocop/cop/layout/line_length.rb +8 -1
- data/lib/rubocop/cop/layout/space_around_block_parameters.rb +24 -18
- data/lib/rubocop/cop/layout/space_around_operators.rb +4 -1
- data/lib/rubocop/cop/layout/space_inside_parens.rb +35 -13
- data/lib/rubocop/cop/layout/trailing_whitespace.rb +37 -13
- data/lib/rubocop/cop/lint/ambiguous_regexp_literal.rb +6 -3
- data/lib/rubocop/cop/lint/constant_definition_in_block.rb +23 -2
- data/lib/rubocop/cop/lint/debugger.rb +17 -28
- data/lib/rubocop/cop/lint/duplicate_branch.rb +93 -0
- data/lib/rubocop/cop/lint/duplicate_case_condition.rb +2 -12
- data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +77 -0
- data/lib/rubocop/cop/lint/else_layout.rb +29 -3
- data/lib/rubocop/cop/lint/empty_block.rb +82 -0
- data/lib/rubocop/cop/lint/empty_class.rb +93 -0
- data/lib/rubocop/cop/lint/flip_flop.rb +8 -2
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +38 -6
- data/lib/rubocop/cop/lint/loop.rb +4 -4
- data/lib/rubocop/cop/lint/nested_percent_literal.rb +14 -0
- data/lib/rubocop/cop/lint/no_return_in_begin_end_blocks.rb +58 -0
- data/lib/rubocop/cop/lint/number_conversion.rb +46 -13
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +27 -8
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +19 -16
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +44 -11
- data/lib/rubocop/cop/lint/shadowed_exception.rb +4 -5
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +95 -0
- data/lib/rubocop/cop/lint/to_json.rb +1 -1
- data/lib/rubocop/cop/lint/unmodified_reduce_accumulator.rb +185 -0
- data/lib/rubocop/cop/lint/useless_access_modifier.rb +2 -2
- data/lib/rubocop/cop/lint/useless_method_definition.rb +2 -4
- data/lib/rubocop/cop/lint/useless_setter_call.rb +6 -1
- data/lib/rubocop/cop/metrics/class_length.rb +9 -3
- data/lib/rubocop/cop/metrics/parameter_lists.rb +4 -1
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
- data/lib/rubocop/cop/mixin/configurable_numbering.rb +3 -3
- data/lib/rubocop/cop/mixin/line_length_help.rb +1 -1
- data/lib/rubocop/cop/mixin/statement_modifier.rb +9 -4
- data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +12 -2
- data/lib/rubocop/cop/naming/heredoc_delimiter_case.rb +11 -5
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +67 -18
- data/lib/rubocop/cop/naming/predicate_name.rb +2 -1
- data/lib/rubocop/cop/naming/variable_number.rb +98 -8
- data/lib/rubocop/cop/offense.rb +3 -3
- data/lib/rubocop/cop/security/open.rb +12 -10
- data/lib/rubocop/cop/style/accessor_grouping.rb +1 -1
- data/lib/rubocop/cop/style/and_or.rb +1 -3
- data/lib/rubocop/cop/style/arguments_forwarding.rb +142 -0
- data/lib/rubocop/cop/style/bisected_attr_accessor.rb +0 -4
- data/lib/rubocop/cop/style/case_like_if.rb +0 -4
- data/lib/rubocop/cop/style/class_equality_comparison.rb +19 -4
- data/lib/rubocop/cop/style/collection_compact.rb +91 -0
- data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +162 -0
- data/lib/rubocop/cop/style/double_negation.rb +6 -1
- data/lib/rubocop/cop/style/format_string_token.rb +47 -2
- data/lib/rubocop/cop/style/hash_syntax.rb +3 -3
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +7 -2
- data/lib/rubocop/cop/style/if_inside_else.rb +37 -1
- data/lib/rubocop/cop/style/if_unless_modifier.rb +7 -3
- data/lib/rubocop/cop/style/infinite_loop.rb +4 -0
- data/lib/rubocop/cop/style/keyword_parameters_order.rb +12 -0
- data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +10 -13
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +6 -11
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/require_parentheses.rb +7 -11
- data/lib/rubocop/cop/style/mixin_grouping.rb +0 -4
- data/lib/rubocop/cop/style/multiple_comparison.rb +55 -7
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +104 -0
- data/lib/rubocop/cop/style/nil_lambda.rb +52 -0
- data/lib/rubocop/cop/style/raise_args.rb +21 -6
- data/lib/rubocop/cop/style/redundant_begin.rb +14 -4
- data/lib/rubocop/cop/style/redundant_parentheses.rb +4 -0
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +7 -1
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
- data/lib/rubocop/cop/style/redundant_self.rb +3 -0
- data/lib/rubocop/cop/style/safe_navigation.rb +16 -4
- data/lib/rubocop/cop/style/semicolon.rb +3 -0
- data/lib/rubocop/cop/style/static_class.rb +97 -0
- data/lib/rubocop/cop/style/string_concatenation.rb +13 -1
- data/lib/rubocop/cop/style/swap_values.rb +108 -0
- data/lib/rubocop/cop/style/ternary_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/trailing_underscore_variable.rb +3 -1
- data/lib/rubocop/cop/style/while_until_modifier.rb +9 -0
- data/lib/rubocop/cop/team.rb +6 -1
- data/lib/rubocop/cop/util.rb +5 -1
- data/lib/rubocop/ext/regexp_node.rb +17 -9
- data/lib/rubocop/ext/regexp_parser.rb +84 -0
- data/lib/rubocop/formatter/formatter_set.rb +2 -1
- data/lib/rubocop/formatter/git_hub_actions_formatter.rb +47 -0
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -1
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -1
- data/lib/rubocop/magic_comment.rb +2 -2
- data/lib/rubocop/options.rb +6 -1
- data/lib/rubocop/rspec/shared_contexts.rb +4 -0
- data/lib/rubocop/target_ruby.rb +57 -1
- data/lib/rubocop/version.rb +56 -6
- metadata +21 -5
data/lib/rubocop/cop/offense.rb
CHANGED
@@ -213,7 +213,7 @@ module RuboCop
|
|
213
213
|
# returns `true` if two offenses contain same attributes
|
214
214
|
def ==(other)
|
215
215
|
COMPARISON_ATTRIBUTES.all? do |attribute|
|
216
|
-
|
216
|
+
public_send(attribute) == other.public_send(attribute)
|
217
217
|
end
|
218
218
|
end
|
219
219
|
|
@@ -221,7 +221,7 @@ module RuboCop
|
|
221
221
|
|
222
222
|
def hash
|
223
223
|
COMPARISON_ATTRIBUTES.reduce(0) do |hash, attribute|
|
224
|
-
hash ^
|
224
|
+
hash ^ public_send(attribute).hash
|
225
225
|
end
|
226
226
|
end
|
227
227
|
|
@@ -234,7 +234,7 @@ module RuboCop
|
|
234
234
|
# comparison result
|
235
235
|
def <=>(other)
|
236
236
|
COMPARISON_ATTRIBUTES.each do |attribute|
|
237
|
-
result =
|
237
|
+
result = public_send(attribute) <=> other.public_send(attribute)
|
238
238
|
return result unless result.zero?
|
239
239
|
end
|
240
240
|
0
|
@@ -3,35 +3,37 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Security
|
6
|
-
# This cop checks for the use of `Kernel#open`.
|
6
|
+
# This cop checks for the use of `Kernel#open` and `URI.open`.
|
7
7
|
#
|
8
|
-
# `Kernel#open`
|
9
|
-
# by prefixing a pipe symbol (e.g., `open("| ls")`).
|
10
|
-
# a serious security risk by using variable input to
|
11
|
-
# `Kernel#open`. It would be better to use
|
12
|
-
# `URI#open` explicitly.
|
8
|
+
# `Kernel#open` and `URI.open` enable not only file access but also process
|
9
|
+
# invocation by prefixing a pipe symbol (e.g., `open("| ls")`).
|
10
|
+
# So, it may lead to a serious security risk by using variable input to
|
11
|
+
# the argument of `Kernel#open` and `URI.open`. It would be better to use
|
12
|
+
# `File.open`, `IO.popen` or `URI.parse#open` explicitly.
|
13
13
|
#
|
14
14
|
# @example
|
15
15
|
# # bad
|
16
16
|
# open(something)
|
17
|
+
# URI.open(something)
|
17
18
|
#
|
18
19
|
# # good
|
19
20
|
# File.open(something)
|
20
21
|
# IO.popen(something)
|
21
22
|
# URI.parse(something).open
|
22
23
|
class Open < Base
|
23
|
-
MSG = 'The use of `
|
24
|
+
MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
|
24
25
|
RESTRICT_ON_SEND = %i[open].freeze
|
25
26
|
|
26
27
|
def_node_matcher :open?, <<~PATTERN
|
27
|
-
(send nil? :open $!str ...)
|
28
|
+
(send ${nil? (const {nil? cbase} :URI)} :open $!str ...)
|
28
29
|
PATTERN
|
29
30
|
|
30
31
|
def on_send(node)
|
31
|
-
open?(node) do |code|
|
32
|
+
open?(node) do |receiver, code|
|
32
33
|
return if safe?(code)
|
33
34
|
|
34
|
-
|
35
|
+
message = format(MSG, receiver: receiver ? "#{receiver.source}." : 'Kernel#')
|
36
|
+
add_offense(node.loc.selector, message: message)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -7,7 +7,7 @@ module RuboCop
|
|
7
7
|
# By default it enforces accessors to be placed in grouped declarations,
|
8
8
|
# but it can be configured to enforce separating them in multiple declarations.
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# NOTE: `Sorbet` is not compatible with "grouped" style. Consider "separated" style
|
11
11
|
# or disabling this cop.
|
12
12
|
#
|
13
13
|
# @example EnforcedStyle: grouped (default)
|
@@ -66,9 +66,7 @@ module RuboCop
|
|
66
66
|
node.each_child_node do |expr|
|
67
67
|
if expr.send_type?
|
68
68
|
correct_send(expr, corrector)
|
69
|
-
elsif expr.return_type?
|
70
|
-
correct_other(expr, corrector)
|
71
|
-
elsif expr.assignment?
|
69
|
+
elsif expr.return_type? || expr.assignment?
|
72
70
|
correct_other(expr, corrector)
|
73
71
|
end
|
74
72
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# In Ruby 2.7, arguments forwarding has been added.
|
7
|
+
#
|
8
|
+
# This cop identifies places where `do_something(*args, &block)`
|
9
|
+
# can be replaced by `do_something(...)`.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# def foo(*args, &block)
|
14
|
+
# bar(*args, &block)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # bad
|
18
|
+
# def foo(*args, **kwargs, &block)
|
19
|
+
# bar(*args, **kwargs, &block)
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# def foo(...)
|
24
|
+
# bar(...)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @example AllowOnlyRestArgument: true (default)
|
28
|
+
# # good
|
29
|
+
# def foo(*args)
|
30
|
+
# bar(*args)
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @example AllowOnlyRestArgument: false
|
34
|
+
# # bad
|
35
|
+
# # The following code can replace the arguments with `...`,
|
36
|
+
# # but it will change the behavior. Because `...` forwards block also.
|
37
|
+
# def foo(*args)
|
38
|
+
# bar(*args)
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
class ArgumentsForwarding < Base
|
42
|
+
include RangeHelp
|
43
|
+
extend AutoCorrector
|
44
|
+
extend TargetRubyVersion
|
45
|
+
|
46
|
+
minimum_target_ruby_version 2.7
|
47
|
+
|
48
|
+
MSG = 'Use arguments forwarding.'
|
49
|
+
|
50
|
+
def_node_matcher :use_rest_arguments?, <<~PATTERN
|
51
|
+
(args (restarg $_) $...)
|
52
|
+
PATTERN
|
53
|
+
|
54
|
+
def_node_matcher :only_rest_arguments?, <<~PATTERN
|
55
|
+
(send _ _ (splat (lvar %1)))
|
56
|
+
PATTERN
|
57
|
+
|
58
|
+
def_node_matcher :forwarding_method_arguments?, <<~PATTERN
|
59
|
+
{
|
60
|
+
(send _ _
|
61
|
+
(splat (lvar %1))
|
62
|
+
(block-pass (lvar %2)))
|
63
|
+
(send _ _
|
64
|
+
(splat (lvar %1))
|
65
|
+
(hash (kwsplat (lvar %3)))
|
66
|
+
(block-pass (lvar %2)))
|
67
|
+
}
|
68
|
+
PATTERN
|
69
|
+
|
70
|
+
def on_def(node)
|
71
|
+
return unless node.body
|
72
|
+
return unless (rest_args_name, args = use_rest_arguments?(node.arguments))
|
73
|
+
|
74
|
+
node.each_descendant(:send) do |send_node|
|
75
|
+
kwargs_name, block_name = extract_argument_names_from(args)
|
76
|
+
|
77
|
+
next unless forwarding_method?(send_node, rest_args_name, kwargs_name, block_name) &&
|
78
|
+
all_lvars_as_forwarding_method_arguments?(node, send_node)
|
79
|
+
|
80
|
+
register_offense_to_forwarding_method_arguments(send_node)
|
81
|
+
register_offense_to_method_definition_arguments(node)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
alias on_defs on_def
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_argument_names_from(args)
|
89
|
+
kwargs_name = args.first.source.delete('**') if args.first&.kwrestarg_type?
|
90
|
+
block_arg_name = args.last.source.delete('&') if args.last&.blockarg_type?
|
91
|
+
|
92
|
+
[kwargs_name, block_arg_name].map { |name| name&.to_sym }
|
93
|
+
end
|
94
|
+
|
95
|
+
def forwarding_method?(node, rest_arg, kwargs, block_arg)
|
96
|
+
return only_rest_arguments?(node, rest_arg) unless allow_only_rest_arguments?
|
97
|
+
|
98
|
+
forwarding_method_arguments?(node, rest_arg, block_arg, kwargs)
|
99
|
+
end
|
100
|
+
|
101
|
+
def all_lvars_as_forwarding_method_arguments?(def_node, forwarding_method)
|
102
|
+
lvars = def_node.body.each_descendant(:lvar, :lvasgn)
|
103
|
+
|
104
|
+
begin_pos = forwarding_method.source_range.begin_pos
|
105
|
+
end_pos = forwarding_method.source_range.end_pos
|
106
|
+
|
107
|
+
lvars.all? do |lvar|
|
108
|
+
lvar.source_range.begin_pos.between?(begin_pos, end_pos)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def register_offense_to_forwarding_method_arguments(forwarding_method)
|
113
|
+
add_offense(arguments_range(forwarding_method)) do |corrector|
|
114
|
+
range = range_between(
|
115
|
+
forwarding_method.loc.selector.end_pos, forwarding_method.source_range.end_pos
|
116
|
+
)
|
117
|
+
corrector.replace(range, '(...)')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def register_offense_to_method_definition_arguments(method_definition)
|
122
|
+
add_offense(arguments_range(method_definition)) do |corrector|
|
123
|
+
arguments_range = range_with_surrounding_space(
|
124
|
+
range: method_definition.arguments.source_range, side: :left
|
125
|
+
)
|
126
|
+
corrector.replace(arguments_range, '(...)')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def arguments_range(node)
|
131
|
+
arguments = node.arguments
|
132
|
+
|
133
|
+
range_between(arguments.first.source_range.begin_pos, arguments.last.source_range.end_pos)
|
134
|
+
end
|
135
|
+
|
136
|
+
def allow_only_rest_arguments?
|
137
|
+
cop_config.fetch('AllowOnlyRestArgument', true)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -227,10 +227,6 @@ module RuboCop
|
|
227
227
|
range_between(node.parent.loc.keyword.begin_pos, node.loc.expression.end_pos)
|
228
228
|
end
|
229
229
|
|
230
|
-
def indent(node)
|
231
|
-
' ' * node.loc.column
|
232
|
-
end
|
233
|
-
|
234
230
|
# Named captures work with `=~` (if regexp is on lhs) and with `match` (both sides)
|
235
231
|
def regexp_with_working_captures?(node)
|
236
232
|
case node.type
|
@@ -21,7 +21,7 @@ module RuboCop
|
|
21
21
|
include IgnoredMethods
|
22
22
|
extend AutoCorrector
|
23
23
|
|
24
|
-
MSG = 'Use `
|
24
|
+
MSG = 'Use `instance_of?(%<class_name>s)` instead of comparing classes.'
|
25
25
|
|
26
26
|
RESTRICT_ON_SEND = %i[== equal? eql?].freeze
|
27
27
|
|
@@ -36,13 +36,28 @@ module RuboCop
|
|
36
36
|
return if def_node && ignored_method?(def_node.method_name)
|
37
37
|
|
38
38
|
class_comparison_candidate?(node) do |receiver_node, class_node|
|
39
|
-
range =
|
39
|
+
range = offense_range(receiver_node, node)
|
40
|
+
class_name = class_name(class_node, node)
|
40
41
|
|
41
|
-
add_offense(range) do |corrector|
|
42
|
-
corrector.replace(range, "instance_of?(#{
|
42
|
+
add_offense(range, message: format(MSG, class_name: class_name)) do |corrector|
|
43
|
+
corrector.replace(range, "instance_of?(#{class_name})")
|
43
44
|
end
|
44
45
|
end
|
45
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def class_name(class_node, node)
|
51
|
+
if node.children.first.method?(:name)
|
52
|
+
class_node.source.delete('"').delete("'")
|
53
|
+
else
|
54
|
+
class_node.source
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def offense_range(receiver_node, node)
|
59
|
+
range_between(receiver_node.loc.selector.begin_pos, node.source_range.end_pos)
|
60
|
+
end
|
46
61
|
end
|
47
62
|
end
|
48
63
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# This cop checks for places where custom logic on rejection nils from arrays
|
7
|
+
# and hashes can be replaced with `{Array,Hash}#{compact,compact!}`.
|
8
|
+
#
|
9
|
+
# It is marked as unsafe by default because false positives may occur in the
|
10
|
+
# nil check of block arguments to the receiver object.
|
11
|
+
# For example, `[[1, 2], [3, nil]].reject { |first, second| second.nil? }`
|
12
|
+
# and `[[1, 2], [3, nil]].compact` are not compatible. This will work fine
|
13
|
+
# when the receiver is a hash object.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # bad
|
17
|
+
# array.reject { |e| e.nil? }
|
18
|
+
# array.select { |e| !e.nil? }
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# array.compact
|
22
|
+
#
|
23
|
+
# # bad
|
24
|
+
# hash.reject! { |k, v| v.nil? }
|
25
|
+
# hash.select! { |k, v| !v.nil? }
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# hash.compact!
|
29
|
+
#
|
30
|
+
class CollectionCompact < Base
|
31
|
+
include RangeHelp
|
32
|
+
extend AutoCorrector
|
33
|
+
|
34
|
+
MSG = 'Use `%<good>s` instead of `%<bad>s`.'
|
35
|
+
|
36
|
+
RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
|
37
|
+
|
38
|
+
def_node_matcher :reject_method?, <<~PATTERN
|
39
|
+
(block
|
40
|
+
(send
|
41
|
+
_ ${:reject :reject!})
|
42
|
+
$(args ...)
|
43
|
+
(send
|
44
|
+
$(lvar _) :nil?))
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
def_node_matcher :select_method?, <<~PATTERN
|
48
|
+
(block
|
49
|
+
(send
|
50
|
+
_ ${:select :select!})
|
51
|
+
$(args ...)
|
52
|
+
(send
|
53
|
+
(send
|
54
|
+
$(lvar _) :nil?) :!))
|
55
|
+
PATTERN
|
56
|
+
|
57
|
+
def on_send(node)
|
58
|
+
block_node = node.parent
|
59
|
+
return unless block_node&.block_type?
|
60
|
+
|
61
|
+
return unless (method_name, args, receiver =
|
62
|
+
reject_method?(block_node) || select_method?(block_node))
|
63
|
+
|
64
|
+
return unless args.last.source == receiver.source
|
65
|
+
|
66
|
+
range = offense_range(node, block_node)
|
67
|
+
good = good_method_name(method_name)
|
68
|
+
message = format(MSG, good: good, bad: range.source)
|
69
|
+
|
70
|
+
add_offense(range, message: message) do |corrector|
|
71
|
+
corrector.replace(range, good)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def good_method_name(method_name)
|
78
|
+
if method_name.to_s.end_with?('!')
|
79
|
+
'compact!'
|
80
|
+
else
|
81
|
+
'compact'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def offense_range(send_node, block_node)
|
86
|
+
range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# When using `class_eval` (or other `eval`) with string interpolation,
|
7
|
+
# add a comment block showing its appearance if interpolated (a practice used in Rails code).
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # from activesupport/lib/active_support/core_ext/string/output_safety.rb
|
11
|
+
#
|
12
|
+
# # bad
|
13
|
+
# UNSAFE_STRING_METHODS.each do |unsafe_method|
|
14
|
+
# if 'String'.respond_to?(unsafe_method)
|
15
|
+
# class_eval <<-EOT, __FILE__, __LINE__ + 1
|
16
|
+
# def #{unsafe_method}(*params, &block)
|
17
|
+
# to_str.#{unsafe_method}(*params, &block)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def #{unsafe_method}!(*params)
|
21
|
+
# @dirty = true
|
22
|
+
# super
|
23
|
+
# end
|
24
|
+
# EOT
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # good, inline comments in heredoc
|
29
|
+
# UNSAFE_STRING_METHODS.each do |unsafe_method|
|
30
|
+
# if 'String'.respond_to?(unsafe_method)
|
31
|
+
# class_eval <<-EOT, __FILE__, __LINE__ + 1
|
32
|
+
# def #{unsafe_method}(*params, &block) # def capitalize(*params, &block)
|
33
|
+
# to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block)
|
34
|
+
# end # end
|
35
|
+
#
|
36
|
+
# def #{unsafe_method}!(*params) # def capitalize!(*params)
|
37
|
+
# @dirty = true # @dirty = true
|
38
|
+
# super # super
|
39
|
+
# end # end
|
40
|
+
# EOT
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # good, block comments in heredoc
|
45
|
+
# class_eval <<-EOT, __FILE__, __LINE__ + 1
|
46
|
+
# # def capitalize!(*params)
|
47
|
+
# # @dirty = true
|
48
|
+
# # super
|
49
|
+
# # end
|
50
|
+
#
|
51
|
+
# def #{unsafe_method}!(*params)
|
52
|
+
# @dirty = true
|
53
|
+
# super
|
54
|
+
# end
|
55
|
+
# EOT
|
56
|
+
#
|
57
|
+
# # good, block comments before heredoc
|
58
|
+
# class_eval(
|
59
|
+
# # def capitalize!(*params)
|
60
|
+
# # @dirty = true
|
61
|
+
# # super
|
62
|
+
# # end
|
63
|
+
#
|
64
|
+
# <<-EOT, __FILE__, __LINE__ + 1
|
65
|
+
# def #{unsafe_method}!(*params)
|
66
|
+
# @dirty = true
|
67
|
+
# super
|
68
|
+
# end
|
69
|
+
# EOT
|
70
|
+
# )
|
71
|
+
class DocumentDynamicEvalDefinition < Base
|
72
|
+
BLOCK_COMMENT_REGEXP = /^\s*#(?!{)/.freeze
|
73
|
+
COMMENT_REGEXP = /\s*#(?!{).*/.freeze
|
74
|
+
MSG = 'Add a comment block showing its appearance if interpolated.'
|
75
|
+
|
76
|
+
RESTRICT_ON_SEND = %i[eval class_eval module_eval instance_eval].freeze
|
77
|
+
|
78
|
+
def on_send(node)
|
79
|
+
arg_node = node.first_argument
|
80
|
+
|
81
|
+
return unless arg_node&.dstr_type? && interpolated?(arg_node)
|
82
|
+
return if inline_comment_docs?(arg_node) || comment_block_docs?(arg_node)
|
83
|
+
|
84
|
+
add_offense(node.loc.selector)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def interpolated?(arg_node)
|
90
|
+
arg_node.each_child_node(:begin).any?
|
91
|
+
end
|
92
|
+
|
93
|
+
def inline_comment_docs?(node)
|
94
|
+
node.each_child_node(:begin).all? do |begin_node|
|
95
|
+
source_line = processed_source.lines[begin_node.first_line - 1]
|
96
|
+
source_line.match?(COMMENT_REGEXP)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def comment_block_docs?(arg_node)
|
101
|
+
comments = heredoc_comment_blocks(arg_node.loc.heredoc_body.line_span)
|
102
|
+
.concat(preceding_comment_blocks(arg_node.parent))
|
103
|
+
|
104
|
+
return if comments.none?
|
105
|
+
|
106
|
+
regexp = comment_regexp(arg_node)
|
107
|
+
comments.any? { |comment| regexp.match?(comment) } || regexp.match?(comments.join)
|
108
|
+
end
|
109
|
+
|
110
|
+
def preceding_comment_blocks(node)
|
111
|
+
# Collect comments in the method call, but outside the heredoc
|
112
|
+
comments = processed_source.each_comment_in_lines(node.loc.expression.line_span)
|
113
|
+
|
114
|
+
comments.each_with_object({}) do |comment, hash|
|
115
|
+
merge_adjacent_comments(comment.text, comment.loc.line, hash)
|
116
|
+
end.values
|
117
|
+
end
|
118
|
+
|
119
|
+
def heredoc_comment_blocks(heredoc_body)
|
120
|
+
# Collect comments inside the heredoc
|
121
|
+
line_range = (heredoc_body.begin - 1)..(heredoc_body.end - 1)
|
122
|
+
lines = processed_source.lines[line_range]
|
123
|
+
|
124
|
+
lines.each_with_object({}).with_index(line_range.begin) do |(line, hash), index|
|
125
|
+
merge_adjacent_comments(line, index, hash)
|
126
|
+
end.values
|
127
|
+
end
|
128
|
+
|
129
|
+
def merge_adjacent_comments(line, index, hash)
|
130
|
+
# Combine adjacent comment lines into a single string
|
131
|
+
return unless (line = line.dup.gsub!(BLOCK_COMMENT_REGEXP, ''))
|
132
|
+
|
133
|
+
hash[index] = if hash.keys.last == index - 1
|
134
|
+
[hash.delete(index - 1), line].join("\n")
|
135
|
+
else
|
136
|
+
line
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def comment_regexp(arg_node)
|
141
|
+
# Replace the interpolations with wildcards
|
142
|
+
regexp_parts = arg_node.child_nodes.map do |n|
|
143
|
+
n.begin_type? ? /.+/ : source_to_regexp(n.source)
|
144
|
+
end
|
145
|
+
|
146
|
+
Regexp.new(regexp_parts.join)
|
147
|
+
end
|
148
|
+
|
149
|
+
def source_to_regexp(source)
|
150
|
+
# Get the source in the heredoc being `eval`ed, without any comments
|
151
|
+
# and turn it into a regexp
|
152
|
+
return /\s+/ if source.blank?
|
153
|
+
|
154
|
+
source = source.gsub(COMMENT_REGEXP, '')
|
155
|
+
return if source.blank?
|
156
|
+
|
157
|
+
/\s*#{Regexp.escape(source.strip)}/
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|