rubocop 1.48.0 → 1.50.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +22 -7
  4. data/lib/rubocop/cli/command/execute_runner.rb +7 -2
  5. data/lib/rubocop/cli.rb +6 -6
  6. data/lib/rubocop/config.rb +3 -3
  7. data/lib/rubocop/config_loader.rb +8 -8
  8. data/lib/rubocop/cop/autocorrect_logic.rb +28 -12
  9. data/lib/rubocop/cop/cop.rb +2 -2
  10. data/lib/rubocop/cop/correctors/parentheses_corrector.rb +1 -1
  11. data/lib/rubocop/cop/correctors/percent_literal_corrector.rb +2 -2
  12. data/lib/rubocop/cop/gemspec/dependency_version.rb +1 -1
  13. data/lib/rubocop/cop/gemspec/deprecated_attribute_assignment.rb +1 -1
  14. data/lib/rubocop/cop/internal_affairs/cop_description.rb +1 -1
  15. data/lib/rubocop/cop/internal_affairs/example_heredoc_delimiter.rb +2 -2
  16. data/lib/rubocop/cop/internal_affairs/inherit_deprecated_cop_class.rb +1 -1
  17. data/lib/rubocop/cop/internal_affairs/redundant_source_range.rb +29 -2
  18. data/lib/rubocop/cop/layout/block_end_newline.rb +7 -21
  19. data/lib/rubocop/cop/layout/class_structure.rb +1 -0
  20. data/lib/rubocop/cop/layout/empty_comment.rb +1 -1
  21. data/lib/rubocop/cop/layout/empty_lines.rb +1 -1
  22. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +2 -0
  23. data/lib/rubocop/cop/layout/end_alignment.rb +5 -1
  24. data/lib/rubocop/cop/layout/extra_spacing.rb +6 -1
  25. data/lib/rubocop/cop/layout/first_argument_indentation.rb +6 -1
  26. data/lib/rubocop/cop/layout/first_array_element_line_break.rb +25 -34
  27. data/lib/rubocop/cop/layout/first_hash_element_line_break.rb +7 -19
  28. data/lib/rubocop/cop/layout/first_method_argument_line_break.rb +42 -52
  29. data/lib/rubocop/cop/layout/first_method_parameter_line_break.rb +38 -55
  30. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +2 -2
  31. data/lib/rubocop/cop/layout/initial_indentation.rb +1 -1
  32. data/lib/rubocop/cop/layout/multiline_array_line_breaks.rb +8 -27
  33. data/lib/rubocop/cop/layout/multiline_hash_key_line_breaks.rb +7 -26
  34. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +4 -21
  35. data/lib/rubocop/cop/layout/multiline_method_parameter_line_breaks.rb +6 -30
  36. data/lib/rubocop/cop/layout/redundant_line_break.rb +6 -7
  37. data/lib/rubocop/cop/layout/space_before_first_arg.rb +1 -1
  38. data/lib/rubocop/cop/layout/space_inside_parens.rb +2 -2
  39. data/lib/rubocop/cop/lint/deprecated_class_methods.rb +3 -3
  40. data/lib/rubocop/cop/lint/duplicate_match_pattern.rb +122 -0
  41. data/lib/rubocop/cop/lint/empty_interpolation.rb +1 -1
  42. data/lib/rubocop/cop/lint/nested_method_definition.rb +2 -2
  43. data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +1 -1
  44. data/lib/rubocop/cop/lint/redundant_string_coercion.rb +35 -15
  45. data/lib/rubocop/cop/lint/syntax.rb +4 -0
  46. data/lib/rubocop/cop/lint/to_enum_arguments.rb +7 -1
  47. data/lib/rubocop/cop/lint/unreachable_loop.rb +3 -3
  48. data/lib/rubocop/cop/lint/useless_method_definition.rb +10 -2
  49. data/lib/rubocop/cop/lint/useless_rescue.rb +4 -1
  50. data/lib/rubocop/cop/lint/void.rb +7 -3
  51. data/lib/rubocop/cop/metrics/block_nesting.rb +1 -1
  52. data/lib/rubocop/cop/metrics/class_length.rb +1 -0
  53. data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
  54. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  55. data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -0
  56. data/lib/rubocop/cop/mixin/hash_transform_method.rb +1 -1
  57. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  58. data/lib/rubocop/cop/naming/ascii_identifiers.rb +1 -1
  59. data/lib/rubocop/cop/naming/inclusive_language.rb +23 -4
  60. data/lib/rubocop/cop/naming/method_name.rb +2 -2
  61. data/lib/rubocop/cop/style/accessor_grouping.rb +4 -4
  62. data/lib/rubocop/cop/style/class_and_module_children.rb +1 -1
  63. data/lib/rubocop/cop/style/class_equality_comparison.rb +42 -9
  64. data/lib/rubocop/cop/style/collection_compact.rb +3 -0
  65. data/lib/rubocop/cop/style/copyright.rb +1 -1
  66. data/lib/rubocop/cop/style/data_inheritance.rb +75 -0
  67. data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +2 -2
  68. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  69. data/lib/rubocop/cop/style/double_negation.rb +2 -2
  70. data/lib/rubocop/cop/style/file_empty.rb +3 -3
  71. data/lib/rubocop/cop/style/frozen_string_literal_comment.rb +1 -1
  72. data/lib/rubocop/cop/style/hash_except.rb +4 -4
  73. data/lib/rubocop/cop/style/hash_syntax.rb +4 -1
  74. data/lib/rubocop/cop/style/if_unless_modifier.rb +38 -12
  75. data/lib/rubocop/cop/style/map_to_hash.rb +4 -1
  76. data/lib/rubocop/cop/style/map_to_set.rb +4 -1
  77. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +3 -7
  78. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +43 -36
  79. data/lib/rubocop/cop/style/multiline_method_signature.rb +6 -3
  80. data/lib/rubocop/cop/style/parallel_assignment.rb +26 -18
  81. data/lib/rubocop/cop/style/percent_literal_delimiters.rb +2 -3
  82. data/lib/rubocop/cop/style/percent_q_literals.rb +1 -1
  83. data/lib/rubocop/cop/style/redundant_fetch_block.rb +6 -4
  84. data/lib/rubocop/cop/style/redundant_line_continuation.rb +179 -0
  85. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  86. data/lib/rubocop/cop/style/redundant_percent_q.rb +1 -1
  87. data/lib/rubocop/cop/style/redundant_regexp_character_class.rb +2 -2
  88. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +1 -1
  89. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -3
  90. data/lib/rubocop/cop/style/sole_nested_conditional.rb +2 -2
  91. data/lib/rubocop/cop/style/struct_inheritance.rb +1 -1
  92. data/lib/rubocop/cop/style/trailing_body_on_class.rb +1 -0
  93. data/lib/rubocop/cop/style/trivial_accessors.rb +1 -1
  94. data/lib/rubocop/cop/style/unless_logical_operators.rb +1 -0
  95. data/lib/rubocop/cops_documentation_generator.rb +10 -3
  96. data/lib/rubocop/ext/regexp_node.rb +1 -1
  97. data/lib/rubocop/ext/regexp_parser.rb +1 -1
  98. data/lib/rubocop/formatter/simple_text_formatter.rb +1 -1
  99. data/lib/rubocop/options.rb +4 -1
  100. data/lib/rubocop/result_cache.rb +1 -1
  101. data/lib/rubocop/rspec/support.rb +1 -0
  102. data/lib/rubocop/server/cache.rb +1 -1
  103. data/lib/rubocop/server/helper.rb +1 -1
  104. data/lib/rubocop/server/server_command/exec.rb +1 -1
  105. data/lib/rubocop/version.rb +1 -1
  106. data/lib/rubocop.rb +3 -0
  107. metadata +12 -9
