rubocop-lsp 0.1.2 → 0.1.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e39f6df6dfef436bd3d67d745e2b300e9eead65c37ddeb8d28e05ac916d6ce5
4
- data.tar.gz: d537d77e74bef4a92db2a71a05fd4e1b8c4832f4ca40a9a524b94433e9a1f204
3
+ metadata.gz: ac8fd19fc238910e86f2c8abfa03012be286ff2af399b44ea4b5fdda63980bc8
4
+ data.tar.gz: 5c4c6707a1ab16c7fd16fed74befca83d74688665f08d89d95fa5ed15aba986e
5
5
  SHA512:
6
- metadata.gz: 5de228c6fa093e2835e58686a6452d04971d5808a84ed15b2cae42a2ccbecc126e0ca115695b0c82075d455b2cf59778d35a3b6ca5cc44d8ba0f5c2e09fbb6b5
7
- data.tar.gz: 33eeba13254dc0ee9617ef1b1d8f559da3a91ac3e9c9daeafc528fe38bdafab90975260ed7b04a8a4d22771b097c381574ef0398d3219aa48fd125bd47319423
6
+ metadata.gz: 657ba77190d8707641aa2163d75342f150a021f1a6575862335b5a5d948c27801e3d1827353a5ce2db008a54932f9910bedc11ff97b66e68a2fbe58a9fbc18fc
7
+ data.tar.gz: 941e7c81ce62851f4c5b7afe18028cb0033168f4d9ef99f3e902695d2ea5ed2352f7818a9e5e1908f1b3a8b7c40250b0c0bed5b9e1f5361b7abc3a544353b028
data/.rubocop.yml CHANGED
@@ -4,3 +4,6 @@ inherit_gem:
4
4
  AllCops:
5
5
  TargetRubyVersion: 2.6
6
6
  SuggestExtensions: false
7
+
8
+ Layout/HashAlignment:
9
+ EnforcedColonStyle: table
@@ -1,3 +1,4 @@
1
1
  {
2
- "rubocop-lsp.trace.server": "verbose"
2
+ // "rubocop-lsp.trace.server": "verbose",
3
+ "editor.formatOnSave": true
3
4
  }
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubocop-lsp (0.1.2)
4
+ rubocop-lsp (0.1.6)
5
5
  language_server-protocol (>= 3.16)
6
6
  rubocop (>= 1.0)
7
7
 
@@ -9,75 +9,66 @@ require "rubocop/lsp/handler"
9
9
  module Rubocop
10
10
  module Lsp
11
11
  module Cli
12
- Interface = LanguageServer::Protocol::Interface
13
- Constant = LanguageServer::Protocol::Constant
14
-
15
12
  def self.start(_argv)
16
13
  handler = Handler.new
17
14
 
18
15
  handler.config do
19
16
  on("initialize") do
20
- DiagnosticStore.clear_diagnostics
21
- Interface::InitializeResult.new(
22
- capabilities: Interface::ServerCapabilities.new(
23
- text_document_sync: Interface::TextDocumentSyncOptions.new(
24
- change: Constant::TextDocumentSyncKind::FULL,
25
- open_close: true,
26
- ),
27
- document_formatting_provider: true,
28
- code_action_provider: true
29
- ),
30
- )
17
+ store.clear
18
+
19
+ respond_with_capabilities
31
20
  end
32
21
 
33
22
  on("shutdown") do
34
- DiagnosticStore.clear_diagnostics
23
+ store.clear
24
+
35
25
  nil
36
26
  end
37
27
 
38
28
  on("textDocument/didChange") do |request|
39
- uri = request[:params][:textDocument][:uri]
40
- text = request[:params][:contentChanges][0][:text]
29
+ uri = request.dig(:params, :textDocument, :uri)
30
+ text = request.dig(:params, :contentChanges, 0, :text)
41
31
 
42
- send_diagnostics(uri: uri, text: text)
32
+ collector = collect_diagnostics(uri: uri, text: text)
33
+ send_diagnostics(collector)
43
34
 
44
35
  nil
45
36
  end
46
37
 
47
38
  on("textDocument/didOpen") do |request|
48
- uri = request[:params][:textDocument][:uri]
49
- text = request[:params][:textDocument][:text]
39
+ uri = request.dig(:params, :textDocument, :uri)
40
+ text = request.dig(:params, :textDocument, :text)
50
41
 
