rubocop 1.52.1 → 1.53.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/config/default.yml +38 -1
  4. data/lib/rubocop/cli/command/lsp.rb +19 -0
  5. data/lib/rubocop/cli.rb +3 -0
  6. data/lib/rubocop/config_loader_resolver.rb +4 -3
  7. data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
  8. data/lib/rubocop/cop/bundler/gem_version.rb +2 -2
  9. data/lib/rubocop/cop/gemspec/dependency_version.rb +2 -2
  10. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +3 -3
  11. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  12. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -1
  13. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  14. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
  15. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  16. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  17. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  18. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
  19. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  20. data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
  21. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
  22. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
  23. data/lib/rubocop/cop/lint/missing_super.rb +31 -5
  24. data/lib/rubocop/cop/lint/mixed_case_range.rb +109 -0
  25. data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
  26. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +120 -0
  27. data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
  28. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  29. data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
  30. data/lib/rubocop/cop/lint/void.rb +1 -1
  31. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  32. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  33. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  34. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  35. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  36. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  37. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  38. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  39. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  40. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  41. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  42. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  43. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  44. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  45. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  46. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  47. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  48. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  49. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  50. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +97 -0
  51. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  52. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  53. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  54. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  55. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  56. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  57. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  58. data/lib/rubocop/cop/util.rb +1 -1
  59. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  60. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  61. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  62. data/lib/rubocop/lsp/logger.rb +22 -0
  63. data/lib/rubocop/lsp/routes.rb +223 -0
  64. data/lib/rubocop/lsp/runtime.rb +79 -0
  65. data/lib/rubocop/lsp/server.rb +62 -0
  66. data/lib/rubocop/lsp/severity.rb +27 -0
  67. data/lib/rubocop/options.rb +11 -1
  68. data/lib/rubocop/version.rb +1 -1
  69. data/lib/rubocop.rb +8 -0
  70. metadata +30 -3
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks if `return` or `return nil` is used in predicate method definitions.
7
+ #
8
+ # @safety
9
+ # Autocorrection is marked as unsafe because the change of the return value
10
+ # from `nil` to `false` could potentially lead to incompatibility issues.
11
+ #
12
+ # @example
13
+ # # bad
14
+ # def foo?
15
+ # return if condition
16
+ #
17
+ # do_something?
18
+ # end
19
+ #
20
+ # # bad
21
+ # def foo?
22
+ # return nil if condition
23
+ #
24
+ # do_something?
25
+ # end
26
+ #
27
+ # # good
28
+ # def foo?
29
+ # return false if condition
30
+ #
31
+ # do_something?
32
+ # end
33
+ #
34
+ # @example AllowedMethod: ['foo?']
35
+ # # good
36
+ # def foo?
37
+ # return if condition
38
+ #
39
+ # do_something?
40
+ # end
41
+ #
42
+ # @example AllowedPattern: [/foo/]
43
+ # # good
44
+ # def foo?
45
+ # return if condition
46
+ #
47
+ # do_something?
48
+ # end
49
+ #
50
+ class ReturnNilInPredicateMethodDefinition < Base
51
+ extend AutoCorrector
52
+ include AllowedMethods
53
+ include AllowedPattern
54
+
55
+ MSG = 'Use `return false` instead of `%<prefer>s` in the predicate method.'
56
+
57
+ # @!method return_nil?(node)
58
+ def_node_matcher :return_nil?, <<~PATTERN
59
+ {(return) (return (nil))}
60
+ PATTERN
61
+
62
+ def on_def(node)
63
+ return unless node.predicate_method?
64
+ return if allowed_method?(node.method_name) || matches_allowed_pattern?(node.method_name)
65
+ return unless (body = node.body)
66
+
67
+ body.each_descendant(:return) do |return_node|
68
+ next unless return_nil?(return_node)
69
+
70
+ message = format(MSG, prefer: return_node.source)
71
+
72
+ add_offense(return_node, message: message) do |corrector|
73
+ corrector.replace(return_node, 'return false')
74
+ end
75
+ end
76
+ end
77
+ alias on_defs on_def
78
+ end
79
+ end
80
+ end
81
+ end
@@ -198,7 +198,7 @@ module RuboCop
198
198
  end
