rubocop 1.48.0 → 1.50.2

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.
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)