ruby-lsp 0.0.1 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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