rubocop 1.69.1 → 1.70.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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +19 -2
  5. data/lib/rubocop/cli/command/execute_runner.rb +3 -3
  6. data/lib/rubocop/config.rb +13 -4
  7. data/lib/rubocop/config_loader.rb +4 -0
  8. data/lib/rubocop/config_loader_resolver.rb +14 -3
  9. data/lib/rubocop/config_validator.rb +18 -8
  10. data/lib/rubocop/cop/autocorrect_logic.rb +31 -34
  11. data/lib/rubocop/cop/base.rb +6 -0
  12. data/lib/rubocop/cop/bundler/duplicated_gem.rb +1 -1
  13. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  14. data/lib/rubocop/cop/internal_affairs/cop_enabled.rb +85 -0
  15. data/lib/rubocop/cop/internal_affairs/node_type_predicate.rb +4 -3
  16. data/lib/rubocop/cop/internal_affairs/operator_keyword.rb +4 -2
  17. data/lib/rubocop/cop/internal_affairs.rb +1 -0
  18. data/lib/rubocop/cop/layout/argument_alignment.rb +1 -7
  19. data/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +1 -0
  20. data/lib/rubocop/cop/layout/empty_lines_around_begin_body.rb +5 -6
  21. data/lib/rubocop/cop/layout/extra_spacing.rb +1 -1
  22. data/lib/rubocop/cop/layout/first_argument_indentation.rb +2 -7
  23. data/lib/rubocop/cop/layout/first_array_element_indentation.rb +2 -7
  24. data/lib/rubocop/cop/layout/first_hash_element_indentation.rb +1 -6
  25. data/lib/rubocop/cop/layout/hash_alignment.rb +6 -4
  26. data/lib/rubocop/cop/layout/heredoc_argument_closing_parenthesis.rb +1 -0
  27. data/lib/rubocop/cop/layout/line_continuation_leading_space.rb +11 -2
  28. data/lib/rubocop/cop/layout/line_continuation_spacing.rb +7 -1
  29. data/lib/rubocop/cop/layout/line_end_string_concatenation_indentation.rb +1 -1
  30. data/lib/rubocop/cop/layout/line_length.rb +1 -0
  31. data/lib/rubocop/cop/layout/multiline_method_argument_line_breaks.rb +24 -0
  32. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  33. data/lib/rubocop/cop/layout/space_after_method_name.rb +1 -1
  34. data/lib/rubocop/cop/layout/space_around_operators.rb +3 -3
  35. data/lib/rubocop/cop/layout/trailing_whitespace.rb +5 -3
  36. data/lib/rubocop/cop/lint/constant_reassignment.rb +152 -0
  37. data/lib/rubocop/cop/lint/duplicate_set_element.rb +20 -7
  38. data/lib/rubocop/cop/lint/literal_in_interpolation.rb +11 -3
  39. data/lib/rubocop/cop/lint/nested_method_definition.rb +5 -1
  40. data/lib/rubocop/cop/lint/non_atomic_file_operation.rb +4 -2
  41. data/lib/rubocop/cop/lint/numeric_operation_with_constant_result.rb +6 -14
  42. data/lib/rubocop/cop/lint/shared_mutable_default.rb +65 -0
  43. data/lib/rubocop/cop/lint/syntax.rb +4 -1
  44. data/lib/rubocop/cop/lint/unescaped_bracket_in_regexp.rb +1 -4
  45. data/lib/rubocop/cop/lint/void.rb +3 -2
  46. data/lib/rubocop/cop/metrics/class_length.rb +9 -9
  47. data/lib/rubocop/cop/metrics/method_length.rb +8 -1
  48. data/lib/rubocop/cop/mixin/check_line_breakable.rb +7 -7
  49. data/lib/rubocop/cop/mixin/comments_help.rb +6 -1
  50. data/lib/rubocop/cop/mixin/dig_help.rb +1 -1
  51. data/lib/rubocop/cop/mixin/line_length_help.rb +5 -4
  52. data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +46 -22
  53. data/lib/rubocop/cop/mixin/space_before_punctuation.rb +1 -1
  54. data/lib/rubocop/cop/mixin/statement_modifier.rb +1 -1
  55. data/lib/rubocop/cop/mixin/string_literals_help.rb +1 -1
  56. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  57. data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -1
  58. data/lib/rubocop/cop/style/access_modifier_declarations.rb +32 -1
  59. data/lib/rubocop/cop/style/and_or.rb +1 -1
  60. data/lib/rubocop/cop/style/arguments_forwarding.rb +1 -4
  61. data/lib/rubocop/cop/style/block_delimiters.rb +8 -1
  62. data/lib/rubocop/cop/style/class_and_module_children.rb +5 -2
  63. data/lib/rubocop/cop/style/each_for_simple_loop.rb +3 -6
  64. data/lib/rubocop/cop/style/empty_else.rb +4 -2
  65. data/lib/rubocop/cop/style/empty_literal.rb +1 -1
  66. data/lib/rubocop/cop/style/empty_method.rb +1 -1
  67. data/lib/rubocop/cop/style/exact_regexp_match.rb +1 -2
  68. data/lib/rubocop/cop/style/exponential_notation.rb +1 -1
  69. data/lib/rubocop/cop/style/file_null.rb +20 -4
  70. data/lib/rubocop/cop/style/float_division.rb +8 -4
  71. data/lib/rubocop/cop/style/hash_except.rb +54 -67
  72. data/lib/rubocop/cop/style/hash_syntax.rb +5 -2
  73. data/lib/rubocop/cop/style/if_with_semicolon.rb +6 -4
  74. data/lib/rubocop/cop/style/it_assignment.rb +36 -0
  75. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +11 -1
  76. data/lib/rubocop/cop/style/method_call_with_args_parentheses.rb +2 -0
  77. data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -1
  78. data/lib/rubocop/cop/style/missing_else.rb +2 -0
  79. data/lib/rubocop/cop/style/multiple_comparison.rb +34 -22
  80. data/lib/rubocop/cop/style/mutable_constant.rb +1 -1
  81. data/lib/rubocop/cop/style/object_then.rb +13 -15
  82. data/lib/rubocop/cop/style/quoted_symbols.rb +1 -1
  83. data/lib/rubocop/cop/style/raise_args.rb +5 -3
  84. data/lib/rubocop/cop/style/random_with_offset.rb +3 -3
  85. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +2 -1
  86. data/lib/rubocop/cop/style/redundant_initialize.rb +12 -3
  87. data/lib/rubocop/cop/style/redundant_line_continuation.rb +12 -9
  88. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -4
  89. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +3 -0
  90. data/lib/rubocop/cop/style/redundant_self_assignment.rb +6 -5
  91. data/lib/rubocop/cop/style/safe_navigation.rb +1 -1
  92. data/lib/rubocop/cop/style/send_with_literal_method_name.rb +2 -1
  93. data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
  94. data/lib/rubocop/cop/style/single_line_methods.rb +2 -3
  95. data/lib/rubocop/cop/style/slicing_with_range.rb +40 -11
  96. data/lib/rubocop/cop/style/super_arguments.rb +63 -15
  97. data/lib/rubocop/cop/style/yoda_condition.rb +8 -4
  98. data/lib/rubocop/cop/style/yoda_expression.rb +1 -0
  99. data/lib/rubocop/cop/util.rb +9 -2
  100. data/lib/rubocop/formatter/formatter_set.rb +1 -1
  101. data/lib/rubocop/lsp/diagnostic.rb +189 -0
  102. data/lib/rubocop/lsp/logger.rb +2 -2
  103. data/lib/rubocop/lsp/routes.rb +7 -23
  104. data/lib/rubocop/lsp/runtime.rb +15 -49
  105. data/lib/rubocop/lsp/stdin_runner.rb +83 -0
  106. data/lib/rubocop/magic_comment.rb +3 -3
  107. data/lib/rubocop/path_util.rb +11 -8
  108. data/lib/rubocop/rspec/shared_contexts.rb +4 -1
  109. data/lib/rubocop/runner.rb +5 -6
  110. data/lib/rubocop/target_ruby.rb +15 -0
  111. data/lib/rubocop/version.rb +1 -1
  112. data/lib/rubocop.rb +3 -0
  113. data/lib/ruby_lsp/rubocop/addon.rb +78 -0
  114. data/lib/ruby_lsp/rubocop/wraps_built_in_lsp_runtime.rb +50 -0
  115. metadata +16 -8
