rubocop 1.23.0 → 1.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/config/default.yml +45 -1
- data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
- data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
- data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
- data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
- data/lib/rubocop/cli.rb +1 -0
- data/lib/rubocop/config_loader_resolver.rb +1 -1
- data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
- data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
- data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
- data/lib/rubocop/cop/documentation.rb +19 -2
- data/lib/rubocop/cop/gemspec/require_mfa.rb +5 -5
- data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +46 -0
- data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
- data/lib/rubocop/cop/internal_affairs.rb +1 -0
- data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
- data/lib/rubocop/cop/layout/dot_position.rb +4 -0
- data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
- data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
- data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
- data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
- data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
- data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
- data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
- data/lib/rubocop/cop/metrics/block_length.rb +1 -0
- data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
- data/lib/rubocop/cop/metrics/method_length.rb +1 -0
- data/lib/rubocop/cop/metrics/module_length.rb +1 -1
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
- data/lib/rubocop/cop/naming/block_forwarding.rb +102 -0
- data/lib/rubocop/cop/security/open.rb +11 -1
- data/lib/rubocop/cop/style/character_literal.rb +8 -1
- data/lib/rubocop/cop/style/collection_compact.rb +31 -13
- data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
- data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
- data/lib/rubocop/cop/style/file_read.rb +112 -0
- data/lib/rubocop/cop/style/file_write.rb +98 -0
- data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
- data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
- data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
- data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
- data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
- data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
- data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
- data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
- data/lib/rubocop/cop/style/redundant_self.rb +1 -1
- data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
- data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
- data/lib/rubocop/cop/team.rb +1 -1
- data/lib/rubocop/options.rb +6 -1
- data/lib/rubocop/remote_config.rb +1 -3
- data/lib/rubocop/result_cache.rb +1 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +7 -0
- metadata +13 -5
@@ -13,6 +13,11 @@ module RuboCop
|
|
13
13
|
# @api private
|
14
14
|
module EnforceSuperclass
|
15
15
|
def self.included(base)
|
16
|
+
warn Rainbow(
|
17
|
+
'`RuboCop::Cop::EnforceSuperclass` is deprecated and will be removed in RuboCop 2.0. ' \
|
18
|
+
'Please upgrade to RuboCop Rails 2.9 or newer to continue.'
|
19
|
+
).yellow
|
20
|
+
|
16
21
|
# @!method class_definition(node)
|
17
22
|
base.def_node_matcher :class_definition, <<~PATTERN
|
18
23
|
(class (const _ !:#{base::SUPERCLASS}) #{base::BASE_PATTERN} ...)
|
@@ -43,7 +43,7 @@ module RuboCop
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def value_delta(pair)
|
46
|
-
return 0 if pair.value_on_new_line?
|
46
|
+
return 0 if pair.value_on_new_line? || pair.value_omission?
|
47
47
|
|
48
48
|
correct_value_column = pair.loc.operator.end.column + 1
|
49
49
|
actual_value_column = pair.value.loc.column
|
@@ -111,7 +111,8 @@ module RuboCop
|
|
111
111
|
correct_value_column = first_pair.key.loc.column +
|
112
112
|
current_pair.delimiter(true).length +
|
113
113
|
max_key_width
|
114
|
-
|
114
|
+
|
115
|
+
current_pair.value_omission? ? 0 : correct_value_column - current_pair.value.loc.column
|
115
116
|
end
|
116
117
|
end
|
117
118
|
|
@@ -134,7 +135,7 @@ module RuboCop
|
|
134
135
|
end
|
135
136
|
|
136
137
|
def value_delta(first_pair, current_pair)
|
137
|
-
first_pair.value_delta(current_pair)
|
138
|
+
current_pair.value_omission? ? 0 : first_pair.value_delta(current_pair)
|
138
139
|
end
|
139
140
|
end
|
140
141
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# This module checks for Ruby 3.1's hash value omission syntax.
|
6
|
+
module HashShorthandSyntax
|
7
|
+
OMIT_HASH_VALUE_MSG = 'Omit the hash value.'
|
8
|
+
EXPLICIT_HASH_VALUE_MSG = 'Explicit the hash value.'
|
9
|
+
|
10
|
+
def on_pair(node)
|
11
|
+
return if target_ruby_version <= 3.0
|
12
|
+
|
13
|
+
hash_key_source = node.key.source
|
14
|
+
|
15
|
+
if enforced_shorthand_syntax == 'always'
|
16
|
+
return if node.value_omission? || require_hash_value?(hash_key_source, node)
|
17
|
+
|
18
|
+
message = OMIT_HASH_VALUE_MSG
|
19
|
+
replacement = "#{hash_key_source}:"
|
20
|
+
else
|
21
|
+
return unless node.value_omission?
|
22
|
+
|
23
|
+
message = EXPLICIT_HASH_VALUE_MSG
|
24
|
+
replacement = "#{hash_key_source}: #{hash_key_source}"
|
25
|
+
end
|
26
|
+
|
27
|
+
add_offense(node.value, message: message) do |corrector|
|
28
|
+
corrector.replace(node, replacement)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def enforced_shorthand_syntax
|
35
|
+
cop_config.fetch('EnforcedShorthandSyntax', 'always')
|
36
|
+
end
|
37
|
+
|
38
|
+
def require_hash_value?(hash_key_source, node)
|
39
|
+
return true if without_parentheses_call_expr_follows?(node)
|
40
|
+
|
41
|
+
hash_value = node.value
|
42
|
+
return true unless hash_value.send_type? || hash_value.lvar_type?
|
43
|
+
|
44
|
+
hash_key_source != hash_value.source || hash_key_source.end_with?('!', '?')
|
45
|
+
end
|
46
|
+
|
47
|
+
def without_parentheses_call_expr_follows?(node)
|
48
|
+
return false unless (ancestor = node.parent.parent)
|
49
|
+
return false unless (right_sibling = ancestor.right_sibling)
|
50
|
+
|
51
|
+
ancestor.respond_to?(:parenthesized?) && !ancestor.parenthesized? &&
|
52
|
+
right_sibling.respond_to?(:parenthesized?) && !right_sibling.parenthesized?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Naming
|
6
|
+
# In Ruby 3.1, anonymous block forwarding has been added.
|
7
|
+
#
|
8
|
+
# This cop identifies places where `do_something(&block)` can be replaced
|
9
|
+
# by `do_something(&)`.
|
10
|
+
#
|
11
|
+
# It also supports the opposite style by alternative `explicit` option.
|
12
|
+
#
|
13
|
+
# @example EnforcedStyle: anonymous (default)
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# def foo(&block)
|
17
|
+
# bar(&block)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# def foo(&)
|
22
|
+
# bar(&)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example EnforcedStyle: explicit
|
26
|
+
#
|
27
|
+
# # bad
|
28
|
+
# def foo(&)
|
29
|
+
# bar(&)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # good
|
33
|
+
# def foo(&block)
|
34
|
+
# bar(&block)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
class BlockForwarding < Base
|
38
|
+
include ConfigurableEnforcedStyle
|
39
|
+
extend AutoCorrector
|
40
|
+
extend TargetRubyVersion
|
41
|
+
|
42
|
+
minimum_target_ruby_version 3.1
|
43
|
+
|
44
|
+
MSG = 'Use %<style>s block forwarding.'
|
45
|
+
|
46
|
+
def on_def(node)
|
47
|
+
return if node.arguments.empty?
|
48
|
+
|
49
|
+
last_argument = node.arguments.last
|
50
|
+
return if expected_block_forwarding_style?(node, last_argument)
|
51
|
+
|
52
|
+
register_offense(last_argument)
|
53
|
+
|
54
|
+
node.each_descendant(:block_pass) do |block_pass_node|
|
55
|
+
next if block_pass_node.children.first&.sym_type?
|
56
|
+
|
57
|
+
register_offense(block_pass_node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
alias on_defs on_def
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def expected_block_forwarding_style?(node, last_argument)
|
65
|
+
if style == :anonymous
|
66
|
+
!explicit_block_argument?(last_argument) ||
|
67
|
+
use_kwarg_in_method_definition?(node) ||
|
68
|
+
use_block_argument_as_local_variable?(node, last_argument)
|
69
|
+
else
|
70
|
+
!anonymous_block_argument?(last_argument)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def use_kwarg_in_method_definition?(node)
|
75
|
+
node.arguments.each_descendant(:kwarg, :kwoptarg).any?
|
76
|
+
end
|
77
|
+
|
78
|
+
def anonymous_block_argument?(node)
|
79
|
+
node.blockarg_type? && node.name.nil?
|
80
|
+
end
|
81
|
+
|
82
|
+
def explicit_block_argument?(node)
|
83
|
+
node.blockarg_type? && !node.name.nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
def use_block_argument_as_local_variable?(node, last_argument)
|
87
|
+
return if node.body.nil?
|
88
|
+
|
89
|
+
node.body.each_descendant(:lvar).any? do |lvar|
|
90
|
+
!lvar.parent.block_pass_type? && lvar.source == last_argument.source[1..-1]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def register_offense(block_argument)
|
95
|
+
add_offense(block_argument, message: format(MSG, style: style)) do |corrector|
|
96
|
+
corrector.replace(block_argument, '&')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -3,7 +3,8 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Security
|
6
|
-
# This cop checks for the use of `Kernel#open` and `URI.open
|
6
|
+
# This cop checks for the use of `Kernel#open` and `URI.open` with dynamic
|
7
|
+
# data.
|
7
8
|
#
|
8
9
|
# `Kernel#open` and `URI.open` enable not only file access but also process
|
9
10
|
# invocation by prefixing a pipe symbol (e.g., `open("| ls")`).
|
@@ -11,6 +12,9 @@ module RuboCop
|
|
11
12
|
# the argument of `Kernel#open` and `URI.open`. It would be better to use
|
12
13
|
# `File.open`, `IO.popen` or `URI.parse#open` explicitly.
|
13
14
|
#
|
15
|
+
# NOTE: `open` and `URI.open` with literal strings are not flagged by this
|
16
|
+
# cop.
|
17
|
+
#
|
14
18
|
# @safety
|
15
19
|
# This cop could register false positives if `open` is redefined
|
16
20
|
# in a class and then used without a receiver in that class.
|
@@ -18,12 +22,18 @@ module RuboCop
|
|
18
22
|
# @example
|
19
23
|
# # bad
|
20
24
|
# open(something)
|
25
|
+
# open("| #{something}")
|
21
26
|
# URI.open(something)
|
22
27
|
#
|
23
28
|
# # good
|
24
29
|
# File.open(something)
|
25
30
|
# IO.popen(something)
|
26
31
|
# URI.parse(something).open
|
32
|
+
#
|
33
|
+
# # good (literal strings)
|
34
|
+
# open("foo.text")
|
35
|
+
# open("| foo")
|
36
|
+
# URI.open("http://example.com")
|
27
37
|
class Open < Base
|
28
38
|
MSG = 'The use of `%<receiver>sopen` is a serious security risk.'
|
29
39
|
RESTRICT_ON_SEND = %i[open].freeze
|
@@ -4,6 +4,12 @@ module RuboCop
|
|
4
4
|
module Cop
|
5
5
|
module Style
|
6
6
|
# Checks for uses of the character literal ?x.
|
7
|
+
# Starting with Ruby 1.9 character literals are
|
8
|
+
# essentially one-character strings, so this syntax
|
9
|
+
# is mostly redundant at this point.
|
10
|
+
#
|
11
|
+
# ? character literal can be used to express meta and control character.
|
12
|
+
# That's a good use case of ? literal so it doesn't count it as an offense.
|
7
13
|
#
|
8
14
|
# @example
|
9
15
|
# # bad
|
@@ -12,8 +18,9 @@ module RuboCop
|
|
12
18
|
# # good
|
13
19
|
# 'x'
|
14
20
|
#
|
15
|
-
# # good
|
21
|
+
# # good - control & meta escapes
|
16
22
|
# ?\C-\M-d
|
23
|
+
# "\C-\M-d" # same as above
|
17
24
|
class CharacterLiteral < Base
|
18
25
|
include StringHelp
|
19
26
|
extend AutoCorrector
|
@@ -16,6 +16,7 @@ module RuboCop
|
|
16
16
|
#
|
17
17
|
# @example
|
18
18
|
# # bad
|
19
|
+
# array.reject(&:nil?)
|
19
20
|
# array.reject { |e| e.nil? }
|
20
21
|
# array.select { |e| !e.nil? }
|
21
22
|
#
|
@@ -23,6 +24,7 @@ module RuboCop
|
|
23
24
|
# array.compact
|
24
25
|
#
|
25
26
|
# # bad
|
27
|
+
# hash.reject!(&:nil?)
|
26
28
|
# hash.reject! { |k, v| v.nil? }
|
27
29
|
# hash.select! { |k, v| !v.nil? }
|
28
30
|
#
|
@@ -37,11 +39,18 @@ module RuboCop
|
|
37
39
|
|
38
40
|
RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
|
39
41
|
|
42
|
+
# @!method reject_method_with_block_pass?(node)
|
43
|
+
def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
|
44
|
+
(send _ {:reject :reject!}
|
45
|
+
(block_pass
|
46
|
+
(sym :nil?)))
|
47
|
+
PATTERN
|
48
|
+
|
40
49
|
# @!method reject_method?(node)
|
41
50
|
def_node_matcher :reject_method?, <<~PATTERN
|
42
51
|
(block
|
43
52
|
(send
|
44
|
-
_
|
53
|
+
_ {:reject :reject!})
|
45
54
|
$(args ...)
|
46
55
|
(send
|
47
56
|
$(lvar _) :nil?))
|
@@ -51,7 +60,7 @@ module RuboCop
|
|
51
60
|
def_node_matcher :select_method?, <<~PATTERN
|
52
61
|
(block
|
53
62
|
(send
|
54
|
-
_
|
63
|
+
_ {:select :select!})
|
55
64
|
$(args ...)
|
56
65
|
(send
|
57
66
|
(send
|
@@ -59,16 +68,9 @@ module RuboCop
|
|
59
68
|
PATTERN
|
60
69
|
|
61
70
|
def on_send(node)
|
62
|
-
|
63
|
-
return unless block_node&.block_type?
|
64
|
-
|
65
|
-
return unless (method_name, args, receiver =
|
66
|
-
reject_method?(block_node) || select_method?(block_node))
|
71
|
+
return unless (range = offense_range(node))
|
67
72
|
|
68
|
-
|
69
|
-
|
70
|
-
range = offense_range(node, block_node)
|
71
|
-
good = good_method_name(method_name)
|
73
|
+
good = good_method_name(node.method_name)
|
72
74
|
message = format(MSG, good: good, bad: range.source)
|
73
75
|
|
74
76
|
add_offense(range, message: message) { |corrector| corrector.replace(range, good) }
|
@@ -76,6 +78,22 @@ module RuboCop
|
|
76
78
|
|
77
79
|
private
|
78
80
|
|
81
|
+
def offense_range(node)
|
82
|
+
if reject_method_with_block_pass?(node)
|
83
|
+
range(node, node)
|
84
|
+
else
|
85
|
+
block_node = node.parent
|
86
|
+
|
87
|
+
return unless block_node&.block_type?
|
88
|
+
unless (args, receiver = reject_method?(block_node) || select_method?(block_node))
|
89
|
+
return
|
90
|
+
end
|
91
|
+
return unless args.last.source == receiver.source
|
92
|
+
|
93
|
+
range(node, block_node)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
79
97
|
def good_method_name(method_name)
|
80
98
|
if method_name.to_s.end_with?('!')
|
81
99
|
'compact!'
|
@@ -84,8 +102,8 @@ module RuboCop
|
|
84
102
|
end
|
85
103
|
end
|
86
104
|
|
87
|
-
def
|
88
|
-
range_between(
|
105
|
+
def range(begin_pos_node, end_pos_node)
|
106
|
+
range_between(begin_pos_node.loc.selector.begin_pos, end_pos_node.loc.end.end_pos)
|
89
107
|
end
|
90
108
|
end
|
91
109
|
end
|
@@ -77,14 +77,14 @@ module RuboCop
|
|
77
77
|
|
78
78
|
def collection_looping_method?(node)
|
79
79
|
# TODO: Remove `Symbol#to_s` after supporting only Ruby >= 2.7.
|
80
|
-
method_name = node.
|
80
|
+
method_name = node.method_name.to_s
|
81
81
|
method_name.start_with?('each') || method_name.end_with?('_each')
|
82
82
|
end
|
83
83
|
|
84
84
|
def same_collection_looping?(node, sibling)
|
85
85
|
sibling&.block_type? &&
|
86
86
|
sibling.send_node.method?(node.method_name) &&
|
87
|
-
sibling.
|
87
|
+
sibling.receiver == node.receiver &&
|
88
88
|
sibling.send_node.arguments == node.send_node.arguments
|
89
89
|
end
|
90
90
|
end
|
@@ -79,6 +79,8 @@ module RuboCop
|
|
79
79
|
when_nodes.each do |when_node|
|
80
80
|
conditions = when_node.conditions
|
81
81
|
|
82
|
+
replace_then_with_line_break(corrector, conditions, when_node)
|
83
|
+
|
82
84
|
next unless conditions.size > 1
|
83
85
|
|
84
86
|
range = range_between(conditions.first.source_range.begin_pos,
|
@@ -97,6 +99,14 @@ module RuboCop
|
|
97
99
|
line_beginning = case_range.adjust(begin_pos: -case_range.column)
|
98
100
|
corrector.insert_before(line_beginning, comments)
|
99
101
|
end
|
102
|
+
|
103
|
+
def replace_then_with_line_break(corrector, conditions, when_node)
|
104
|
+
return unless when_node.parent.parent && when_node.then?
|
105
|
+
|
106
|
+
range = range_between(conditions.last.source_range.end_pos, when_node.loc.begin.end_pos)
|
107
|
+
|
108
|
+
corrector.replace(range, "\n")
|
109
|
+
end
|
100
110
|
end
|
101
111
|
end
|
102
112
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Favor `File.(bin)read` convenience methods.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# ## text mode
|
10
|
+
# # bad
|
11
|
+
# File.open(filename).read
|
12
|
+
# File.open(filename, &:read)
|
13
|
+
# File.open(filename) { |f| f.read }
|
14
|
+
# File.open(filename) do |f|
|
15
|
+
# f.read
|
16
|
+
# end
|
17
|
+
# File.open(filename, 'r').read
|
18
|
+
# File.open(filename, 'r', &:read)
|
19
|
+
# File.open(filename, 'r') do |f|
|
20
|
+
# f.read
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# File.read(filename)
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# ## binary mode
|
28
|
+
# # bad
|
29
|
+
# File.open(filename, 'rb').read
|
30
|
+
# File.open(filename, 'rb', &:read)
|
31
|
+
# File.open(filename, 'rb') do |f|
|
32
|
+
# f.read
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# File.binread(filename)
|
37
|
+
#
|
38
|
+
class FileRead < Base
|
39
|
+
extend AutoCorrector
|
40
|
+
include RangeHelp
|
41
|
+
|
42
|
+
MSG = 'Use `File.%<read_method>s`.'
|
43
|
+
|
44
|
+
RESTRICT_ON_SEND = %i[open].freeze
|
45
|
+
|
46
|
+
READ_FILE_START_TO_FINISH_MODES = %w[r rt rb r+ r+t r+b].to_set.freeze
|
47
|
+
|
48
|
+
# @!method file_open?(node)
|
49
|
+
def_node_matcher :file_open?, <<~PATTERN
|
50
|
+
(send
|
51
|
+
(const {nil? cbase} :File)
|
52
|
+
:open
|
53
|
+
$_
|
54
|
+
(str $%READ_FILE_START_TO_FINISH_MODES)?
|
55
|
+
$(block-pass (sym :read))?
|
56
|
+
)
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
# @!method send_read?(node)
|
60
|
+
def_node_matcher :send_read?, <<~PATTERN
|
61
|
+
(send _ :read)
|
62
|
+
PATTERN
|
63
|
+
|
64
|
+
# @!method block_read?(node)
|
65
|
+
def_node_matcher :block_read?, <<~PATTERN
|
66
|
+
(block _ (args (arg $_)) (send (lvar $_) :read))
|
67
|
+
PATTERN
|
68
|
+
|
69
|
+
def on_send(node)
|
70
|
+
evidence(node) do |filename, mode, read_node|
|
71
|
+
message = format(MSG, read_method: read_method(mode))
|
72
|
+
|
73
|
+
add_offense(read_node, message: message) do |corrector|
|
74
|
+
range = range_between(node.loc.selector.begin_pos, read_node.loc.expression.end_pos)
|
75
|
+
replacement = "#{read_method(mode)}(#{filename.source})"
|
76
|
+
|
77
|
+
corrector.replace(range, replacement)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def evidence(node)
|
85
|
+
file_open?(node) do |filename, mode_array, block_pass|
|
86
|
+
read_node?(node, block_pass) do |read_node|
|
87
|
+
yield(filename, mode_array.first || 'r', read_node)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_node?(node, block_pass)
|
93
|
+
if block_pass.any?
|
94
|
+
yield(node)
|
95
|
+
elsif file_open_read?(node.parent)
|
96
|
+
yield(node.parent)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_open_read?(node)
|
101
|
+
return true if send_read?(node)
|
102
|
+
|
103
|
+
block_read?(node) { |block_arg, read_lvar| block_arg == read_lvar }
|
104
|
+
end
|
105
|
+
|
106
|
+
def read_method(mode)
|
107
|
+
mode.end_with?('b') ? :binread : :read
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Favor `File.(bin)write` convenience methods.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# ## text mode
|
10
|
+
# # bad
|
11
|
+
# File.open(filename, 'w').write(content)
|
12
|
+
# File.open(filename, 'w') do |f|
|
13
|
+
# f.write(content)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# File.write(filename, content)
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# ## binary mode
|
21
|
+
# # bad
|
22
|
+
# File.open(filename, 'wb').write(content)
|
23
|
+
# File.open(filename, 'wb') do |f|
|
24
|
+
# f.write(content)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# File.binwrite(filename, content)
|
29
|
+
#
|
30
|
+
class FileWrite < Base
|
31
|
+
extend AutoCorrector
|
32
|
+
include RangeHelp
|
33
|
+
|
34
|
+
MSG = 'Use `File.%<write_method>s`.'
|
35
|
+
|
36
|
+
RESTRICT_ON_SEND = %i[open].to_set.freeze
|
37
|
+
|
38
|
+
TRUNCATING_WRITE_MODES = %w[w wt wb w+ w+t w+b].to_set.freeze
|
39
|
+
|
40
|
+
# @!method file_open?(node)
|
41
|
+
def_node_matcher :file_open?, <<~PATTERN
|
42
|
+
(send
|
43
|
+
(const {nil? cbase} :File)
|
44
|
+
:open
|
45
|
+
$_
|
46
|
+
(str $%TRUNCATING_WRITE_MODES)
|
47
|
+
(block-pass (sym :write))?
|
48
|
+
)
|
49
|
+
PATTERN
|
50
|
+
|
51
|
+
# @!method send_write?(node)
|
52
|
+
def_node_matcher :send_write?, <<~PATTERN
|
53
|
+
(send _ :write $_)
|
54
|
+
PATTERN
|
55
|
+
|
56
|
+
# @!method block_write?(node)
|
57
|
+
def_node_matcher :block_write?, <<~PATTERN
|
58
|
+
(block _ (args (arg $_)) (send (lvar $_) :write $_))
|
59
|
+
PATTERN
|
60
|
+
|
61
|
+
def on_send(node)
|
62
|
+
evidence(node) do |filename, mode, content, write_node|
|
63
|
+
message = format(MSG, write_method: write_method(mode))
|
64
|
+
|
65
|
+
add_offense(write_node, message: message) do |corrector|
|
66
|
+
range = range_between(node.loc.selector.begin_pos, write_node.loc.expression.end_pos)
|
67
|
+
replacement = "#{write_method(mode)}(#{filename.source}, #{content.source})"
|
68
|
+
|
69
|
+
corrector.replace(range, replacement)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def evidence(node)
|
75
|
+
file_open?(node) do |filename, mode|
|
76
|
+
file_open_write?(node.parent) do |content|
|
77
|
+
yield(filename, mode, content, node.parent)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def file_open_write?(node)
|
85
|
+
content = send_write?(node) || block_write?(node) do |block_arg, lvar, write_arg|
|
86
|
+
write_arg if block_arg == lvar
|
87
|
+
end
|
88
|
+
|
89
|
+
yield(content) if content
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_method(mode)
|
93
|
+
mode.end_with?('b') ? :binwrite : :write
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -101,7 +101,8 @@ module RuboCop
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def requires_parens?(node)
|
104
|
-
node.call_type? && node.arguments.any? && !node.parenthesized?
|
104
|
+
(node.call_type? && node.arguments.any? && !node.parenthesized?) ||
|
105
|
+
node.or_type? || node.and_type?
|
105
106
|
end
|
106
107
|
|
107
108
|
def multi_argument(node)
|
@@ -19,6 +19,10 @@ module RuboCop
|
|
19
19
|
# * ruby19_no_mixed_keys - forces use of ruby 1.9 syntax and forbids mixed
|
20
20
|
# syntax hashes
|
21
21
|
#
|
22
|
+
# This cop has `EnforcedShorthandSyntax` option.
|
23
|
+
# It can enforce either the use of the explicit hash value syntax or
|
24
|
+
# the use of Ruby 3.1's hash value shorthand syntax.
|
25
|
+
#
|
22
26
|
# @example EnforcedStyle: ruby19 (default)
|
23
27
|
# # bad
|
24
28
|
# {:a => 2}
|
@@ -54,8 +58,26 @@ module RuboCop
|
|
54
58
|
# # good
|
55
59
|
# {a: 1, b: 2}
|
56
60
|
# {:c => 3, 'd' => 4}
|
61
|
+
#
|
62
|
+
# @example EnforcedShorthandSyntax: always (default)
|
63
|
+
#
|
64
|
+
# # bad
|
65
|
+
# {foo: foo, bar: bar}
|
66
|
+
#
|
67
|
+
# # good
|
68
|
+
# {foo:, bar:}
|
69
|
+
#
|
70
|
+
# @example EnforcedShorthandSyntax: never
|
71
|
+
#
|
72
|
+
# # bad
|
73
|
+
# {foo:, bar:}
|
74
|
+
#
|
75
|
+
# # good
|
76
|
+
# {foo: foo, bar: bar}
|
77
|
+
#
|
57
78
|
class HashSyntax < Base
|
58
79
|
include ConfigurableEnforcedStyle
|
80
|
+
include HashShorthandSyntax
|
59
81
|
include RangeHelp
|
60
82
|
extend AutoCorrector
|
61
83
|
|