ruby-lsp-rails 0.2.3 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23121486fc120767731f9aa1e710d7e6a80ffa6a3832ea87cf3d142e6a3adb59
4
- data.tar.gz: 75d08ce0a40859d1d45a6244ba5efc4d582eef554df5ea36332d486190ec5e71
3
+ metadata.gz: 79a5ef8571e1fd77b8bbd08a782ef68f1116ce4b71e206eee890d573dd2b90dd
4
+ data.tar.gz: de8459cb09768c703b0f864f008238a40b78210acdef0e9eba8f4d4fd37c65d7
5
5
  SHA512:
6
- metadata.gz: 1b1ffcb3291ab359dbd37218902feb2294e4092f4380111ded16824deab8980f77ce86e87525d34f4debcf7daceca73bac4d655ad52f7668265df943e821d0f5
7
- data.tar.gz: 7c6d601aa7170cfa330079b1edae2c9d99bd70761c85b09eabdc0232b1789be88abe1451000bf308f57f2216ef6d8c57c29e8662d16b0b40b264952e60208ac3
6
+ metadata.gz: f0160d705fba4c081925b29efce3b2d8cc9a8243e8b9e53fa8a7820ffe62b5f0ecf12c73d5546b38f3289d807018f7bc7cbd82acbc296a2151c342ffcbf709be
7
+ data.tar.gz: 7d033dea267d94c5068bbbf4698e2c5b8bab63708021f78c67a90eed6b2e43c7a48e731a68fa5bd7c8d3a0c49a55eb600b14f1d40001f70fb050cbb5a538a7d4
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "support/rails_document_client"
5
+
4
6
  module RubyLsp
5
7
  module Rails
6
8
  # ![Hover demo](../../hover.gif)
@@ -29,7 +31,7 @@ module RubyLsp
29
31
 
30
32
  @response = T.let(nil, ResponseType)
31
33
  @client = client
32
- emitter.register(self, :on_const)
34
+ emitter.register(self, :on_const, :on_command, :on_const_path_ref, :on_call)
33
35
  end
34
36
 
35
37
  sig { params(node: SyntaxTree::Const).void }
@@ -46,6 +48,36 @@ module RubyLsp
46
48
  contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: content)
47
49
  @response = RubyLsp::Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
48
50
  end
51
+
52
+ sig { params(node: SyntaxTree::Command).void }
53
+ def on_command(node)
54
+ message = node.message
55
+ @response = generate_rails_document_link_hover(message.value, message)
56
+ end
57
+
58
+ sig { params(node: SyntaxTree::ConstPathRef).void }
59
+ def on_const_path_ref(node)
60
+ @response = generate_rails_document_link_hover(full_constant_name(node), node)
61
+ end
62
+
63
+ sig { params(node: SyntaxTree::CallNode).void }
64
+ def on_call(node)
65
+ message = node.message
66
+ return if message.is_a?(Symbol)
67
+
68
+ @response = generate_rails_document_link_hover(message.value, message)
69
+ end
70
+
71
+ private
72
+
73
+ sig { params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover)) }
74
+ def generate_rails_document_link_hover(name, node)
75
+ urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
76
+ return if urls.empty?
77
+
78
+ contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: urls.join("\n\n"))
79
+ RubyLsp::Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
80
+ end
49
81
  end
50
82
  end
51
83
  end
@@ -28,7 +28,10 @@ module RubyLsp
28
28
  url = URI(app_uri_path.read.chomp)
29
29
 
30
30
  @ssl = T.let(url.scheme == "https", T::Boolean)
31
- @uri = T.let(T.must(url.path), T.nilable(String))
31
+ @address = T.let(
32
+ [url.host, url.path].reject { |component| component.nil? || component.empty? }.join("/"),
33
+ T.nilable(String),
34
+ )
32
35
  @port = T.let(T.must(url.port).to_i, Integer)
33
36
  end
34
37
  end
@@ -62,9 +65,9 @@ module RubyLsp
62
65
 
63
66
  sig { params(path: String, timeout: T.nilable(Float)).returns(Net::HTTPResponse) }
64
67
  def request(path, timeout = nil)
65
- raise ServerAddressUnknown unless @uri
68
+ raise ServerAddressUnknown unless @address
66
69
 
67
- http = Net::HTTP.new(@uri, @port)
70
+ http = Net::HTTP.new(@address, @port)
68
71
  http.use_ssl = @ssl
69
72
  http.read_timeout = timeout if timeout
70
73
  http.get("/ruby_lsp_rails/#{path}")
