ruby-lsp 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fefb041d92fb2000d3da23248ed622abd596a95264b421349f27d104b85f997
4
- data.tar.gz: 312918f128e0a49a2f5b2066354df2fe45f9ed37af5e5eace6c76caa101155e5
3
+ metadata.gz: 96c21e90970f4cbf198609a42471cf554ce86a3b6af9296ded95994ad373317a
4
+ data.tar.gz: a28aa2c13ba4d79f53082160b0eca7e8461efad831b6c6cca2a6a4d34765a6c0
5
5
  SHA512:
6
- metadata.gz: 7ab96bd356fe94e3a95572bec24053eed9b467c71acec431a80c600e87ea5a3c31f0f2aee20c7b3b407b5aabc9b5736e0bb385dedf2840747c892fe7ad090b69
7
- data.tar.gz: 2ec3f3c88c851e86e7ef6485c7719a271dbda81717990446900fe121844daac905b5f554833b59769a4c6f626bb50579d5e51970e7a306d206c898d0a289d36c
6
+ metadata.gz: 5ab1bf48ae4943a710f34ef0aace64c1d8991271bdca71482a2367c3a103299c52ebaef203fa32945236d689dda67d2bb1b5cc5ca9ffa698f5dd057dd62e242f
7
+ data.tar.gz: 8b9453d71b65634e1ea275f3a2552548f530dc144acd18cdcbcee493f153b824a13bc60bbbe5ac937a993d9e9c56ce8cb70773d354b5cea7acda3f4f44722ffd
data/CHANGELOG.md CHANGED
@@ -6,6 +6,10 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.3]
10
+
11
+ - Resolve generic source URIs for jump to gem source (https://github.com/Shopify/ruby-lsp/pull/237)
12
+
9
13
  ## [0.2.2]
10
14
 
11
15
  - Support document links (https://github.com/Shopify/ruby-lsp/pull/195)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.2.3
@@ -1,30 +1,78 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/source_uri"
5
+
4
6
  module RubyLsp
5
7
  module Requests
6
8
  # ![Document link demo](../../misc/document_link.gif)
7
9
  #
8
10
  # The [document link](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentLink)
9
- # makes `# source://PATH_TO_FILE:line` comments in a Ruby/RBI file clickable if the file exists.
11
+ # makes `# source://PATH_TO_FILE#line` comments in a Ruby/RBI file clickable if the file exists.
10
12
  # When the user clicks the link, it'll open that location.
11
13
  #
12
14
  # # Example
13
15
  #
14
16
  # ```ruby
15
- # # source://syntax_tree-3.2.1/lib/syntax_tree.rb:51 <- it will be clickable and will take the user to that location
17
+ # # source://syntax_tree/3.2.1/lib/syntax_tree.rb#51 <- it will be clickable and will take the user to that location
16
18
  # def format(source, maxwidth = T.unsafe(nil))
17
19
  # end
18
20
  # ```
19
21
  class DocumentLink < BaseRequest
20
22
  extend T::Sig
21
23
 
22
- RUBY_ROOT = "RUBY_ROOT"
24
+ GEM_TO_VERSION_MAP = T.let(
25
+ [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].map! do |s|
26
+ [s.name, s.version.to_s]
27
+ end.to_h.freeze,
28
+ T::Hash[String, String]
29
+ )
30
+
31
+ class << self
32
+ extend T::Sig
33
+
34
+ sig { returns(T::Hash[String, T::Array[String]]) }
35
+ def gem_paths
36
+ @gem_paths ||= T.let(begin
37
+ lookup = {}
38
+
39
+ Gem::Specification.stubs.each do |stub|
40
+ spec = stub.to_spec
41
+ lookup[spec.name] = {}
42
+ lookup[spec.name][spec.version.to_s] = {}
43
+
44
+ Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
45
+ lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
46
+ end
47
+ end
23
48
 
24
- sig { params(document: Document).void }
25
- def initialize(document)
26
- super
49
+ Gem::Specification.default_stubs.each do |stub|
50
+ spec = stub.to_spec
51
+ lookup[spec.name] = {}
52
+ lookup[spec.name][spec.version.to_s] = {}
53
+ prefix_matchers = [//]
54
+ prefix_matchers.concat(spec.require_paths.map { |rp| Regexp.new("^#{rp}/") })
55
+ prefix_matcher = Regexp.union(prefix_matchers)
56
+
57
+ spec.files.each do |file|
58
+ path = file.sub(prefix_matcher, "")
59
+ lookup[spec.name][spec.version.to_s][path] = "#{RbConfig::CONFIG["rubylibdir"]}/#{path}"
60
+ end
61
+ end
62
+
63
+ lookup
64
+ end, T.nilable(T::Hash[String, T::Array[String]]))
65
+ end
66
+ end
27
67
 
68
+ sig { params(uri: String, document: Document).void }
69
+ def initialize(uri, document)
70
+ super(document)
71
+
72
+ # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
73
+ # in the URI
74
+ version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(uri)
75
+ @gem_version = T.let(version_match && version_match[0], T.nilable(String))
28
76
  @links = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentLink])
29
77
  end
30
78
 
@@ -36,24 +84,36 @@ module RubyLsp
36
84
 
37
85
  sig { params(node: SyntaxTree::Comment).void }
38
86
  def visit_comment(node)
39
- match = node.value.match(%r{source://(?<path>.*):(?<line>\d+)$})
87
+ match = node.value.match(%r{source://.*#\d+$})
40
88
  return unless match
41
89
 
42
- file_path = if match[:path].start_with?(RUBY_ROOT)
43
- match[:path].sub(RUBY_ROOT, RbConfig::CONFIG["rubylibdir"])
44
- else
45
- File.join(Bundler.bundle_path, "gems", match[:path])
46
- end
47
- return unless File.exist?(file_path)
48
-
49
- target = "file://#{file_path}##{match[:line]}"
90
+ uri = T.cast(URI(match[0]), URI::Source)
91
+ gem_version = resolve_version(uri)
92
+ file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, uri.path)
93
+ return if file_path.nil?
50
94
 
51
95
  @links << LanguageServer::Protocol::Interface::DocumentLink.new(
52
96
  range: range_from_syntax_tree_node(node),
53
- target: target,
54
- tooltip: "Jump to #{target.delete_prefix("file://")}"
97
+ target: "file://#{file_path}##{uri.line_number}",
98
+ tooltip: "Jump to #{file_path}##{uri.line_number}"
55
99
  )
56
100
  end
101
+
102
+ private
103
+
104
+ # Try to figure out the gem version for a source:// link. The order of precedence is:
105
+ # 1. The version in the URI
106
+ # 2. The version in the RBI file name
107
+ # 3. The version from the gemspec
108
+ sig { params(uri: URI::Source).returns(T.nilable(String)) }
109
+ def resolve_version(uri)
110
+ version = uri.gem_version
111
+ return version unless version.nil? || version.empty?
112
+
113
+ return @gem_version unless @gem_version.nil? || @gem_version.empty?
114
+
115
+ GEM_TO_VERSION_MAP[uri.gem_name]
116
+ end
57
117
  end
58
118
  end
59
119
  end
@@ -0,0 +1,82 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "uri/file"
5
+
6
+ module URI
7
+ # Must be kept in sync with the one in Tapioca
8
+ class Source < URI::File
9
+ extend T::Sig
10
+
11
+ COMPONENT = T.let([
12
+ :scheme,
13
+ :gem_name,
14
+ :gem_version,
15
+ :path,
16
+ :line_number,
17
+ ].freeze, T::Array[Symbol])
18
+
19
+ T.unsafe(self).alias_method(:gem_name, :host)
20
+ T.unsafe(self).alias_method(:line_number, :fragment)
21
+
22
+ sig { returns(T.nilable(String)) }
23
+ attr_reader :gem_version
24
+
25
+ class << self
26
+ extend T::Sig
27
+
28
+ sig do
29
+ params(
30
+ gem_name: String,
31
+ gem_version: T.nilable(String),
32
+ path: String,
33
+ line_number: T.nilable(String)
34
+ ).returns(URI::Source)
35
+ end
36
+ def build(gem_name:, gem_version:, path:, line_number:)
37
+ super(
38
+ {
39
+ scheme: "source",
40
+ host: gem_name,
41
+ path: DEFAULT_PARSER.escape("/#{gem_version}/#{path}"),
42
+ fragment: line_number,
43
+ }
44
+ )
45
+ end
46
+ end
47
+
48
+ sig { params(v: T.nilable(String)).void }
49
+ def set_path(v) # rubocop:disable Naming/AccessorMethodName
50
+ return if v.nil?
51
+
52
+ gem_version, path = v.delete_prefix("/").split("/", 2)
53
+
54
+ @gem_version = T.let(gem_version, T.nilable(String))
55
+ @path = T.let(path, T.nilable(String))
56
+ end
57
+
58
+ sig { params(v: T.nilable(String)).returns(T::Boolean) }
59
+ def check_host(v)
60
+ return true unless v
61
+
62
+ if /[A-Za-z][A-Za-z0-9\-_]*/ !~ v
63
+ raise InvalidComponentError,
64
+ "bad component(expected gem name): #{v}"
65
+ end
66
+
67
+ true
68
+ end
69
+
70
+ sig { returns(String) }
71
+ def to_s
72
+ "source://#{gem_name}/#{gem_version}#{path}##{line_number}"
73
+ end
74
+
75
+ if URI.respond_to?(:register_scheme)
76
+ URI.register_scheme("SOURCE", self)
77
+ else
78
+ @@schemes = T.let(@@schemes, T::Hash[String, Class]) # rubocop:disable Style/ClassVars
79
+ @@schemes["SOURCE"] = self
80
+ end
81
+ end
82
+ end
@@ -98,8 +98,9 @@ module RubyLsp
98
98
  end
99
99
 
100
100
  on("textDocument/documentLink") do |request|
101
- store.cache_fetch(request.dig(:params, :textDocument, :uri), :document_link) do |document|
102
- RubyLsp::Requests::DocumentLink.new(document).run
101
+ uri = request.dig(:params, :textDocument, :uri)
102
+ store.cache_fetch(uri, :document_link) do |document|
103
+ RubyLsp::Requests::DocumentLink.new(uri, document).run
103
104
  end
104
105
  end
105
106
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
11
+ date: 2022-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -86,6 +86,7 @@ files:
86
86
  - lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb
87
87
  - lib/ruby_lsp/requests/support/selection_range.rb
88
88
  - lib/ruby_lsp/requests/support/semantic_token_encoder.rb
89
+ - lib/ruby_lsp/requests/support/source_uri.rb
89
90
  - lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb
90
91
  - lib/ruby_lsp/server.rb
91
92
  - lib/ruby_lsp/store.rb