rubocop 1.79.2 → 1.81.7

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +14 -2
  4. data/exe/rubocop +1 -8
  5. data/lib/rubocop/cli/command/auto_generate_config.rb +2 -2
  6. data/lib/rubocop/cli.rb +6 -2
  7. data/lib/rubocop/config_loader.rb +3 -1
  8. data/lib/rubocop/config_loader_resolver.rb +5 -4
  9. data/lib/rubocop/config_store.rb +5 -0
  10. data/lib/rubocop/cop/autocorrect_logic.rb +4 -4
  11. data/lib/rubocop/cop/correctors/alignment_corrector.rb +7 -4
  12. data/lib/rubocop/cop/correctors/for_to_each_corrector.rb +7 -2
  13. data/lib/rubocop/cop/internal_affairs/node_pattern_groups.rb +3 -1
  14. data/lib/rubocop/cop/internal_affairs/on_send_without_on_csend.rb +1 -1
  15. data/lib/rubocop/cop/layout/class_structure.rb +1 -1
  16. data/lib/rubocop/cop/layout/dot_position.rb +1 -1
  17. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +30 -12
  18. data/lib/rubocop/cop/layout/empty_lines_after_module_inclusion.rb +1 -1
  19. data/lib/rubocop/cop/layout/hash_alignment.rb +2 -5
  20. data/lib/rubocop/cop/layout/line_length.rb +9 -1
  21. data/lib/rubocop/cop/layout/multiline_operation_indentation.rb +8 -4
  22. data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +8 -0
  23. data/lib/rubocop/cop/layout/trailing_whitespace.rb +1 -1
  24. data/lib/rubocop/cop/lint/constant_overwritten_in_rescue.rb +3 -2
  25. data/lib/rubocop/cop/lint/cop_directive_syntax.rb +13 -7
  26. data/lib/rubocop/cop/lint/debugger.rb +0 -2
  27. data/lib/rubocop/cop/lint/deprecated_open_ssl_constant.rb +4 -1
  28. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +5 -42
  29. data/lib/rubocop/cop/lint/empty_interpolation.rb +11 -0
  30. data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +17 -8
  31. data/lib/rubocop/cop/lint/rescue_exception.rb +1 -4
  32. data/lib/rubocop/cop/lint/self_assignment.rb +6 -5
  33. data/lib/rubocop/cop/lint/shadowed_argument.rb +7 -7
  34. data/lib/rubocop/cop/lint/uri_escape_unescape.rb +2 -0
  35. data/lib/rubocop/cop/lint/void.rb +7 -0
  36. data/lib/rubocop/cop/message_annotator.rb +1 -1
  37. data/lib/rubocop/cop/mixin/check_line_breakable.rb +1 -1
  38. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -7
  39. data/lib/rubocop/cop/mixin/trailing_comma.rb +1 -1
  40. data/lib/rubocop/cop/naming/method_name.rb +4 -2
  41. data/lib/rubocop/cop/naming/predicate_method.rb +19 -6
  42. data/lib/rubocop/cop/security/json_load.rb +33 -11
  43. data/lib/rubocop/cop/style/array_intersect.rb +46 -12
  44. data/lib/rubocop/cop/style/array_intersect_with_single_element.rb +47 -0
  45. data/lib/rubocop/cop/style/bitwise_predicate.rb +8 -1
  46. data/lib/rubocop/cop/style/conditional_assignment.rb +7 -3
  47. data/lib/rubocop/cop/style/constant_visibility.rb +14 -9
  48. data/lib/rubocop/cop/style/double_negation.rb +1 -1
  49. data/lib/rubocop/cop/style/endless_method.rb +15 -2
  50. data/lib/rubocop/cop/style/explicit_block_argument.rb +1 -1
  51. data/lib/rubocop/cop/style/float_division.rb +15 -1
  52. data/lib/rubocop/cop/style/hash_syntax.rb +1 -1
  53. data/lib/rubocop/cop/style/infinite_loop.rb +1 -1
  54. data/lib/rubocop/cop/style/nil_comparison.rb +9 -7
  55. data/lib/rubocop/cop/style/one_line_conditional.rb +17 -9
  56. data/lib/rubocop/cop/style/redundant_begin.rb +34 -0
  57. data/lib/rubocop/cop/style/redundant_condition.rb +1 -1
  58. data/lib/rubocop/cop/style/redundant_exception.rb +1 -1
  59. data/lib/rubocop/cop/style/redundant_format.rb +26 -5
  60. data/lib/rubocop/cop/style/redundant_interpolation.rb +11 -2
  61. data/lib/rubocop/cop/style/redundant_parentheses.rb +14 -11
  62. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +4 -0
  63. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +8 -0
  64. data/lib/rubocop/cop/style/safe_navigation.rb +18 -1
  65. data/lib/rubocop/cop/style/semicolon.rb +23 -7
  66. data/lib/rubocop/cop/style/sole_nested_conditional.rb +8 -1
  67. data/lib/rubocop/cop/style/string_concatenation.rb +17 -13
  68. data/lib/rubocop/cop/style/symbol_array.rb +1 -1
  69. data/lib/rubocop/cop/style/trailing_comma_in_arguments.rb +45 -0
  70. data/lib/rubocop/cop/style/unless_else.rb +10 -9
  71. data/lib/rubocop/cop/utils/format_string.rb +10 -0
  72. data/lib/rubocop/cop/variable_force/variable.rb +1 -1
  73. data/lib/rubocop/cop/variable_force.rb +9 -7
  74. data/lib/rubocop/cops_documentation_generator.rb +4 -4
  75. data/lib/rubocop/formatter/disabled_config_formatter.rb +18 -5
  76. data/lib/rubocop/lsp/diagnostic.rb +21 -20
  77. data/lib/rubocop/lsp/routes.rb +62 -6
  78. data/lib/rubocop/lsp/runtime.rb +2 -2
  79. data/lib/rubocop/lsp/server.rb +2 -2
  80. data/lib/rubocop/lsp/stdin_runner.rb +0 -16
  81. data/lib/rubocop/result_cache.rb +1 -1
  82. data/lib/rubocop/runner.rb +6 -4
  83. data/lib/rubocop/target_finder.rb +9 -9
  84. data/lib/rubocop/target_ruby.rb +10 -1
  85. data/lib/rubocop/version.rb +1 -1
  86. data/lib/rubocop.rb +1 -0
  87. data/lib/ruby_lsp/rubocop/addon.rb +23 -8
  88. data/lib/ruby_lsp/rubocop/runtime_adapter.rb +49 -15
  89. metadata +7 -6
