yoda-language-server 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|