rubocop 1.52.0 → 1.53.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 (92) 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/cop_description.rb +32 -8
  11. data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +3 -3
  12. data/lib/rubocop/cop/layout/class_structure.rb +7 -0
  13. data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -2
  14. data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
  15. data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
  16. data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
  17. data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
  18. data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
  19. data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
  20. data/lib/rubocop/cop/lint/ambiguous_block_association.rb +2 -1
  21. data/lib/rubocop/cop/lint/debugger.rb +1 -1
  22. data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
  23. data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
  24. data/lib/rubocop/cop/lint/erb_new_arguments.rb +1 -2
  25. data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
  26. data/lib/rubocop/cop/lint/identity_comparison.rb +0 -1
  27. data/lib/rubocop/cop/lint/incompatible_io_select_with_fiber_scheduler.rb +1 -2
  28. data/lib/rubocop/cop/lint/inherit_exception.rb +3 -1
  29. data/lib/rubocop/cop/lint/missing_super.rb +31 -5
  30. data/lib/rubocop/cop/lint/mixed_case_range.rb +109 -0
  31. data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
  32. data/lib/rubocop/cop/lint/ordered_magic_comments.rb +0 -1
  33. data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +120 -0
  34. data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
  35. data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +2 -2
  36. data/lib/rubocop/cop/lint/send_with_mixin_argument.rb +1 -2
  37. data/lib/rubocop/cop/lint/shadowed_exception.rb +5 -11
  38. data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
  39. data/lib/rubocop/cop/lint/useless_assignment.rb +2 -1
  40. data/lib/rubocop/cop/lint/void.rb +1 -1
  41. data/lib/rubocop/cop/metrics/utils/abc_size_calculator.rb +1 -2
  42. data/lib/rubocop/cop/migration/department_name.rb +2 -2
  43. data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
  44. data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
  45. data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
  46. data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
  47. data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
  48. data/lib/rubocop/cop/style/begin_block.rb +1 -2
  49. data/lib/rubocop/cop/style/block_comments.rb +1 -1
  50. data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
  51. data/lib/rubocop/cop/style/class_equality_comparison.rb +17 -39
  52. data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
  53. data/lib/rubocop/cop/style/dir.rb +1 -1
  54. data/lib/rubocop/cop/style/dir_empty.rb +8 -14
  55. data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
  56. data/lib/rubocop/cop/style/eval_with_location.rb +3 -3
  57. data/lib/rubocop/cop/style/file_read.rb +2 -2
  58. data/lib/rubocop/cop/style/hash_transform_keys.rb +2 -2
  59. data/lib/rubocop/cop/style/hash_transform_values.rb +2 -2
  60. data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
  61. data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
  62. data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
  63. data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
  64. data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
  65. data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
  66. data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
  67. data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
  68. data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
  69. data/lib/rubocop/cop/style/redundant_regexp_argument.rb +86 -0
  70. data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
  71. data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
  72. data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
  73. data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
  74. data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
  75. data/lib/rubocop/cop/style/signal_exception.rb +1 -1
  76. data/lib/rubocop/cop/style/sole_nested_conditional.rb +3 -1
  77. data/lib/rubocop/cop/style/special_global_vars.rb +1 -2
  78. data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
  79. data/lib/rubocop/cop/util.rb +1 -1
  80. data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
  81. data/lib/rubocop/cop/variable_force/assignment.rb +14 -1
  82. data/lib/rubocop/cops_documentation_generator.rb +1 -1
  83. data/lib/rubocop/ext/regexp_parser.rb +4 -1
  84. data/lib/rubocop/lsp/logger.rb +22 -0
  85. data/lib/rubocop/lsp/routes.rb +223 -0
  86. data/lib/rubocop/lsp/runtime.rb +79 -0
  87. data/lib/rubocop/lsp/server.rb +62 -0
  88. data/lib/rubocop/lsp/severity.rb +27 -0
  89. data/lib/rubocop/options.rb +11 -1
  90. data/lib/rubocop/version.rb +1 -1
  91. data/lib/rubocop.rb +8 -0
  92. metadata +36 -9
@@ -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
@@ -69,6 +69,12 @@ module RuboCop
69
69
  meta_assignment_node.type == REST_ASSIGNMENT_TYPE
70
70
  end
71
71
 
72
+ def for_assignment?
73
+ return false unless meta_assignment_node
74
+
75
+ meta_assignment_node.for_type?
76
+ end
77
+
72
78
  def operator
