rubocop 1.52.1 → 1.53.1

Sign up to get free protection for your applications and to get access to all the features.
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