rubocop-lsp 0.1.0 → 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: 5008f097c351d59bce2270ed7339a927fb6793307512bcef8166e36b95c89d5c
4
- data.tar.gz: 2fdfc892459fe05ffbd07c3e0f9af182b91d3ec6b7849792ca169977847fd489
3
+ metadata.gz: de59c33976acd6f570c9f4812248e905b8fc8b3ca2c61f5bc1a6acb82d903ee7
4
+ data.tar.gz: 4af340c2c6c89bdd5c19e152a26e94614ac83e7770387564f936276fc063a5c4
5
5
  SHA512:
6
- metadata.gz: 1f136d5ba9f513c6800daa16ef9f27d6c6241d5a260c303c38ac03673e14edfdcd5ca7327b36d05f14eb4cdeb5a861240ec6306e870d96392f9557cc62ac89df
7
- data.tar.gz: 5edada0a8d7b8edc6645cc30ba5f476ff0f7a4670211600d0b4e7fb7a40f0aafeaf2adde292a7095c0d242824ba0d4beee4a68169b40b85362da189985bf0176
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.0)
4
+ rubocop-lsp (0.1.4)
5
5
  language_server-protocol (>= 3.16)
6
6
  rubocop (>= 1.0)
7
7
 
@@ -9,28 +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
- code_action_provider: true
28
- ),
29
- )
17
+ store.clear
18
+
19
+ respond_with_capabilities
30
20
  end
31
21
 
32
22
  on("shutdown") do
33
- DiagnosticStore.clear_diagnostics
23
+ store.clear
24
+
34
25
  nil
35
26
  end
36
27
 
@@ -38,7 +29,8 @@ module Rubocop
38
29
  uri = request[:params][:textDocument][:uri]
39
30
  text = request[:params][:contentChanges][0][:text]
40
31
 
41
- send_diagnostics(uri: uri, text: text)
32
+ collector = collect_diagnostics(uri: uri, text: text)
33
+ send_diagnostics(collector)
42
34
 
43
35
  nil
44
36
  end
@@ -47,7 +39,8 @@ module Rubocop
47
39
  uri = request[:params][:textDocument][:uri]
48
40
  text = request[:params][:textDocument][:text]
49
41
 
50
- send_diagnostics(uri: uri, text: text)
42
+ collector = collect_diagnostics(uri: uri, text: text)
43
+ send_diagnostics(collector)
51
44
 
52
45
  nil
53
46
  end
@@ -55,18 +48,24 @@ module Rubocop
55
48
  on("textDocument/didClose") do |request|
56
49
  uri = request[:params][:textDocument][:uri]
57
50
 
58
- DiagnosticStore.remove_diagnostics_for(uri)
59
-
60
- send_diagnostics(uri: uri, diagnostics: [])
51
+ clear_diagnostics(uri: uri)
61
52
 
62
53
  nil
63
54
  end
64
55
 
56
+ on("textDocument/formatting") do |request|
57
+ uri = request[:params][:textDocument][:uri]
58
+
59
+ collector = collect_diagnostics(uri: uri, text: store.text_for(uri))
60
+ send_text_edits(collector)
61
+ end
62
+
65
63
  on("textDocument/codeAction") do |request|
66
64
  uri = request[:params][:textDocument][:uri]
67
65
  range = request[:params][:range]
66
+ line_range = Range.new(range[:start][:line], range[:end][:line])
68
67
 
69
- DiagnosticStore.code_actions_for(uri, range)
68
+ send_code_actions(uri, line_range)
70
69
  end
71
70
  end
72
71
 
@@ -1,64 +1,31 @@
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 :diagnostics
5
+ class DiagnosticCollector < RuboCop::Formatter::BaseFormatter
6
+ attr_reader :uri, :file, :text, :formatted_text, :diagnostics
8
7
 
9
- def initialize
8
+ def initialize(uri:, text:)
10
9
  super({}, nil)
10
+ @uri = uri
11
+ @file = CGI.unescape(URI.parse(uri).path)
12
+ @text = text
13
+ @formatted_text = nil
11
14
  end
12
15
 
13
- def run(file:, text:)
16
+ def run
14
17
  config_store = ::RuboCop::ConfigStore.new