@@ -73,7 +73,7 @@ module RuboCop
73
73
  LINE_CONTINUATION_PATTERN = /(\\\n)/.freeze
74
74
  ALLOWED_STRING_TOKENS = %i[tSTRING tSTRING_CONTENT].freeze
75
75
  ARGUMENT_TYPES = %i[
76
- kFALSE kNIL kSELF kTRUE tCONSTANT tCVAR tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR
76
+ kDEF kFALSE kNIL kSELF kTRUE tCONSTANT tCVAR tFLOAT tGVAR tIDENTIFIER tINTEGER tIVAR
77
77
  tLBRACK tLCURLY tLPAREN_ARG tSTRING tSTRING_BEG tSYMBOL tXSTRING_BEG
78
78
  ].freeze
79
79
  ARGUMENT_TAKING_FLOW_TOKEN_TYPES = %i[tIDENTIFIER kRETURN kBREAK kNEXT kYIELD].freeze
@@ -112,10 +112,13 @@ module RuboCop
112
112
  end
113
113
 
114
114
  def inside_string_literal_or_method_with_argument?(range)
115
+ line_range = range_by_whole_lines(range)
116
+
115
117
  processed_source.tokens.each_cons(2).any? do |token, next_token|
116
118
  next if token.line == next_token.line
