ruby-lsp 0.0.1 → 0.0.4

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +7 -16
  3. data/.github/pull_request_template.md +15 -0
  4. data/.github/workflows/ci.yml +31 -0
  5. data/.github/workflows/publish_docs.yml +32 -0
  6. data/.gitignore +9 -12
  7. data/.rubocop.yml +20 -2
  8. data/.vscode/settings.json +5 -0
  9. data/CHANGELOG.md +29 -0
  10. data/Gemfile +8 -4
  11. data/Gemfile.lock +76 -14
  12. data/README.md +69 -2
  13. data/Rakefile +5 -0
  14. data/VERSION +1 -1
  15. data/bin/tapioca +29 -0
  16. data/bin/test +7 -1
  17. data/dev.yml +7 -7
  18. data/exe/ruby-lsp +19 -2
  19. data/lib/internal.rb +7 -0
  20. data/lib/ruby-lsp.rb +4 -1
  21. data/lib/ruby_lsp/cli.rb +88 -0
  22. data/lib/ruby_lsp/document.rb +113 -0
  23. data/lib/ruby_lsp/handler.rb +236 -0
  24. data/lib/ruby_lsp/requests/base_request.rb +33 -0
  25. data/lib/ruby_lsp/requests/code_actions.rb +37 -0
  26. data/lib/ruby_lsp/requests/diagnostics.rb +37 -0
  27. data/lib/ruby_lsp/requests/document_highlight.rb +96 -0
  28. data/lib/ruby_lsp/requests/document_symbol.rb +216 -0
  29. data/lib/ruby_lsp/requests/folding_ranges.rb +213 -0
  30. data/lib/ruby_lsp/requests/formatting.rb +52 -0
  31. data/lib/ruby_lsp/requests/rubocop_request.rb +50 -0
  32. data/lib/ruby_lsp/requests/selection_ranges.rb +103 -0
  33. data/lib/ruby_lsp/requests/semantic_highlighting.rb +112 -0
  34. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +88 -0
  35. data/lib/ruby_lsp/requests/support/selection_range.rb +17 -0
  36. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +60 -0
  37. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +27 -0
  38. data/lib/ruby_lsp/requests.rb +24 -0
  39. data/lib/ruby_lsp/store.rb +59 -0
  40. data/rakelib/check_docs.rake +56 -0
  41. data/ruby-lsp.gemspec +5 -1
  42. data/{shipit.yml → shipit.production.yml} +0 -0
  43. data/sorbet/config +4 -0
  44. data/sorbet/rbi/.rubocop.yml +8 -0
  45. data/sorbet/rbi/gems/ansi@1.5.0.rbi +338 -0
  46. data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
  47. data/sorbet/rbi/gems/builder@3.2.4.rbi +418 -0
  48. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  49. data/sorbet/rbi/gems/debug@1.5.0.rbi +1273 -0
  50. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +867 -0
  51. data/sorbet/rbi/gems/io-console@0.5.11.rbi +8 -0
  52. data/sorbet/rbi/gems/irb@1.4.1.rbi +376 -0
  53. data/sorbet/rbi/gems/language_server-protocol@3.16.0.3.rbi +7325 -0
  54. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  55. data/sorbet/rbi/gems/minitest-reporters@1.5.0.rbi +612 -0
  56. data/sorbet/rbi/gems/minitest@5.15.0.rbi +994 -0
  57. data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
  58. data/sorbet/rbi/gems/parser@3.1.2.0.rbi +3968 -0
  59. data/sorbet/rbi/gems/prettier_print@0.1.0.rbi +734 -0
  60. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  61. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +227 -0
  62. data/sorbet/rbi/gems/rake@13.0.6.rbi +1853 -0
  63. data/sorbet/rbi/gems/rbi@0.0.14.rbi +2337 -0
  64. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +1854 -0
  65. data/sorbet/rbi/gems/reline@0.3.1.rbi +1274 -0
  66. data/sorbet/rbi/gems/rexml@3.2.5.rbi +3852 -0
  67. data/sorbet/rbi/gems/rubocop-ast@1.18.0.rbi +4180 -0
  68. data/sorbet/rbi/gems/rubocop-minitest@0.20.0.rbi +1369 -0
  69. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +246 -0
  70. data/sorbet/rbi/gems/rubocop-shopify@2.6.0.rbi +8 -0
  71. data/sorbet/rbi/gems/rubocop-sorbet@0.6.8.rbi +652 -0
  72. data/sorbet/rbi/gems/rubocop@1.30.0.rbi +36729 -0
  73. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +732 -0
  74. data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
  75. data/sorbet/rbi/gems/syntax_tree@2.7.1.rbi +6777 -0
  76. data/sorbet/rbi/gems/tapioca@0.8.1.rbi +1972 -0
  77. data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
  78. data/sorbet/rbi/gems/unicode-display_width@2.1.0.rbi +27 -0
  79. data/sorbet/rbi/gems/unparser@0.6.5.rbi +2789 -0
  80. data/sorbet/rbi/gems/webrick@1.7.0.rbi +1779 -0
  81. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +289 -0
  82. data/sorbet/rbi/gems/yard@0.9.27.rbi +13048 -0
  83. data/sorbet/rbi/shims/fiddle.rbi +4 -0
  84. data/sorbet/rbi/shims/hash.rbi +6 -0
  85. data/sorbet/rbi/shims/rdoc.rbi +4 -0
  86. data/sorbet/tapioca/config.yml +13 -0
  87. data/sorbet/tapioca/require.rb +7 -0
  88. metadata +119 -9
  89. data/.vscode/launch.json +0 -19
  90. data/bin/package_extension +0 -5
  91. data/bin/style +0 -10
  92. data/lib/ruby/lsp/cli.rb +0 -37
  93. data/lib/ruby/lsp.rb +0 -3