51
- send_diagnostics(uri: uri, text: text)
42
+ collector = collect_diagnostics(uri: uri, text: text)
43
+ send_diagnostics(collector)
52
44
 
53
45
  nil
54
46
  end
55
47
 
56
48
  on("textDocument/didClose") do |request|
57
- uri = request[:params][:textDocument][:uri]
49
+ uri = request.dig(:params, :textDocument, :uri)
58
50
 
59
- DiagnosticStore.remove_diagnostics_for(uri)
60
-
61
- send_diagnostics(uri: uri, diagnostics: [])
51
+ clear_diagnostics(uri: uri)
62
52
 
63
53
  nil
64
54
  end
65
55
 
66
56
  on("textDocument/formatting") do |request|
67
- uri = request[:params][:textDocument][:uri]
68
- file = CGI.unescape(URI.parse(uri).path)
69
-
70
- collector = DiagnosticCollector.new(file: file, text: File.binread(file))
71
- collector.run
57
+ uri = request.dig(:params, :textDocument, :uri)
72
58
 
73
- collector.text_edits_from_formatting
59
+ collector = collect_diagnostics(uri: uri, text: store.text_for(uri))
60
+ send_text_edits(collector)
74
61
  end
75
62
 
76
63
  on("textDocument/codeAction") do |request|
77
- uri = request[:params][:textDocument][:uri]
78
- range = request[:params][:range]
64
+ uri = request.dig(:params, :textDocument, :uri)
65
+ range = request.dig(:params, :range)
66
+ start_line = range.dig(:start, :line)
67
+ end_line = range.dig(:end, :line)
68
+
69
+ line_range = Range.new(start_line, end_line)
79
70
 
80
- DiagnosticStore.code_actions_for(uri, range)
71
+ send_code_actions(uri, line_range)
81
72
  end
82
73
  end
83
74
 
@@ -1,82 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class DiagnosticCollector < RuboCop::Formatter::BaseFormatter
4
- Interface = LanguageServer::Protocol::Interface
5
- Constant = LanguageServer::Protocol::Constant
3
+ require "rubocop/lsp/model/diagnostic_collection"
6
4
 
7
- attr_reader :file, :text, :formatted_text, :diagnostics
5
+ class DiagnosticCollector < RuboCop::Runner
6
+ RUBOCOP_FLAGS = [
7
+ "--stderr", # Print any output to stderr so that our stdout does not get polluted
8
+ "--format",
9
+ "quiet", # Supress any progress output by setting the formatter to `quiet`
10
+ "--auto-correct", # Apply the autocorrects on the supplied buffer
11
+ ]
8
12
 
9
- def initialize(file:, text:)
10
- super({}, nil)
11
- @file = file
13
+ attr_reader :uri, :file, :text, :formatted_text, :diagnostics
14
+
15
+ def initialize(uri:, text:)
16
+ @uri = uri
17
+ @file = CGI.unescape(URI.parse(uri).path)
12
18
  @text = text
13
19
  @formatted_text = nil
14
- end
15
-
16
- def run
17
- config_store = ::RuboCop::ConfigStore.new
18
- options, paths = ::RuboCop::Options.new.parse(["--stderr", "--auto-correct"])
19
- # config_store.options_config = options[:config] if options[:config]
20
- options[:stdin] = text
21
- paths << file
22
- runner = ::RuboCop::Runner.new(options, config_store)
23
- runner.send(:formatter_set).clear.push(self)
24
- runner.run(paths)
25
- @formatted_text = options[:stdin]
26
- end
27
-
28
- def file_finished(_file, offenses)
29
- @diagnostics = offenses.map do |offense|
30
- diagnostic_from_offense(offense)
31
- end
32
- end
33
20
 
