rubocop 1.23.0 → 1.24.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 +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
|
|