Skip to content

Commit 1ebb9c0

Browse files
committed
Merge branch 'main' into andyw8/vinistock/server-architecture-exploration
2 parents f10be92 + fc99832 commit 1ebb9c0

File tree

2 files changed

+44
-41
lines changed

2 files changed

+44
-41
lines changed

lib/ruby_lsp/ruby_lsp_rails/server.rb

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,10 @@ def execute(request, params)
4444
end
4545

4646
class Server
47-
VOID = Object.new
48-
49-
def initialize
47+
def initialize(stdout: $stdout, override_default_output_device: true)
5048
# Grab references to the original pipes so that we can change the default output device further down
5149
@stdin = $stdin
52-
@stdout = $stdout
50+
@stdout = stdout
5351
@stderr = $stderr
5452
@stdin.sync = true
5553
@stdout.sync = true
@@ -61,7 +59,7 @@ def initialize
6159
# # Set the default output device to be $stderr. This means that using `puts` by itself will default to printing
6260
# # to $stderr and only explicit `$stdout.puts` will go to $stdout. This reduces the chance that output coming
6361
# # from the Rails app will be accidentally sent to the client
64-
$> = $stderr
62+
$> = $stderr if override_default_output_device
6563

6664
@running = true
6765
end
@@ -79,47 +77,43 @@ def start
7977
json = @stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
8078

8179
request = JSON.parse(json, symbolize_names: true)
82-
response = execute(request.fetch(:method), request[:params])
83-
next if response == VOID
84-
85-
json_response = response.to_json
86-
@stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
80+
execute(request.fetch(:method), request[:params])
8781
end
8882
end
8983

9084
def execute(request, params)
9185
case request
9286
when "shutdown"
9387
@running = false
94-
VOID
9588
when "model"
96-
resolve_database_info_from_model(params.fetch(:name))
89+
write_response(resolve_database_info_from_model(params.fetch(:name)))
9790
when "association_target_location"
98-
resolve_association_target(params)
91+
write_response(resolve_association_target(params))
9992
when "reload"
10093
::Rails.application.reloader.reload!
101-
VOID
10294
when "route_location"
103-
route_location(params.fetch(:name))
95+
write_response(route_location(params.fetch(:name)))
10496
when "route_info"
105-
resolve_route_info(params)
97+
write_response(resolve_route_info(params))
10698
when "register_server_addon"
10799
require params[:server_addon_path]
108100
ServerAddon.finalize_registrations!
109-
VOID
110101
when "server_addon/delegate"
111102
server_addon_name = params[:server_addon_name]
112103
request_name = params.delete(:request_name)
113-
ServerAddon.delegate(server_addon_name, request_name, params)
114-
else
115-
VOID
104+
write_response(ServerAddon.delegate(server_addon_name, request_name, params))
116105
end
117106
rescue => e
118-
{ error: e.full_message(highlight: false) }
107+
write_response({ error: e.full_message(highlight: false) })
119108
end
120109

121110
private
122111

112+
def write_response(response)
113+
json_response = response.to_json
114+
@stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
115+
end
116+
123117
def resolve_route_info(requirements)
124118
if requirements[:controller]
125119
requirements[:controller] = requirements.fetch(:controller).underscore.delete_suffix("_controller")

test/ruby_lsp_rails/server_test.rb

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,27 @@
66

77
class ServerTest < ActiveSupport::TestCase
88
setup do
9-
@server = RubyLsp::Rails::Server.new
9+
@stdout = StringIO.new
10+
@server = RubyLsp::Rails::Server.new(stdout: @stdout, override_default_output_device: false)
1011
end
1112

1213
test "returns nil if model doesn't exist" do
13-
response = @server.execute("model", { name: "Foo" })
14+
@server.execute("model", { name: "Foo" })
1415
assert_nil(response.fetch(:result))
1516
end
1617

1718
test "returns nil if class is not a model" do
18-
response = @server.execute("model", { name: "Time" })
19+
@server.execute("model", { name: "Time" })
1920
assert_nil(response.fetch(:result))
2021
end
2122

2223
test "returns nil if class is an abstract model" do
23-
response = @server.execute("model", { name: "ApplicationRecord" })
24+
@server.execute("model", { name: "ApplicationRecord" })
2425
assert_nil(response.fetch(:result))
2526
end
2627

2728
test "returns nil if constant is not a class" do
28-
response = @server.execute("model", { name: "RUBY_VERSION" })
29+
@server.execute("model", { name: "RUBY_VERSION" })
2930
assert_nil(response.fetch(:result))
3031
end
3132

@@ -38,21 +39,21 @@ def <(other)
3839
end
3940
end
4041