@@ -0,0 +1,124 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "net/http"
5
+
6
+ module RubyLsp
7
+ module Rails
8
+ module Support
9
+ class RailsDocumentClient
10
+ RAILS_DOC_HOST = "https://api.rubyonrails.org"
11
+
12
+ SUPPORTED_RAILS_DOC_NAMESPACES = T.let(
13
+ Regexp.union(
14
+ /ActionDispatch/,
15
+ /ActionController/,
16
+ /AbstractController/,
17
+ /ActiveRecord/,
18
+ /ActiveModel/,
19
+ /ActiveStorage/,
20
+ /ActionText/,
21
+ /ActiveJob/,
22
+ ).freeze,
23
+ Regexp,
24
+ )
25
+
26
+ RAILTIES_VERSION = T.let(
27
+ [*::Gem::Specification.default_stubs, *::Gem::Specification.stubs].find do |s|
28
+ s.name == "railties"
29
+ end&.version&.to_s,
30
+ T.nilable(String),
31
+ )
32
+
33
+ class << self
34
+ extend T::Sig
35
+
36
+ sig { params(name: String).returns(T::Array[String]) }
37
+ def generate_rails_document_urls(name)
38
+ docs = search_index&.fetch(name, nil)
39
+
40
+ return [] unless docs
41
+
42
+ docs.map do |doc|
43
+ owner = doc[:owner]
44
+
45
+ link_name =
46
+ # class/module name
47
+ if owner == name
48
+ name
49
+ else
50
+ "#{owner}##{name}"
51
+ end
52
+
53
+ "[Rails Document: `#{link_name}`](#{doc[:url]})"
54
+ end
55
+ end
56
+
57
+ sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
58
+ private def search_index
59
+ @rails_documents ||= T.let(
60
+ build_search_index,
61
+ T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]]),
62
+ )
63
+ end
64
+
65
+ sig { returns(T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, String]]])) }
66
+ private def build_search_index
67
+ return unless RAILTIES_VERSION
68
+
69
+ warn("Fetching Rails Documents...")
70
+
71
+ response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/js/search_index.js"))
72
+
73
+ body = case response
74
+ when Net::HTTPSuccess
75
+ response.body
76
+ when Net::HTTPRedirection
77
+ # If the version's doc is not found, e.g. Rails main, it'll be redirected
78
+ # In this case, we just fetch the latest doc
79
+ response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/js/search_index.js"))
80
+ response.body if response.is_a?(Net::HTTPSuccess)
81
+ else
82
+ warn("Response failed: #{response.inspect}")
83
+ nil
84
+ end
85
+
86
+ process_search_index(body) if body
87
+ rescue StandardError => e
88
+ warn("Exception occurred when fetching Rails document index: #{e.inspect}")
89
+ end
90
+
91
+ sig { params(js: String).returns(T::Hash[String, T::Array[T::Hash[Symbol, String]]]) }
92
+ private def process_search_index(js)
93
+ raw_data = js.sub("var search_data = ", "")
94
+ info = JSON.parse(raw_data).dig("index", "info")
95
+
96
+ # An entry looks like this:
97
+ #
98
+ # ["belongs_to", # method or module/class
99
+ # "ActiveRecord::Associations::ClassMethods", # method owner
100
+ # "classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to", # path to the document
101
+ # "(name, scope = nil, **options)", # method's parameters
102
+ # "<p>Specifies a one-to-one association with another class..."] # document preview
103
+ #
104
+ info.each_with_object({}) do |(method_or_class, method_owner, doc_path, _, doc_preview), table|
105
+ # If a method doesn't have documentation, there's no need to generate the link to it.
106
+ next if doc_preview.nil? || doc_preview.empty?
107
+
108
+ # If the method or class/module is not from the supported namespace, reject it
109
+ next unless [method_or_class, method_owner].any? do |elem|
110
+ elem.match?(SUPPORTED_RAILS_DOC_NAMESPACES)
111
+ end
112
+
113
+ owner = method_owner.empty? ? method_or_class : method_owner
114
+ table[method_or_class] ||= []
115
+ # It's possible to have multiple modules defining the same method name. For example,
116
+ # both `ActiveRecord::FinderMethods` and `ActiveRecord::Associations::CollectionProxy` defines `#find`
117
+ table[method_or_class] << { owner: owner, url: "#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/#{doc_path}" }
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.2.3"
6
+ VERSION = "0.2.4"
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-09 00:00:00.000000000 Z
11
+ date: 2023-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.8.0
33
+ version: 0.9.1
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: 0.9.0
36
+ version: 0.10.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.8.0
43
+ version: 0.9.1
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.9.0
46
+ version: 0.10.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +73,7 @@ files:
73
73
  - lib/ruby_lsp/ruby_lsp_rails/extension.rb
74
74
  - lib/ruby_lsp/ruby_lsp_rails/hover.rb
75
75
  - lib/ruby_lsp/ruby_lsp_rails/rails_client.rb
76
+ - lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
76
77
  - lib/ruby_lsp_rails/rack_app.rb
77
78
  - lib/ruby_lsp_rails/railtie.rb
78
79
  - lib/ruby_lsp_rails/version.rb
@@ -100,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
101
  - !ruby/object:Gem::Version
101
102
  version: '0'
102
103
  requirements: []
103
- rubygems_version: 3.4.17
104
+ rubygems_version: 3.4.18
104
105
  signing_key:
105
106
  specification_version: 4
106
107
  summary: A Ruby LSP extension for Rails