199
199
 
200
200
  def command_or_kernel_call?(name, node)
201
- return unless node.method?(name)
201
+ return false unless node.method?(name)
202
202
 
203
203
  node.command?(name) || kernel_call?(node, name)
204
204
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ # Checks for the use of `YAML.load`, `YAML.safe_load`, and `YAML.parse` with
7
+ # `File.read` argument.
8
+ #
9
+ # NOTE: `YAML.safe_load_file` was introduced in Ruby 3.0.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # YAML.load(File.read(path))
15
+ # YAML.parse(File.read(path))
16
+ #
17
+ # # good
18
+ # YAML.load_file(path)
19
+ # YAML.parse_file(path)
20
+ #
21
+ # # bad
22
+ # YAML.safe_load(File.read(path)) # Ruby 3.0 and newer
23
+ #
24
+ # # good
25
+ # YAML.safe_load_file(path) # Ruby 3.0 and newer
26
+ #
27
+ class YAMLFileRead < Base
28
+ extend AutoCorrector
29
+
30
+ MSG = 'Use `%<prefer>s` instead.'
31
+ RESTRICT_ON_SEND = %i[load safe_load parse].freeze
32
+
33
+ # @!method yaml_file_read?(node)
34
+ def_node_matcher :yaml_file_read?, <<~PATTERN
35
+ (send
36
+ (const {cbase nil?} :YAML) _
37
+ (send
38
+ (const {cbase nil?} :File) :read $_) $...)
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ return if node.method?(:safe_load) && target_ruby_version <= 2.7
43
+ return unless (file_path, rest_arguments = yaml_file_read?(node))
44
+
45
+ range = offense_range(node)
46
+ rest_arguments = if rest_arguments.empty?
47
+ ''
48
+ else
49
+ ", #{rest_arguments.map(&:source).join(', ')}"
50
+ end
51
+ prefer = "#{node.method_name}_file(#{file_path.source}#{rest_arguments})"
52
+
53
+ add_offense(range, message: format(MSG, prefer: prefer)) do |corrector|
54
+ corrector.replace(range, prefer)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def offense_range(node)
61
+ node.loc.selector.join(node.source_range.end)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -142,7 +142,7 @@ module RuboCop
142
142
  end
143
143
 
144
144
  def escape_string(string)
