rubocop 1.23.0 → 1.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +45 -1
  4. data/lib/rubocop/cli/command/auto_genenerate_config.rb +1 -1
  5. data/lib/rubocop/cli/command/init_dotfile.rb +1 -1
  6. data/lib/rubocop/cli/command/show_docs_url.rb +48 -0
  7. data/lib/rubocop/cli/command/suggest_extensions.rb +1 -1
  8. data/lib/rubocop/cli.rb +1 -0
  9. data/lib/rubocop/config_loader_resolver.rb +1 -1
  10. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  11. data/lib/rubocop/cop/correctors/each_to_for_corrector.rb +1 -1
  12. data/lib/rubocop/cop/correctors/if_then_corrector.rb +55 -0
  13. data/lib/rubocop/cop/documentation.rb +19 -2
  14. data/lib/rubocop/cop/gemspec/require_mfa.rb +5 -5
  15. data/lib/rubocop/cop/internal_affairs/redundant_method_dispatch_node.rb +46 -0
  16. data/lib/rubocop/cop/internal_affairs/undefined_config.rb +3 -1
  17. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  18. data/lib/rubocop/cop/layout/comment_indentation.rb +31 -2
  19. data/lib/rubocop/cop/layout/dot_position.rb +4 -0
  20. data/lib/rubocop/cop/layout/hash_alignment.rb +1 -1
  21. data/lib/rubocop/cop/layout/space_after_colon.rb +1 -1
  22. data/lib/rubocop/cop/layout/space_before_first_arg.rb +4 -0
  23. data/lib/rubocop/cop/lint/constant_definition_in_block.rb +1 -1
  24. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +16 -4
  25. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +6 -0
  26. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +4 -0
  27. data/lib/rubocop/cop/metrics/block_length.rb +1 -0
  28. data/lib/rubocop/cop/metrics/cyclomatic_complexity.rb +0 -9
  29. data/lib/rubocop/cop/metrics/method_length.rb +1 -0
  30. data/lib/rubocop/cop/metrics/module_length.rb +1 -1
  31. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  32. data/lib/rubocop/cop/mixin/enforce_superclass.rb +5 -0
  33. data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +4 -3
  34. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +56 -0
  35. data/lib/rubocop/cop/naming/block_forwarding.rb +102 -0
  36. data/lib/rubocop/cop/security/open.rb +11 -1
  37. data/lib/rubocop/cop/style/character_literal.rb +8 -1
  38. data/lib/rubocop/cop/style/collection_compact.rb +31 -13
  39. data/lib/rubocop/cop/style/combinable_loops.rb +2 -2
  40. data/lib/rubocop/cop/style/empty_case_condition.rb +10 -0
  41. data/lib/rubocop/cop/style/file_read.rb +112 -0
  42. data/lib/rubocop/cop/style/file_write.rb +98 -0
  43. data/lib/rubocop/cop/style/hash_conversion.rb +2 -1
  44. data/lib/rubocop/cop/style/hash_syntax.rb +22 -0
  45. data/lib/rubocop/cop/style/if_inside_else.rb +15 -0
  46. data/lib/rubocop/cop/style/map_to_hash.rb +68 -0
  47. data/lib/rubocop/cop/style/numeric_literals.rb +10 -1
  48. data/lib/rubocop/cop/style/one_line_conditional.rb +18 -39
  49. data/lib/rubocop/cop/style/redundant_interpolation.rb +17 -3
  50. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +5 -1
  51. data/lib/rubocop/cop/style/redundant_self.rb +1 -1
  52. data/lib/rubocop/cop/style/safe_navigation.rb +1 -5
  53. data/lib/rubocop/cop/style/single_line_block_params.rb +2 -2
  54. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  55. data/lib/rubocop/cop/team.rb +1 -1
  56. data/lib/rubocop/options.rb +6 -1
  57. data/lib/rubocop/remote_config.rb +1 -3
  58. data/lib/rubocop/result_cache.rb +1 -1
  59. data/lib/rubocop/version.rb +1 -1
  60. data/lib/rubocop.rb +7 -0
  61. metadata +13 -5
@@ -135,7 +135,7 @@ module RuboCop
135
135
 
136
136
  def extract_body(node)
137
137
  case node.type
138
- when :class, :module, :block, :def, :defs
138
+ when :class, :module, :block, :numblock, :def, :defs
139
139
  node.body
140
140
  when :casgn
141
141
  _scope, _name, value = *node
@@ -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
- correct_value_column - current_pair.value.loc.column
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
- _ ${:reject :reject!})
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
- _ ${:select :select!})
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
- block_node = node.parent
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
- return unless args.last.source == receiver.source
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 offense_range(send_node, block_node)
88
- range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
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.send_node.method_name.to_s
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.send_node.receiver == node.send_node.receiver &&
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