117
119
 
118
- inside_string_literal?(range, token) || method_with_argument?(token, next_token)
120
+ inside_string_literal?(range, token) ||
121
+ method_with_argument?(line_range, token, next_token)
119
122
  end
120
123
  end
121
124
 
@@ -129,11 +132,10 @@ module RuboCop
129
132
  return true unless (node = find_node_for_line(range.last_line))
130
133
  return false if argument_newline?(node)
131
134
 
132
- source = node.source
133
- while (node = node.parent)
134
- source = node.source
135
- end
136
- parse(source.gsub("\\\n", "\n")).valid_syntax?
135
+ # Check if source is still valid without the continuation
136
+ source = processed_source.raw_source.dup
137
+ source[range.begin_pos, range.length] = "\n"
138
+ parse(source).valid_syntax?
137
139
  end
138
140
 
139
141
  def inspect_end_of_ruby_code_line_continuation
@@ -156,8 +158,9 @@ module RuboCop
156
158
  #
157
159
  # do_something \
158
160
  # argument
159
- def method_with_argument?(current_token, next_token)
161
+ def method_with_argument?(line_range, current_token, next_token)
160
162
  return false unless ARGUMENT_TAKING_FLOW_TOKEN_TYPES.include?(current_token.type)
163
+ return false unless current_token.pos.overlaps?(line_range)
161
164
 
162
165
  ARGUMENT_TYPES.include?(next_token.type)
163
166
  end
@@ -181,7 +184,7 @@ module RuboCop
181
184
 
182
185
  def find_node_for_line(last_line)
183
186
  processed_source.ast.each_node do |node|
184
- return node if node.respond_to?(:expression) && node.expression&.last_line == last_line
187
+ return node if same_line?(node, last_line)
185
188
  end
186
189
  end
187
190
 
@@ -166,10 +166,7 @@ module RuboCop
166
166
  def_node_matcher :interpolation?, '[^begin ^^dstr]'
167
167
 
168
168
  def allow_in_multiline_conditions?
169
- parentheses_around_condition_config = config.for_cop('Style/ParenthesesAroundCondition')
170
- return false unless parentheses_around_condition_config['Enabled']
171
-
172
- !!parentheses_around_condition_config['AllowInMultilineConditions']
169
+ !!config.for_enabled_cop('Style/ParenthesesAroundCondition')['AllowInMultilineConditions']
173
170
  end
174
171
 
175
172
  def check_send(begin_node, node)
@@ -73,6 +73,9 @@ module RuboCop
73
73
  new_argument.gsub!("'", "\\\\'")
74
74
  new_argument.gsub!('\"', '"')
75
75
  quote = "'"
76
+ elsif new_argument.include?('\'')
77
+ new_argument.gsub!("'", "\\\\'")
78
+ quote = "'"
76
79
  elsif new_argument.include?('\\')
77
80
  quote = '"'
78
81
  else
@@ -52,7 +52,7 @@ module RuboCop
52
52
  # rubocop:disable Metrics/AbcSize
53
53
  def on_lvasgn(node)
54
54
  return unless (rhs = node.rhs)
55
- return unless rhs.send_type? && method_returning_self?(rhs.method_name)
55
+ return unless rhs.call_type? && method_returning_self?(rhs.method_name)
56
56
  return unless (receiver = rhs.receiver)
57
57
 
58
58
  receiver_type = ASSIGNMENT_TYPE_TO_RECEIVER_TYPE[node.type]