@@ -69,10 +69,11 @@ module RuboCop
69
69
 
70
70
  def each_semicolon
71
71
  tokens_for_lines.each do |line, tokens|
72
- semicolon_pos = semicolon_position(tokens)
72
+ next unless (semicolon_pos = semicolon_position(tokens))
73
+
73
74
  after_expr_pos = semicolon_pos == -1 ? -2 : semicolon_pos
74
75
 
75
- yield line, tokens[semicolon_pos].column, tokens[after_expr_pos] if semicolon_pos
76
+ yield line, tokens[semicolon_pos].column, tokens[after_expr_pos]
76
77
  end
77
78
  end
78
79
 
@@ -119,6 +120,7 @@ module RuboCop
119
120
  tokens[1]&.type == :tSTRING_DBEG && tokens[2]&.semicolon?
120
121
  end
121
122
 
123
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
122
124
  def register_semicolon(line, column, after_expression, token_before_semicolon = nil)
123
125
  range = source_range(processed_source.buffer, line, column)
124
126
 
@@ -130,14 +132,19 @@ module RuboCop
130
132
  # without parentheses.
131
133
  # See: https://github.com/rubocop/rubocop/issues/10791
132
134
  if token_before_semicolon&.regexp_dots?
133
- range_node = find_range_node(token_before_semicolon)
134
- corrector.wrap(range_node, '(', ')') if range_node
135
+ node = find_node(range_nodes, token_before_semicolon)
136
+ elsif token_before_semicolon&.type == :tLABEL
137
+ node = find_node(value_omission_pair_nodes, token_before_semicolon).parent
138
+ space = node.parent.loc.selector.end.join(node.source_range.begin)
139
+ corrector.remove(space)
135
140
  end