@@ -27,12 +27,16 @@ module RuboCop
27
27
  # var.class.equal?(Date)
28
28
  # var.class.eql?(Date)
29
29
  # var.class.name == 'Date'
30
+ # var.class.to_s == 'Date'
31
+ # var.class.inspect == 'Date'
30
32
  #
31
33
  # @example AllowedMethods: [`==`]
32
34
  # # good
33
35
  # var.instance_of?(Date)
34
36
  # var.class == Date
35
37
  # var.class.name == 'Date'
38
+ # var.class.to_s == 'Date'
39
+ # var.class.inspect == 'Date'
36
40
  #
37
41
  # # bad
38
42
  # var.class.equal?(Date)
@@ -47,6 +51,8 @@ module RuboCop
47
51
  # var.class.equal?(Date)
48
52
  # var.class.eql?(Date)
49
53
  # var.class.name == 'Date'
54
+ # var.class.to_s == 'Date'
55
+ # var.class.inspect == 'Date'
50
56
  #
51
57
  # @example AllowedPatterns: ['eq']
52
58
  # # good
@@ -57,6 +63,8 @@ module RuboCop
57
63
  # # bad
58
64
  # var.class == Date
59
65
  # var.class.name == 'Date'
66
+ # var.class.to_s == 'Date'
67
+ # var.class.inspect == 'Date'
60
68
  #
