solargraph 0.40.4 → 0.42.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -5
- data/CHANGELOG.md +32 -0
- data/README.md +15 -0
- data/SPONSORS.md +1 -0
- data/lib/.rubocop.yml +4 -3
- data/lib/solargraph.rb +8 -7
- data/lib/solargraph/api_map.rb +40 -111
- data/lib/solargraph/bench.rb +13 -16
- data/lib/solargraph/compat.rb +15 -1
- data/lib/solargraph/diagnostics/rubocop.rb +10 -2
- data/lib/solargraph/diagnostics/rubocop_helpers.rb +18 -0
- data/lib/solargraph/diagnostics/type_check.rb +1 -1
- data/lib/solargraph/language_server/host.rb +108 -7
- data/lib/solargraph/language_server/host/diagnoser.rb +9 -1
- data/lib/solargraph/language_server/host/sources.rb +1 -1
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +1 -0
- data/lib/solargraph/language_server/message/extended/environment.rb +3 -3
- data/lib/solargraph/language_server/message/initialize.rb +37 -35
- data/lib/solargraph/language_server/message/text_document/formatting.rb +28 -7
- data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
- data/lib/solargraph/library.rb +132 -22
- data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -1
- data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +0 -1
- data/lib/solargraph/shell.rb +5 -1
- data/lib/solargraph/source/chain/head.rb +0 -16
- data/lib/solargraph/source/source_chainer.rb +2 -1
- data/lib/solargraph/source_map/mapper.rb +0 -5
- data/lib/solargraph/type_checker/checks.rb +4 -4
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace.rb +1 -0
- data/lib/solargraph/workspace/config.rb +4 -3
- data/lib/solargraph/yard_map.rb +41 -39
- data/lib/solargraph/yard_map/core_fills.rb +1 -0
- data/solargraph.gemspec +1 -0
- metadata +16 -2
@@ -7,6 +7,24 @@ module Solargraph
|
|
7
7
|
module RubocopHelpers
|
8
8
|
module_function
|
9
9
|
|
10
|
+
# Requires a specific version of rubocop, or the latest installed version
|
11
|
+
# if _version_ is `nil`.
|
12
|
+
#
|
13
|
+
# @param version [String]
|
14
|
+
# @raise [InvalidRubocopVersionError] if _version_ is not installed
|
15
|
+
def require_rubocop(version = nil)
|
16
|
+
begin
|
17
|
+
gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path
|
18
|
+
gem_lib_path = File.join(gem_path, 'lib')
|
19
|
+
$LOAD_PATH.unshift(gem_lib_path) unless $LOAD_PATH.include?(gem_lib_path)
|
20
|
+
rescue Gem::MissingSpecVersionError => e
|
21
|
+
raise InvalidRubocopVersionError,
|
22
|
+
"could not find '#{e.name}' (#{e.requirement}) - "\
|
23
|
+
"did find: [#{e.specs.map { |s| s.version.version }.join(', ')}]"
|
24
|
+
end
|
25
|
+
require 'rubocop'
|
26
|
+
end
|
27
|
+
|
10
28
|
# Generate command-line options for the specified filename and code.
|
11
29
|
#
|
12
30
|
# @param filename [String]
|
@@ -7,7 +7,7 @@ module Solargraph
|
|
7
7
|
#
|
8
8
|
class TypeCheck < Base
|
9
9
|
def diagnose source, api_map
|
10
|
-
return [] unless args.include?('always') || api_map.workspaced?(source.filename)
|
10
|
+
# return [] unless args.include?('always') || api_map.workspaced?(source.filename)
|
11
11
|
severity = Diagnostics::Severities::ERROR
|
12
12
|
level = (args.reverse.find { |a| ['normal', 'typed', 'strict', 'strong'].include?(a) }) || :normal
|
13
13
|
checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'diff/lcs'
|
3
4
|
require 'observer'
|
5
|
+
require 'securerandom'
|
4
6
|
require 'set'
|
5
7
|
|
6
8
|
module Solargraph
|
@@ -106,9 +108,12 @@ module Solargraph
|
|
106
108
|
end
|
107
109
|
message
|
108
110
|
elsif request['id']
|
109
|
-
|
110
|
-
|
111
|
-
|
111
|
+
if requests[request['id']]
|
112
|
+
requests[request['id']].process(request['result'])
|
113
|
+
requests.delete request['id']
|
114
|
+
else
|
115
|
+
logger.warn "Discarding client response to unrecognized message #{request['id']}"
|
116
|
+
end
|
112
117
|
else
|
113
118
|
logger.warn "Invalid message received."
|
114
119
|
logger.debug request
|
@@ -164,8 +169,6 @@ module Solargraph
|
|
164
169
|
# @return [void]
|
165
170
|
def open_from_disk uri
|
166
171
|
sources.open_from_disk(uri)
|
167
|
-
library = library_for(uri)
|
168
|
-
# library.open_from_disk uri_to_file(uri)
|
169
172
|
diagnoser.schedule uri
|
170
173
|
end
|
171
174
|
|
@@ -192,7 +195,7 @@ module Solargraph
|
|
192
195
|
def diagnose uri
|
193
196
|
if sources.include?(uri)
|
194
197
|
library = library_for(uri)
|
195
|
-
if library.synchronized?
|
198
|
+
if library.mapped? && library.synchronized?
|
196
199
|
logger.info "Diagnosing #{uri}"
|
197
200
|
begin
|
198
201
|
results = library.diagnose uri_to_file(uri)
|
@@ -277,6 +280,7 @@ module Solargraph
|
|
277
280
|
begin
|
278
281
|
lib = Solargraph::Library.load(path, name)
|
279
282
|
libraries.push lib
|
283
|
+
async_library_map lib
|
280
284
|
rescue WorkspaceTooLargeError => e
|
281
285
|
send_notification 'window/showMessage', {
|
282
286
|
'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
|
@@ -631,6 +635,7 @@ module Solargraph
|
|
631
635
|
|
632
636
|
# @return [void]
|
633
637
|
def catalog
|
638
|
+
return unless libraries.all?(&:mapped?)
|
634
639
|
libraries.each(&:catalog)
|
635
640
|
end
|
636
641
|
|
@@ -669,7 +674,8 @@ module Solargraph
|
|
669
674
|
# @return [Source::Updater]
|
670
675
|
def generate_updater params
|
671
676
|
changes = []
|
672
|
-
params['contentChanges'].each do |
|
677
|
+
params['contentChanges'].each do |recvd|
|
678
|
+
chng = check_diff(params['textDocument']['uri'], recvd)
|
673
679
|
changes.push Solargraph::Source::Change.new(
|
674
680
|
(chng['range'].nil? ?
|
675
681
|
nil :
|
@@ -685,6 +691,36 @@ module Solargraph
|
|
685
691
|
)
|
686
692
|
end
|
687
693
|
|
694
|
+
# @param uri [String]
|
695
|
+
# @param change [Hash]
|
696
|
+
# @return [Hash]
|
697
|
+
def check_diff uri, change
|
698
|
+
return change if change['range']
|
699
|
+
source = sources.find(uri)
|
700
|
+
return change if source.code.length + 1 != change['text'].length
|
701
|
+
diffs = Diff::LCS.diff(source.code, change['text'])
|
702
|
+
return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
|
703
|
+
# @type [Diff::LCS::Change]
|
704
|
+
diff = diffs.first.first
|
705
|
+
return change unless diff.adding? && ['.', ':'].include?(diff.element)
|
706
|
+
position = Solargraph::Position.from_offset(source.code, diff.position)
|
707
|
+
{
|
708
|
+
'range' => {
|
709
|
+
'start' => {
|
710
|
+
'line' => position.line,
|
711
|
+
'character' => position.character
|
712
|
+
},
|
713
|
+
'end' => {
|
714
|
+
'line' => position.line,
|
715
|
+
'character' => position.character
|
716
|
+
}
|
717
|
+
},
|
718
|
+
'text' => diff.element
|
719
|
+
}
|
720
|
+
rescue Solargraph::FileNotFoundError
|
721
|
+
change
|
722
|
+
end
|
723
|
+
|
688
724
|
# @return [Hash]
|
689
725
|
def dynamic_capability_options
|
690
726
|
@dynamic_capability_options ||= {
|
@@ -741,6 +777,71 @@ module Solargraph
|
|
741
777
|
def prepare_rename?
|
742
778
|
client_capabilities['rename'] && client_capabilities['rename']['prepareSupport']
|
743
779
|
end
|
780
|
+
|
781
|
+
def client_supports_progress?
|
782
|
+
client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
|
783
|
+
end
|
784
|
+
|
785
|
+
# @param library [Library]
|
786
|
+
# @return [void]
|
787
|
+
def async_library_map library
|
788
|
+
return if library.mapped?
|
789
|
+
Thread.new do
|
790
|
+
if client_supports_progress?
|
791
|
+
uuid = SecureRandom.uuid
|
792
|
+
send_request 'window/workDoneProgress/create', {
|
793
|
+
token: uuid
|
794
|
+
} do |response|
|
795
|
+
do_async_library_map library, response.nil? ? uuid : nil
|
796
|
+
end
|
797
|
+
else
|
798
|
+
do_async_library_map library
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
def do_async_library_map library, uuid = nil
|
804
|
+
total = library.workspace.sources.length
|
805
|
+
if uuid
|
806
|
+
send_notification '$/progress', {
|
807
|
+
token: uuid,
|
808
|
+
value: {
|
809
|
+
kind: 'begin',
|
810
|
+
title: "Mapping workspace",
|
811
|
+
message: "0/#{total} files",
|
812
|
+
cancellable: false,
|
813
|
+
percentage: 0
|
814
|
+
}
|
815
|
+
}
|
816
|
+
end
|
817
|
+
pct = 0
|
818
|
+
mod = 10
|
819
|
+
while library.next_map
|
820
|
+
next unless uuid
|
821
|
+
cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
|
822
|
+
if cur > pct && cur % mod == 0
|
823
|
+
pct = cur
|
824
|
+
send_notification '$/progress', {
|
825
|
+
token: uuid,
|
826
|
+
value: {
|
827
|
+
kind: 'report',
|
828
|
+
cancellable: false,
|
829
|
+
message: "#{library.source_map_hash.keys.length}/#{total} files",
|
830
|
+
percentage: pct
|
831
|
+
}
|
832
|
+
}
|
833
|
+
end
|
834
|
+
end
|
835
|
+
if uuid
|
836
|
+
send_notification '$/progress', {
|
837
|
+
token: uuid,
|
838
|
+
value: {
|
839
|
+
kind: 'end',
|
840
|
+
message: 'Mapping complete'
|
841
|
+
}
|
842
|
+
}
|
843
|
+
end
|
844
|
+
end
|
744
845
|
end
|
745
846
|
end
|
746
847
|
end
|
@@ -62,7 +62,15 @@ module Solargraph
|
|
62
62
|
end
|
63
63
|
current = mutex.synchronize { queue.shift }
|
64
64
|
return if queue.include?(current)
|
65
|
-
|
65
|
+
begin
|
66
|
+
host.diagnose current
|
67
|
+
rescue InvalidOffsetError
|
68
|
+
# @todo This error can occur when the Source is out of sync with
|
69
|
+
# with the ApiMap. It's probably not the best way to handle it,
|
70
|
+
# but it's quick and easy.
|
71
|
+
Logging.logger.warn "Deferring diagnosis due to invalid offset: #{current}"
|
72
|
+
mutex.synchronize { queue.push current }
|
73
|
+
end
|
66
74
|
end
|
67
75
|
|
68
76
|
private
|
@@ -21,6 +21,7 @@ module Solargraph
|
|
21
21
|
docs = pins
|
22
22
|
.reject { |pin| pin.documentation.empty? && pin.return_type.undefined? }
|
23
23
|
result = params
|
24
|
+
.transform_keys(&:to_sym)
|
24
25
|
.merge(pins.first.resolve_completion_item)
|
25
26
|
.merge(documentation: markup_content(join_docs(docs)))
|
26
27
|
result[:detail] = pins.first.detail
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Make sure the environment page can report RuboCop's version
|
4
|
-
require 'rubocop'
|
5
|
-
|
6
3
|
module Solargraph
|
7
4
|
module LanguageServer
|
8
5
|
module Message
|
@@ -12,6 +9,9 @@ module Solargraph
|
|
12
9
|
#
|
13
10
|
class Environment < Base
|
14
11
|
def process
|
12
|
+
# Make sure the environment page can report RuboCop's version
|
13
|
+
require 'rubocop'
|
14
|
+
|
15
15
|
page = Solargraph::Page.new(host.options['viewsPath'])
|
16
16
|
content = page.render('environment', layout: true, locals: { config: host.options, folders: host.folders })
|
17
17
|
set_result(
|
@@ -1,49 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'benchmark'
|
4
|
-
|
5
3
|
module Solargraph
|
6
4
|
module LanguageServer
|
7
5
|
module Message
|
8
6
|
class Initialize < Base
|
9
7
|
def process
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
changeNotifications: true
|
27
|
-
}
|
8
|
+
host.configure params['initializationOptions']
|
9
|
+
host.client_capabilities = params['capabilities']
|
10
|
+
if support_workspace_folders?
|
11
|
+
host.prepare_folders params['workspaceFolders']
|
12
|
+
elsif params['rootUri']
|
13
|
+
host.prepare UriHelpers.uri_to_file(params['rootUri'])
|
14
|
+
else
|
15
|
+
host.prepare params['rootPath']
|
16
|
+
end
|
17
|
+
result = {
|
18
|
+
capabilities: {
|
19
|
+
textDocumentSync: 2, # @todo What should this be?
|
20
|
+
workspace: {
|
21
|
+
workspaceFolders: {
|
22
|
+
supported: true,
|
23
|
+
changeNotifications: true
|
28
24
|
}
|
29
25
|
}
|
30
26
|
}
|
31
|
-
result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
|
32
|
-
result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
|
33
|
-
# result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
|
34
|
-
result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
|
35
|
-
result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
|
36
|
-
result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
|
37
|
-
result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
|
38
|
-
result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
|
39
|
-
result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
|
40
|
-
result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
|
41
|
-
result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
|
42
|
-
# @todo Temporarily disabled
|
43
|
-
# result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
|
44
|
-
set_result result
|
45
27
|
}
|
46
|
-
|
28
|
+
result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion')
|
29
|
+
result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp')
|
30
|
+
# result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting')
|
31
|
+
result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover')
|
32
|
+
result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
|
33
|
+
result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
|
34
|
+
result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
|
35
|
+
result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
|
36
|
+
result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
|
37
|
+
result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
|
38
|
+
result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange')
|
39
|
+
# @todo Temporarily disabled
|
40
|
+
# result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction')
|
41
|
+
set_result result
|
47
42
|
end
|
48
43
|
|
49
44
|
private
|
@@ -56,6 +51,7 @@ module Solargraph
|
|
56
51
|
end
|
57
52
|
|
58
53
|
def static_completion
|
54
|
+
return {} unless host.options['completion']
|
59
55
|
{
|
60
56
|
completionProvider: {
|
61
57
|
resolveProvider: true,
|
@@ -89,18 +85,21 @@ module Solargraph
|
|
89
85
|
end
|
90
86
|
|
91
87
|
def static_hover
|
88
|
+
return {} unless host.options['hover']
|
92
89
|
{
|
93
90
|
hoverProvider: true
|
94
91
|
}
|
95
92
|
end
|
96
93
|
|
97
94
|
def static_document_formatting
|
95
|
+
return {} unless host.options['formatting']
|
98
96
|
{
|
99
97
|
documentFormattingProvider: true
|
100
98
|
}
|
101
99
|
end
|
102
100
|
|
103
101
|
def static_document_symbols
|
102
|
+
return {} unless host.options['symbols']
|
104
103
|
{
|
105
104
|
documentSymbolProvider: true
|
106
105
|
}
|
@@ -113,6 +112,7 @@ module Solargraph
|
|
113
112
|
end
|
114
113
|
|
115
114
|
def static_definitions
|
115
|
+
return {} unless host.options['definitions']
|
116
116
|
{
|
117
117
|
definitionProvider: true
|
118
118
|
}
|
@@ -125,12 +125,14 @@ module Solargraph
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def static_references
|
128
|
+
return {} unless host.options['references']
|
128
129
|
{
|
129
130
|
referencesProvider: true
|
130
131
|
}
|
131
132
|
end
|
132
133
|
|
133
134
|
def static_folding_range
|
135
|
+
return {} unless host.options['folding']
|
134
136
|
{
|
135
137
|
foldingRangeProvider: true
|
136
138
|
}
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rubocop'
|
4
3
|
require 'securerandom'
|
5
4
|
require 'tmpdir'
|
6
5
|
|
@@ -11,21 +10,22 @@ module Solargraph
|
|
11
10
|
class Formatting < Base
|
12
11
|
include Solargraph::Diagnostics::RubocopHelpers
|
13
12
|
|
14
|
-
class BlankRubocopFormatter < ::RuboCop::Formatter::BaseFormatter; end
|
15
|
-
|
16
13
|
def process
|
17
14
|
file_uri = params['textDocument']['uri']
|
18
15
|
config = config_for(file_uri)
|
19
16
|
original = host.read_text(file_uri)
|
20
17
|
args = cli_args(file_uri, config)
|
21
18
|
|
19
|
+
require_rubocop(config['version'])
|
22
20
|
options, paths = RuboCop::Options.new.parse(args)
|
23
21
|
options[:stdin] = original
|
24
|
-
redirect_stdout do
|
22
|
+
corrections = redirect_stdout do
|
25
23
|
RuboCop::Runner.new(options, RuboCop::ConfigStore.new).run(paths)
|
26
24
|
end
|
27
25
|
result = options[:stdin]
|
28
26
|
|
27
|
+
log_corrections(corrections)
|
28
|
+
|
29
29
|
format original, result
|
30
30
|
rescue RuboCop::ValidationError, RuboCop::ConfigNotFoundError => e
|
31
31
|
set_error(Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}")
|
@@ -33,6 +33,17 @@ module Solargraph
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
+
def log_corrections(corrections)
|
37
|
+
corrections = corrections&.strip
|
38
|
+
return if corrections&.empty?
|
39
|
+
|
40
|
+
Solargraph.logger.info('Formatting result:')
|
41
|
+
corrections.each_line do |line|
|
42
|
+
next if line.strip.empty?
|
43
|
+
Solargraph.logger.info(line.strip)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
36
47
|
def config_for(file_uri)
|
37
48
|
conf = host.formatter_config(file_uri)
|
38
49
|
return {} unless conf.is_a?(Hash)
|
@@ -40,12 +51,12 @@ module Solargraph
|
|
40
51
|
conf['rubocop'] || {}
|
41
52
|
end
|
42
53
|
|
43
|
-
def cli_args
|
54
|
+
def cli_args file_uri, config
|
55
|
+
file = UriHelpers.uri_to_file(file_uri)
|
44
56
|
args = [
|
45
57
|
config['cops'] == 'all' ? '--auto-correct-all' : '--auto-correct',
|
46
58
|
'--cache', 'false',
|
47
|
-
'--format',
|
48
|
-
'TextDocument::Formatting::BlankRubocopFormatter',
|
59
|
+
'--format', formatter_class(config).name,
|
49
60
|
]
|
50
61
|
|
51
62
|
['except', 'only'].each do |arg|
|
@@ -57,6 +68,16 @@ module Solargraph
|
|
57
68
|
args + [file]
|
58
69
|
end
|
59
70
|
|
71
|
+
def formatter_class(config)
|
72
|
+
if self.class.const_defined?('BlankRubocopFormatter')
|
73
|
+
BlankRubocopFormatter
|
74
|
+
else
|
75
|
+
require_rubocop(config['version'])
|
76
|
+
klass = Class.new(::RuboCop::Formatter::BaseFormatter)
|
77
|
+
self.class.const_set 'BlankRubocopFormatter', klass
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
60
81
|
def cop_list(value)
|
61
82
|
value = value.join(',') if value.respond_to?(:join)
|
62
83
|
return nil if value == '' || !value.is_a?(String)
|