136
141
 
142
+ corrector.wrap(node, '(', ')') if node
137
143
  corrector.remove(range)
138
144
  end
139
145
  end
140
146
  end
147
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
141
148
 
142
149
  def expressions_per_line(exprs)
143
150
  # create a map matching lines to the number of expressions on them
@@ -153,9 +160,9 @@ module RuboCop
153
160
  end
154
161
  end
155
162
 
156
- def find_range_node(token_before_semicolon)
157
- range_nodes.detect do |range_node|
158
- range_node.source_range.contains?(token_before_semicolon.pos)
163
+ def find_node(nodes, token_before_semicolon)
164
+ nodes.detect do |node|
165
+ node.source_range.overlaps?(token_before_semicolon.pos)
159
166
  end
160
167
  end
161
168
 
@@ -166,6 +173,15 @@ module RuboCop
166
173
  @range_nodes = ast.range_type? ? [ast] : []
167
174
  @range_nodes.concat(ast.each_descendant(:range).to_a)
168
175
  end
176
+
177
+ def value_omission_pair_nodes
178
+ if instance_variable_defined?(:@value_omission_pair_nodes)
179
+ return @value_omission_pair_nodes
180
+ end
181
+
182
+ ast = processed_source.ast
183
+ @value_omission_pair_nodes = ast.each_descendant(:pair).to_a.select(&:value_omission?)
184
+ end
169
185
  end
170
186
  end
171
187
  end
@@ -129,6 +129,7 @@ module RuboCop
129
129
  corrector.remove(range_with_surrounding_space(range, newlines: false))
130
130
  end
131
131
 
132
+ # rubocop:disable Metrics/AbcSize
132
133
  def correct_for_basic_condition_style(corrector, node, if_branch)