@@ -0,0 +1,112 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # The [semantic
7
+ # highlighting](https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens)
8
+ # request informs the editor of the correct token types to provide consistent and accurate highlighting for themes.
9
+ #
10
+ # # Example
11
+ #
12
+ # ```ruby
13
+ # def foo
14
+ # var = 1 # --> semantic highlighting: local variable
15
+ # some_invocation # --> semantic highlighting: method invocation
16
+ # var # --> semantic highlighting: local variable
17
+ # end
18
+ # ```
19
+ class SemanticHighlighting < BaseRequest
20
+ TOKEN_TYPES = [
21
+ :variable,
22
+ :method,
23
+ ].freeze
24
+
25
+ TOKEN_MODIFIERS = {
26
+ declaration: 0,
27
+ definition: 1,
28
+ readonly: 2,
29
+ static: 3,
30
+ deprecated: 4,
31
+ abstract: 5,
32
+ async: 6,
33
+ modification: 7,
34
+ documentation: 8,
35
+ default_library: 9,
36
+ }.freeze
37
+
38
+ SemanticToken = Struct.new(:location, :length, :type, :modifier)
39
+
40
+ def initialize(document, encoder: nil)
41
+ super(document)
42
+
43
+ @encoder = encoder
44
+ @tokens = []
45
+ @tree = document.tree
46
+ end
47
+
48
+ def run
49
+ visit(@tree)
50
+ return @tokens unless @encoder
51
+
52
+ @encoder.encode(@tokens)
53
+ end
54
+
55
+ def visit_m_assign(node)
56
+ node.target.parts.each do |var_ref|
57
+ add_token(var_ref.value.location, :variable)
58
+ end
59
+ end
60
+
61
+ def visit_var_field(node)
62
+ case node.value
63
+ when SyntaxTree::Ident
64
+ add_token(node.value.location, :variable)
65
+ end
66
+ end
67
+
68
+ def visit_var_ref(node)
69
+ case node.value
70
+ when SyntaxTree::Ident
71
+ add_token(node.value.location, :variable)
72
+ end
73
+ end
74
+
75
+ def visit_a_ref_field(node)
76
+ add_token(node.collection.value.location, :variable)
77
+ end
78
+
79
+ def visit_call(node)
80
+ visit(node.receiver)
81
+ add_token(node.message.location, :method)
82
+ visit(node.arguments)
83
+ end
84
+
85
+ def visit_command(node)
86
+ add_token(node.message.location, :method)
87
+ visit(node.arguments)
88
+ end
89
+
90
+ def visit_command_call(node)
91
+ visit(node.receiver)
92
+ add_token(node.message.location, :method)
93
+ visit(node.arguments)
94
+ end
95
+
96
+ def visit_fcall(node)
97
+ add_token(node.value.location, :method)
98
+ visit(node.arguments)
99
+ end
100
+
101
+ def visit_vcall(node)
102
+ add_token(node.value.location, :method)
103
+ end
104
+
105
+ def add_token(location, type, modifiers = [])
106
+ length = location.end_char - location.start_char
107
+ modifiers_indices = modifiers.filter_map { |modifier| TOKEN_MODIFIERS[modifier] }
108
+ @tokens.push(SemanticToken.new(location, length, TOKEN_TYPES.index(type), modifiers_indices))
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,88 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class RuboCopDiagnostic
8
+ RUBOCOP_TO_LSP_SEVERITY = {
9
+ convention: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
10
+ info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
11
+ refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
12
+ warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
13
+ error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
14
+ fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
15
+ }.freeze
16
+
17
+ attr_reader :replacements
18
+
19
+ def initialize(offense, uri)
20
+ @offense = offense
21
+ @uri = uri
22
+ @replacements = offense.correctable? ? offense_replacements : []
23
+ end
24
+
25
+ def correctable?
26
+ @offense.correctable?
27
+ end
28
+
29
+ def in_range?(range)
30
+ range.cover?(@offense.line - 1)
31
+ end
32
+
33
+ def to_lsp_code_action
34
+ LanguageServer::Protocol::Interface::CodeAction.new(
35
+ title: "Autocorrect #{@offense.cop_name}",
36
+ kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
37
+ edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
38
+ document_changes: [
39
+ LanguageServer::Protocol::Interface::TextDocumentEdit.new(
40
+ text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
41
+ uri: @uri,
42
+ version: nil
43
+ ),
44
+ edits: @replacements
45
+ ),
46
+ ]
47
+ ),
48
+ is_preferred: true,
49
+ )
50
+ end
51
+
52
+ def to_lsp_diagnostic
53
+ LanguageServer::Protocol::Interface::Diagnostic.new(
54
+ message: @offense.message,
55
+ source: "RuboCop",
56
+ code: @offense.cop_name,
57
+ severity: RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name],
58
+ range: LanguageServer::Protocol::Interface::Range.new(
59
+ start: LanguageServer::Protocol::Interface::Position.new(
60
+ line: @offense.line - 1,
61
+ character: @offense.column
62
+ ),
63
+ end: LanguageServer::Protocol::Interface::Position.new(
64
+ line: @offense.last_line - 1,
65
+ character: @offense.last_column
66
+ )
67
+ )
68
+ )
69
+ end
70
+
71
+ private
72
+
73
+ def offense_replacements
74
+ @offense.corrector.as_replacements.map do |range, replacement|
75
+ LanguageServer::Protocol::Interface::TextEdit.new(
76
+ range: LanguageServer::Protocol::Interface::Range.new(
77
+ start: LanguageServer::Protocol::Interface::Position.new(line: range.line - 1, character: range.column),
78
+ end: LanguageServer::Protocol::Interface::Position.new(line: range.last_line - 1,
79
+ character: range.last_column)
80
+ ),
81
+ new_text: replacement
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class SelectionRange < LanguageServer::Protocol::Interface::SelectionRange
8
+ def cover?(position)
9
+ line_range = (range.start.line..range.end.line)
10
+ character_range = (range.start.character..range.end.character)
11
+
12
+ line_range.cover?(position[:line]) && character_range.cover?(position[:character])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class SemanticTokenEncoder
8
+ def initialize
9
+ @current_row = 0
10
+ @current_column = 0
11
+ end
12
+
13
+ def encode(tokens)
14
+ delta = tokens
15
+ .sort_by do |token|
16
+ [token.location.start_line, token.location.start_column]
17
+ end
18
+ .flat_map do |token|
19
+ compute_delta(token)
20
+ end
21
+
22
+ LanguageServer::Protocol::Interface::SemanticTokens.new(data: delta)
23
+ end
24
+
25
+ # The delta array is computed according to the LSP specification:
26
+ # > The protocol for the token format relative uses relative
27
+ # > positions, because most tokens remain stable relative to
28
+ # > each other when edits are made in a file. This simplifies
29
+ # > the computation of a delta if a server supports it. So each
30
+ # > token is represented using 5 integers.
31
+
32
+ # For more information on how each number is calculated, read:
33
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
34
+ def compute_delta(token)
35
+ row = token.location.start_line - 1
36
+ column = token.location.start_column
37
+ delta_line = row - @current_row
38
+
39
+ delta_column = column
40
+ delta_column -= @current_column if delta_line == 0
41
+
42
+ [delta_line, delta_column, token.length, token.type, encode_modifiers(token.modifier)]
43
+ ensure
44
+ @current_row = row
45
+ @current_column = column
46
+ end
47
+
48
+ # Encode an array of modifiers to positions onto a bit flag
49
+ # For example, [:default_library] will be encoded as
50
+ # 0b1000000000, as :default_library is the 10th bit according
51
+ # to the token modifiers index map.
52
+ def encode_modifiers(modifiers)
53
+ modifiers.inject(0) do |encoded_modifiers, modifier|
54
+ encoded_modifiers | (1 << modifier)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ class SyntaxErrorDiagnostic
8
+ def initialize(edit)
9
+ @edit = edit
10
+ end
11
+
12
+ def correctable?
13
+ false
14
+ end
15
+
16
+ def to_lsp_diagnostic
17
+ LanguageServer::Protocol::Interface::Diagnostic.new(
18
+ message: "Syntax error",
19
+ source: "SyntaxTree",
20
+ severity: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
21
+ range: @edit[:range]
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ autoload :BaseRequest, "ruby_lsp/requests/base_request"
7
+ autoload :DocumentSymbol, "ruby_lsp/requests/document_symbol"
8
+ autoload :FoldingRanges, "ruby_lsp/requests/folding_ranges"
9
+ autoload :SelectionRanges, "ruby_lsp/requests/selection_ranges"
10
+ autoload :SemanticHighlighting, "ruby_lsp/requests/semantic_highlighting"
11
+ autoload :RuboCopRequest, "ruby_lsp/requests/rubocop_request"
12
+ autoload :Formatting, "ruby_lsp/requests/formatting"
13
+ autoload :Diagnostics, "ruby_lsp/requests/diagnostics"
14
+ autoload :CodeActions, "ruby_lsp/requests/code_actions"
15
+ autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
16
+
17
+ module Support
18
+ autoload :RuboCopDiagnostic, "ruby_lsp/requests/support/rubocop_diagnostic"
19
+ autoload :SelectionRange, "ruby_lsp/requests/support/selection_range"
20
+ autoload :SemanticTokenEncoder, "ruby_lsp/requests/support/semantic_token_encoder"
21
+ autoload :SyntaxErrorDiagnostic, "ruby_lsp/requests/support/syntax_error_diagnostic"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,59 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "cgi"
5
+ require "uri"
6
+ require "ruby_lsp/document"
7
+
8
+ module RubyLsp
9
+ class Store
10
+ extend T::Sig
11
+
12
+ sig { void }
13
+ def initialize
14
+ @state = T.let({}, T::Hash[String, Document])
15
+ end
16
+
17
+ sig { params(uri: String).returns(Document) }
18
+ def get(uri)
19
+ document = @state[uri]
20
+ return document unless document.nil?
21
+
22
+ set(uri, File.binread(CGI.unescape(URI.parse(uri).path)))
23
+ T.must(@state[uri])
24
+ end
25
+
26
+ sig { params(uri: String, content: String).void }
27
+ def set(uri, content)
28
+ @state[uri] = Document.new(content)
29
+ rescue SyntaxTree::Parser::ParseError
30
+ # Do not update the store if there are syntax errors
31
+ end
32
+
33
+ sig { params(uri: String, edits: T::Array[Document::EditShape]).void }
34
+ def push_edits(uri, edits)
35
+ T.must(@state[uri]).push_edits(edits)
36
+ end
37
+
38
+ sig { void }
39
+ def clear
40
+ @state.clear
41
+ end
42
+
43
+ sig { params(uri: String).void }
44
+ def delete(uri)
45
+ @state.delete(uri)
46
+ end
47
+
48
+ sig do
49
+ params(
50
+ uri: String,
51
+ request_name: Symbol,
52
+ block: T.proc.params(document: Document).returns(T.untyped)
53
+ ).returns(T.untyped)
54
+ end
55
+ def cache_fetch(uri, request_name, &block)
56
+ get(uri).cache_fetch(request_name, &block)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,56 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ desc "Check if all LSP requests are documented"
5
+ task :check_docs do
6
+ require "language_server-protocol"
7
+ require "syntax_tree"
8
+ require "logger"
9
+ require "ruby_lsp/requests/base_request"
10
+ require "ruby_lsp/requests/rubocop_request"
11
+
12
+ Dir["#{Dir.pwd}/lib/ruby_lsp/requests/*.rb"].each do |file|
13
+ require(file)
14
+ YARD.parse(file, [], Logger::Severity::FATAL)
15
+ end
16
+
17
+ spec_matcher = %r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}
18
+ error_messages = RubyLsp::Requests
19
+ .constants # rubocop:disable Sorbet/ConstantsFromStrings
20
+ .each_with_object(Hash.new { |h, k| h[k] = [] }) do |request, errors|
21
+ full_name = "RubyLsp::Requests::#{request}"
22
+ docs = YARD::Registry.at(full_name).docstring
23
+ next if /:nodoc:/.match?(docs)
24
+
25
+ if docs.empty?
26
+ errors[full_name] << "Missing documentation for request handler class"
27
+ elsif !spec_matcher.match?(docs)
28
+ errors[full_name] << <<~MESSAGE
29
+ Documentation for request handler classes must link to the official LSP specification.
30
+
31
+ For example, if your request handles text document hover, you should add a link to
32
+ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover.
33
+ MESSAGE
34
+ elsif !/# Example/.match?(docs)
35
+ errors[full_name] << <<~MESSAGE
36
+ Documentation for request handler class must contain an example.
37
+
38
+ = Example
39
+ def my_method # <-- something happens here
40
+ end
41
+ MESSAGE
42
+ end
43
+ end
44
+
45
+ formatted_errors = error_messages.map { |name, errors| "#{name}: #{errors.join(", ")}" }
46
+
47
+ if error_messages.any?
48
+ puts <<~MESSAGE
49
+ The following requests have invalid documentation:
50
+
51
+ #{formatted_errors.join("\n")}
52
+ MESSAGE
53
+
54
+ exit!
55
+ end
56
+ end
data/ruby-lsp.gemspec CHANGED
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.summary = "A simple language server for ruby"
11
11
  s.description = "A simple language server for ruby"
12
12
  s.homepage = "https://github.com/Shopify/ruby-lsp"
13
+ s.license = "MIT"
13
14
 
14
15
  s.files = Dir.chdir(File.expand_path(__dir__)) do
15
16
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
@@ -18,5 +19,8 @@ Gem::Specification.new do |s|
18
19
  s.executables = s.files.grep(/\Aexe/) { |f| File.basename(f) }
19
20
  s.require_paths = ["lib"]
20
21
 
21
- s.add_dependency "language_server-protocol"
22
+ s.add_dependency("language_server-protocol")
23
+ s.add_dependency("rubocop", ">= 1.0")
24
+ s.add_dependency("sorbet-runtime")
25
+ s.add_dependency("syntax_tree", ">= 2.3")
22
26
  end
File without changes
data/sorbet/config ADDED
@@ -0,0 +1,4 @@
1
+ --dir
2
+ .
3
+ --ignore=vendor/
4
+ --ignore=test/fixtures/
@@ -0,0 +1,8 @@
1
+ inherit_gem:
2
+ rubocop-sorbet: config/rbi.yml
3
+
4
+ Lint/EmptyFile:
5
+ Enabled: false
6
+
7
+ Sorbet/TypeAliasName:
8
+ Enabled: false