73
79
  assignment_node = meta_assignment_node || @node
74
80
  assignment_node.loc.operator.source
@@ -78,7 +84,8 @@ module RuboCop
78
84
  unless instance_variable_defined?(:@meta_assignment_node)
79
85
  @meta_assignment_node = operator_assignment_node ||
80
86
  multiple_assignment_node ||
81
- rest_assignment_node
87
+ rest_assignment_node ||
88
+ for_assignment_node
82
89
  end
83
90
 
84
91
  @meta_assignment_node
@@ -109,6 +116,12 @@ module RuboCop
109
116
 
110
117
  node.parent
111
118
  end
119
+
120
+ def for_assignment_node
121
+ return nil unless node.parent&.for_type?
122
+
123
+ node.parent
124
+ end
112
125
  end
113
126
  end
114
127
  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
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Lsp
5
+ # Severity for Language Server Protocol of RuboCop.
6
+ # @api private
7
+ class Severity
8
+ SEVERITIES = {
9
+ fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
10
+ error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
11
+ warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
12
+ convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
13
+ refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::HINT,
14
+ info: LanguageServer::Protocol::Constant::DiagnosticSeverity::HINT
15
+ }.freeze
16
+
17
+ def self.find_by(rubocop_severity)
18
+ if (severity = SEVERITIES[rubocop_severity.to_sym])
19
+ return severity
20
+ end
21
+
22
+ Logger.log("Unknown severity: #{rubocop_severity}")
23
+ LanguageServer::Protocol::Constant::DiagnosticSeverity::HINT
24
+ end
25
+ end
26
+ end
27
+ end
@@ -16,7 +16,7 @@ module RuboCop
16
16
  'root of the project. RuboCop will use this path to determine which ' \
17
17
  'cops are enabled (via eg. Include/Exclude), and so that certain cops ' \
18
18
  'like Naming/FileName can be checked.'
19
- EXITING_OPTIONS = %i[version verbose_version show_cops show_docs_url].freeze
19
+ EXITING_OPTIONS = %i[version verbose_version show_cops show_docs_url lsp].freeze
20
20
  DEFAULT_MAXIMUM_EXCLUSION_ITEMS = 15
21
21
 
22
22
  def initialize
@@ -49,12 +49,14 @@ module RuboCop
49
49
 
50
50
  private
51
51
 
52
+ # rubocop:disable Metrics/AbcSize
52
53
  def define_options
53
54
  OptionParser.new do |opts|
54
55
  opts.banner = rainbow.wrap('Usage: rubocop [options] [file1, file2, ...]').bright
55
56
 
56
57
  add_check_options(opts)
57
58
  add_cache_options(opts)
59
+ add_lsp_option(opts)
58
60
  add_server_options(opts)
59
61
  add_output_options(opts)
60
62
  add_autocorrection_options(opts)
@@ -66,6 +68,7 @@ module RuboCop
66
68
  add_profile_options(opts) if RUBY_ENGINE == 'ruby' && !Platform.windows?
67
69
  end
68
70
  end
71
+ # rubocop:enable Metrics/AbcSize
69
72
 
70
73
  def add_check_options(opts) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
71
74
  section(opts, 'Basic Options') do # rubocop:disable Metrics/BlockLength
@@ -205,6 +208,12 @@ module RuboCop
205
208
  end
206
209
  end
207
210
 
211
+ def add_lsp_option(opts)
212
+ section(opts, 'LSP Option') do
213
+ option(opts, '--lsp')
214
+ end
215
+ end
216
+
208
217
  def add_server_options(opts)
209
218
  section(opts, 'Server Options') do
210
219
  option(opts, '--[no-]server')
@@ -620,6 +629,7 @@ module RuboCop
620
629
  stop_server: 'Stop server process.',
621
630
  server_status: 'Show server status.',
622
631
  no_detach: 'Run the server process in the foreground.',
632
+ lsp: 'Start a language server listening on STDIN.',
623
633
  raise_cop_error: ['Raise cop-related errors with cause and location.',
624
634
  'This is used to prevent cops from failing silently.',
625
635
  'Default is false.'],
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  # This module holds the RuboCop version information.
5
5
  module Version
6
- STRING = '1.52.0'
6
+ STRING = '1.53.0'
7
7
 
8
8
  MSG = '%<version>s (using Parser %<parser_version>s, ' \
9
9
  'rubocop-ast %<rubocop_ast_version>s, ' \