133
134
  range = range_between(
134
135
  node.condition.source_range.end_pos, if_branch.condition.source_range.begin_pos
@@ -137,8 +138,14 @@ module RuboCop
137
138
 
138
139
  corrector.replace(if_branch.condition, chainable_condition(if_branch))
139
140
 
140
- corrector.remove(range_by_whole_lines(node.loc.end, include_final_newline: true))
141
+ end_range = if same_line?(node.loc.end, node.if_branch.loc.end)
142
+ node.loc.end
143
+ else
144
+ range_by_whole_lines(node.loc.end, include_final_newline: true)
145
+ end
146
+ corrector.remove(end_range)
141
147
  end
148
+ # rubocop:enable Metrics/AbcSize
142
149
 
143
150
  def autocorrect_outer_condition_modify_form(corrector, node, if_branch)
144
151
  correct_node(corrector, if_branch)
@@ -100,7 +100,7 @@ module RuboCop
100
100
  node.receiver.str_type? &&
101
101
  node.first_argument.str_type? &&
102
102
  node.multiline? &&
103
- node.source =~ /\+\s*\n/
103
+ node.source.match?(/\+\s*\n/)
104
104
  end
105
105
 
106
106
  def find_topmost_plus_node(node)
@@ -141,22 +141,26 @@ module RuboCop
141
141
  end
142
142
 
143
143
  def replacement(parts)
144
- interpolated_parts = parts.map do |part|
145
- case part.type
146
- when :str
147
- adjust_str(part)
148
- when :dstr
149
- part.children.all?(&:str_type?) ? adjust_str(part) : part.value
150
- else
151
- "\#{#{part.source}}"
152
- end
153
- end
144
+ interpolated_parts = parts.map { |part| adjust_str(part) }
154
145
 
155
146
  "\"#{handle_quotes(interpolated_parts).join}\""
156
147
  end
157
148
 
158
- def adjust_str(node)
159
- single_quoted?(node) ? node.value.gsub(/(\\|")/, '\\\\\&') : node.value.inspect[1..-2]
149
+ def adjust_str(part)
150
+ case part.type
151
+ when :str
152
+ if single_quoted?(part)
153
+ part.value.gsub(/(\\|"|#\{|#@|#\$)/, '\\\\\&')
154
+ else
155
+ part.value.inspect[1..-2]
156
+ end
157
+ when :dstr, :begin
158
+ part.children.map do |child|
159
+ adjust_str(child)
160
+ end.join
161
+ else
162
+ "\#{#{part.source}}"
163
+ end
160
164
  end
161
165
 
162
166
  def handle_quotes(parts)
@@ -81,7 +81,7 @@ module RuboCop
81
81
 
82
82
  content = *sym
83
83
  content = content.map { |c| c.is_a?(AST::Node) ? c.source : c }.join
84
- content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s\(\)]*\))/, '')
84
+ content_without_delimiter_pairs = content.gsub(/(\[[^\s\[\]]*\])|(\([^\s()]*\))/, '')
85
85
 
86
86
  content.include?(' ') || DELIMITERS.any? do |delimiter|
87
87
  content_without_delimiter_pairs.include?(delimiter)
@@ -10,6 +10,9 @@ module RuboCop
10
10
  # for all parenthesized multi-line method calls with arguments.
11
11
  # * `comma`: Requires a comma after the last argument, but only for
12
12
  # parenthesized method calls where each argument is on its own line.
13
+ # * `diff_comma`: Requires a comma after the last argument, but only
14
+ # when that argument is followed by an immediate newline, even if
15
+ # there is an inline comment on the same line.
13
16
  # * `no_comma`: Requires that there is no comma after the last
14
17
  # argument.
15
18
  #
@@ -75,6 +78,48 @@ module RuboCop
75
78
  # 2,
76
79
  # )
77
80
  #
81
+ # @example EnforcedStyleForMultiline: diff_comma
82
+ # # bad
83
+ # method(1, 2,)
84
+ #
85
+ # # good
86
+ # method(1, 2)
87
+ #
88
+ # # good
89
+ # method(
90
+ # 1, 2,
91
+ # 3,
92
+ # )
93
+ #
94
+ # # good
95
+ # method(
96
+ # 1, 2, 3,
97
+ # )
98
+ #
99
+ # # good
100
+ # method(
101
+ # 1,
102
+ # 2,
103
+ # )
104
+ #
105
+ # # bad
106
+ # method(1, [
107
+ # 2,
108
+ # ],)
109
+ #
110
+ # # good
111
+ # method(1, [
112
+ # 2,
113
+ # ])
114
+ #
115
+ # # bad
116
+ # object[1, 2,
117
+ # 3, 4,]
118
+ #
119
+ # # good
120
+ # object[1, 2,
121
+ # 3, 4]
122
+ #
78
123
  # @example EnforcedStyleForMultiline: no_comma (default)
79
124
  # # bad
80
125
  # method(1, 2,)
@@ -20,7 +20,6 @@ module RuboCop
20
20
  # # do a different thing...
21
21
  # end
22
22
  class UnlessElse < Base
23
- include RangeHelp
24
23
  extend AutoCorrector
25
24
 
26
25
  MSG = 'Do not use `unless` with `else`. Rewrite these with the positive case first.'
@@ -29,25 +28,27 @@ module RuboCop
29
28
  return unless node.unless? && node.else?
30
29
 
31
30
  add_offense(node) do |corrector|
32
- body_range = range_between_condition_and_else(node, node.condition)
33
- else_range = range_between_else_and_end(node)
34
-
35
31
  next if part_of_ignored_node?(node)
36
32
 
37
33
  corrector.replace(node.loc.keyword, 'if')
38
- corrector.replace(body_range, else_range.source)
39
- corrector.replace(else_range, body_range.source)
34
+
35
+ body_range = range_between_condition_and_else(node)
36
+ else_range = range_between_else_and_end(node)
37
+
38
+ corrector.swap(body_range, else_range)
40
39
  end
41
40
 
42
41
  ignore_node(node)
43
42
  end
44
43
 
45
- def range_between_condition_and_else(node, condition)
46
- range_between(condition.source_range.end_pos, node.loc.else.begin_pos)
44
+ def range_between_condition_and_else(node)
45
+ range = node.loc.begin ? node.loc.begin.end : node.condition.source_range
46
+
47
+ range.end.join(node.loc.else.begin)
47
48
  end
48
49
 
49
50
  def range_between_else_and_end(node)
50
- range_between(node.loc.else.end_pos, node.loc.end.begin_pos)
51
+ node.loc.else.end.join(node.loc.end.begin)
51
52
  end
52
53
  end
53
54
  end
@@ -71,6 +71,16 @@ module RuboCop
71
71
  name && @source.include?('{')
72
72
  end
73
73
 
74
+ def variable_width?
75
+ !!width&.start_with?('*')
76
+ end
77
+
78
+ def variable_width_argument_number
79
+ return unless variable_width?
80
+
81
+ width == '*' ? 1 : width.match(DIGIT_DOLLAR)['arg_number'].to_i
82
+ end
83
+
74
84
  # Number of arguments required for the format sequence
75
85
  def arity
76
86
  @source.scan('*').count + 1
@@ -79,7 +79,7 @@ module RuboCop
79
79
  parent = parent.parent if parent&.begin_type?
80
80
  return false if parent.nil?
81
81
 
82
- parent.type?(:if, :while, :until) && parent.modifier_form?
82
+ parent.basic_conditional? && parent.modifier_form?
83
83
  end
84
84
 
85
85
  def capture_with_block!
@@ -238,11 +238,16 @@ module RuboCop
238
238
  end
239
239
 
240
240
  def process_loop(node)
241
- if POST_CONDITION_LOOP_TYPES.include?(node.type)
241
+ if node.post_condition_loop?
242
242
  # See the comment at the end of file for this behavior.
243
243
  condition_node, body_node = *node
244
244
  process_node(body_node)
245
245
  process_node(condition_node)
246
+ elsif node.for_type?
247
+ # In `for item in items` the rightmost expression is evaluated first.
248
+ process_node(node.collection)
249
+ process_node(node.variable)
250
+ process_node(node.body) if node.body
246
251
  else
247
252
  process_children(node)
248
253
  end
@@ -356,17 +361,14 @@ module RuboCop
356
361
  end
357
362
 
358
363
  def reference_assignments(loop_assignments, loop_node)
359
- node = loop_assignments.first.node
360
-
361
364
  # If inside a branching statement, mark all as referenced.
362
365
  # Otherwise, mark only the last assignment as referenced.
363
366
  # Note that `rescue` must be considered as branching because of
364
367
  # the `retry` keyword.
365
- if node.each_ancestor(*BRANCH_NODES).any? || node.parent.each_descendant(*BRANCH_NODES).any?
366
- loop_assignments.each { |assignment| assignment.reference!(loop_node) }
367
- else
368
- loop_assignments.last&.reference!(loop_node)
368
+ loop_assignments.each do |assignment|
369
+ assignment.reference!(loop_node) if assignment.node.each_ancestor(*BRANCH_NODES).any?
369
370
  end
371
+ loop_assignments.last&.reference!(loop_node)
370
372
  end
371
373
 
372
374
  def scanned_node?(node)
@@ -194,10 +194,10 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
194
194
 
195
195
  def configurations(department, cop, cop_config)
196
196
  header = ['Name', 'Default value', 'Configurable values']
197
- configs = cop_config
198
- .each_key
199
- .reject { |key| key.start_with?('Supported') }
200
- .reject { |key| key.start_with?('AllowMultipleStyles') }
197
+ configs = cop_config.each_key.reject do |key|
198
+ key == 'AllowMultipleStyles' ||
199
+ (key != 'SupportedTypes' && key.start_with?('Supported'))
200
+ end
201
201
  return '' if configs.empty?
202
202
 
203
203
  content = configs.map do |name|
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Formatter
5
5
  # This formatter displays a YAML configuration file where all cops that
6
6
  # detected any offenses are configured to not detect the offense.
7
- class DisabledConfigFormatter < BaseFormatter
7
+ class DisabledConfigFormatter < BaseFormatter # rubocop:disable Metrics/ClassLength
8
8
  include PathUtil
9
9
 
10
10
  HEADING = <<~COMMENTS
@@ -17,6 +17,22 @@ module RuboCop
17
17
  # versions of RuboCop, may require this file to be generated again.
18
18
  COMMENTS
19
19
 
20
+ EXCLUDED_CONFIG_KEYS = %w[
21
+ AutoCorrect
22
+ Description
23
+ Enabled
24
+ Exclude
25
+ Include
26
+ Reference
27
+ References
28
+ Safe
29
+ SafeAutoCorrect
30
+ StyleGuide
31
+ VersionAdded
32
+ VersionChanged
33
+ VersionRemoved
34
+ ].freeze
35
+
20
36
  @config_to_allow_offenses = {}
21
37
  @detected_styles = {}
22
38
 
@@ -163,10 +179,7 @@ module RuboCop
163
179
  end
164
180
 
165
181
  def cop_config_params(default_cfg, cfg)
166
- default_cfg.keys -
167
- %w[Description StyleGuide Reference References Enabled Exclude Safe
168
- SafeAutoCorrect VersionAdded VersionChanged VersionRemoved] -
169
- cfg.keys
182
+ default_cfg.keys - EXCLUDED_CONFIG_KEYS - cfg.keys
170
183
  end
171
184
 
172
185
  def output_cop_param_comments(output_buffer, params, default_cfg)
@@ -16,8 +16,8 @@ module RuboCop
16
16
  # Diagnostic for Language Server Protocol of RuboCop.
17
17
  # @api private
18
18
  class Diagnostic
19
- def initialize(document_encoding, offense, uri, cop_class)
20
- @document_encoding = document_encoding
19
+ def initialize(position_encoding, offense, uri, cop_class)
20
+ @position_encoding = position_encoding
21
21
  @offense = offense
22
22
  @uri = uri
23
23
  @cop_class = cop_class
@@ -45,11 +45,11 @@ module RuboCop
45
45
  range: LanguageServer::Protocol::Interface::Range.new(
46
46
  start: LanguageServer::Protocol::Interface::Position.new(
47
47
  line: @offense.line - 1,
48
- character: highlighted.begin_pos
48
+ character: to_position_character(highlighted.begin_pos)
49
49
  ),
50
50
  end: LanguageServer::Protocol::Interface::Position.new(
51
51
  line: @offense.line - 1,
52
- character: highlighted.end_pos
52
+ character: to_position_character(highlighted.end_pos)
53
53
  )
54
54
  ),
55
55
  data: {
@@ -107,11 +107,11 @@ module RuboCop
107
107
  range: LanguageServer::Protocol::Interface::Range.new(
108
108
  start: LanguageServer::Protocol::Interface::Position.new(
109
109
  line: range.line - 1,
110
- character: range.column
110
+ character: to_position_character(range.column)
111
111
  ),
112
112
  end: LanguageServer::Protocol::Interface::Position.new(
113
113
  line: range.last_line - 1,
114
- character: range.last_column
114
+ character: to_position_character(range.last_column)
115
115
  )
116
116
  ),
117
117
  new_text: replacement
@@ -149,7 +149,7 @@ module RuboCop
149
149
 
150
150
  eol = LanguageServer::Protocol::Interface::Position.new(
151
151
  line: @offense.line - 1,
152
- character: length_of_line(@offense.source_line)
152
+ character: to_position_character
153
153
  )
154
154
 
155
155
  # TODO: fails for multiline strings - may be preferable to use block
@@ -162,19 +162,6 @@ module RuboCop
162
162
  [inline_comment]
163
163
  end
164
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
165
  def correctable?
179
166
  !@offense.corrector.nil?
180
167
  end
@@ -184,6 +171,20 @@ module RuboCop
184
171
  uri.scheme = 'file' if uri.scheme.nil?
185
172
  uri
186
173
  end
174
+
175
+ def to_position_character(utf8_index = nil)
176
+ str = utf8_index ? @offense.source_line[0, utf8_index] : @offense.source_line
177
+ case @position_encoding
178
+ when 'utf-8', Encoding::UTF_8
179
+ str.bytesize
180
+ when 'utf-32', Encoding::UTF_32
181
+ str.size
182
+ else # 'utf-16'
183
+ # utf-16 is default position encoding on LSP
184
+ # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
185
+ str.size + str.count("\u{10000}-\u{10FFFF}")
186
+ end
187
+ end
187
188
  end
188
189
  end
189
190
  end
@@ -15,7 +15,7 @@ module RuboCop
15
15
  module LSP
16
16
  # Routes for Language Server Protocol of RuboCop.
17
17
  # @api private
18
- class Routes
18
+ class Routes # rubocop:disable Metrics/ClassLength
19
19
  CONFIGURATION_FILE_PATTERNS = [
20
20
  RuboCop::ConfigFinder::DOTFILE,
21
21
  RuboCop::CLI::Command::AutoGenerateConfig::AUTO_GENERATED_FILE
@@ -42,6 +42,7 @@ module RuboCop
42
42
 
43
43
  handle 'initialize' do |request|
44
44
  initialization_options = extract_initialization_options_from(request)
45
+ @position_encoding = initialization_options[:position_encoding]
45
46
 
46
47
  @server.configure(initialization_options)
47
48
 
@@ -51,9 +52,10 @@ module RuboCop
51
52
  capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
52
53
  document_formatting_provider: true,
53
54
  text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
54
- change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL,
55
+ change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::INCREMENTAL,
55
56
  open_close: true
56
- )
57
+ ),
58
+ position_encoding: @position_encoding
57
59
  )