41-
response = @server.execute("model", { name: "TestClassWithOverwrittenLessThan" })
42+
@server.execute("model", { name: "TestClassWithOverwrittenLessThan" })
4243
assert_nil(response.fetch(:result))
4344
end
4445

4546
test "handles older Rails version which don't have `schema_dump_path`" do
4647
ActiveRecord::Tasks::DatabaseTasks.send(:alias_method, :old_schema_dump_path, :schema_dump_path)
4748
ActiveRecord::Tasks::DatabaseTasks.undef_method(:schema_dump_path)
48-
response = @server.execute("model", { name: "User" })
49+
@server.execute("model", { name: "User" })
4950
assert_nil(response.fetch(:result)[:schema_file])
5051
ensure
5152
ActiveRecord::Tasks::DatabaseTasks.send(:alias_method, :schema_dump_path, :old_schema_dump_path)
5253
end
5354

5455
test "resolve association returns the location of the target class of a has_many association" do
55-
response = @server.execute(
56+
@server.execute(
5657
"association_target_location",
5758
{ model_name: "Organization", association_name: :memberships },
5859
)
@@ -61,7 +62,7 @@ def <(other)
6162
end
6263

6364
test "resolve association returns the location of the target class of a belongs_to association" do
64-
response = @server.execute(
65+
@server.execute(
6566
"association_target_location",
6667
{ model_name: "Membership", association_name: :organization },
6768
)
@@ -70,7 +71,7 @@ def <(other)
7071
end
7172

7273
test "resolve association returns the location of the target class of a has_one association" do
73-
response = @server.execute(
74+
@server.execute(
7475
"association_target_location",
7576
{ model_name: "User", association_name: :profile },
7677
)
@@ -79,7 +80,7 @@ def <(other)
7980
end
8081

8182
test "resolve association returns the location of the target class of a has_and_belongs_to_many association" do
82-
response = @server.execute(
83+
@server.execute(
8384
"association_target_location",
8485
{ model_name: "Profile", association_name: :labels },
8586
)
@@ -88,23 +89,23 @@ def <(other)
8889
end
8990

9091
test "resolve association handles invalid model name" do
91-
response = @server.execute(
92+
@server.execute(
9293
"association_target_location",
9394
{ model_name: "NotHere", association_name: :labels },
9495
)
9596
assert_nil(response.fetch(:result))
9697
end
9798

9899
test "resolve association handles invalid association name" do
99-
response = @server.execute(
100+
@server.execute(
100101
"association_target_location",
101102
{ model_name: "Membership", association_name: :labels },
102103
)
103104
assert_nil(response.fetch(:result))
104105
end
105106

106107
test "resolve association handles class_name option" do
107-
response = @server.execute(
108+
@server.execute(
108109
"association_target_location",
109110
{ model_name: "User", association_name: :location },
110111
)
@@ -113,18 +114,18 @@ def <(other)
113114
end
114115

115116
test "route location returns the location for a valid route" do
116-
response = @server.execute("route_location", { name: "user_path" })
117+
@server.execute("route_location", { name: "user_path" })
117118
location = response[:result][:location]
118119
assert_match %r{test/dummy/config/routes.rb:4$}, location
119120
end
120121

121122
test "route location returns nil for invalid routes" do
122-
response = @server.execute("route_location", { name: "invalid_path" })
123+
@server.execute("route_location", { name: "invalid_path" })
123124
assert_nil response[:result]
124125
end
125126

126127
test "route info" do
127-
response = @server.execute("route_info", { controller: "UsersController", action: "index" })
128+
@server.execute("route_info", { controller: "UsersController", action: "index" })
128129

129130
result = response[:result]
130131

@@ -159,7 +160,8 @@ def execute(request, params)
159160
end
160161

161162
test "prints in the Rails application or server are automatically redirected to stderr" do
162-
server = RubyLsp::Rails::Server.new
163+
stdout = StringIO.new
164+
server = RubyLsp::Rails::Server.new(stdout: stdout)
163165

164166
server.instance_eval do
165167
def resolve_route_info(requirements)
@@ -168,11 +170,18 @@ def resolve_route_info(requirements)
168170
end
169171
end
170172

171-
stdout, stderr = capture_subprocess_io do
173+
_, stderr = capture_subprocess_io do
172174
server.execute("route_info", { controller: "UsersController", action: "index" })
173175
end
174176

175-
assert_empty(stdout)
177+
refute_match("Hello", stdout.string)
176178
assert_equal("Hello\n", stderr)
177179
end
180+
181+
private
182+
183+
def response
184+
_headers, content = @stdout.string.split("\r\n\r\n")
185+
JSON.parse(content, symbolize_names: true)
186+
end
178187
end

0 commit comments

Comments
 (0)