ruby-lsp-rails 0.2.8 → 0.3.0

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: c3d8b40085e6c7ca1e23283423a0966da489180e36e4fb50415f41b1a37f5294
4
- data.tar.gz: e98455cb80f6c386e9aa1c3fa516a29cbf0027826aed8c5af029cc1de61aaa05
3
+ metadata.gz: ecc8c1b4cd4569bdb9c816f842a90f0dfb1122931adcd9c96d1e2eb8a3799471
4
+ data.tar.gz: 3c1bcc4f69a361ae6a26b635331f6475413fed38bac9a88c7ed45ffea02129eb
5
5
  SHA512:
6
- metadata.gz: ff23cfda2194b449136438eee3475801c4568d7efa9dea08c9c60bd11f732af31c10a30b95014c7d366fbf5ad86dd7471dd3419ef4ef2808f031307d1ffc841e
7
- data.tar.gz: 4a776b1c73b4c2683ba6db906afc3dccd2221f0dd4943a56d1a7fd1f0b4c43f92a789231d82e85aec2ba664fd505013c3c3a594a7b57d6931b35bfd433998ca0
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
- Note: When using the Test Explorer view, if your code contains a statement to pause execution (e.g. `debugger`) it will
52
- cause the test runner to hang.
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
- This gem consists of two components that enable enhanced Rails functionality in the editor:
57
-
58
- 1. A Rack app that automatically exposes APIs when Rails server is running
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
- This is why the Rails server needs to be running for some features to work.
59
+ When extension is stopped (e.g. by quitting the editor), the server instance is shut down.
62
60
 
63
- > **Note**
64
- >
65
- > There is no need to restart the Ruby LSP every time the Rails server is booted.
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
 
@@ -1,9 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
- require "pathname"
6
-
7
4
  require "ruby_lsp_rails/version"
8
5
  require "ruby_lsp_rails/railtie"
9
6
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  require "ruby_lsp/addon"
5
5
 
6
- require_relative "rails_client"
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(RailsClient) }
15
+ sig { returns(RunnerClient) }
16
16
  def client
17
- @client ||= T.let(RailsClient.new, T.nilable(RailsClient))
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; end
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
- ).returns(T.nilable(Listener[T::Array[Interface::CodeLens]]))
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
- ).returns(T.nilable(Listener[T.nilable(Interface::Hover)]))
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 < ::RubyLsp::Listener
35
+ class CodeLens
36
36
  extend T::Sig
37
- extend T::Generic
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 { override.returns(ResponseType) }
43
- attr_reader :_response
44
-
45
- sig { params(uri: URI::Generic, dispatcher: Prism::Dispatcher).void }
46
- def initialize(uri, dispatcher)
47
- @_response = T.let([], ResponseType)
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
- @_response << create_code_lens(
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
- @_response << create_code_lens(
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
- @_response << create_code_lens(
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 < ::RubyLsp::Listener
19
+ class Hover
20
20
  extend T::Sig
21
- extend T::Generic
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: RailsClient,
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
- urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
57
- content << urls.join("\n\n") unless urls.empty?
58
- return if content.empty?
47
+ generate_column_content(name)
59
48
 
60
- contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: content)
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
- content = generate_column_content(T.must(entries.first).name)
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
- @_response = generate_rails_document_link_hover(message_value, message_loc)
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).returns(T.nilable(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
- content = +""
95
- content << "[Schema](#{URI::Generic.build(scheme: "file", path: schema_file)})\n\n" if schema_file
96
- content << model[:columns].map { |name, type| "**#{name}**: #{type}\n" }.join("\n")
97
- content
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).returns(T.nilable(Interface::Hover)) }
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
- contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: urls.join("\n\n"))
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 |app|
15
- unless config.ruby_lsp_rails.server == false
16
- app.routes.prepend do
17
- T.bind(self, ActionDispatch::Routing::Mapper)
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
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.2.8"
6
+ VERSION = "0.3.0"
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.8
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: 2023-12-04 00:00:00.000000000 Z
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.13.0
61
+ version: 0.14.0
62
62
  - - "<"
63
63
  - !ruby/object:Gem::Version
64
- version: 0.14.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.13.0
71
+ version: 0.14.0
72
72
  - - "<"
73
73
  - !ruby/object:Gem::Version
74
- version: 0.14.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/rails_client.rb
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.4.21
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