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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -3
  3. data/README.md +50 -48
  4. data/client/atom/main.js +13 -3
  5. data/client/vscode/.vscode/launch.json +7 -4
  6. data/client/vscode/package-lock.json +585 -1454
  7. data/client/vscode/package.json +10 -7
  8. data/client/vscode/src/extension.ts +3 -3
  9. data/client/vscode/src/test/completion.test.ts +39 -0
  10. data/client/vscode/src/test/helper.ts +38 -0
  11. data/client/vscode/src/test/index.ts +5 -3
  12. data/client/vscode/testFixture/completion.rb +1 -0
  13. data/exe/yoda +1 -20
  14. data/lib/yoda.rb +2 -1
  15. data/lib/yoda/commands.rb +34 -0
  16. data/lib/yoda/commands/base.rb +10 -0
  17. data/lib/yoda/commands/complete.rb +36 -0
  18. data/lib/yoda/commands/file_cursor_parsable.rb +29 -0
  19. data/lib/yoda/{runner → commands}/infer.rb +4 -9
  20. data/lib/yoda/commands/setup.rb +37 -0
  21. data/lib/yoda/errors.rb +34 -0
  22. data/lib/yoda/evaluation/evaluator.rb +2 -0
  23. data/lib/yoda/server.rb +60 -15
  24. data/lib/yoda/server/completion_provider.rb +8 -8
  25. data/lib/yoda/server/definition_provider.rb +8 -8
  26. data/lib/yoda/server/hover_provider.rb +6 -6
  27. data/lib/yoda/server/initialization_provider.rb +85 -0
  28. data/lib/yoda/server/{client_info.rb → session.rb} +6 -2
  29. data/lib/yoda/server/signature_provider.rb +6 -6
  30. data/lib/yoda/store/actions.rb +3 -1
  31. data/lib/yoda/store/actions/build_core_index.rb +44 -0
  32. data/lib/yoda/store/actions/import_core_library.rb +14 -9
  33. data/lib/yoda/store/actions/import_gem.rb +98 -0
  34. data/lib/yoda/store/actions/import_std_library.rb +35 -0
  35. data/lib/yoda/store/adapters.rb +1 -0
  36. data/lib/yoda/store/adapters/memory_adapter.rb +81 -0
  37. data/lib/yoda/store/objects.rb +4 -0
  38. data/lib/yoda/store/objects/addressable.rb +0 -12
  39. data/lib/yoda/store/objects/base.rb +4 -14
  40. data/lib/yoda/store/objects/merger.rb +4 -4
  41. data/lib/yoda/store/objects/patchable.rb +19 -0
  42. data/lib/yoda/store/objects/project_status.rb +169 -0
  43. data/lib/yoda/store/objects/serializable.rb +39 -0
  44. data/lib/yoda/store/project.rb +28 -114
  45. data/lib/yoda/store/project/cache.rb +79 -0
  46. data/lib/yoda/store/project/library_doc_loader.rb +102 -0
  47. data/lib/yoda/store/query/find_constant.rb +2 -1
  48. data/lib/yoda/store/registry.rb +15 -0
  49. data/lib/yoda/store/yard_importer.rb +58 -28
  50. data/lib/yoda/typing/evaluator.rb +8 -5
  51. data/lib/yoda/version.rb +1 -1
  52. data/package.json +32 -11
  53. data/scripts/benchmark.rb +1 -1
  54. data/yoda-language-server.gemspec +1 -0
  55. metadata +37 -7
  56. data/client/vscode/src/test/extension.test.ts +0 -22
  57. data/lib/yoda/runner/setup.rb +0 -26
  58. 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 ClientInfo
5
- attr_reader :client_info
4
+ # @type Session
5
+ attr_reader :session
6
6
 
7
- # @param client_info [ClientInfo]
8
- def initialize(client_info)
9
- @client_info = client_info
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 = client_info.file_store.get(uri)
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(client_info.registry, ast, comments, location)
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(client_info.registry, cut_source, location)
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 ClientInfo
5
- attr_reader :client_info
4
+ # @type Session
5
+ attr_reader :session
6
6
 
7
- # @param client_info [ClientInfo]
8
- def initialize(client_info)
9
- @client_info = client_info
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 = client_info.file_store.get(uri)
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(client_info.registry, source, location)
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: client_info.uri_of_path(path),
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 :client_info
4
+ attr_reader :session
5
5
 
6
- # @param client_info [ClientInfo]
7
- def initialize(client_info)
8
- @client_info = client_info
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 = client_info.file_store.get(uri)
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(client_info.registry, source, location)
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 ClientInfo
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
- project.setup
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 :client_info
4
+ attr_reader :session
5
5
 
6
- # @param client_info [ClientInfo]
7
- def initialize(client_info)
8
- @client_info = client_info
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 = client_info.file_store.get(uri)
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(client_info.registry, cut_source, location)
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)
@@ -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/import_gems'
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
- load_core_patches.each do |patch|
14
- registry.add_patch(patch)
15
- end
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 load_core_patches
21
- core_doc_files.map { |yardoc_file| YardImporter.import(yardoc_file) }
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