58
60
  )
59
61
  )
@@ -76,7 +78,12 @@ module RuboCop
76
78
 
77
79
  handle 'textDocument/didChange' do |request|
78
80
  params = request[:params]
79
- result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
81
+ file_uri = params[:textDocument][:uri]
82
+ text = @text_cache[file_uri]
83
+ params[:contentChanges].each do |content|
84
+ text = change_text(text, content[:text], content[:range])
85
+ end
86
+ result = diagnostic(file_uri, text)
80
87
  @server.write(result)
81
88
  end
82
89
 
@@ -179,14 +186,26 @@ module RuboCop
179
186
 
180
187
  def extract_initialization_options_from(request)
181
188
  safe_autocorrect = request.dig(:params, :initializationOptions, :safeAutocorrect)
189
+ position_encodings = request.dig(:params, :capabilities, :general, :positionEncodings)
182
190
 
183
191
  {
184
192
  safe_autocorrect: safe_autocorrect.nil? || safe_autocorrect == true,
185
193
  lint_mode: request.dig(:params, :initializationOptions, :lintMode) == true,
186
- layout_mode: request.dig(:params, :initializationOptions, :layoutMode) == true
194
+ layout_mode: request.dig(:params, :initializationOptions, :layoutMode) == true,
195
+ position_encoding: position_encoding(position_encodings)
187
196
  }
