ruby-lsp-rails 0.2.3 → 0.2.4

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