ruby-lsp-rails 0.2.8 → 0.2.10

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: 41272ca8c9212dc08dc09beeef481c282656946e182e0c62c72585ecc6cfe658
4
+ data.tar.gz: 0dc3c80ab252123291ab7b77a02b22fff1f27dd5bc07eb49ea51dfdbff4310f2
5
5
  SHA512:
6
- metadata.gz: ff23cfda2194b449136438eee3475801c4568d7efa9dea08c9c60bd11f732af31c10a30b95014c7d366fbf5ad86dd7471dd3419ef4ef2808f031307d1ffc841e
7
- data.tar.gz: 4a776b1c73b4c2683ba6db906afc3dccd2221f0dd4943a56d1a7fd1f0b4c43f92a789231d82e85aec2ba664fd505013c3c3a594a7b57d6931b35bfd433998ca0
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
@@ -48,8 +46,9 @@ 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
 
@@ -60,8 +59,7 @@ This gem consists of two components that enable enhanced Rails functionality in
60
59
 
61
60
  This is why the Rails server needs to be running for some features to work.
62
61
 
63
- > **Note**
64
- >
62
+ > [!NOTE]
65
63
  > There is no need to restart the Ruby LSP every time the Rails server is booted.
66
64
  > If the server is shut down, the extra features will temporarily disappear and reappear once the server is running again.
67
65
 
@@ -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
- ).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
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
- 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"
@@ -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
@@ -12,15 +12,13 @@ module RubyLsp
12
12
 
13
13
  initializer "ruby_lsp_rails.setup" do |_app|
14
14
  config.after_initialize do |app|
15
- unless config.ruby_lsp_rails.server == false
15
+ # If we start the app with `bin/rails console` then `Rails::Server` is not defined.
16
+ if defined?(::Rails::Server) && config.ruby_lsp_rails.server
16
17
  app.routes.prepend do
17
18
  T.bind(self, ActionDispatch::Routing::Mapper)
18
19
  mount(RackApp.new => RackApp::BASE_PATH)
19
20
  end
20
- end
21
21
 
22
- # If we start the app with `bin/rails console` then `Rails::Server` is not defined.
23
- if defined?(::Rails::Server)
24
22
  ssl_enable, host, port = ::Rails::Server::Options.new.parse!(ARGV).values_at(:SSLEnable, :Host, :Port)
25
23
  app_uri = "#{ssl_enable ? "https" : "http"}://#{host}:#{port}"
26
24
  app_uri_path = ::Rails.root.join("tmp", "app_uri.txt")
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.2.8"
6
+ VERSION = "0.2.10"
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.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: 2023-12-04 00:00:00.000000000 Z
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.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
@@ -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.4.21
134
+ rubygems_version: 3.5.6
133
135
  signing_key:
134
136
  specification_version: 4
135
137
  summary: A Ruby LSP addon for Rails