188
197
  end
189
198
 
199
+ def position_encoding(position_encodings)
200
+ if position_encodings&.include?('utf-8')
201
+ 'utf-8'
202
+ elsif position_encodings&.include?('utf-32')
203
+ 'utf-32'
204
+ else
205
+ 'utf-16'
206
+ end
207
+ end
208
+
190
209
  def format_file(file_uri, command: nil)
191
210
  unless (text = @text_cache[file_uri])
192
211
  Logger.log("Format request arrived before text synchronized; skipping: `#{file_uri}'")
@@ -214,11 +233,48 @@ module RuboCop
214
233
  method: 'textDocument/publishDiagnostics',
215
234
  params: {
216
235
  uri: file_uri,
217
- diagnostics: @server.offenses(convert_file_uri_to_path(file_uri), text)
236
+ diagnostics: @server.offenses(convert_file_uri_to_path(file_uri),
237
+ text, @position_encoding)
218
238
  }
219
239
  }
220
240
  end
221
241
 
242
+ def change_text(orig_text, text, range)
243
+ return text unless range
244
+
245
+ start_pos = text_pos(orig_text, range[:start])
246
+ end_pos = text_pos(orig_text, range[:end])
247
+ orig_text[start_pos...end_pos] = text
248
+ orig_text
249
+ end
250
+
251
+ def text_pos(text, range)
252
+ line = range[:line]
253
+ char = range[:character]
254
+ pos = 0
255
+ text.each_line.with_index do |l, i|
256
+ if i == line
257
+ pos += line_pos(l, char)
258
+ return pos
259
+ end
260
+ pos += l.size
261
+ end
262
+ pos
263
+ end
264
+
265
+ def line_pos(line, char)
266
+ case @position_encoding
267
+ when 'utf-8'
268
+ line.byteslice(0, char).size
269
+ when 'utf-32'
270
+ char
271
+ else # 'utf-16'
272
+ # utf-16 is default position encoding on LSP
273
+ # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
274
+ line.encode('utf-16be').byteslice(0, char * 2).size
275
+ end
276
+ end
277
+
222
278
  def convert_file_uri_to_path(uri)
