ruby-lsp 0.2.2 → 0.2.3

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