15
- options, paths = ::RuboCop::Options.new.parse(["--stderr"])
18
+ options, paths = ::RuboCop::Options.new.parse(["--stderr", "--auto-correct"])
16
19
  # config_store.options_config = options[:config] if options[:config]
17
20
  options[:stdin] = text
18
21
  paths << file
19
22
  runner = ::RuboCop::Runner.new(options, config_store)
20
23
  runner.send(:formatter_set).clear.push(self)
21
24
  runner.run(paths)
25
+ @formatted_text = options[:stdin]
22
26
  end
23
27
 
24
28
  def file_finished(_file, offenses)
25
- @diagnostics = offenses.map do |offense|
26
- diagnostic_from_offense(offense)
27
- end
28
- end
29
-
30
- private
31
-
32
- def diagnostic_from_offense(offense)
33
- Interface::Diagnostic.new(
34
- message: offense.message,
35
- severity: Constant::DiagnosticSeverity::INFORMATION,
36
- range: Interface::Range.new(
37
- start: Interface::Position.new(line: offense.line - 1, character: offense.column),
38
- end: Interface::Position.new(line: offense.last_line - 1, character: offense.last_column)
39
- ),
40
- data: {
41
- cop_name: offense.cop_name,
42
- edits: replacements_from_offense(offense),
43
- }
44
- )
45
- end
46
-
47
- def replacements_from_offense(offense)
48
- return unless offense.correctable?
49
-
50
- offense.corrector.as_replacements.map do |range, replacement|
51
- edit_from_replacement(range, replacement)
52
- end
53
- end
54
-
55
- def edit_from_replacement(range, replacement)
56
- Interface::TextEdit.new(
57
- range: Interface::Range.new(
58
- start: Interface::Position.new(line: range.line - 1, character: range.column),
59
- end: Interface::Position.new(line: range.last_line - 1, character: range.last_column)
60
- ),
61
- new_text: replacement
62
- )
29
+ @diagnostics = Model::DiagnosticCollection.new(uri, offenses)
63
30
  end
64
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
69
- collector.run(file: file, text: text)
70
- DiagnosticStore.set_diagnostics_for(uri, collector.diagnostics)
113
+ collector = DiagnosticCollector.new(uri: uri, text: text)
114
+ collector.run
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.0"
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.0
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,79 +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.code_actions_for(uri, range)
24
- line_range = Range.new(range[:start][:line], range[:end][:line])
25
-
26
- file_diagnostics = diagnostics_for(uri)
27
- line_diagnostics = file_diagnostics.select { |diagnostic| line_range.include?(diagnostic.range.start.line) }
28
- actions = line_diagnostics.map { |diagnostic| code_action_from(uri, diagnostic) }
29
-
30
- unless actions.empty?
31
- actions << code_action_from_diagnostics(uri, line_diagnostics, title: "Autocorrect all offenses on line")
32
- actions << code_action_from_diagnostics(uri, file_diagnostics, title: "Autocorrect all offenses in file")
33
- end
34
-
35
- actions
36
- end
37
-
38
- def self.code_action_from_diagnostics(uri, diagnostics, title:)
39
- diagnostics = diagnostics.sort_by do |diagnostic|
40
- [diagnostic.range.start.line, diagnostic.range.start.character]
41
- end
42
-
43
- LanguageServer::Protocol::Interface::CodeAction.new(
44
- title: title,
45
- kind: "rubocop.fix",
46
- edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
47
- document_changes: [
48
- LanguageServer::Protocol::Interface::TextDocumentEdit.new(
49
- text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
50
- uri: uri,
51
- version: nil
52
- ),
53
- edits: diagnostics.flat_map { |d| d.data[:edits] }
54
- ),
55
- ]
56
- ),
57
- is_preferred: true,
58
- )
59
- end
60
-
61
- def self.code_action_from(uri, diagnostic)
62
- LanguageServer::Protocol::Interface::CodeAction.new(
63
- title: "Autocorrect `#{diagnostic.data[:cop_name]}`",
64
- kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
65
- edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
66
- document_changes: [
67
- LanguageServer::Protocol::Interface::TextDocumentEdit.new(
68
- text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
69
- uri: uri,
70
- version: nil
71
- ),
72
- edits: diagnostic.data[:edits]
73
- ),
74
- ]
75
- ),
76
- is_preferred: true,
77
- )
78
- end
79
- end