ruby-lsp-rails 0.2.8 → 0.3.0
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 +4 -4
- data/README.md +10 -13
- data/lib/ruby-lsp-rails.rb +0 -3
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +15 -13
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +14 -14
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +25 -34
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +84 -0
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +87 -0
- data/lib/ruby_lsp_rails/railtie.rb +4 -23
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +9 -9
- data/lib/ruby_lsp/ruby_lsp_rails/rails_client.rb +0 -77
- data/lib/ruby_lsp_rails/rack_app.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecc8c1b4cd4569bdb9c816f842a90f0dfb1122931adcd9c96d1e2eb8a3799471
|
4
|
+
data.tar.gz: 3c1bcc4f69a361ae6a26b635331f6475413fed38bac9a88c7ed45ffea02129eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eff545775dfa463c99b301a94ab450070f683d3f70a3c974683ac1b311a53d10da57acbd051a6fd0e4c8b005629c1d18bd8d61e33c94548e2fcf2b22b70df606
|
7
|
+
data.tar.gz: 927c991dc2d499be19c626b4a64cfcc11064f2bbadad41abb71ebea13e1e33592810c7fe6135393d7a1c2140d760e8518eee085d3a2d596942af5e031ff8f272
|
data/README.md
CHANGED
@@ -36,8 +36,6 @@ end
|
|
36
36
|
1. Start your Rails server
|
37
37
|
1. Hover over an ActiveRecord model to see its details
|
38
38
|
|
39
|
-
Nested models (e.g. `Admin::User`) are not yet supported.
|
40
|
-
|
41
39
|
### Documentation
|
42
40
|
|
43
41
|
See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-depth details about the
|
@@ -48,22 +46,21 @@ See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-de
|
|
48
46
|
1. Open a test which inherits from `ActiveSupport::TestCase` or one of its descendants, such as `ActionDispatch::IntegrationTest`.
|
49
47
|
2. Click on the "Run", "Run in Terminal" or "Debug" code lens which appears above the test class, or an individual test.
|
50
48
|
|
51
|
-
|
52
|
-
|
49
|
+
> [!NOTE]
|
50
|
+
> When using the Test Explorer view, if your code contains a statement to pause execution (e.g. `debugger`) it will
|
51
|
+
> cause the test runner to hang.
|
53
52
|
|
54
53
|
## How It Works
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
1. A Ruby LSP addon that connects to the exposed APIs to fetch runtime information from the Rails server
|
55
|
+
When Ruby LSP Rails starts, it spawns a `rails runner` instance which runs
|
56
|
+
`[server.rb](https://github.com/Shopify/ruby-lsp-rails/blob/main/lib/ruby_lsp/ruby_lsp_rails/server.rb)`.
|
57
|
+
The addon communicates with this process over a pipe (i.e. `stdin` and `stdout`) to fetch runtime information about the application.
|
60
58
|
|
61
|
-
|
59
|
+
When extension is stopped (e.g. by quitting the editor), the server instance is shut down.
|
62
60
|
|
63
|
-
>
|
64
|
-
>
|
65
|
-
>
|
66
|
-
> If the server is shut down, the extra features will temporarily disappear and reappear once the server is running again.
|
61
|
+
> [!NOTE]
|
62
|
+
> Prior to v0.3, `ruby-lsp-rails` used a different approach which involved mounting a Rack application within the Rails app.
|
63
|
+
> That approach was brittle and susceptible to the application's configuration, such as routing and middleware.
|
67
64
|
|
68
65
|
## Contributing
|
69
66
|
|
data/lib/ruby-lsp-rails.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require "ruby_lsp/addon"
|
5
5
|
|
6
|
-
require_relative "
|
6
|
+
require_relative "runner_client"
|
7
7
|
require_relative "hover"
|
8
8
|
require_relative "code_lens"
|
9
9
|
|
@@ -12,39 +12,41 @@ module RubyLsp
|
|
12
12
|
class Addon < ::RubyLsp::Addon
|
13
13
|
extend T::Sig
|
14
14
|
|
15
|
-
sig { returns(
|
15
|
+
sig { returns(RunnerClient) }
|
16
16
|
def client
|
17
|
-
@client ||= T.let(
|
17
|
+
@client ||= T.let(RunnerClient.new, T.nilable(RunnerClient))
|
18
18
|
end
|
19
19
|
|
20
20
|
sig { override.params(message_queue: Thread::Queue).void }
|
21
|
-
def activate(message_queue)
|
22
|
-
client.check_if_server_is_running!
|
23
|
-
end
|
21
|
+
def activate(message_queue); end
|
24
22
|
|
25
23
|
sig { override.void }
|
26
|
-
def deactivate
|
24
|
+
def deactivate
|
25
|
+
client.shutdown
|
26
|
+
end
|
27
27
|
|
28
28
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
29
29
|
sig do
|
30
30
|
override.params(
|
31
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
31
32
|
uri: URI::Generic,
|
32
33
|
dispatcher: Prism::Dispatcher,
|
33
|
-
).
|
34
|
+
).void
|
34
35
|
end
|
35
|
-
def create_code_lens_listener(uri, dispatcher)
|
36
|
-
CodeLens.new(uri, dispatcher)
|
36
|
+
def create_code_lens_listener(response_builder, uri, dispatcher)
|
37
|
+
CodeLens.new(response_builder, uri, dispatcher)
|
37
38
|
end
|
38
39
|
|
39
40
|
sig do
|
40
41
|
override.params(
|
42
|
+
response_builder: ResponseBuilders::Hover,
|
41
43
|
nesting: T::Array[String],
|
42
44
|
index: RubyIndexer::Index,
|
43
45
|
dispatcher: Prism::Dispatcher,
|
44
|
-
).
|
46
|
+
).void
|
45
47
|
end
|
46
|
-
def create_hover_listener(nesting, index, dispatcher)
|
47
|
-
Hover.new(client, nesting, index, dispatcher)
|
48
|
+
def create_hover_listener(response_builder, nesting, index, dispatcher)
|
49
|
+
Hover.new(client, response_builder, nesting, index, dispatcher)
|
48
50
|
end
|
49
51
|
|
50
52
|
sig { override.returns(String) }
|
@@ -32,26 +32,26 @@ module RubyLsp
|
|
32
32
|
# ````
|
33
33
|
#
|
34
34
|
# The code lenses will be displayed above the class and above each test method.
|
35
|
-
class CodeLens
|
35
|
+
class CodeLens
|
36
36
|
extend T::Sig
|
37
|
-
|
37
|
+
include Requests::Support::Common
|
38
38
|
|
39
|
-
ResponseType = type_member { { fixed: T::Array[::RubyLsp::Interface::CodeLens] } }
|
40
39
|
BASE_COMMAND = "bin/rails test"
|
41
40
|
|
42
|
-
sig
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
sig do
|
42
|
+
params(
|
43
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
44
|
+
uri: URI::Generic,
|
45
|
+
dispatcher: Prism::Dispatcher,
|
46
|
+
).void
|
47
|
+
end
|
48
|
+
def initialize(response_builder, uri, dispatcher)
|
49
|
+
@response_builder = response_builder
|
48
50
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
49
51
|
@group_id = T.let(1, Integer)
|
50
52
|
@group_id_stack = T.let([], T::Array[Integer])
|
51
53
|
|
52
54
|
dispatcher.register(self, :on_call_node_enter, :on_class_node_enter, :on_def_node_enter, :on_class_node_leave)
|
53
|
-
|
54
|
-
super(dispatcher)
|
55
55
|
end
|
56
56
|
|
57
57
|
sig { params(node: Prism::CallNode).void }
|
@@ -131,7 +131,7 @@ module RubyLsp
|
|
131
131
|
grouping_data = { group_id: @group_id_stack.last, kind: kind }
|
132
132
|
grouping_data[:id] = @group_id if kind == :group
|
133
133
|
|
134
|
-
@
|
134
|
+
@response_builder << create_code_lens(
|
135
135
|
node,
|
136
136
|
title: "Run",
|
137
137
|
command_name: "rubyLsp.runTest",
|
@@ -139,7 +139,7 @@ module RubyLsp
|
|
139
139
|
data: { type: "test", **grouping_data },
|
140
140
|
)
|
141
141
|
|
142
|
-
@
|
142
|
+
@response_builder << create_code_lens(
|
143
143
|
node,
|
144
144
|
title: "Run In Terminal",
|
145
145
|
command_name: "rubyLsp.runTestInTerminal",
|
@@ -147,7 +147,7 @@ module RubyLsp
|
|
147
147
|
data: { type: "test_in_terminal", **grouping_data },
|
148
148
|
)
|
149
149
|
|
150
|
-
@
|
150
|
+
@response_builder << create_code_lens(
|
151
151
|
node,
|
152
152
|
title: "Debug",
|
153
153
|
command_name: "rubyLsp.debugTest",
|
@@ -16,28 +16,22 @@ module RubyLsp
|
|
16
16
|
# User.all
|
17
17
|
# # ^ hovering here will show information about the User model
|
18
18
|
# ```
|
19
|
-
class Hover
|
19
|
+
class Hover
|
20
20
|
extend T::Sig
|
21
|
-
|
22
|
-
|
23
|
-
ResponseType = type_member { { fixed: T.nilable(::RubyLsp::Interface::Hover) } }
|
24
|
-
|
25
|
-
sig { override.returns(ResponseType) }
|
26
|
-
attr_reader :_response
|
21
|
+
include Requests::Support::Common
|
27
22
|
|
28
23
|
sig do
|
29
24
|
params(
|
30
|
-
client:
|
25
|
+
client: RunnerClient,
|
26
|
+
response_builder: ResponseBuilders::Hover,
|
31
27
|
nesting: T::Array[String],
|
32
28
|
index: RubyIndexer::Index,
|
33
29
|
dispatcher: Prism::Dispatcher,
|
34
30
|
).void
|
35
31
|
end
|
36
|
-
def initialize(client, nesting, index, dispatcher)
|
37
|
-
super(dispatcher)
|
38
|
-
|
39
|
-
@_response = T.let(nil, ResponseType)
|
32
|
+
def initialize(client, response_builder, nesting, index, dispatcher)
|
40
33
|
@client = client
|
34
|
+
@response_builder = response_builder
|
41
35
|
@nesting = nesting
|
42
36
|
@index = index
|
43
37
|
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter, :on_call_node_enter)
|
@@ -49,16 +43,10 @@ module RubyLsp
|
|
49
43
|
return unless entries
|
50
44
|
|
51
45
|
name = T.must(entries.first).name
|
52
|
-
content = +""
|
53
|
-
column_info = generate_column_content(name)
|
54
|
-
content << column_info if column_info
|
55
46
|
|
56
|
-
|
57
|
-
content << urls.join("\n\n") unless urls.empty?
|
58
|
-
return if content.empty?
|
47
|
+
generate_column_content(name)
|
59
48
|
|
60
|
-
|
61
|
-
@_response = RubyLsp::Interface::Hover.new(range: range_from_location(node.location), contents: contents)
|
49
|
+
generate_rails_document_link_hover(name, node.location)
|
62
50
|
end
|
63
51
|
|
64
52
|
sig { params(node: Prism::ConstantReadNode).void }
|
@@ -66,11 +54,7 @@ module RubyLsp
|
|
66
54
|
entries = @index.resolve(node.name.to_s, @nesting)
|
67
55
|
return unless entries
|
68
56
|
|
69
|
-
|
70
|
-
return unless content
|
71
|
-
|
72
|
-
contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: content)
|
73
|
-
@_response = RubyLsp::Interface::Hover.new(range: range_from_location(node.location), contents: contents)
|
57
|
+
generate_column_content(T.must(entries.first).name)
|
74
58
|
end
|
75
59
|
|
76
60
|
sig { params(node: Prism::CallNode).void }
|
@@ -80,30 +64,37 @@ module RubyLsp
|
|
80
64
|
|
81
65
|
return unless message_value && message_loc
|
82
66
|
|
83
|
-
|
67
|
+
generate_rails_document_link_hover(message_value, message_loc)
|
84
68
|
end
|
85
69
|
|
86
70
|
private
|
87
71
|
|
88
|
-
sig { params(name: String).
|
72
|
+
sig { params(name: String).void }
|
89
73
|
def generate_column_content(name)
|
90
74
|
model = @client.model(name)
|
91
75
|
return if model.nil?
|
92
76
|
|
93
77
|
schema_file = model[:schema_file]
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
78
|
+
|
79
|
+
@response_builder.push(
|
80
|
+
"[Schema](#{URI::Generic.build(scheme: "file", path: schema_file)})",
|
81
|
+
category: :links,
|
82
|
+
) if schema_file
|
83
|
+
|
84
|
+
@response_builder.push(
|
85
|
+
model[:columns].map do |name, type|
|
86
|
+
"**#{name}**: #{type}\n"
|
87
|
+
end.join("\n"),
|
88
|
+
category: :documentation,
|
89
|
+
)
|
98
90
|
end
|
99
91
|
|
100
|
-
sig { params(name: String, location: Prism::Location).
|
92
|
+
sig { params(name: String, location: Prism::Location).void }
|
101
93
|
def generate_rails_document_link_hover(name, location)
|
102
94
|
urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
|
103
95
|
return if urls.empty?
|
104
96
|
|
105
|
-
|
106
|
-
RubyLsp::Interface::Hover.new(range: range_from_location(location), contents: contents)
|
97
|
+
@response_builder.push(urls.join("\n\n"), category: :links)
|
107
98
|
end
|
108
99
|
end
|
109
100
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "json"
|
5
|
+
require "open3"
|
6
|
+
|
7
|
+
# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe
|
8
|
+
# from the client, so it will become full and eventually hang or crash.
|
9
|
+
# Instead, return a response with an `error` key.
|
10
|
+
|
11
|
+
module RubyLsp
|
12
|
+
module Rails
|
13
|
+
class RunnerClient
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { void }
|
17
|
+
def initialize
|
18
|
+
stdin, stdout, stderr, wait_thread = Open3.popen3(
|
19
|
+
"bin/rails",
|
20
|
+
"runner",
|
21
|
+
"#{__dir__}/server.rb",
|
22
|
+
"start",
|
23
|
+
)
|
24
|
+
@stdin = T.let(stdin, IO)
|
25
|
+
@stdout = T.let(stdout, IO)
|
26
|
+
@stderr = T.let(stderr, IO)
|
27
|
+
@wait_thread = T.let(wait_thread, Process::Waiter)
|
28
|
+
@stdin.binmode # for Windows compatibility
|
29
|
+
@stdout.binmode # for Windows compatibility
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
33
|
+
def model(name)
|
34
|
+
make_request("model", name: name)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { void }
|
38
|
+
def shutdown
|
39
|
+
send_notification("shutdown")
|
40
|
+
Thread.pass while @wait_thread.alive?
|
41
|
+
[@stdin, @stdout, @stderr].each(&:close)
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { returns(T::Boolean) }
|
45
|
+
def stopped?
|
46
|
+
[@stdin, @stdout, @stderr].all?(&:closed?) && !@wait_thread.alive?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
sig { params(request: T.untyped, params: T.untyped).returns(T.untyped) }
|
52
|
+
def make_request(request, params = nil)
|
53
|
+
send_message(request, params)
|
54
|
+
read_response
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { params(request: T.untyped, params: T.untyped).void }
|
58
|
+
def send_message(request, params = nil)
|
59
|
+
message = { method: request }
|
60
|
+
message[:params] = params if params
|
61
|
+
json = message.to_json
|
62
|
+
|
63
|
+
@stdin.write("Content-Length: #{json.length}\r\n\r\n", json)
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :send_notification, :send_message
|
67
|
+
|
68
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
69
|
+
def read_response
|
70
|
+
headers = @stdout.gets("\r\n\r\n")
|
71
|
+
raw_response = @stdout.read(T.must(headers)[/Content-Length: (\d+)/i, 1].to_i)
|
72
|
+
|
73
|
+
response = JSON.parse(T.must(raw_response), symbolize_names: true)
|
74
|
+
|
75
|
+
if response[:error]
|
76
|
+
warn("Ruby LSP Rails error: " + response[:error])
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
response.fetch(:result)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
begin
|
8
|
+
T::Configuration.default_checked_level = :never
|
9
|
+
# Suppresses call validation errors
|
10
|
+
T::Configuration.call_validation_error_handler = ->(*) {}
|
11
|
+
# Suppresses errors caused by T.cast, T.let, T.must, etc.
|
12
|
+
T::Configuration.inline_type_error_handler = ->(*) {}
|
13
|
+
# Suppresses errors caused by incorrect parameter ordering
|
14
|
+
T::Configuration.sig_validation_error_handler = ->(*) {}
|
15
|
+
rescue
|
16
|
+
# Need this rescue so that if another gem has
|
17
|
+
# already set the checked level by the time we
|
18
|
+
# get to it, we don't fail outright.
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
module RubyLsp
|
23
|
+
module Rails
|
24
|
+
class Server
|
25
|
+
VOID = Object.new
|
26
|
+
|
27
|
+
extend T::Sig
|
28
|
+
|
29
|
+
sig { params(model_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
30
|
+
def resolve_database_info_from_model(model_name)
|
31
|
+
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
32
|
+
unless const && const < ActiveRecord::Base && !const.abstract_class?
|
33
|
+
return {
|
34
|
+
result: nil,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
schema_file = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
|
39
|
+
|
40
|
+
{
|
41
|
+
result: {
|
42
|
+
columns: const.columns.map { |column| [column.name, column.type] },
|
43
|
+
schema_file: ::Rails.root + schema_file,
|
44
|
+
},
|
45
|
+
}
|
46
|
+
rescue => e
|
47
|
+
{
|
48
|
+
error: e.message,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { void }
|
53
|
+
def start
|
54
|
+
$stdin.sync = true
|
55
|
+
$stdout.sync = true
|
56
|
+
|
57
|
+
running = T.let(true, T::Boolean)
|
58
|
+
|
59
|
+
while running
|
60
|
+
headers = $stdin.gets("\r\n\r\n")
|
61
|
+
request = $stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
62
|
+
|
63
|
+
json = JSON.parse(request, symbolize_names: true)
|
64
|
+
request_method = json.fetch(:method)
|
65
|
+
params = json[:params]
|
66
|
+
|
67
|
+
response = case request_method
|
68
|
+
when "shutdown"
|
69
|
+
running = false
|
70
|
+
VOID
|
71
|
+
when "model"
|
72
|
+
resolve_database_info_from_model(params.fetch(:name))
|
73
|
+
else
|
74
|
+
VOID
|
75
|
+
end
|
76
|
+
|
77
|
+
next if response == VOID
|
78
|
+
|
79
|
+
json_response = response.to_json
|
80
|
+
$stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
RubyLsp::Rails::Server.new.start if ARGV.first == "start"
|
@@ -2,36 +2,17 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "rails"
|
5
|
-
require "ruby_lsp_rails/rack_app"
|
6
5
|
|
7
6
|
module RubyLsp
|
8
7
|
module Rails
|
9
8
|
class Railtie < ::Rails::Railtie
|
10
9
|
config.ruby_lsp_rails = ActiveSupport::OrderedOptions.new
|
11
|
-
config.ruby_lsp_rails.server = true
|
12
10
|
|
13
11
|
initializer "ruby_lsp_rails.setup" do |_app|
|
14
|
-
config.after_initialize do |
|
15
|
-
unless config.ruby_lsp_rails.server
|
16
|
-
|
17
|
-
|
18
|
-
mount(RackApp.new => RackApp::BASE_PATH)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# If we start the app with `bin/rails console` then `Rails::Server` is not defined.
|
23
|
-
if defined?(::Rails::Server)
|
24
|
-
ssl_enable, host, port = ::Rails::Server::Options.new.parse!(ARGV).values_at(:SSLEnable, :Host, :Port)
|
25
|
-
app_uri = "#{ssl_enable ? "https" : "http"}://#{host}:#{port}"
|
26
|
-
app_uri_path = ::Rails.root.join("tmp", "app_uri.txt")
|
27
|
-
app_uri_path.write(app_uri)
|
28
|
-
|
29
|
-
at_exit do
|
30
|
-
# The app_uri.txt file should only exist when the server is running. The addon uses its presence to
|
31
|
-
# report if the server is running or not. If the server is not running, some of the addon features
|
32
|
-
# will not be available.
|
33
|
-
File.delete(app_uri_path) if File.exist?(app_uri_path)
|
34
|
-
end
|
12
|
+
config.after_initialize do |_app|
|
13
|
+
unless config.ruby_lsp_rails.server.nil?
|
14
|
+
ActiveSupport::Deprecation.new.warn("The `ruby_lsp_rails.server` configuration option is no longer " \
|
15
|
+
"needed and will be removed in a future release.")
|
35
16
|
end
|
36
17
|
end
|
37
18
|
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.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -58,20 +58,20 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
61
|
+
version: 0.14.0
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: 0.
|
64
|
+
version: 0.15.0
|
65
65
|
type: :runtime
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
69
|
- - ">="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: 0.
|
71
|
+
version: 0.14.0
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: 0.
|
74
|
+
version: 0.15.0
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: sorbet-runtime
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,9 +100,9 @@ files:
|
|
100
100
|
- lib/ruby_lsp/ruby_lsp_rails/addon.rb
|
101
101
|
- lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
|
102
102
|
- lib/ruby_lsp/ruby_lsp_rails/hover.rb
|
103
|
-
- lib/ruby_lsp/ruby_lsp_rails/
|
103
|
+
- lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
|
104
|
+
- lib/ruby_lsp/ruby_lsp_rails/server.rb
|
104
105
|
- lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
|
105
|
-
- lib/ruby_lsp_rails/rack_app.rb
|
106
106
|
- lib/ruby_lsp_rails/railtie.rb
|
107
107
|
- lib/ruby_lsp_rails/version.rb
|
108
108
|
- lib/tasks/ruby_lsp_rails_tasks.rake
|
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: '0'
|
131
131
|
requirements: []
|
132
|
-
rubygems_version: 3.
|
132
|
+
rubygems_version: 3.5.6
|
133
133
|
signing_key:
|
134
134
|
specification_version: 4
|
135
135
|
summary: A Ruby LSP addon for Rails
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "net/http"
|
5
|
-
|
6
|
-
module RubyLsp
|
7
|
-
module Rails
|
8
|
-
class RailsClient
|
9
|
-
class ServerAddressUnknown < StandardError; end
|
10
|
-
|
11
|
-
extend T::Sig
|
12
|
-
|
13
|
-
SERVER_NOT_RUNNING_MESSAGE = "Rails server is not running. " \
|
14
|
-
"To get Rails features in the editor, boot the Rails server"
|
15
|
-
|
16
|
-
sig { returns(Pathname) }
|
17
|
-
attr_reader :root
|
18
|
-
|
19
|
-
sig { void }
|
20
|
-
def initialize
|
21
|
-
project_root = T.let(Bundler.with_unbundled_env { Bundler.default_gemfile }.dirname, Pathname)
|
22
|
-
dummy_path = project_root.join("test", "dummy")
|
23
|
-
|
24
|
-
@root = T.let(dummy_path.exist? ? dummy_path : project_root, Pathname)
|
25
|
-
app_uri_path = @root.join("tmp", "app_uri.txt")
|
26
|
-
|
27
|
-
if app_uri_path.exist?
|
28
|
-
url = URI(app_uri_path.read.chomp)
|
29
|
-
|
30
|
-
@ssl = T.let(url.scheme == "https", T::Boolean)
|
31
|
-
@address = T.let(
|
32
|
-
[url.host, url.path].reject { |component| component.nil? || component.empty? }.join("/"),
|
33
|
-
T.nilable(String),
|
34
|
-
)
|
35
|
-
@port = T.let(T.must(url.port).to_i, Integer)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
40
|
-
def model(name)
|
41
|
-
response = request("models/#{name}")
|
42
|
-
return unless response.code == "200"
|
43
|
-
|
44
|
-
JSON.parse(response.body.chomp, symbolize_names: true)
|
45
|
-
rescue Errno::ECONNREFUSED,
|
46
|
-
Errno::EADDRNOTAVAIL,
|
47
|
-
Errno::ECONNRESET,
|
48
|
-
Net::ReadTimeout,
|
49
|
-
Net::OpenTimeout,
|
50
|
-
ServerAddressUnknown
|
51
|
-
nil
|
52
|
-
end
|
53
|
-
|
54
|
-
sig { void }
|
55
|
-
def check_if_server_is_running!
|
56
|
-
request("activate", 0.2)
|
57
|
-
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Errno::ECONNRESET, ServerAddressUnknown
|
58
|
-
warn(SERVER_NOT_RUNNING_MESSAGE)
|
59
|
-
rescue Net::ReadTimeout, Net::OpenTimeout
|
60
|
-
# If the server is running, but the initial request is taking too long, we don't want to block the
|
61
|
-
# initialization of the Ruby LSP
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
sig { params(path: String, timeout: T.nilable(Float)).returns(Net::HTTPResponse) }
|
67
|
-
def request(path, timeout = nil)
|
68
|
-
raise ServerAddressUnknown unless @address
|
69
|
-
|
70
|
-
http = Net::HTTP.new(@address, @port)
|
71
|
-
http.use_ssl = @ssl
|
72
|
-
http.read_timeout = timeout if timeout
|
73
|
-
http.get("/ruby_lsp_rails/#{path}")
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module RubyLsp
|
5
|
-
module Rails
|
6
|
-
class RackApp
|
7
|
-
extend T::Sig
|
8
|
-
|
9
|
-
BASE_PATH = "/ruby_lsp_rails/"
|
10
|
-
|
11
|
-
sig { params(env: T::Hash[T.untyped, T.untyped]).returns(T::Array[T.untyped]) }
|
12
|
-
def call(env)
|
13
|
-
request = ActionDispatch::Request.new(env)
|
14
|
-
path = request.path
|
15
|
-
|
16
|
-
route, argument = path.delete_prefix(BASE_PATH).split("/")
|
17
|
-
|
18
|
-
case route
|
19
|
-
when "activate"
|
20
|
-
[200, { "Content-Type" => "application/json" }, []]
|
21
|
-
when "models"
|
22
|
-
resolve_database_info_from_model(argument)
|
23
|
-
else
|
24
|
-
not_found
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
sig { params(model_name: String).returns(T::Array[T.untyped]) }
|
31
|
-
def resolve_database_info_from_model(model_name)
|
32
|
-
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
33
|
-
|
34
|
-
if const && const < ActiveRecord::Base
|
35
|
-
begin
|
36
|
-
schema_file = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
|
37
|
-
rescue => e
|
38
|
-
warn("Could not locate schema: #{e.message}")
|
39
|
-
end
|
40
|
-
|
41
|
-
body = JSON.dump({
|
42
|
-
columns: const.columns.map { |column| [column.name, column.type] },
|
43
|
-
schema_file: schema_file,
|
44
|
-
})
|
45
|
-
|
46
|
-
[200, { "Content-Type" => "application/json" }, [body]]
|
47
|
-
else
|
48
|
-
not_found
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
sig { returns(T::Array[T.untyped]) }
|
53
|
-
def not_found
|
54
|
-
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|