rubocop-lsp 0.1.3 → 0.1.4

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: 03b55f861c0619105ceaf9da7ba457972dd562cd41b31409dc0455e8765b6089
4
- data.tar.gz: 46df16a989903fad8f80d01e54a428811a2ce85dada49e8cd0cbf8cbbfc01bf3
3
+ metadata.gz: de59c33976acd6f570c9f4812248e905b8fc8b3ca2c61f5bc1a6acb82d903ee7
4
+ data.tar.gz: 4af340c2c6c89bdd5c19e152a26e94614ac83e7770387564f936276fc063a5c4
5
5
  SHA512:
6
- metadata.gz: ea5e70919e3e09b28d4f93cb6074054e826686e1458490338843260a84582deca878f55378485a480a9ee0c285144cce16c8bd3630e45aa27c6dbd6b31a596cc
7
- data.tar.gz: 2acdcb7cf4f8e83c24aa62ae6b44214eb64d294baa8f7b91a10a8de2a8634cf596b40eb8dba6aed91daeb96e01c60f5d2d7b00ab8f725e042663b5b51acffd22
6
+ metadata.gz: 7cb778276e436791c288856e37f36e00ff22293f507368bb0170a3ccf8f0b289ca2f652a15f9eb5b24d28c8a70f3e18fe8732da3590045b252cc9c193a293a8f
7
+ data.tar.gz: d68688323dd99d361f11fbb7ce070e66b31e5492ef90f7829d536215fb0439e2efa0ffb9bc67df81f410e451c89b3bd149ea629d836b55181cf2debae6aed353
@@ -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.3)
4
+ rubocop-lsp (0.1.4)
5
5
  language_server-protocol (>= 3.16)
6
6
  rubocop (>= 1.0)
7
7
 
@@ -9,29 +9,19 @@ 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
 
@@ -39,7 +29,8 @@ module Rubocop
39
29
  uri = request[:params][:textDocument][:uri]
40
30
  text = request[: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
@@ -48,7 +39,8 @@ module Rubocop
48
39
  uri = request[:params][:textDocument][:uri]
49
40
  text = request[: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
@@ -56,28 +48,24 @@ module Rubocop
56
48
  on("textDocument/didClose") do |request|
57
49
  uri = request[: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
57
  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
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
64
  uri = request[:params][:textDocument][:uri]
78
65
  range = request[:params][:range]
66
+ line_range = Range.new(range[:start][:line], range[:end][:line])
79
67
 
80
- DiagnosticStore.code_actions_for(uri, range)
68
+ send_code_actions(uri, line_range)
81
69
  end
82
70
  end
83
71
 
@@ -1,14 +1,14 @@
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::Formatter::BaseFormatter
6
+ attr_reader :uri, :file, :text, :formatted_text, :diagnostics
8
7
 
9
- def initialize(file:, text:)
8
+ def initialize(uri:, text:)
10
9
  super({}, nil)
11
- @file = file
10
+ @uri = uri
11
+ @file = CGI.unescape(URI.parse(uri).path)
12
12
  @text = text
13
13
  @formatted_text = nil
14
14
  end
@@ -26,57 +26,6 @@ class DiagnosticCollector < RuboCop::Formatter::BaseFormatter
26
26
  end
27
27
 
28
28
  def file_finished(_file, offenses)
29
- @diagnostics = offenses.map do |offense|
30
- diagnostic_from_offense(offense)
31
- end
32
- end
33
-
34
- def text_edits_from_formatting
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
- }
62
- )
63
- end
64
-
65
- def replacements_from_offense(offense)
66
- return unless offense.correctable?
67
-
68
- offense.corrector.as_replacements.map do |range, replacement|
69
- edit_from_replacement(range, replacement)
70
- end
71
- end
72
-
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
- )
29
+ @diagnostics = Model::DiagnosticCollection.new(uri, offenses)
81
30
  end
82
31
  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,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Model
4
+ class Diagnostic
5
+ attr_reader :cop_name, :message, :start, :end, :replacements, :diagnostic_response
6
+
7
+ def initialize(offense)
8
+ @cop_name = offense.cop_name
9
+ @message = offense.message
10
+ @start = Interface::Position.new(line: offense.line - 1, character: offense.column)
11
+ @end = Interface::Position.new(line: offense.last_line - 1, character: offense.last_column)
12
+ @replacements = replacements_from_offense(offense)
13
+ @diagnostic_response = diagnostic_from_offense(offense)
14
+ end
15
+
16
+ def correctable?
17
+ !!@replacements
18
+ end
19
+
20
+ private
21
+
22
+ def diagnostic_from_offense(offense)
23
+ Interface::Diagnostic.new(
24
+ message: offense.message,
25
+ severity: Constant::DiagnosticSeverity::INFORMATION,
26
+ range: Interface::Range.new(
27
+ start: @start,
28
+ end: @end
29
+ )
30
+ )
31
+ end
32
+
33
+ def replacements_from_offense(offense)
34
+ return unless offense.correctable?
35
+
36
+ offense.corrector.as_replacements.map do |range, replacement|
37
+ edit_from_replacement(range, replacement)
38
+ end
39
+ end
40
+
41
+ def edit_from_replacement(range, replacement)
42
+ Interface::TextEdit.new(
43
+ range: Interface::Range.new(
44
+ start: Interface::Position.new(line: range.line - 1, character: range.column),
45
+ end: Interface::Position.new(line: range.last_line - 1, character: range.last_column)
46
+ ),
47
+ new_text: replacement
48
+ )
49
+ end
50
+ end
51
+ 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: "rubocop.fix",
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.3"
5
+ VERSION = "0.1.4"
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.3
4
+ version: 0.1.4
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-07-26 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