34
- def formatting_text_edits
35
- return unless formatted_text
36
-
37
- [
38
- Interface::TextEdit.new(
39
- range: Interface::Range.new(
40
- start: Interface::Position.new(line: 0, character: 0),
41
- end: Interface::Position.new(line: text.size, character: text.size)
42
- ),
43
- new_text: formatted_text
44
- ),
45
- ]
46
- end
47
-
48
- private
49
-
50
- def diagnostic_from_offense(offense)
51
- Interface::Diagnostic.new(
52
- message: offense.message,
53
- severity: Constant::DiagnosticSeverity::INFORMATION,
54
- range: Interface::Range.new(
55
- start: Interface::Position.new(line: offense.line - 1, character: offense.column),
56
- end: Interface::Position.new(line: offense.last_line - 1, character: offense.last_column)
57
- ),
58
- data: {
59
- cop_name: offense.cop_name,
60
- edits: replacements_from_offense(offense),
61
- }
21
+ super(
22
+ ::RuboCop::Options.new.parse(RUBOCOP_FLAGS).first,
23
+ ::RuboCop::ConfigStore.new
62
24
  )
63
25
  end
64
26
 
65
- def replacements_from_offense(offense)
66
- return unless offense.correctable?
27
+ def run
28
+ # Send text via "stdin".
29
+ # RuboCop reads all of stdin into the "stdin" option, when `--stdin`
30
+ # flag is supplied
31
+ @options[:stdin] = text
32
+
33
+ # Invoke the actual run method with just this file in `paths`
34
+ super([file])
67
35
 
68
- offense.corrector.as_replacements.map do |range, replacement|
69
- edit_from_replacement(range, replacement)
70
- end
36
+ # RuboCop applies autocorrections to the "stdin" option,
37
+ # so read that into the formatted text attribute
38
+ @formatted_text = @options[:stdin]
71
39
  end
72
40
 
73
- def edit_from_replacement(range, replacement)
74
- Interface::TextEdit.new(
75
- range: Interface::Range.new(
76
- start: Interface::Position.new(line: range.line - 1, character: range.column),
77
- end: Interface::Position.new(line: range.last_line - 1, character: range.last_column)
78
- ),
79
- new_text: replacement
80
- )
41
+ def file_finished(_file, offenses)
42
+ @diagnostics = Model::DiagnosticCollection.new(uri, offenses)
81
43
  end
82
44
  end
@@ -1,18 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rubocop/lsp/diagnostic_store"
3
+ require "rubocop/lsp/state_store"
4
4
  require "rubocop/lsp/diagnostic_collector"
5
5
 
6
6
  class Handler
7
7
  Interface = LanguageServer::Protocol::Interface
8
8
  Constant = LanguageServer::Protocol::Constant
9
9
 
10
- attr_reader :writer, :reader
11
-
12
10
  def initialize
13
11
  @writer = LanguageServer::Protocol::Transport::Stdio::Writer.new
14
12
  @reader = LanguageServer::Protocol::Transport::Stdio::Reader.new
15
13
  @handlers = {}
14
+ @store = StateStore.new
16
15
  end
17
16
 
18
17
  def config(&blk)
@@ -27,6 +26,8 @@ class Handler
27
26
 
28
27
  private
29
28
 
29
+ attr_reader :writer, :reader, :store
30
+
30
31
  def on(message, &blk)
31
32
  @handlers[message.to_s] = blk
32
33
  end
@@ -51,22 +52,69 @@ class Handler
51
52
  writer.write(method: method, params: params)
52
53
  end
53
54
 
54
- def send_diagnostics(uri:, text: nil, diagnostics: [])
55
- diagnostics = collect_diagnostics(uri: uri, text: text) if text
55
+ def respond_with_capabilities
56
+ Interface::InitializeResult.new(
57
+ capabilities: Interface::ServerCapabilities.new(
58
+ text_document_sync: Interface::TextDocumentSyncOptions.new(
59
+ change: Constant::TextDocumentSyncKind::FULL,
60
+ open_close: true,
61
+ ),
62
+ document_formatting_provider: true,
63
+ code_action_provider: true
64
+ ),
65
+ )
66
+ end
56
67
 
68
+ def notify_diagnostics(uri:, diagnostics_response:)
57
69
  notify(
58
70
  method: "textDocument/publishDiagnostics",
59
71
  params: Interface::PublishDiagnosticsParams.new(
60
72
  uri: uri,
61
- diagnostics: diagnostics
73
+ diagnostics: diagnostics_response
62
74
  )
63
75
  )
64
76
  end
65
77
 
78
+ def clear_diagnostics(uri:)
79
+ store.delete(uri)
80
+
81
+ notify_diagnostics(uri: uri, diagnostics_response: [])
82
+ end
83
+
84
+ def send_diagnostics(collector)
85
+ notify_diagnostics(
86
+ uri: collector.uri,
87
+ diagnostics_response: collector.diagnostics.response
88
+ )
89
+ end
90
+
91
+ def send_code_actions(uri, line_range)
92
+ store.code_actions_for(uri, line_range)
93
+ end
94
+
95
+ def send_text_edits(collector)
96
+ return unless collector.formatted_text
97
+
98
+ [
99
+ Interface::TextEdit.new(
100
+ range: Interface::Range.new(
101
+ start: Interface::Position.new(line: 0, character: 0),
102
+ end: Interface::Position.new(
103
+ line: collector.text.size,
104
+ character: collector.text.size
105
+ )
106
+ ),
107
+ new_text: collector.formatted_text
108
+ ),
109
+ ]
110
+ end
111
+
66
112
  def collect_diagnostics(uri:, text:)
67
- file = CGI.unescape(URI.parse(uri).path)
68
- collector = DiagnosticCollector.new(file: file, text: text)
113
+ collector = DiagnosticCollector.new(uri: uri, text: text)
69
114
  collector.run
70
- DiagnosticStore.set_diagnostics_for(uri, collector.diagnostics)
115
+
116
+ store.set(uri: collector.uri, text: collector.text, diagnostics: collector.diagnostics)
117
+
118
+ collector
71
119
  end
72
120
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Model
4
+ class Diagnostic
5
+ attr_reader :badge, :message, :start, :end, :replacements
6
+
7
+ DOCS_URLS = {
8
+ "Bundler": "https://docs.rubocop.org/rubocop/cops_bundler.html#%{anchor}",
9
+ "Gemspec": "https://docs.rubocop.org/rubocop/cops_gemspec.html#%{anchor}",
10
+ "Layout": "https://docs.rubocop.org/rubocop/cops_layout.html#%{anchor}",
11
+ "Lint": "https://docs.rubocop.org/rubocop/cops_lint.html#%{anchor}",
12
+ "Metrics": "https://docs.rubocop.org/rubocop/cops_metrics.html#%{anchor}",
13
+ "Migration": "https://docs.rubocop.org/rubocop/cops_migration.html#%{anchor}",
14
+ "Naming": "https://docs.rubocop.org/rubocop/cops_naming.html#%{anchor}",
15
+ "Security": "https://docs.rubocop.org/rubocop/cops_security.html#%{anchor}",
16
+ "Style": "https://docs.rubocop.org/rubocop/cops_style.html#%{anchor}",
17
+ "Minitest": "https://docs.rubocop.org/rubocop-minitest/cops_minitest.html#%{anchor}",
18
+ "Performance": "https://docs.rubocop.org/rubocop-performance/cops_performance.html#%{anchor}",
19
+ "Rails": "https://docs.rubocop.org/rubocop-rails/cops_rails.html#%{anchor}",
20
+ "RSpec": "https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#%{anchor}",
21
+ "RSpec/Capybara": "https://docs.rubocop.org/rubocop-rspec/cops_rspec/capybara.html#%{anchor}",
22
+ "RSpec/FactoryBot": "https://docs.rubocop.org/rubocop-rspec/cops_rspec/factorybot.html#%{anchor}",
23
+ "RSpec/Rails": "https://docs.rubocop.org/rubocop-rspec/cops_rspec/rails.html#%{anchor}",
24
+ "Sorbet": "https://github.com/Shopify/rubocop-sorbet/blob/master/manual/cops_sorbet.md#%{anchor}",
25
+ }
26
+
27
+ def initialize(offense)
28
+ @badge = RuboCop::Cop::Badge.parse(offense.cop_name)
29
+ @message = offense.message
30
+ @start = Interface::Position.new(line: offense.line - 1, character: offense.column)
31
+ @end = Interface::Position.new(line: offense.last_line - 1, character: offense.last_column)
32
+ @replacements = replacements_from_offense(offense)
33
+ end
34
+
35
+ def correctable?
36
+ !!@replacements
37
+ end
38
+
39
+ def cop_name
40
+ @cop_name ||= badge.to_s
41
+ end
42
+
43
+ def diagnostic_response
44
+ @diagnostic_response ||= Interface::Diagnostic.new(
45
+ message: message,
46
+ "source": "RuboCop",
47
+ code: cop_name,
48
+ code_description: Interface::CodeDescription.new(
49
+ href: doc_url
50
+ ),
51
+ severity: Constant::DiagnosticSeverity::INFORMATION,
52
+ range: Interface::Range.new(
53
+ start: start,
54
+ end: self.end
55
+ )
56
+ )
57
+ end
58
+
59
+ private
60
+
61
+ def doc_url
62
+ @doc_url ||= begin
63
+ anchor = cop_name.downcase.gsub(%r{/}, "")
64
+ docs_url_template = DOCS_URLS[badge.department]
65
+
66
+ return unless docs_url_template
67
+
68
+ format(docs_url_template, anchor: anchor)
69
+ end
70
+ end
71
+
72
+ def replacements_from_offense(offense)
73
+ return unless offense.correctable?
74
+
75
+ offense.corrector.as_replacements.map do |range, replacement|
76
+ edit_from_replacement(range, replacement)
77
+ end
78
+ end
79
+
80
+ def edit_from_replacement(range, replacement)
81
+ Interface::TextEdit.new(
82
+ range: Interface::Range.new(
83
+ start: Interface::Position.new(line: range.line - 1, character: range.column),
84
+ end: Interface::Position.new(line: range.last_line - 1, character: range.last_column)
85
+ ),
86
+ new_text: replacement
87
+ )
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop/lsp/model/diagnostic"
4
+
5
+ module Model
6
+ Interface = LanguageServer::Protocol::Interface
7
+ Constant = LanguageServer::Protocol::Constant
8
+
9
+ class DiagnosticCollection
10
+ attr_reader :uri, :diagnostics
11
+
12
+ def initialize(uri, offenses)
13
+ @uri = uri
14
+ @diagnostics = offenses.map do |offense|
15
+ Diagnostic.new(offense)
16
+ end
17
+ end
18
+
19
+ def response
20
+ diagnostics.map(&:diagnostic_response)
21
+ end
22
+
23
+ def correctable_diagnostics
24
+ @correctable_diagnostics ||= diagnostics.select(&:correctable?)
25
+ end
26
+
27
+ def code_actions(line_range)
28
+ line_diagnostics = correctable_diagnostics.select do |diagnostic|
29
+ line_range.include?(diagnostic.start.line)
30
+ end
31
+
32
+ actions = line_diagnostics.map do |diagnostic|
33
+ code_action_from(
34
+ diagnostic,
35
+ title: "Autocorrect `#{diagnostic.cop_name}`",
36
+ kind: Constant::CodeActionKind::QUICK_FIX
37
+ )
38
+ end
39
+
40
+ if actions.any?
41
+ actions << code_action_from(
42
+ line_diagnostics,
43
+ title: "Autocorrect all offenses on line",
44
+ kind: "rubocop.fix"
45
+ )
46
+ end
47
+
48
+ actions
49
+ end
50
+
51
+ private
52
+
53
+ def code_action_from(diagnostics, title:, kind:)
54
+ diagnostics = Array(diagnostics)
55
+
56
+ Interface::CodeAction.new(
57
+ title: title,
58
+ kind: kind,
59
+ edit: Interface::WorkspaceEdit.new(
60
+ document_changes: [
61
+ Interface::TextDocumentEdit.new(
62
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
63
+ uri: uri,
64
+ version: nil
65
+ ),
66
+ edits: diagnostics.flat_map(&:replacements)
67
+ ),
68
+ ]
69
+ ),
70
+ is_preferred: true,
71
+ )
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class State
4
+ attr_reader :uri, :text, :diagnostics
5
+
6
+ def initialize(uri:, text:, diagnostics: [])
7
+ @uri = uri
8
+ @text = text
9
+ @diagnostics = diagnostics
10
+ end
11
+
12
+ def code_actions(line_range)
13
+ diagnostics.code_actions(line_range)
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop/lsp/state"
4
+
5
+ class StateStore
6
+ def initialize
7
+ @state_map = {}
8
+ end
9
+
10
+ def clear
11
+ @state_map = {}
12
+ end
13
+
14
+ def delete(uri)
15
+ @state_map.delete(uri)
16
+ []
17
+ end
18
+
19
+ def set(uri:, text:, diagnostics: [])
20
+ @state_map[uri] = State.new(uri: uri, text: text, diagnostics: diagnostics || [])
21
+ end
22
+
23
+ def get(uri)
24
+ @state_map[uri]
25
+ end
26
+
27
+ def text_for(uri)
28
+ state = get(uri)
29
+
30
+ return state.text if state
31
+
32
+ file = CGI.unescape(URI.parse(uri).path)
33
+ File.binread(file)
34
+ end
35
+
36
+ def code_actions_for(uri, line_range)
37
+ state = get(uri)
38
+
39
+ return [] unless state
40
+
41
+ state.code_actions(line_range)
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :state_map
47
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rubocop
4
4
  module Lsp
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.6"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-22 00:00:00.000000000 Z
11
+ date: 2021-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -60,8 +60,11 @@ files:
60
60
  - lib/rubocop/lsp.rb