61
69
  class ClassEqualityComparison < Base
62
70
  include RangeHelp
@@ -64,14 +72,15 @@ module RuboCop
64
72
  include AllowedPattern
65
73
  extend AutoCorrector
66
74
 
67
- MSG = 'Use `instance_of?(%<class_name>s)` instead of comparing classes.'
75
+ MSG = 'Use `instance_of?%<class_argument>s` instead of comparing classes.'
68
76
 
69
77
  RESTRICT_ON_SEND = %i[== equal? eql?].freeze
78
+ CLASS_NAME_METHODS = %i[name to_s inspect].freeze
70
79
 
71
80
  # @!method class_comparison_candidate?(node)
72
81
  def_node_matcher :class_comparison_candidate?, <<~PATTERN
73
82
  (send
74
- {$(send _ :class) (send $(send _ :class) :name)}
83
+ {$(send _ :class) (send $(send _ :class) #class_name_method?)}
75
84
  {:== :equal? :eql?} $_)
76
85
  PATTERN
77
86
 
@@ -83,10 +92,12 @@ module RuboCop
83
92
 
84
93
  class_comparison_candidate?(node) do |receiver_node, class_node|
85
94
  range = offense_range(receiver_node, node)
86
- class_name = class_name(class_node, node)
95
+ class_argument = (class_name = class_name(class_node, node)) ? "(#{class_name})" : ''
87
96
 
88
- add_offense(range, message: format(MSG, class_name: class_name)) do |corrector|
89
- corrector.replace(range, "instance_of?(#{class_name})")
97
+ add_offense(range, message: format(MSG, class_argument: class_argument)) do |corrector|
98
+ next unless class_name
99
+
100
+ corrector.replace(range, "instance_of?#{class_argument}")
90
101
  end
91
102
  end
92
103
  end
@@ -94,19 +105,41 @@ module RuboCop
94
105
  private
95
106
 
96
107
  def class_name(class_node, node)
97
- if node.children.first.method?(:name)
98
- return class_node.receiver.source if class_node.receiver
108
+ if class_name_method?(node.children.first.method_name)
109
+ if (receiver = class_node.receiver) && class_name_method?(class_node.method_name)
110
+ return receiver.source
111
+ end
99
112
 
100
113
  if class_node.str_type?
101
- value = class_node.source.delete('"').delete("'")
102
- value.prepend('::') if class_node.each_ancestor(:class, :module).any?
114
+ value = trim_string_quotes(class_node)
115
+ value.prepend('::') if require_cbase?(class_node)
103
116
  return value
117
+ elsif unable_to_determine_type?(class_node)
118
+ # When a variable or return value of a method is used, it returns nil
119
+ # because the type is not known and cannot be suggested.
120
+ return
104
121
  end
105
122
  end
106
123
 
107
124
  class_node.source
108
125
  end
109
126
 
127
+ def class_name_method?(method_name)
128
+ CLASS_NAME_METHODS.include?(method_name)
129
+ end
130
+
131
+ def require_cbase?(class_node)
132
+ class_node.each_ancestor(:class, :module).any?
133
+ end
134
+
135
+ def unable_to_determine_type?(class_node)
136
+ class_node.variable? || class_node.call_type?
137
+ end
138
+
139
+ def trim_string_quotes(class_node)
140
+ class_node.source.delete('"').delete("'")
141
+ end
142
+
110
143
  def offense_range(receiver_node, node)
111
144
  range_between(receiver_node.loc.selector.begin_pos, node.source_range.end_pos)
112
145
  end
@@ -36,11 +36,14 @@ module RuboCop
36
36
  class CollectionCompact < Base
37
37
  include RangeHelp
38
38
  extend AutoCorrector
39
+ extend TargetRubyVersion
39
40
 
40
41
  MSG = 'Use `%<good>s` instead of `%<bad>s`.'
41
42
  RESTRICT_ON_SEND = %i[reject reject! select select!].freeze
42
43
  TO_ENUM_METHODS = %i[to_enum lazy].freeze
43
44
 
45
+ minimum_target_ruby_version 2.4
46
+
44
47
  # @!method reject_method_with_block_pass?(node)
45
48
  def_node_matcher :reject_method_with_block_pass?, <<~PATTERN
46
49
  (send !nil? {:reject :reject!}
@@ -82,7 +82,7 @@ module RuboCop
82
82
  def notice_found?(processed_source)
83
83
  notice_found = false
84
84
  notice_regexp = Regexp.new(notice)
85
- processed_source.each_token do |token|
85
+ processed_source.tokens.each do |token|
86
86
  break unless token.comment?
87
87
 
88
88
  notice_found = notice_regexp.match?(token.text)
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for inheritance from `Data.define` to avoid creating the anonymous parent class.
7
+ #
8
+ # @safety
9
+ # Autocorrection is unsafe because it will change the inheritance
10
+ # tree (e.g. return value of `Module#ancestors`) of the constant.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # class Person < Data.define(:first_name, :last_name)
15
+ # def age
16
+ # 42
17
+ # end
18
+ # end
19
+ #
20
+ # # good
21
+ # Person = Data.define(:first_name, :last_name) do
22
+ # def age
23
+ # 42
24
+ # end
25
+ # end
26
+ class DataInheritance < Base
27
+ include RangeHelp
28
+ extend AutoCorrector
29
+ extend TargetRubyVersion
30
+
31
+ MSG = "Don't extend an instance initialized by `Data.define`. " \
32
+ 'Use a block to customize the class.'
33
+
34
+ minimum_target_ruby_version 3.2
35
+
36
+ def on_class(node)
37
+ return unless data_define?(node.parent_class)
38
+
39
+ add_offense(node.parent_class.source_range) do |corrector|
40
+ corrector.remove(range_with_surrounding_space(node.loc.keyword, newlines: false))
41
+ corrector.replace(node.loc.operator, '=')
42
+
43
+ correct_parent(node.parent_class, corrector)
44
+ end
45
+ end
46
+
47
+ # @!method data_define?(node)
48
+ def_node_matcher :data_define?, <<~PATTERN
49
+ {(send (const {nil? cbase} :Data) :define ...)
50
+ (block (send (const {nil? cbase} :Data) :define ...) ...)}
51
+ PATTERN
52
+
53
+ private
54
+
55
+ def correct_parent(parent, corrector)
56
+ if parent.block_type?
57
+ corrector.remove(range_with_surrounding_space(parent.loc.end, newlines: false))
58
+ elsif (class_node = parent.parent).body.nil?
59
+ corrector.remove(range_for_empty_class_body(class_node, parent))
60
+ else
61
+ corrector.insert_after(parent, ' do')
62
+ end
63
+ end
64
+
65
+ def range_for_empty_class_body(class_node, data_define)
66
+ if class_node.single_line?
67
+ range_between(data_define.source_range.end_pos, class_node.source_range.end_pos)
68
+ else
69
+ range_by_whole_lines(class_node.loc.end, include_final_newline: true)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -34,8 +34,8 @@ module RuboCop
34
34
  extend AutoCorrector
35
35
 
36
36
  # rubocop:enable Lint/RedundantCopDisableDirective
37
- MSG = 'Rubocop disable/enable directives are not permitted.'
38
- MSG_FOR_COPS = 'Rubocop disable/enable directives for %<cops>s are not permitted.'
37
+ MSG = 'RuboCop disable/enable directives are not permitted.'
38
+ MSG_FOR_COPS = 'RuboCop disable/enable directives for %<cops>s are not permitted.'
39
39
 
40
40
  def on_new_investigation
41
41
  processed_source.comments.each do |comment|
@@ -111,7 +111,7 @@ module RuboCop
111
111
  return if comments.none?
112
112
 
113
113
  regexp = comment_regexp(arg_node)
114
- comments.any? { |comment| regexp.match?(comment) } || regexp.match?(comments.join)
114
+ comments.any?(regexp) || regexp.match?(comments.join)
115
115
  end
116
116
 
117
117
  def preceding_comment_blocks(node)
@@ -103,12 +103,12 @@ module RuboCop
103
103
  def find_def_node_from_ascendant(node)
104
104
  return unless (parent = node.parent)
105
105
  return parent if parent.def_type? || parent.defs_type?
106
- return node.parent.child_nodes.first if define_mehod?(parent)
106
+ return node.parent.child_nodes.first if define_method?(parent)
107
107
 
108
108
  find_def_node_from_ascendant(node.parent)
109
109
  end
110
110
 
111
- def define_mehod?(node)
111
+ def define_method?(node)
112
112
  return false unless node.block_type?
113
113
 
114
114
  child = node.child_nodes.first
@@ -6,9 +6,9 @@ module RuboCop
6
6
  # Prefer to use `File.empty?('path/to/file')` when checking if a file is empty.
7
7
  #
8
8
  # @safety
9
- # This cop's autocorrection is unsafe it because `File.size`, `File.read`,
10
- # and `File.binread` raise `ENOENT` exception when there is no file
11
- # corresponding to the path, while `File.empty?` does not raise an exception.
9
+ # This cop is unsafe, because `File.size`, `File.read`, and `File.binread`
10
+ # raise `ENOENT` exception when there is no file corresponding to the path,
11
+ # while `File.empty?` does not raise an exception.
12
12
  #
13
13
  # @example
14
14
  # # bad
@@ -148,7 +148,7 @@ module RuboCop
148
148
  end
149
149
 
150
150
  def frozen_string_literal_comment(processed_source)
151
- processed_source.find_token do |token|
151
+ processed_source.tokens.find do |token|
152
152
  token.text.start_with?(FROZEN_STRING_LITERAL)
153
153
  end
154
154
  end
@@ -122,8 +122,8 @@ module RuboCop
122
122
  end
123
123
 
124
124
  def safe_to_register_offense?(block, except_key)
125
- extracted = extract_body_if_nagated(block.body)
126
- if extracted.method?('in?') || extracted.method?('include?') || \
125
+ extracted = extract_body_if_negated(block.body)
126
+ if extracted.method?('in?') || extracted.method?('include?') ||
127
127
  extracted.method?('exclude?')
128
128
  return true
129
129
  end
@@ -132,7 +132,7 @@ module RuboCop
132
132
  except_key.sym_type? || except_key.str_type?
133
133
  end
134
134
 
135
- def extract_body_if_nagated(body)
135
+ def extract_body_if_negated(body)
136
136
  return body unless body.method?('!')
137
137
 
138
138
  body.receiver
@@ -161,7 +161,7 @@ module RuboCop
161
161
 
162
162
  def except_key(node)
163
163
  key_argument = node.argument_list.first.source
164
- body = extract_body_if_nagated(node.body)
164
+ body = extract_body_if_negated(node.body)
165
165
  lhs, _method_name, rhs = *body
166
166
  return if [lhs, rhs].map(&:source).none?(key_argument)
167
167
 
@@ -88,6 +88,9 @@ module RuboCop
88
88
  # {foo: foo, bar: bar}
89
89
  #
90
90
  # # good
91
+ # {foo: foo, bar:}
92
+ #
93
+ # # good
91
94
  # {foo:, bar:}
92
95
  #
93
96
  # @example EnforcedShorthandSyntax: consistent
@@ -207,7 +210,7 @@ module RuboCop
207
210
  return true if /\A[_a-z]\w*[?!]?\z/i.match?(sym_name)
208
211
 
209
212
  # For more complicated hash keys, let the parser validate the syntax.
210
- ProcessedSource.new("{ #{sym_name}: :foo }", target_ruby_version).valid_syntax?
213
+ parse("{ #{sym_name}: :foo }").valid_syntax?
211
214
  end
212
215
 
213
216
  def check(pairs, delim, msg)
@@ -11,6 +11,18 @@ module RuboCop
11
11
  # cop. The tab size is configured in the `IndentationWidth` of the
12
12
  # `Layout/IndentationStyle` cop.
13
13
  #
14
+ # One-line pattern matching is always allowed. To ensure that there are few cases
15
+ # where the match variable is not used, and to prevent oversights. The variable `x`
16
+ # becomes undefined and raises `NameError` when the following example is changed to
17
+ # the modifier form:
18
+ #
19
+ # [source,ruby]
20
+ # ----
21
+ # if [42] in [x]
22
+ # x # `x` is undefined when using modifier form.
23
+ # end
24
+ # ----
25
+ #
14
26
  # NOTE: It is allowed when `defined?` argument has an undefined value,
15
27
  # because using the modifier form causes the following incompatibility:
16
28
  #
@@ -66,14 +78,10 @@ module RuboCop
66
78
  end
67
79
 
68
80
  def on_if(node)
69
- return if defined_nodes(node).any? { |n| defined_argument_is_undefined?(node, n) }
70
-
71
- msg = if single_line_as_modifier?(node) && !named_capture_in_condition?(node)
72
- MSG_USE_MODIFIER
73
- elsif too_long_due_to_modifier?(node)
74
- MSG_USE_NORMAL
75
- end
76
- return unless msg
81
+ condition = node.condition
82
+ return if defined_nodes(condition).any? { |n| defined_argument_is_undefined?(node, n) } ||
83
+ pattern_matching_nodes(condition).any?
84
+ return unless (msg = message(node))
77
85
 
78
86
  add_offense(node.loc.keyword, message: format(msg, keyword: node.keyword)) do |corrector|
79
87
  autocorrect(corrector, node)
@@ -82,11 +90,11 @@ module RuboCop
82
90
 
83
91
  private
84
92
 
85
- def defined_nodes(node)
86
- if node.condition.defined_type?
87
- [node.condition]
93
+ def defined_nodes(condition)
94
+ if condition.defined_type?
95
+ [condition]
88
96
  else
89
- node.condition.each_descendant.select(&:defined_type?)
97
+ condition.each_descendant.select(&:defined_type?)
90
98
  end
91
99
  end
92
100
 
@@ -100,6 +108,24 @@ module RuboCop
100
108
  end
101
109
  end
102
110
 
111
+ def pattern_matching_nodes(condition)
112
+ if condition.match_pattern_type? || condition.match_pattern_p_type?
113
+ [condition]
114
+ else
115
+ condition.each_descendant.select do |node|
116
+ node.match_pattern_type? || node.match_pattern_p_type?
117
+ end
118
+ end
119
+ end
120
+
121
+ def message(node)
122
+ if single_line_as_modifier?(node) && !named_capture_in_condition?(node)
123
+ MSG_USE_MODIFIER
124
+ elsif too_long_due_to_modifier?(node)
125
+ MSG_USE_NORMAL
126
+ end
127
+ end
128
+
103
129
  def autocorrect(corrector, node)
104
130
  replacement = if node.modifier_form?
105
131
  replacement_for_modifier_form(corrector, node)
@@ -39,7 +39,10 @@ module RuboCop
39
39
 
40
40
  # @!method map_to_h?(node)
41
41
  def_node_matcher :map_to_h?, <<~PATTERN
42
- $(send (block $(send _ {:map :collect}) ...) :to_h)
42
+ {
43
+ $(send ({block numblock} $(send _ {:map :collect}) ...) :to_h)
44
+ $(send $(send _ {:map :collect} (block_pass sym)) :to_h)
45
+ }
43
46
  PATTERN
44
47
 
45
48
  def on_send(node)
@@ -32,7 +32,10 @@ module RuboCop
32
32
 
33
33
  # @!method map_to_set?(node)
34
34
  def_node_matcher :map_to_set?, <<~PATTERN
35
- $(send (block $(send _ {:map :collect}) ...) :to_set)
35
+ {
36
+ $(send ({block numblock} $(send _ {:map :collect}) ...) :to_set)
37
+ $(send $(send _ {:map :collect} (block_pass sym)) :to_set)
38
+ }
36
39
  PATTERN
37
40
 
38
41
  def on_send(node)
@@ -50,17 +50,13 @@ module RuboCop
50
50
  return false unless (last_argument = node.last_argument)
51
51
  return false if !last_argument.hash_type? || !last_argument.pairs.last&.value_omission?
52
52
 
53
- modifier_form?(node) || exist_next_line_expression?(node)
54
- end
55
-
56
- def modifier_form?(node)
57
- node.parent.respond_to?(:modifier_form?) && node.parent.modifier_form?
53
+ node.parent&.conditional? || !last_expression?(node)
58
54
  end
59
55
 
60
56
  # Require hash value omission be enclosed in parentheses to prevent the following issue:
61
57
  # https://bugs.ruby-lang.org/issues/18396.
62
- def exist_next_line_expression?(node)
63
- node.parent&.assignment? ? node.parent.right_sibling : node.right_sibling
58
+ def last_expression?(node)
59
+ !(node.parent&.assignment? ? node.parent.right_sibling : node.right_sibling)
64
60
  end
65
61
 
66
62
  def syntax_like_method_call?(node)
@@ -7,21 +7,19 @@ module RuboCop
7
7
  # method calls containing parameters.
8
8
  #
9
9
  # In the default style (require_parentheses), macro methods are allowed.
10
- # Additional methods can be added to the `AllowedMethods`
11
- # or `AllowedPatterns` list. These options are
12
- # valid only in the default style. Macros can be included by
13
- # either setting `IgnoreMacros` to false or adding specific macros to
14
- # the `IncludedMacros` list.
10
+ # Additional methods can be added to the `AllowedMethods` or
11
+ # `AllowedPatterns` list. These options are valid only in the default
12
+ # style. Macros can be included by either setting `IgnoreMacros` to false
13
+ # or adding specific macros to the `IncludedMacros` list.
15
14
  #
16
- # Precedence of options is all follows:
15
+ # Precedence of options is as follows:
17
16
  #
18
17
  # 1. `AllowedMethods`
19
18
  # 2. `AllowedPatterns`
20
19
  # 3. `IncludedMacros`
21
20
  #
22
- # eg. If a method is listed in both
23
- # `IncludedMacros` and `AllowedMethods`, then the latter takes
24
- # precedence (that is, the method is allowed).
21
+ # If a method is listed in both `IncludedMacros` and `AllowedMethods`,
22
+ # then the latter takes precedence (that is, the method is allowed).
25
23
  #
26
24
  # In the alternative style (omit_parentheses), there are three additional
27
25
  # options.
@@ -40,14 +38,29 @@ module RuboCop
40
38
  # to `true` allows the presence of parentheses in such a method call
41
39
  # even with arguments.
42
40
  #
43
- # NOTE: Parentheses are still allowed in cases where omitting them
44
- # results in ambiguous or syntactically incorrect code. For example,
45
- # parentheses are required around a method with arguments when inside an
46
- # endless method definition introduced in Ruby 3.0. Parentheses are also
47
- # allowed when forwarding arguments with the triple-dot syntax introduced
48
- # in Ruby 2.7 as omitting them starts an endless range.
49
- # And Ruby 3.1's hash omission syntax has a case that requires parentheses
50
- # because of the following issue: https://bugs.ruby-lang.org/issues/18396.
41
+ # NOTE: The style of `omit_parentheses` allows parentheses in cases where
42
+ # omitting them results in ambiguous or syntactically incorrect code.
43
+ #
44
+ # Non-exhaustive list of examples:
45
+ #
46
+ # - Parentheses are required allowed in method calls with arguments inside
47
+ # literals, logical operators, setting default values in position and
48
+ # keyword arguments, chaining and more.
49
+ # - Parentheses are allowed in method calls with arguments inside
50
+ # operators to avoid ambiguity.
51
+ # triple-dot syntax introduced in Ruby 2.7 as omitting them starts an
52
+ # endless range.
53
+ # - Parentheses are allowed when forwarding arguments with the
54
+ # triple-dot syntax introduced in Ruby 2.7 as omitting them starts an
55
+ # endless range.
56
+ # - Parentheses are required in calls with arguments when inside an
57
+ # endless method definition introduced in Ruby 3.0.
58
+ # - Ruby 3.1's hash omission syntax allows parentheses if the method call
59
+ # is in conditionals and requires parentheses if the call
60
+ # is not the value-returning expression. See
61
+ # https://bugs.ruby-lang.org/issues/18396.
62
+ # - Parentheses are required in anonymous arguments, keyword arguments
63
+ # and block passing in Ruby 3.2.
51
64
  #
52
65
  # @example EnforcedStyle: require_parentheses (default)
53
66
  #
@@ -80,34 +93,28 @@ module RuboCop
80
93
  # array.delete e
81
94
  #
82
95
  # # bad
83
- # foo.enforce(strict: true)
96
+ # action.enforce(strict: true)
84
97
  #
85
98
  # # good
86
- # foo.enforce strict: true
99
+ # action.enforce strict: true
87
100
  #
88
101
  # # good
89
- # # Allows parens for calls that won't produce valid Ruby or be ambiguous.
90
- # model.validate strict(true)
102
+ # # Parentheses are allowed for code that can be ambiguous without
103
+ # # them.
104
+ # action.enforce(condition) || other_condition
91
105
  #
92
106
  # # good
93
- # # Allows parens for calls that won't produce valid Ruby or be ambiguous.
107
+ # # Parentheses are allowed for calls that won't produce valid Ruby
108
+ # # without them.
94
109
  # yield path, File.basename(path)
95
110
  #
96
111
  # # good
97
- # # Operators methods calls with parens
98
- # array&.[](index)
99
- #
100
- # # good
101
- # # Operators methods without parens, if you prefer
102
- # array.[] index
103
- #
104
- # # good
105
- # # Operators methods calls with parens
106
- # array&.[](index)
107
- #
108
- # # good
109
- # # Operators methods without parens, if you prefer
110
- # array.[] index
112
+ # # Omitting the parentheses in Ruby 3.1 hash omission syntax can lead
113
+ # # to ambiguous code. We allow them in conditionals and non-last
114
+ # # expressions. See https://bugs.ruby-lang.org/issues/18396
115
+ # if meets(criteria:, action:)
116
+ # safe_action(action) || dangerous_action(action)
117
+ # end
111
118
  #
112
119
  # @example IgnoreMacros: true (default)
113
120
  #
@@ -28,14 +28,17 @@ module RuboCop
28
28
  return unless node.arguments?
29
29
  return if opening_line(node) == closing_line(node)
30
30
  return if correction_exceeds_max_line_length?(node)
31
+ return unless (begin_of_arguments = node.arguments.loc.begin)
31
32
 
32
- add_offense(node) { |corrector| autocorrect(corrector, node) }
33
+ add_offense(node) do |corrector|
34
+ autocorrect(corrector, node, begin_of_arguments)
35
+ end
33
36
  end
34
37
  alias on_defs on_def
35
38
 
36
39
  private
37
40
 
38
- def autocorrect(corrector, node)
41
+ def autocorrect(corrector, node, begin_of_arguments)
39
42
  arguments = node.arguments
40
43
  joined_arguments = arguments.map(&:source).join(', ')
41
44
  last_line_source_of_arguments = last_line_source_of_arguments(arguments)
@@ -47,7 +50,7 @@ module RuboCop
47
50
  end
48
51
 
49
52
  corrector.remove(arguments_range(node))
50
- corrector.insert_after(arguments.loc.begin, joined_arguments)
53
+ corrector.insert_after(begin_of_arguments, joined_arguments)
51
54
  end
52
55
 
53
56
  def last_line_source_of_arguments(arguments)