223
279
  URI.decode_www_form_component(uri.delete_prefix('file://'))
224
280
  end
@@ -44,14 +44,14 @@ module RuboCop
44
44
  @runner.formatted_source
45
45
  end
46
46
 
47
- def offenses(path, text, document_encoding = nil, prism_result: nil)
47
+ def offenses(path, text, position_encoding, prism_result: nil)
48
48
  diagnostic_options = {}
49
49
  diagnostic_options[:only] = config_only_options if @lint_mode || @layout_mode
50
50
 
51
51
  @runner.run(path, text, diagnostic_options, prism_result: prism_result)
52
52
  @runner.offenses.map do |offense|
53
53
  Diagnostic.new(
54
- document_encoding, offense, path, @cop_registry[offense.cop_name]&.first
54
+ position_encoding, offense, path, @cop_registry[offense.cop_name]&.first
55
55
  ).to_lsp_diagnostic(@runner.config_for_working_directory)
56
56
  end
57
57
  end
@@ -51,8 +51,8 @@ module RuboCop
51
51
  @runtime.format(path, text, command: command)
52
52
  end
53
53
 
54
- def offenses(path, text)
55
- @runtime.offenses(path, text)
54
+ def offenses(path, text, position_encoding)
55
+ @runtime.offenses(path, text, position_encoding)
56
56
  end
