rubocop-lsp 0.1.2 → 0.1.6

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