@@ -77,6 +77,7 @@ module RuboCop
77
77
  corrector.remove(correction_range(node))
78
78
  end
79
79
  end
80
+ alias on_csend on_send
80
81
 
81
82
  private
82
83
 
@@ -88,7 +89,7 @@ module RuboCop
88
89
  def_node_matcher :redundant_self_assignment?, <<~PATTERN
89
90
  (send
90
91
  (self) _
91
- (send
92
+ (call
92
93
  (send
93
94
  {(self) nil?} %1) #method_returning_self?
94
95
  ...))
@@ -96,10 +97,10 @@ module RuboCop
96
97
 
97
98
  # @!method redundant_nonself_assignment?(node, receiver, method_name)
98
99
  def_node_matcher :redundant_nonself_assignment?, <<~PATTERN
99
- (send
100
+ (call
100
101
  %1 _
101
- (send
102
- (send
102
+ (call
103
+ (call
103
104
  %1 %2) #method_returning_self?
104
105
  ...))
105
106
  PATTERN
@@ -323,7 +323,7 @@ module RuboCop
323
323
  return true if unsafe_method?(method)
324
324
 
325
325
  method.each_ancestor(:send).any? do |ancestor|
326
- break true unless config.for_cop('Lint/SafeNavigationChain')['Enabled']
326
+ break true unless config.cop_enabled?('Lint/SafeNavigationChain')
327
327
 
328
328
  break true if unsafe_method?(ancestor)
329
329
  break true if nil_methods.include?(ancestor.method_name)
@@ -68,7 +68,7 @@ module RuboCop
68
68
  def on_send(node)
69
69
  return if allow_send? && !node.method?(:public_send)
70
70
  return unless (first_argument = node.first_argument)
71
- return unless STATIC_METHOD_NAME_NODE_TYPES.include?(first_argument.type)
71
+ return unless first_argument.type?(*STATIC_METHOD_NAME_NODE_TYPES)
72
72
 
73
73
  offense_range = offense_range(node)
74
74
  method_name = first_argument.value
@@ -83,6 +83,7 @@ module RuboCop
83
83
  end
84
84
  end
85
85
  end
86
+ alias on_csend on_send
86
87
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
87
88
 
88
89
  private
@@ -68,8 +68,7 @@ module RuboCop
68
68
  end
69
69
 
70
70
  def single_line_blocks_preferred?
71
- redundant_line_break_config = @config.for_cop('Layout/RedundantLineBreak')
72
- redundant_line_break_config['Enabled'] && redundant_line_break_config['InspectBlocks']
71
+ @config.for_enabled_cop('Layout/RedundantLineBreak')['InspectBlocks']
73
72
  end
74
73
  end
75
74
  end
@@ -134,10 +134,9 @@ module RuboCop
134
134
  end
135
135
 
136
136
  def disallow_endless_method_style?
137
- endless_method_config = config.for_cop('Style/EndlessMethod')
138
- return true unless endless_method_config['Enabled']
137
+ return true unless config.cop_enabled?('Style/EndlessMethod')
139
138
 
140
- endless_method_config['EnforcedStyle'] == 'disallow'
139
+ config.for_cop('Style/EndlessMethod')['EnforcedStyle'] == 'disallow'
141
140
  end
142
141
  end
143
142
  end
@@ -78,38 +78,67 @@ module RuboCop
78
78
  return unless node.arguments.one?
79
79
 
80
80
  range_node = node.first_argument
81
- selector = node.loc.selector
82
- unless (message, removal_range = offense_message_with_removal_range(range_node, selector))
83
- return
84
- end
81
+ offense_range = find_offense_range(node)
82
+ return unless (message, removal_range =
83
+ offense_message_with_removal_range(node, range_node, offense_range))
84
+
85
+ # Changing the range to beginningless or endless when unparenthesized
86
+ # changes the semantics of the code, and thus will not be considered
87
+ # an offense.
88
+ return if removal_range != offense_range && unparenthesized_call?(node)
85
89
 
86
- add_offense(selector, message: message) do |corrector|
90
+ add_offense(offense_range, message: message) do |corrector|
87
91
  corrector.remove(removal_range)
88
92
  end
89
93
  end
94
+ alias on_csend on_send
90
95
 
91
96
  private
92
97
 
93
- def offense_message_with_removal_range(range_node, selector)
98
+ def unparenthesized_call?(node)
99
+ node.loc.dot && !node.parenthesized?
100
+ end
101
+
102
+ def find_offense_range(node)
103
+ if node.loc.dot
104
+ node.loc.dot.join(node.source_range.end)
105
+ else
106
+ node.loc.selector
107
+ end
108
+ end
109
+
110
+ def offense_message_with_removal_range(node, range_node, offense_range)
94
111
  if range_from_zero_till_minus_one?(range_node)
95
- [format(MSG_USELESS_RANGE, prefer: selector.source), selector]
112
+ [format(MSG_USELESS_RANGE, prefer: offense_range.source), offense_range]
96
113
  elsif range_till_minus_one?(range_node)
97
114
  [
98
- format(MSG, prefer: endless(range_node), current: selector.source), range_node.end
115
+ offense_message_for_partial_range(node, endless(range_node), offense_range),
116
+ range_node.end
99
117
  ]
100
118
  elsif range_from_zero?(range_node) && target_ruby_version >= 2.7
101
119
  [
102
- format(MSG, prefer: beginless(range_node), current: selector.source), range_node.begin
120
+ offense_message_for_partial_range(node, beginless(range_node), offense_range),
121
+ range_node.begin
103
122
  ]
104
123
  end
105
124
  end
106
125
 
126
+ def offense_message_for_partial_range(node, prefer, offense_range)
127
+ current = node.loc.dot ? arguments_source(node) : offense_range.source
128
+ prefer = "[#{prefer}]" unless node.loc.dot
129
+ format(MSG, prefer: prefer, current: current)
130
+ end
131
+
107
132
  def endless(range_node)
108
- "[#{range_node.begin.source}#{range_node.loc.operator.source}]"
133
+ "#{range_node.begin.source}#{range_node.loc.operator.source}"
109
134
  end
110
135
 
111
136
  def beginless(range_node)
112
- "[#{range_node.loc.operator.source}#{range_node.end.source}]"
137
+ "#{range_node.loc.operator.source}#{range_node.end.source}"
138
+ end
139
+
140
+ def arguments_source(node)
141
+ node.first_argument.source_range.join(node.last_argument.source_range.end).source
113
142
  end
114
143
  end
115
144
  end
@@ -23,6 +23,10 @@ module RuboCop
23
23
  # `define_method`, therefore, `super` used within these blocks will be allowed.
24
24
  # This approach might result in false negatives, yet ensuring safe detection takes precedence.
25
25
  #
26
+ # NOTE: When forwarding the same arguments but replacing the block argument with a new inline
27
+ # block, it is not necessary to explicitly list the non-block arguments. As such, an offense
28
+ # will be registered in this case.
29
+ #
26
30
  # @example
27
31
  # # bad
28
32
  # def method(*args, **kwargs)
@@ -44,6 +48,16 @@ module RuboCop
44
48
  # super()
45
49
  # end
46
50
  #
51
+ # # bad - forwarding with overridden block
52
+ # def method(*args, **kwargs, &block)
53
+ # super(*args, **kwargs) { do_something }
54
+ # end
55
+ #
56
+ # # good - implicitly passing all non-block arguments
57
+ # def method(*args, **kwargs, &block)
58
+ # super { do_something }
59
+ # end
60
+ #
47
61
  # # good - assigning to the block variable before calling super
48
62
  # def method(&block)
49
63
  # # Assigning to the block variable would pass the old value to super,
@@ -58,44 +72,75 @@ module RuboCop
58
72
  ASSIGN_TYPES = %i[or_asgn lvasgn].freeze
59
73
 
60
74
  MSG = 'Call `super` without arguments and parentheses when the signature is identical.'
75
+ MSG_INLINE_BLOCK = 'Call `super` without arguments and parentheses when all positional ' \
76
+ 'and keyword arguments are forwarded.'
61
77
 
62
78
  def on_super(super_node)
63
- def_node = super_node.ancestors.find do |node|
79
+ return unless (def_node = find_def_node(super_node))
80
+
81
+ def_node_args = def_node.arguments.argument_list
82
+ super_args = preprocess_super_args(super_node.arguments)
83
+
84
+ return unless arguments_identical?(def_node, super_node, def_node_args, super_args)
85
+
86
+ # If the number of arguments to the def node and super node are different here,
87
+ # it's because the block argument is not forwarded.
88
+ message = def_node_args.size == super_args.size ? MSG : MSG_INLINE_BLOCK
89
+ add_offense(super_node, message: message) do |corrector|
90
+ corrector.replace(super_node, 'super')
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def find_def_node(super_node)
97
+ super_node.ancestors.find do |node|
64
98
  # When defining dynamic methods, implicitly calling `super` is not possible.
65
99
  # Since there is a possibility of delegation to `define_method`,
66
100
  # `super` used within the block is always allowed.
67
- break if node.block_type?
101
+ break if node.block_type? && !block_sends_to_super?(super_node, node)
68
102
 
69
103
  break node if DEF_TYPES.include?(node.type)
70
104
  end
71
- return unless def_node
72
- return unless arguments_identical?(def_node, def_node.arguments.argument_list,
73
- super_node.arguments)
74
-
75
- add_offense(super_node) { |corrector| corrector.replace(super_node, 'super') }
76
105
  end
77
106
 
78
- private
79
-
80
107
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
81
- def arguments_identical?(def_node, def_args, super_args)
82
- super_args = preprocess_super_args(super_args)
83
- return false if def_args.size != super_args.size
108
+ def arguments_identical?(def_node, super_node, def_args, super_args)
109
+ return false if argument_list_size_differs?(def_args, super_args, super_node)
84
110
 
85
111
  def_args.zip(super_args).each do |def_arg, super_arg|
86
112
  next if positional_arg_same?(def_arg, super_arg)
87
113
  next if positional_rest_arg_same(def_arg, super_arg)
88
114
  next if keyword_arg_same?(def_arg, super_arg)
89
115
  next if keyword_rest_arg_same?(def_arg, super_arg)
90
- next if block_arg_same?(def_node, def_arg, super_arg)
116
+ next if block_arg_same?(def_node, super_node, def_arg, super_arg)
91
117
  next if forward_arg_same?(def_arg, super_arg)
92
118
 
93
119
  return false
94
120
  end
121
+
95
122
  true
96
123
  end
97
124
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
98
125
 
126
+ def argument_list_size_differs?(def_args, super_args, super_node)
127
+ # If the def node has a block argument and the super node has an explicit block,
128
+ # the number of arguments is the same, so ignore the def node block arg.
129
+ def_args_size = def_args.size
130
+ def_args_size -= 1 if def_args.any?(&:blockarg_type?) && block_sends_to_super?(super_node)
131
+
132
+ def_args_size != super_args.size
133
+ end
134
+
135
+ def block_sends_to_super?(super_node, parent_node = super_node.parent)
136
+ # Checks if the send node of a block is the given super node,
137
+ # or a method chain containing it.
138
+ return false unless parent_node
139
+ return false unless parent_node.type?(:block, :numblock)
140
+
141
+ parent_node.send_node.each_node(:super).any?(super_node)
142
+ end
143
+
99
144
  def positional_arg_same?(def_arg, super_arg)
100
145
  return false unless def_arg.arg_type? || def_arg.optarg_type?
101
146
  return false unless super_arg.lvar_type?
@@ -133,8 +178,11 @@ module RuboCop
133
178
  def_arg.name == lvar_node.children.first
134
179
  end
135
180
 
136
- def block_arg_same?(def_node, def_arg, super_arg)
137
- return false unless def_arg.blockarg_type? && super_arg.block_pass_type?
181
+ def block_arg_same?(def_node, super_node, def_arg, super_arg)
182
+ return false unless def_arg.blockarg_type?
183
+ return true if block_sends_to_super?(super_node)
184
+ return false unless super_arg.block_pass_type?
185
+
138
186
  # anonymous forwarding
139
187
  return true if (block_pass_child = super_arg.children.first).nil? && def_arg.name.nil?
140
188
 
@@ -85,6 +85,12 @@ module RuboCop
85
85
  NONCOMMUTATIVE_OPERATORS = %i[===].freeze
86
86
  PROGRAM_NAMES = %i[$0 $PROGRAM_NAME].freeze
87
87
  RESTRICT_ON_SEND = RuboCop::AST::Node::COMPARISON_OPERATORS
88
+ ENFORCE_YODA_STYLES = %i[
89
+ require_for_all_comparison_operators require_for_equality_operators_only
90
+ ].freeze
91
+ EQUALITY_ONLY_STYLES = %i[
92
+ forbid_for_equality_operators_only require_for_equality_operators_only
93
+ ].freeze
88
94
 
89
95
  # @!method file_constant_equal_program_name?(node)
90
96
  def_node_matcher :file_constant_equal_program_name?, <<~PATTERN
@@ -105,13 +111,11 @@ module RuboCop
105
111
  private
106
112
 
107
113
  def enforce_yoda?
108
- style == :require_for_all_comparison_operators ||
109
- style == :require_for_equality_operators_only
114
+ ENFORCE_YODA_STYLES.include?(style)
110
115
  end
111
116
 
112
117
  def equality_only?
113
- style == :forbid_for_equality_operators_only ||
114
- style == :require_for_equality_operators_only
118
+ EQUALITY_ONLY_STYLES.include?(style)
115
119
  end
116
120
 
117
121
  def yoda_compatible_condition?(node)
@@ -50,6 +50,7 @@ module RuboCop
50
50
 
51
51
  def on_send(node)
52
52
  return unless supported_operators.include?(node.method_name.to_s)
53
+ return unless node.arguments?
53
54
 
54
55
  lhs = node.receiver
55
56
  rhs = node.first_argument
@@ -193,11 +193,18 @@ module RuboCop
193
193
  enforced_style.sub(/^Enforced/, 'Supported').sub('Style', 'Styles')
194
194
  end
195
195
 
196
+ def parse_regexp(text)
197
+ Regexp::Parser.parse(text)
198
+ rescue Regexp::Parser::Error
199
+ # Upon encountering an invalid regular expression,
200
+ # we aim to proceed and identify any remaining potential offenses.
201
+ nil
202
+ end
203
+
196
204
  private
197
205
 
198
206
  def compatible_external_encoding_for?(src)
199
- src = src.dup if RUBY_ENGINE == 'jruby'
200
- src.force_encoding(Encoding.default_external).valid_encoding?
207
+ src.dup.force_encoding(Encoding.default_external).valid_encoding?
201
208
  end
202
209
 
203
210
  def include_or_equal?(source, target)
@@ -77,7 +77,7 @@ module RuboCop
77
77
  case formatter_type
78
78
  when Class
79
79
  formatter_type
80
- when /\A[A-Z]/
80
+ when /\A(::)?[A-Z]/
81
81
  custom_formatter_class(formatter_type)
82
82
  else
83
83
  builtin_formatter_class(formatter_type)
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'severity'
4
+
5
+ #
6
+ # This code is based on https://github.com/standardrb/standard.
7
+ #
8
+ # Copyright (c) 2023 Test Double, Inc.
9
+ #
10
+ # The MIT License (MIT)
11
+ #
12
+ # https://github.com/standardrb/standard/blob/main/LICENSE.txt
13
+ #
14
+ module RuboCop
15
+ module LSP
16
+ # Diagnostic for Language Server Protocol of RuboCop.
17
+ # @api private
18
+ class Diagnostic
19
+ def initialize(document_encoding, offense, uri, cop_class)
20
+ @document_encoding = document_encoding
21
+ @offense = offense
22
+ @uri = uri
23
+ @cop_class = cop_class
24
+ end
25
+
26
+ def to_lsp_code_actions
27
+ code_actions = []
28
+
29
+ code_actions << autocorrect_action if correctable?
30
+ code_actions << disable_line_action
31
+
32
+ code_actions
33
+ end
34
+
35
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
36
+ def to_lsp_diagnostic(config)
37
+ highlighted = @offense.highlighted_area
38
+
39
+ LanguageServer::Protocol::Interface::Diagnostic.new(
40
+ message: message,
41
+ source: 'RuboCop',
42
+ code: @offense.cop_name,
43
+ code_description: code_description(config),
44
+ severity: severity,
45
+ range: LanguageServer::Protocol::Interface::Range.new(
46
+ start: LanguageServer::Protocol::Interface::Position.new(
47
+ line: @offense.line - 1,
48
+ character: highlighted.begin_pos
49
+ ),
50
+ end: LanguageServer::Protocol::Interface::Position.new(
51
+ line: @offense.line - 1,
52
+ character: highlighted.end_pos
53
+ )
54
+ ),
55
+ data: {
56
+ correctable: correctable?,
57
+ code_actions: to_lsp_code_actions
58
+ }
59
+ )
60
+ end
61
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
62
+
63
+ private
64
+
65
+ def message
66
+ message = @offense.message
67
+ message += "\n\nThis offense is not autocorrectable.\n" unless correctable?
68
+ message
69
+ end
70
+
71
+ def severity
72
+ Severity.find_by(@offense.severity.name)
73
+ end
74
+
75
+ def code_description(config)
76
+ return unless @cop_class
77
+ return unless (doc_url = @cop_class.documentation_url(config))
78
+
79
+ LanguageServer::Protocol::Interface::CodeDescription.new(href: doc_url)
80
+ end
81
+
82
+ # rubocop:disable Layout/LineLength, Metrics/MethodLength
83
+ def autocorrect_action
84
+ LanguageServer::Protocol::Interface::CodeAction.new(
85
+ title: "Autocorrect #{@offense.cop_name}",
86
+ kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
87
+ edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
88
+ document_changes: [
89
+ LanguageServer::Protocol::Interface::TextDocumentEdit.new(
90
+ text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
91
+ uri: ensure_uri_scheme(@uri.to_s).to_s,
92
+ version: nil
93
+ ),
94
+ edits: correctable? ? offense_replacements : []
95
+ )
96
+ ]
97
+ ),
98
+ is_preferred: true
99
+ )
100
+ end
101
+ # rubocop:enable Layout/LineLength, Metrics/MethodLength
102
+
103
+ # rubocop:disable Metrics/MethodLength
104
+ def offense_replacements
105
+ @offense.corrector.as_replacements.map do |range, replacement|
106
+ LanguageServer::Protocol::Interface::TextEdit.new(
107
+ range: LanguageServer::Protocol::Interface::Range.new(
108
+ start: LanguageServer::Protocol::Interface::Position.new(
109
+ line: range.line - 1,
110
+ character: range.column
111
+ ),
112
+ end: LanguageServer::Protocol::Interface::Position.new(
113
+ line: range.last_line - 1,
114
+ character: range.last_column
115
+ )
116
+ ),
117
+ new_text: replacement
118
+ )
119
+ end
120
+ end
121
+ # rubocop:enable Metrics/MethodLength
122
+
123
+ # rubocop:disable Layout/LineLength, Metrics/MethodLength
124
+ def disable_line_action
125
+ LanguageServer::Protocol::Interface::CodeAction.new(
126
+ title: "Disable #{@offense.cop_name} for this line",
127
+ kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
128
+ edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
129
+ document_changes: [
130
+ LanguageServer::Protocol::Interface::TextDocumentEdit.new(
131
+ text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
132
+ uri: ensure_uri_scheme(@uri.to_s).to_s,
133
+ version: nil
134
+ ),
135
+ edits: line_disable_comment
136
+ )
137
+ ]
138
+ )
139
+ )
140
+ end
141
+ # rubocop:enable Layout/LineLength, Metrics/MethodLength
142
+
143
+ def line_disable_comment
144
+ new_text = if @offense.source_line.include?(' # rubocop:disable ')
145
+ ",#{@offense.cop_name}"
146
+ else
147
+ " # rubocop:disable #{@offense.cop_name}"
148
+ end
149
+
150
+ eol = LanguageServer::Protocol::Interface::Position.new(
151
+ line: @offense.line - 1,
152
+ character: length_of_line(@offense.source_line)
153
+ )
154
+
155
+ # TODO: fails for multiline strings - may be preferable to use block
156
+ # comments to disable some offenses
157
+ inline_comment = LanguageServer::Protocol::Interface::TextEdit.new(
158
+ range: LanguageServer::Protocol::Interface::Range.new(start: eol, end: eol),
159
+ new_text: new_text
160
+ )
161
+
162
+ [inline_comment]
163
+ end
164
+
165
+ def length_of_line(line)
166
+ if @document_encoding == Encoding::UTF_16LE
167
+ line_length = 0
168
+ line.codepoints.each do |codepoint|
169
+ line_length += 1
170
+ line_length += 1 if codepoint > RubyLsp::Document::Scanner::SURROGATE_PAIR_START
171
+ end
172
+ line_length
173
+ else
174
+ line.length
175
+ end
176
+ end
177
+
178
+ def correctable?
179
+ !@offense.corrector.nil?
180
+ end
181
+
182
+ def ensure_uri_scheme(uri)
183
+ uri = URI.parse(uri)
184
+ uri.scheme = 'file' if uri.scheme.nil?
185
+ uri
186
+ end
187
+ end
188
+ end
189
+ end
@@ -14,8 +14,8 @@ module RuboCop
14
14
  # Log for Language Server Protocol of RuboCop.
15
15
  # @api private
16
16
  class Logger
17
- def self.log(message)
18
- warn("[server] #{message}")
17
+ def self.log(message, prefix: '[server]')
18
+ warn("#{prefix} #{message}")
19
19
  end
20
20
  end
21
21
  end