57
57
 
58
58
  def configure(options)
@@ -40,7 +40,6 @@ module RuboCop
40
40
  super(@options, config_store)
41
41
  end
42
42
 
43
- # rubocop:disable Metrics/MethodLength
44
43
  def run(path, contents, options, prism_result: nil)
45
44
  @options = options.merge(DEFAULT_RUBOCOP_OPTIONS)
46
45
  @options[:stdin] = contents
@@ -54,22 +53,7 @@ module RuboCop
54
53
  super([path])
55
54
 
56
55
  raise Interrupt if aborting?
57
- rescue RuboCop::Runner::InfiniteCorrectionLoop => e
58
- if defined?(::RubyLsp::Requests::Formatting::Error)
59
- raise ::RubyLsp::Requests::Formatting::Error, e.message
60
- end
61
-
62
- raise e
63
- rescue RuboCop::ValidationError => e
64
- raise ConfigurationError, e.message
65
- rescue StandardError => e
66
- if defined?(::RubyLsp::Requests::Formatting::Error)
67
- raise ::RubyLsp::Requests::Support::InternalRuboCopError, e
68
- end
69
-
70
- raise e
71
56
  end
72
- # rubocop:enable Metrics/MethodLength
73
57
 
74
58
  def formatted_source
75
59
  @options[:stdin]
@@ -9,7 +9,7 @@ module RuboCop
9
9
  # Provides functionality for caching RuboCop runs.
10
10
  # @api private
11
11
  class ResultCache
12
- NON_CHANGING = %i[color format formatters out debug fail_level
12
+ NON_CHANGING = %i[color format formatters out debug display_time fail_level
13
13
  fix_layout autocorrect safe_autocorrect autocorrect_all
14
14
  cache fail_fast stdin parallel].freeze
15
15