yoda-language-server 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -3
- data/README.md +50 -48
- data/client/atom/main.js +13 -3
- data/client/vscode/.vscode/launch.json +7 -4
- data/client/vscode/package-lock.json +585 -1454
- data/client/vscode/package.json +10 -7
- data/client/vscode/src/extension.ts +3 -3
- data/client/vscode/src/test/completion.test.ts +39 -0
- data/client/vscode/src/test/helper.ts +38 -0
- data/client/vscode/src/test/index.ts +5 -3
- data/client/vscode/testFixture/completion.rb +1 -0
- data/exe/yoda +1 -20
- data/lib/yoda.rb +2 -1
- data/lib/yoda/commands.rb +34 -0
- data/lib/yoda/commands/base.rb +10 -0
- data/lib/yoda/commands/complete.rb +36 -0
- data/lib/yoda/commands/file_cursor_parsable.rb +29 -0
- data/lib/yoda/{runner → commands}/infer.rb +4 -9
- data/lib/yoda/commands/setup.rb +37 -0
- data/lib/yoda/errors.rb +34 -0
- data/lib/yoda/evaluation/evaluator.rb +2 -0
- data/lib/yoda/server.rb +60 -15
- data/lib/yoda/server/completion_provider.rb +8 -8
- data/lib/yoda/server/definition_provider.rb +8 -8
- data/lib/yoda/server/hover_provider.rb +6 -6
- data/lib/yoda/server/initialization_provider.rb +85 -0
- data/lib/yoda/server/{client_info.rb → session.rb} +6 -2
- data/lib/yoda/server/signature_provider.rb +6 -6
- data/lib/yoda/store/actions.rb +3 -1
- data/lib/yoda/store/actions/build_core_index.rb +44 -0
- data/lib/yoda/store/actions/import_core_library.rb +14 -9
- data/lib/yoda/store/actions/import_gem.rb +98 -0
- data/lib/yoda/store/actions/import_std_library.rb +35 -0
- data/lib/yoda/store/adapters.rb +1 -0
- data/lib/yoda/store/adapters/memory_adapter.rb +81 -0
- data/lib/yoda/store/objects.rb +4 -0
- data/lib/yoda/store/objects/addressable.rb +0 -12
- data/lib/yoda/store/objects/base.rb +4 -14
- data/lib/yoda/store/objects/merger.rb +4 -4
- data/lib/yoda/store/objects/patchable.rb +19 -0
- data/lib/yoda/store/objects/project_status.rb +169 -0
- data/lib/yoda/store/objects/serializable.rb +39 -0
- data/lib/yoda/store/project.rb +28 -114
- data/lib/yoda/store/project/cache.rb +79 -0
- data/lib/yoda/store/project/library_doc_loader.rb +102 -0
- data/lib/yoda/store/query/find_constant.rb +2 -1
- data/lib/yoda/store/registry.rb +15 -0
- data/lib/yoda/store/yard_importer.rb +58 -28
- data/lib/yoda/typing/evaluator.rb +8 -5
- data/lib/yoda/version.rb +1 -1
- data/package.json +32 -11
- data/scripts/benchmark.rb +1 -1
- data/yoda-language-server.gemspec +1 -0
- metadata +37 -7
- data/client/vscode/src/test/extension.test.ts +0 -22
- data/lib/yoda/runner/setup.rb +0 -26
- data/lib/yoda/store/actions/import_gems.rb +0 -91
@@ -1,18 +1,18 @@
|
|
1
1
|
module Yoda
|
2
2
|
class Server
|
3
3
|
class CompletionProvider
|
4
|
-
# @type
|
5
|
-
attr_reader :
|
4
|
+
# @type Session
|
5
|
+
attr_reader :session
|
6
6
|
|
7
|
-
# @param
|
8
|
-
def initialize(
|
9
|
-
@
|
7
|
+
# @param session [Session]
|
8
|
+
def initialize(session)
|
9
|
+
@session = session
|
10
10
|
end
|
11
11
|
|
12
12
|
# @param uri [String]
|
13
13
|
# @param position [{Symbol => Integer}]
|
14
14
|
def complete(uri, position)
|
15
|
-
source =
|
15
|
+
source = session.file_store.get(uri)
|
16
16
|
location = Parsing::Location.of_language_server_protocol_position(line: position[:line], character: position[:character])
|
17
17
|
|
18
18
|
if candidates = comment_complete(source, location)
|
@@ -29,7 +29,7 @@ module Yoda
|
|
29
29
|
def comment_complete(source, location)
|
30
30
|
ast, comments = Parsing::Parser.new.parse_with_comments(source)
|
31
31
|
return nil unless Parsing::Query::CurrentCommentQuery.new(comments, location).current_comment
|
32
|
-
completion_worker = Evaluation::CommentCompletion.new(
|
32
|
+
completion_worker = Evaluation::CommentCompletion.new(session.registry, ast, comments, location)
|
33
33
|
return nil unless completion_worker.available?
|
34
34
|
|
35
35
|
completion_items = completion_worker.candidates
|
@@ -47,7 +47,7 @@ module Yoda
|
|
47
47
|
# @return [LanguageServerProtocol::Interface::CompletionList, nil]
|
48
48
|
def complete_from_cut_source(source, location)
|
49
49
|
cut_source = Parsing::SourceCutter.new(source, location).error_recovered_source
|
50
|
-
method_completion_worker = Evaluation::CodeCompletion.new(
|
50
|
+
method_completion_worker = Evaluation::CodeCompletion.new(session.registry, cut_source, location)
|
51
51
|
completion_items = method_completion_worker.candidates
|
52
52
|
return nil if completion_items.empty?
|
53
53
|
|
@@ -1,22 +1,22 @@
|
|
1
1
|
module Yoda
|
2
2
|
class Server
|
3
3
|
class DefinitionProvider
|
4
|
-
# @type
|
5
|
-
attr_reader :
|
4
|
+
# @type Session
|
5
|
+
attr_reader :session
|
6
6
|
|
7
|
-
# @param
|
8
|
-
def initialize(
|
9
|
-
@
|
7
|
+
# @param session [Session]
|
8
|
+
def initialize(session)
|
9
|
+
@session = session
|
10
10
|
end
|
11
11
|
|
12
12
|
# @param uri [String]
|
13
13
|
# @param position [{Symbol => Integer}]
|
14
14
|
# @param include_declaration [Boolean]
|
15
15
|
def provide(uri, position, include_declaration = false)
|
16
|
-
source =
|
16
|
+
source = session.file_store.get(uri)
|
17
17
|
location = Parsing::Location.of_language_server_protocol_position(line: position[:line], character: position[:character])
|
18
18
|
|
19
|
-
node_worker = Evaluation::CurrentNodeExplain.new(
|
19
|
+
node_worker = Evaluation::CurrentNodeExplain.new(session.registry, source, location)
|
20
20
|
references = node_worker.defined_files
|
21
21
|
references.map { |(path, line, column)| create_location(path, line, column) }
|
22
22
|
end
|
@@ -27,7 +27,7 @@ module Yoda
|
|
27
27
|
def create_location(path, line, column)
|
28
28
|
location = Parsing::Location.new(row: line - 1, column: column)
|
29
29
|
LSP::Interface::Location.new(
|
30
|
-
uri:
|
30
|
+
uri: session.uri_of_path(path),
|
31
31
|
range: LSP::Interface::Range.new(Parsing::Range.new(location, location).to_language_server_protocol_range),
|
32
32
|
)
|
33
33
|
end
|
@@ -1,20 +1,20 @@
|
|
1
1
|
module Yoda
|
2
2
|
class Server
|
3
3
|
class HoverProvider
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :session
|
5
5
|
|
6
|
-
# @param
|
7
|
-
def initialize(
|
8
|
-
@
|
6
|
+
# @param session [Session]
|
7
|
+
def initialize(session)
|
8
|
+
@session = session
|
9
9
|
end
|
10
10
|
|
11
11
|
# @param uri [String]
|
12
12
|
# @param position [{Symbol => Integer}]
|
13
13
|
def request_hover(uri, position)
|
14
|
-
source =
|
14
|
+
source = session.file_store.get(uri)
|
15
15
|
location = Parsing::Location.of_language_server_protocol_position(line: position[:line], character: position[:character])
|
16
16
|
|
17
|
-
node_worker = Evaluation::CurrentNodeExplain.new(
|
17
|
+
node_worker = Evaluation::CurrentNodeExplain.new(session.registry, source, location)
|
18
18
|
|
19
19
|
current_node_signature = node_worker.current_node_signature
|
20
20
|
create_hover(current_node_signature) if current_node_signature
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Yoda
|
2
|
+
class Server
|
3
|
+
class InitializationProvider
|
4
|
+
# @return [Session]
|
5
|
+
attr_reader :session
|
6
|
+
|
7
|
+
# @param session [Session]
|
8
|
+
def initialize(session)
|
9
|
+
@session = session
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [LanguageServer::Protocol::Interface::ShowMessageParams, nil]
|
13
|
+
def provide
|
14
|
+
errors = session.setup
|
15
|
+
build_complete_message(errors)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @param errors [Array<BaseError>]
|
21
|
+
# @return [Array<Object>]
|
22
|
+
def build_complete_message(errors)
|
23
|
+
return [] if errors.empty?
|
24
|
+
gem_import_errors = errors.select { |error| error.is_a?(GemImportError) }
|
25
|
+
core_import_errors = errors.select { |error| error.is_a?(CoreImportError) }
|
26
|
+
|
27
|
+
notifications = [
|
28
|
+
{
|
29
|
+
method: 'window/showMessage',
|
30
|
+
params: LanguageServer::Protocol::Interface::ShowMessageParams.new(
|
31
|
+
type: LanguageServer::Protocol::Constant::MessageType::WARNING,
|
32
|
+
message: "Failed to load some libraries (Please check console for details)",
|
33
|
+
)
|
34
|
+
}
|
35
|
+
]
|
36
|
+
|
37
|
+
if gem_message = gem_import_warnings(gem_import_errors)
|
38
|
+
notifications.push(
|
39
|
+
method: 'window/logMessage',
|
40
|
+
params: LanguageServer::Protocol::Interface::LogMessageParams.new(
|
41
|
+
type: LanguageServer::Protocol::Constant::MessageType::WARNING,
|
42
|
+
message: gem_message,
|
43
|
+
),
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
if core_message = core_import_warnings(core_import_errors)
|
48
|
+
notifications.push(
|
49
|
+
method: 'window/logMessage',
|
50
|
+
params: LanguageServer::Protocol::Interface::LogMessageParams.new(
|
51
|
+
type: LanguageServer::Protocol::Constant::MessageType::WARNING,
|
52
|
+
message: core_message,
|
53
|
+
),
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
notifications
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param gem_import_errors [Array<GemImportError>]
|
61
|
+
# @return [String, nil]
|
62
|
+
def gem_import_warnings(gem_import_errors)
|
63
|
+
return if gem_import_errors.empty?
|
64
|
+
warnings = gem_import_errors.map { |error| "- #{error.name} (#{error.version})" }
|
65
|
+
|
66
|
+
<<~EOS
|
67
|
+
Failed to import some gems.
|
68
|
+
Please check these gems are installed for Ruby version #{RUBY_VERSION}.
|
69
|
+
#{warnings.join("\n")}
|
70
|
+
EOS
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param gem_import_errors [Array<GemImportError>]
|
74
|
+
# @return [String, nil]
|
75
|
+
def core_import_warnings(core_import_errors)
|
76
|
+
return if core_import_errors.empty?
|
77
|
+
|
78
|
+
<<~EOS
|
79
|
+
Failed to import some core libraries (Ruby version: #{RUBY_VERSION}).
|
80
|
+
Please execute `yoda setup` with Ruby version #{RUBY_VERSION}.
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -2,7 +2,7 @@ require 'uri'
|
|
2
2
|
|
3
3
|
module Yoda
|
4
4
|
class Server
|
5
|
-
class
|
5
|
+
class Session
|
6
6
|
# @return [String]
|
7
7
|
attr_reader :root_uri
|
8
8
|
|
@@ -12,11 +12,14 @@ module Yoda
|
|
12
12
|
# @return [Store::Project]
|
13
13
|
attr_reader :project
|
14
14
|
|
15
|
+
attr_accessor :client_initialized
|
16
|
+
|
15
17
|
# @param root_uri [String] an uri expression of project root path
|
16
18
|
def initialize(root_uri)
|
17
19
|
@root_uri = root_uri
|
18
20
|
@file_store = FileStore.new
|
19
21
|
@project = Store::Project.new(root_path)
|
22
|
+
@client_initialized = false
|
20
23
|
end
|
21
24
|
|
22
25
|
def root_path
|
@@ -29,7 +32,8 @@ module Yoda
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def setup
|
32
|
-
|
35
|
+
Store::Actions::BuildCoreIndex.run unless Store::Actions::BuildCoreIndex.exists?
|
36
|
+
project.build_cache
|
33
37
|
end
|
34
38
|
|
35
39
|
# @param path [String]
|
@@ -1,21 +1,21 @@
|
|
1
1
|
module Yoda
|
2
2
|
class Server
|
3
3
|
class SignatureProvider
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :session
|
5
5
|
|
6
|
-
# @param
|
7
|
-
def initialize(
|
8
|
-
@
|
6
|
+
# @param session [Session]
|
7
|
+
def initialize(session)
|
8
|
+
@session = session
|
9
9
|
end
|
10
10
|
|
11
11
|
# @param uri [String]
|
12
12
|
# @param position [{Symbol => Integer}]
|
13
13
|
def provide(uri, position)
|
14
|
-
source =
|
14
|
+
source = session.file_store.get(uri)
|
15
15
|
location = Parsing::Location.of_language_server_protocol_position(line: position[:line], character: position[:character])
|
16
16
|
cut_source = Parsing::SourceCutter.new(source, location).error_recovered_source
|
17
17
|
|
18
|
-
signature_worker = Evaluation::SignatureDiscovery.new(
|
18
|
+
signature_worker = Evaluation::SignatureDiscovery.new(session.registry, cut_source, location)
|
19
19
|
|
20
20
|
functions = signature_worker.method_candidates
|
21
21
|
create_signature_help(functions)
|
data/lib/yoda/store/actions.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module Yoda
|
2
2
|
module Store
|
3
3
|
module Actions
|
4
|
+
require 'yoda/store/actions/build_core_index'
|
4
5
|
require 'yoda/store/actions/import_core_library'
|
5
|
-
require 'yoda/store/actions/
|
6
|
+
require 'yoda/store/actions/import_std_library'
|
7
|
+
require 'yoda/store/actions/import_gem'
|
6
8
|
require 'yoda/store/actions/read_file'
|
7
9
|
require 'yoda/store/actions/read_project_files'
|
8
10
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Yoda
|
2
|
+
module Store
|
3
|
+
module Actions
|
4
|
+
# @todo Build index without using shell script
|
5
|
+
class BuildCoreIndex
|
6
|
+
class << self
|
7
|
+
# @return [true, false]
|
8
|
+
def run
|
9
|
+
new.run
|
10
|
+
end
|
11
|
+
|
12
|
+
def exists?
|
13
|
+
[
|
14
|
+
"~/.yoda/sources/ruby-#{RUBY_VERSION}/.yardoc",
|
15
|
+
"~/.yoda/sources/ruby-#{RUBY_VERSION}/.yardoc-stdlib",
|
16
|
+
].all? { |path| File.exists?(File.expand_path(path)) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [true, false]
|
21
|
+
def run
|
22
|
+
build_core_index
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def script_path
|
29
|
+
File.expand_path('../../../../scripts/build_core_index.sh', __dir__)
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_core_index
|
33
|
+
o, e = Open3.capture2e(script_path)
|
34
|
+
STDERR.puts o unless o.empty?
|
35
|
+
if e.success?
|
36
|
+
STDERR.puts "Success to build yard index"
|
37
|
+
else
|
38
|
+
STDERR.puts "Failed to build #{gem_name} #{gem_version}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -5,24 +5,29 @@ module Yoda
|
|
5
5
|
# @return [Registry]
|
6
6
|
attr_reader :registry
|
7
7
|
|
8
|
+
class << self
|
9
|
+
# @return [true, false]
|
10
|
+
def run(registry)
|
11
|
+
new(registry).run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
def initialize(registry)
|
9
16
|
@registry = registry
|
10
17
|
end
|
11
18
|
|
19
|
+
# @return [true, false]
|
12
20
|
def run
|
13
|
-
|
14
|
-
|
15
|
-
|
21
|
+
return false unless File.exist?(doc_path)
|
22
|
+
patch = YardImporter.import(doc_path)
|
23
|
+
registry.add_patch(patch)
|
24
|
+
true
|
16
25
|
end
|
17
26
|
|
18
27
|
private
|
19
28
|
|
20
|
-
def
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def core_doc_files
|
25
|
-
%W(.yoda/sources/ruby-#{RUBY_VERSION}/.yardoc .yoda/sources/ruby-#{RUBY_VERSION}/.yardoc-stdlib).map { |path| File.expand_path(path, '~') }.select { |path| File.exist?(path) }
|
29
|
+
def doc_path
|
30
|
+
File.expand_path("~/.yoda/sources/ruby-#{RUBY_VERSION}/.yardoc")
|
26
31
|
end
|
27
32
|
end
|
28
33
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Yoda
|
4
|
+
module Store
|
5
|
+
module Actions
|
6
|
+
class ImportGem
|
7
|
+
# @return [Registry]
|
8
|
+
attr_reader :registry
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :gem_name, :gem_version
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# @return [true, false]
|
15
|
+
def run(args = {})
|
16
|
+
new(args).run
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param registry [Registry]
|
21
|
+
# @param gem_name [String]
|
22
|
+
# @param gem_version [String]
|
23
|
+
def initialize(registry:, gem_name:, gem_version:)
|
24
|
+
@registry = registry
|
25
|
+
@gem_name = gem_name
|
26
|
+
@gem_version = gem_version
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [true, false]
|
30
|
+
def run
|
31
|
+
create_dependency_doc
|
32
|
+
if yardoc_file = yardoc_path
|
33
|
+
if patch = load_yardoc(yardoc_file, gem_path)
|
34
|
+
registry.add_patch(patch)
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def create_dependency_doc
|
44
|
+
if yardoc_path
|
45
|
+
STDERR.puts "Gem docs for #{gem_name} #{gem_version} already exist"
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
STDERR.puts "Building gem docs for #{gem_name} #{gem_version}"
|
49
|
+
begin
|
50
|
+
o, e = Open3.capture2e("yard gems #{gem_name} #{gem_version}")
|
51
|
+
STDERR.puts o unless o.empty?
|
52
|
+
if e.success?
|
53
|
+
STDERR.puts "Done building gem docs for #{gem_name} #{gem_version}"
|
54
|
+
else
|
55
|
+
STDERR.puts "Failed to build #{gem_name} #{gem_version}"
|
56
|
+
end
|
57
|
+
rescue => ex
|
58
|
+
STDERR.puts ex
|
59
|
+
STDERR.puts ex.backtrace
|
60
|
+
STDERR.puts "Failed to build #{gem_name} #{gem_version}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param yardoc_file [String]
|
65
|
+
# @param gem_source_path [String]
|
66
|
+
# @return [Objects::Patch, nil]
|
67
|
+
def load_yardoc(yardoc_file, gem_source_path)
|
68
|
+
begin
|
69
|
+
YardImporter.import(yardoc_file, root_path: gem_source_path)
|
70
|
+
rescue => ex
|
71
|
+
STDERR.puts ex
|
72
|
+
STDERR.puts ex.backtrace
|
73
|
+
STDERR.puts "Failed to load #{yardoc_file}"
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [String, nil]
|
79
|
+
def yardoc_path
|
80
|
+
YARD::Registry.yardoc_file_for_gem(gem_name, gem_version)
|
81
|
+
rescue Bundler::BundlerError => ex
|
82
|
+
STDERR.puts ex
|
83
|
+
STDERR.puts ex.backtrace
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [String, nil]
|
88
|
+
def gem_path
|
89
|
+
@gem_path ||= begin
|
90
|
+
if spec = Gem.source_index.find_name(gem_name).first
|
91
|
+
spec.full_gem_path
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|