ruby-lsp-rails 0.2.9 → 0.2.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -2
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +8 -6
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +14 -14
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +24 -33
- 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/rack_app.rb +1 -1
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41272ca8c9212dc08dc09beeef481c282656946e182e0c62c72585ecc6cfe658
|
4
|
+
data.tar.gz: 0dc3c80ab252123291ab7b77a02b22fff1f27dd5bc07eb49ea51dfdbff4310f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb753c4c3a4282faeefbd254840be186969179e6eaff83320a79e6fd5d2149e4d35acd68f7c38d8bd436e07b233f5e3cebf553c53317576333b83c4b81582dc0
|
7
|
+
data.tar.gz: 881b3d77f931f890ed68a12b478b7762ea491165e70c4cd61af078eccdeb139c28d9f9eed83406b7fa01927e6b0ea3a07993e0d73ae4c0eede146c6fb124a4aa
|
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
|
@@ -28,23 +28,25 @@ module RubyLsp
|
|
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
25
|
client: RailsClient,
|
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"
|
@@ -31,7 +31,7 @@ module RubyLsp
|
|
31
31
|
def resolve_database_info_from_model(model_name)
|
32
32
|
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
33
33
|
|
34
|
-
if const && const < ActiveRecord::Base
|
34
|
+
if const && const < ActiveRecord::Base && !const.abstract_class?
|
35
35
|
begin
|
36
36
|
schema_file = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
|
37
37
|
rescue => e
|
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.
|
4
|
+
version: 0.2.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-02-13 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
|
@@ -101,6 +101,8 @@ files:
|
|
101
101
|
- lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
|
102
102
|
- lib/ruby_lsp/ruby_lsp_rails/hover.rb
|
103
103
|
- lib/ruby_lsp/ruby_lsp_rails/rails_client.rb
|
104
|
+
- lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
|
105
|
+
- lib/ruby_lsp/ruby_lsp_rails/server.rb
|
104
106
|
- lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
|
105
107
|
- lib/ruby_lsp_rails/rack_app.rb
|
106
108
|
- lib/ruby_lsp_rails/railtie.rb
|
@@ -129,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
131
|
- !ruby/object:Gem::Version
|
130
132
|
version: '0'
|
131
133
|
requirements: []
|
132
|
-
rubygems_version: 3.5.
|
134
|
+
rubygems_version: 3.5.6
|
133
135
|
signing_key:
|
134
136
|
specification_version: 4
|
135
137
|
summary: A Ruby LSP addon for Rails
|