145
- string.inspect[1..-2].tap { |s| s.gsub!(/\\"/, '"') }
145
+ string.inspect[1..-2].tap { |s| s.gsub!('\\"', '"') }
146
146
  end
147
147
 
148
148
  def to_string_literal(string)
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Utils
6
+ # Helper to abstract complexity of building range pairs
7
+ # with octal escape reconstruction (needed for regexp_parser < 2.7).
8
+ class RegexpRanges
9
+ attr_reader :compound_token, :root
10
+
11
+ def initialize(root)
12
+ @root = root
13
+ @compound_token = []
14
+ end
15
+
16
+ def pairs
17
+ unless @pairs
18
+ @pairs = []
19
+ populate(root)
20
+ end
21
+
22
+ # If either bound is a compound the first one is an escape
23
+ # and that's all we need to work with.
24
+ # If there are any cops that wanted to operate on the compound
25
+ # expression we could wrap it with a facade class.
26
+ @pairs.map { |pair| pair.map(&:first) }
27
+ end
28
+
29
+ private
30
+
31
+ def populate(expr)
32
+ expressions = expr.expressions.to_a
33
+
34
+ until expressions.empty?
35
+ current = expressions.shift
36
+
37
+ if escaped_octal?(current)
38
+ compound_token << current
39
+ compound_token.concat(pop_octal_digits(expressions))
40
+ # If we have all the digits we can discard.
41
+ end
42
+
43
+ next unless current.type == :set
44
+
45
+ process_set(expressions, current)
46
+ compound_token.clear
47
+ end
48
+ end
49
+
50
+ def process_set(expressions, current)
51
+ case current.token
52
+ when :range
53
+ @pairs << compose_range(expressions, current)
54
+ when :character
55
+ # Child expressions may include the range we are looking for.
56
+ populate(current)
57
+ when :intersection
58
+ # Each child expression could have child expressions that lead to ranges.
59
+ current.expressions.each do |intersected|
60
+ populate(intersected)
61
+ end
62
+ end
63
+ end
64
+
65
+ def compose_range(expressions, current)
66
+ range_start, range_end = current.expressions
67
+ range_start = if compound_token.size.between?(1, 2) && octal_digit?(range_start.text)
68
+ compound_token.dup << range_start
69
+ else
70
+ [range_start]
71
+ end
72
+ range_end = [range_end]
73
+ range_end.concat(pop_octal_digits(expressions)) if escaped_octal?(range_end.first)
74
+ [range_start, range_end]
75
+ end
76
+
77
+ def escaped_octal?(expr)
78
+ expr.text =~ /^\\[0-7]$/
79
+ end
80
+
81
+ def octal_digit?(char)
82
+ ('0'..'7').cover?(char)
83
+ end
84
+
85
+ def pop_octal_digits(expressions)
86
+ digits = []
87
+
88
+ 2.times do
89
+ next unless (next_child = expressions.first)
90
+ next unless next_child.type == :literal && next_child.text =~ /^[0-7]$/
91
+
92
+ digits << expressions.shift
93
+ end
94
+
95
+ digits
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -198,7 +198,7 @@ class CopsDocumentationGenerator # rubocop:disable Metrics/ClassLength
198
198
  table = ['|===', "| #{header.join(' | ')}\n\n"].join("\n")
199
199
  marked_contents = content.map do |plain_content|
200
200
  # Escape `|` with backslash to prevent the regexp `|` is not used as a table separator.
201
- plain_content.map { |c| "| #{c.gsub(/\|/, '\|')}" }.join("\n")
201
+ plain_content.map { |c| "| #{c.gsub('|', '\|')}" }.join("\n")
202
202
  end
203
203
  table << marked_contents.join("\n\n")
204
204
  table << "\n|===\n"
@@ -68,7 +68,9 @@ module RuboCop
68
68
  return { body: expression } unless (q = quantifier)
69
69
 
70
70
  body = expression.adjust(end_pos: -q.text.length)
71
- q_loc = expression.with(begin_pos: body.end_pos)
71
+ q.origin = origin
72
+ q.source = source if q.respond_to?(:source=) # for regexp_parser 1.8
73
+ q_loc = q.expression
72
74
  { body: body, quantifier: q_loc }
73
75
  end
74
76
  end
@@ -86,6 +88,7 @@ module RuboCop
86
88
  end
87
89
  end
88
90
  ::Regexp::Expression::Base.include Expression::Base
91
+ ::Regexp::Expression::Quantifier.include Expression::Base
89
92
  ::Regexp::Expression::CharacterSet.include Expression::CharacterSet
90
93
  end
91
94
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This code is based on https://github.com/standardrb/standard.
5
+ #
6
+ # Copyright (c) 2023 Test Double, Inc.
7
+ #
8
+ # The MIT License (MIT)
9
+ #
10
+ # https://github.com/standardrb/standard/blob/main/LICENSE.txt
11
+ #
12
+ module RuboCop
13
+ module Lsp
14
+ # Log for Language Server Protocol of RuboCop.
15
+ # @api private
16
+ class Logger
17
+ def self.log(message)
18
+ warn("[server] #{message}")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,223 @@
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
+ # Routes for Language Server Protocol of RuboCop.
17
+ # @api private
18
+ class Routes
19
+ def self.handle(name, &block)
20
+ define_method("handle_#{name}", &block)
21
+ end
22
+
23
+ private_class_method :handle
24
+
25
+ def initialize(server)
26
+ @server = server
27
+
28
+ @text_cache = {}
29
+ end
30
+
31
+ def for(name)
32
+ name = "handle_#{name}"
33
+ return unless respond_to?(name)
34
+
35
+ method(name)
36
+ end
37
+
38
+ handle 'initialize' do |request|
39
+ @server.write(
40
+ id: request[:id],
41
+ result: LanguageServer::Protocol::Interface::InitializeResult.new(
42
+ capabilities: LanguageServer::Protocol::Interface::ServerCapabilities.new(
43
+ document_formatting_provider: true,
44
+ diagnostic_provider: true,
45
+ text_document_sync: LanguageServer::Protocol::Interface::TextDocumentSyncOptions.new(
46
+ change: LanguageServer::Protocol::Constant::TextDocumentSyncKind::FULL,
47
+ open_close: true
48
+ )
49
+ )
50
+ )
51
+ )
52
+ end
53
+
54
+ handle 'initialized' do |_request|
55
+ version = RuboCop::Version::STRING
56
+
57
+ Logger.log("RuboCop #{version} language server initialized, PID #{Process.pid}")
58
+ end
59
+
60
+ handle 'shutdown' do |request|
61
+ Logger.log('Client asked to shutdown RuboCop language server.')
62
+ @server.stop do
63
+ @server.write(id: request[:id], result: nil)
64
+ Logger.log('Exiting...')
65
+ end
66
+ end
67
+
68
+ handle 'textDocument/diagnostic' do |request|
69
+ doc = request[:params][:textDocument]
70
+ result = diagnostic(doc[:uri], doc[:text])
71
+ @server.write(result)
72
+ end
73
+
74
+ handle 'textDocument/didChange' do |request|
75
+ params = request[:params]
76
+ result = diagnostic(params[:textDocument][:uri], params[:contentChanges][0][:text])
77
+ @server.write(result)
78
+ end
79
+
80
+ handle 'textDocument/didOpen' do |request|
81
+ doc = request[:params][:textDocument]
82
+ result = diagnostic(doc[:uri], doc[:text])
83
+ @server.write(result)
84
+ end
85
+
86
+ handle 'textDocument/didClose' do |request|
87
+ @text_cache.delete(request.dig(:params, :textDocument, :uri))
88
+ end
89
+
90
+ handle 'textDocument/formatting' do |request|
91
+ uri = request[:params][:textDocument][:uri]
92
+ @server.write(id: request[:id], result: format_file(uri))
93
+ end
94
+
95
+ handle 'workspace/didChangeConfiguration' do |_request|
96
+ Logger.log('Ignoring workspace/didChangeConfiguration')
97
+ end
98
+
99
+ handle 'workspace/didChangeWatchedFiles' do |request|
100
+ changed = request[:params][:changes].any? do |change|
101
+ change[:uri].end_with?(RuboCop::ConfigFinder::DOTFILE)
102
+ end
103
+
104
+ if changed
105
+ Logger.log('Configuration file changed; restart required')
106
+ @server.stop
107
+ end
108
+ end
109
+
110
+ handle 'workspace/executeCommand' do |request|
111
+ if request[:params][:command] == 'rubocop.formatAutocorrects'
112
+ uri = request[:params][:arguments][0][:uri]
113
+ @server.write(
114
+ id: request[:id],
115
+ method: 'workspace/applyEdit',
116
+ params: {
117
+ label: 'Format with RuboCop autocorrects',
118
+ edit: {
119
+ changes: {
120
+ uri => format_file(uri)
121
+ }
122
+ }
123
+ }
124
+ )
125
+ else
126
+ handle_unsupported_method(request, request[:params][:command])
127
+ end
128
+ end
129
+
130
+ handle 'textDocument/willSave' do |_request|
131
+ # Nothing to do
132
+ end
133
+
134
+ handle 'textDocument/didSave' do |_request|
135
+ # Nothing to do
136
+ end
137
+
138
+ handle '$/cancelRequest' do |_request|
139
+ # Can't cancel anything because single-threaded
140
+ end
141
+
142
+ handle '$/setTrace' do |_request|
143
+ # No-op, we log everything
144
+ end
145
+
146
+ def handle_unsupported_method(request, method = request[:method])
147
+ @server.write(
148
+ id: request[:id],
149
+ error: LanguageServer::Protocol::Interface::ResponseError.new(
150
+ code: LanguageServer::Protocol::Constant::ErrorCodes::METHOD_NOT_FOUND,
151
+ message: "Unsupported Method: #{method}"
152
+ )
153
+ )
154
+ Logger.log("Unsupported Method: #{method}")
155
+ end
156
+
157
+ def handle_method_missing(request)
158
+ return unless request.key?(:id)
159
+
160
+ @server.write(id: request[:id], result: nil)
161
+ end
162
+
163
+ private
164
+
165
+ def format_file(file_uri)
166
+ unless (text = @text_cache[file_uri])
167
+ Logger.log("Format request arrived before text synchronized; skipping: `#{file_uri}'")
168
+
169
+ return []
170
+ end
171
+
172
+ new_text = @server.format(remove_file_protocol_from(file_uri), text)
173
+
174
+ return [] if new_text == text
175
+
176
+ [
177
+ newText: new_text,
178
+ range: {
179
+ start: { line: 0, character: 0 },
180
+ end: { line: text.count("\n") + 1, character: 0 }
181
+ }
182
+ ]
183
+ end
184
+
185
+ def diagnostic(file_uri, text)
186
+ @text_cache[file_uri] = text
187
+ offenses = @server.offenses(remove_file_protocol_from(file_uri), text)
188
+ diagnostics = offenses.map { |offense| to_diagnostic(offense) }
189
+
190
+ {
191
+ method: 'textDocument/publishDiagnostics',
192
+ params: {
193
+ uri: file_uri,
194
+ diagnostics: diagnostics
195
+ }
196
+ }
197
+ end
198
+
199
+ def remove_file_protocol_from(uri)
200
+ uri.delete_prefix('file://')
201
+ end
202
+
203
+ def to_diagnostic(offense)
204
+ code = offense[:cop_name]
205
+ message = offense[:message]
206
+ loc = offense[:location]
207
+ rubocop_severity = offense[:severity]
208
+ severity = Severity.find_by(rubocop_severity)
209
+
210
+ {
211
+ code: code, message: message, range: to_range(loc), severity: severity, source: 'rubocop'
212
+ }
213
+ end
214
+
215
+ def to_range(location)
216
+ {
217
+ start: { character: location[:start_column] - 1, line: location[:start_line] - 1 },
218
+ end: { character: location[:last_column] - 1, line: location[:last_line] - 1 }
219
+ }
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This code is based on https://github.com/standardrb/standard.
5
+ #
6
+ # Copyright (c) 2023 Test Double, Inc.
7
+ #
8
+ # The MIT License (MIT)
9
+ #
10
+ # https://github.com/standardrb/standard/blob/main/LICENSE.txt
11
+ #
12
+ module RuboCop
13
+ module Lsp
14
+ # Runtime for Language Server Protocol of RuboCop.
15
+ # @api private
16
+ class Runtime
17
+ def initialize(config_store)
18
+ @config_store = config_store
19
+ @logged_paths = []
20
+ end
21
+
22
+ # This abuses the `--stdin` option of rubocop and reads the formatted text
23
+ # from the `options[:stdin]` that rubocop mutates. This depends on
24
+ # `parallel: false` as well as the fact that RuboCop doesn't otherwise dup
25
+ # or reassign that options object. Risky business!
26
+ #
27
+ # Reassigning `options[:stdin]` is done here:
28
+ # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cop/team.rb#L131
29
+ # Printing `options[:stdin]`
30
+ # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/cli/command/execute_runner.rb#L95
31
+ # Setting `parallel: true` would break this here:
32
+ # https://github.com/rubocop/rubocop/blob/v1.52.0/lib/rubocop/runner.rb#L72
33
+ def format(path, text)
34
+ formatting_options = {
35
+ stdin: text, force_exclusion: true, autocorrect: true, safe_autocorrect: true
36
+ }
37
+
38
+ redirect_stdout { run_rubocop(formatting_options, path) }
39
+
40
+ formatting_options[:stdin]
41
+ end
42
+
43
+ def offenses(path, text)
44
+ diagnostic_options = {
45
+ stdin: text, force_exclusion: true, formatters: ['json'], format: 'json'
46
+ }
47
+
48
+ json = redirect_stdout { run_rubocop(diagnostic_options, path) }
49
+ results = JSON.parse(json, symbolize_names: true)
50
+
51
+ if results[:files].empty?
52
+ unless @logged_paths.include?(path)
53
+ Logger.log "Ignoring file, per configuration: #{path}"
54
+ @logged_paths << path
55
+ end
56
+ return []
57
+ end
58
+
59
+ results.dig(:files, 0, :offenses)
60
+ end
61
+
62
+ private
63
+
64
+ def redirect_stdout(&block)
65
+ stdout = StringIO.new
66
+
67
+ RuboCop::Server::Helper.redirect(stdout: stdout, &block)
68
+
69
+ stdout.string
70
+ end
71
+
72
+ def run_rubocop(options, path)
73
+ runner = RuboCop::Runner.new(options, @config_store)
74
+
75
+ runner.run([path])
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'language_server-protocol'
4
+ require_relative 'logger'
5
+ require_relative 'routes'
6
+ require_relative 'runtime'
7
+
8
+ #
9
+ # This code is based on https://github.com/standardrb/standard.
10
+ #
11
+ # Copyright (c) 2023 Test Double, Inc.
12
+ #
13
+ # The MIT License (MIT)
14
+ #
15
+ # https://github.com/standardrb/standard/blob/main/LICENSE.txt
16
+ #
17
+ module RuboCop
18
+ module Lsp
19
+ # Language Server Protocol of RuboCop.
20
+ # @api private
21
+ class Server
22
+ def initialize(config_store)
23
+ @reader = LanguageServer::Protocol::Transport::Io::Reader.new($stdin)
24
+ @writer = LanguageServer::Protocol::Transport::Io::Writer.new($stdout)
25
+ @runtime = RuboCop::Lsp::Runtime.new(config_store)
26
+ @routes = Routes.new(self)
27
+ end
28
+
29
+ def start
30
+ @reader.read do |request|
31
+ if !request.key?(:method)
32
+ @routes.handle_method_missing(request)
33
+ elsif (route = @routes.for(request[:method]))
34
+ route.call(request)
35
+ else
36
+ @routes.handle_unsupported_method(request)
37
+ end
38
+ rescue StandardError => e
39
+ log("Error #{e.class} #{e.message[0..100]}")
40
+ log(e.backtrace.inspect)
41
+ end
42
+ end
43
+
44
+ def write(response)
45
+ @writer.write(response)
46
+ end
47
+
48
+ def format(path, text)
49
+ @runtime.format(path, text)
50
+ end
51
+
52
+ def offenses(path, text)
53
+ @runtime.offenses(path, text)
54
+ end
55
+
56
+ def stop(&block)
57
+ at_exit(&block) if block
58
+ exit
59
+ end
60
+ end
61
+ end
62
+ end