61
61
  - lib/rubocop/lsp/cli.rb
62
62
  - lib/rubocop/lsp/diagnostic_collector.rb
63
- - lib/rubocop/lsp/diagnostic_store.rb
64
63
  - lib/rubocop/lsp/handler.rb
64
+ - lib/rubocop/lsp/model/diagnostic.rb
65
+ - lib/rubocop/lsp/model/diagnostic_collection.rb
66
+ - lib/rubocop/lsp/state.rb
67
+ - lib/rubocop/lsp/state_store.rb
65
68
  - lib/rubocop/lsp/version.rb
66
69
  - rubocop-lsp.gemspec
67
70
  homepage: https://github.com/Shopify/rubocop-lsp
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DiagnosticStore
4
- @diagnostic_map = {}
5
-
6
- def self.clear_diagnostics
7
- @diagnostic_map = {}
8
- end
9
-
10
- def self.remove_diagnostics_for(uri)
11
- @diagnostic_map.delete(uri)
12
- []
13
- end
14
-
15
- def self.set_diagnostics_for(uri, diagnostics)
16
- @diagnostic_map[uri] = diagnostics
17
- end
18
-
19
- def self.diagnostics_for(uri)
20
- @diagnostic_map[uri] || []
21
- end
22
-
23
- def self.correctable_diagnostics_for(uri)
24
- diagnostics_for(uri).reject do |diagnostic|
25
- diagnostic.data[:edits].nil?
26
- end
27
- end
28
-
29
- def self.code_actions_for(uri, range)
30
- line_range = Range.new(range[:start][:line], range[:end][:line])
31
-
32
- file_diagnostics = correctable_diagnostics_for(uri)
33
- line_diagnostics = file_diagnostics.select { |diagnostic| line_range.include?(diagnostic.range.start.line) }
34
- actions = line_diagnostics.map { |diagnostic| code_action_from(uri, diagnostic) }
35
-
36
- unless actions.empty?
37
- actions << code_action_from_diagnostics(uri, line_diagnostics, title: "Autocorrect all offenses on line")
38
- end
39
-
40
- actions
41
- end
42
-
43
- def self.code_action_from_diagnostics(uri, diagnostics, title:)
44
- diagnostics = diagnostics.sort_by do |diagnostic|
45
- [diagnostic.range.start.line, diagnostic.range.start.character]
46
- end
47
-
48
- LanguageServer::Protocol::Interface::CodeAction.new(
49
- title: title,
50
- kind: "rubocop.fix",
51
- edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
52
- document_changes: [
53
- LanguageServer::Protocol::Interface::TextDocumentEdit.new(
54
- text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
55
- uri: uri,
56
- version: nil
57
- ),
58
- edits: diagnostics.flat_map { |d| d.data[:edits] }
59
- ),
60
- ]
61
- ),
62
- is_preferred: true,
63
- )
64
- end
65
-
66
- def self.code_action_from(uri, diagnostic)
67
- LanguageServer::Protocol::Interface::CodeAction.new(
68
- title: "Autocorrect `#{diagnostic.data[:cop_name]}`",
69
- kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
70
- edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
71
- document_changes: [
72
- LanguageServer::Protocol::Interface::TextDocumentEdit.new(
73
- text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
74
- uri: uri,
75
- version: nil
76
- ),
77
- edits: diagnostic.data[:edits]
78
- ),
79
- ]
80
- ),
81
- is_preferred: true,
82
- )
83
- end
84
- end