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.
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