ruby-lsp-rails 0.3.6 → 0.3.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65c6258c3267f6d1d080e056f4561d61cf8f9bb93059dec1c0b70086fa0ce540
4
- data.tar.gz: 536aac52752c432fc3c18d76c66151c099533849abc8968aafe832524e25303b
3
+ metadata.gz: 6bb6fcc96e3d0fd9357e7f8700acbae09e0d792499ed0d8b92fd577a97949108
4
+ data.tar.gz: cbb971feed21416f187195d0b23295801dbbc5c875c5d64961f2591e253f375a
5
5
  SHA512:
6
- metadata.gz: 3b527faec645912d5c49aef3d4d2bb094a789410283ddcebcc9be168f703807831a5a5f2561a88dcecf5c4e3d894c0d2ccf9fe617688f09462e88ab7ae1a2ee8
7
- data.tar.gz: b080811bd5697ae68573df2fd6d922dec47e2e16b5264cd86b707f73cab9689fffa858851da2ebed7a819930fbc148234db4de5c4a15dc2e4fffbec3e4fdfbcb
6
+ metadata.gz: dc8c684b4900726219d73c2855a1afe159e0ba02f8a953de86356e1cfb9667fe11dff4cbaf3446d5dd471689151f34072f1a099f82033c959516db30dcf81ce7
7
+ data.tar.gz: 72b8a8ba741fbccd174dc46bb86ef683634630866f209f0968eb6d228992f4986f46bac7f0de249a936649eef80a799c5505f9c8a3514f76d44e77a511b70325
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Ruby DX Slack](https://img.shields.io/badge/Slack-Ruby%20DX-success?logo=slack)](https://join.slack.com/t/ruby-dx/shared_invite/zt-2c8zjlir6-uUDJl8oIwcen_FS_aA~b6Q)
2
+
1
3
  # Ruby LSP Rails
2
4
 
3
5
  Ruby LSP Rails is a [Ruby LSP](https://github.com/Shopify/ruby-lsp) addon for extra Rails editor features, such as:
data/Rakefile CHANGED
@@ -30,4 +30,4 @@ end
30
30
 
31
31
  RubyLsp::CheckDocs.new(FileList["#{__dir__}/lib/ruby_lsp/**/*.rb"], FileList["#{__dir__}/misc/**/*.gif"])
32
32
 
33
- task default: :test
33
+ task default: [:"db:setup", :test]
@@ -5,7 +5,9 @@ require "ruby_lsp/addon"
5
5
 
6
6
  require_relative "../../ruby_lsp_rails/version"
7
7
  require_relative "support/active_support_test_case_helper"
8
+ require_relative "support/associations"
8
9
  require_relative "support/callbacks"
10
+ require_relative "support/location_builder"
9
11
  require_relative "runner_client"
10
12
  require_relative "hover"
11
13
  require_relative "code_lens"
@@ -57,12 +59,12 @@ module RubyLsp
57
59
  sig do
58
60
  override.params(
59
61
  response_builder: ResponseBuilders::Hover,
60
- nesting: T::Array[String],
62
+ node_context: NodeContext,
61
63
  dispatcher: Prism::Dispatcher,
62
64
  ).void
63
65
  end
64
- def create_hover_listener(response_builder, nesting, dispatcher)
65
- Hover.new(@client, response_builder, nesting, T.must(@global_state), dispatcher)
66
+ def create_hover_listener(response_builder, node_context, dispatcher)
67
+ Hover.new(@client, response_builder, node_context, T.must(@global_state), dispatcher)
66
68
  end
67
69
 
68
70
  sig do
@@ -79,13 +81,13 @@ module RubyLsp
79
81
  override.params(
80
82
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
81
83
  uri: URI::Generic,
82
- nesting: T::Array[String],
84
+ node_context: NodeContext,
83
85
  dispatcher: Prism::Dispatcher,
84
86
  ).void
85
87
  end
86
- def create_definition_listener(response_builder, uri, nesting, dispatcher)
88
+ def create_definition_listener(response_builder, uri, node_context, dispatcher)
87
89
  index = T.must(@global_state).index
88
- Definition.new(@client, response_builder, nesting, index, dispatcher)
90
+ Definition.new(@client, response_builder, node_context, index, dispatcher)
89
91
  end
90
92
 
91
93
  sig { params(changes: T::Array[{ uri: String, type: Integer }]).void }
@@ -6,6 +6,7 @@ module RubyLsp
6
6
  # ![CodeLens demo](../../code_lens.gif)
7
7
  #
8
8
  # This feature adds several CodeLens features for Rails applications using Active Support test cases:
9
+ #
9
10
  # - Run tests in the VS Terminal
10
11
  # - Run tests in the VS Code Test Explorer
11
12
  # - Debug tests
@@ -10,6 +10,7 @@ module RubyLsp
10
10
  # definition of the symbol under the cursor.
11
11
  #
12
12
  # Currently supported targets:
13
+ #
13
14
  # - Callbacks
14
15
  # - Named routes (e.g. `users_path`)
15
16
  #
@@ -20,6 +21,7 @@ module RubyLsp
20
21
  # ```
21
22
  #
22
23
  # Notes for named routes:
24
+ #
23
25
  # - It is available only in Rails 7.1 or newer.
24
26
  # - Route may be defined across multiple files, e.g. using `draw`, rather than in `routes.rb`.
25
27
  # - Routes won't be found if not defined for the Rails development environment.
@@ -33,15 +35,15 @@ module RubyLsp
33
35
  params(
34
36
  client: RunnerClient,
35
37
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
36
- nesting: T::Array[String],
38
+ node_context: NodeContext,
37
39
  index: RubyIndexer::Index,
38
40
  dispatcher: Prism::Dispatcher,
39
41
  ).void
40
42
  end
41
- def initialize(client, response_builder, nesting, index, dispatcher)
43
+ def initialize(client, response_builder, node_context, index, dispatcher)
42
44
  @client = client
43
45
  @response_builder = response_builder
44
- @nesting = nesting
46
+ @nesting = T.let(node_context.nesting, T::Array[String])
45
47
  @index = index
46
48
 
47
49
  dispatcher.register(self, :on_call_node_enter)
@@ -55,7 +57,9 @@ module RubyLsp
55
57
 
56
58
  return unless message
57
59
 
58
- if Support::Callbacks::ALL.include?(message)
60
+ if Support::Associations::ALL.include?(message)
61
+ handle_association(node)
62
+ elsif Support::Callbacks::ALL.include?(message)
59
63
  handle_callback(node)
60
64
  elsif message.end_with?("_path") || message.end_with?("_url")
61
65
  handle_route(node)
@@ -84,23 +88,28 @@ module RubyLsp
84
88
  end
85
89
 
86
90
  sig { params(node: Prism::CallNode).void }
87
- def handle_route(node)
88
- result = @client.route_location(T.must(node.message))
91
+ def handle_association(node)
92
+ first_argument = node.arguments&.arguments&.first
93
+ return unless first_argument.is_a?(Prism::SymbolNode)
94
+
95
+ association_name = first_argument.unescaped
96
+
97
+ result = @client.association_target_location(
98
+ model_name: @nesting.join("::"),
99
+ association_name: association_name,
100
+ )
101
+
89
102
  return unless result
90
103
 
91
- *file_parts, line = result.fetch(:location).split(":")
104
+ @response_builder << Support::LocationBuilder.line_location_from_s(result.fetch(:location))
105
+ end
92
106
 
93
- # On Windows, file paths will look something like `C:/path/to/file.rb:123`. Only the last colon is the line
94
- # number and all other parts compose the file path
95
- file_path = file_parts.join(":")
107
+ sig { params(node: Prism::CallNode).void }
108
+ def handle_route(node)
109
+ result = @client.route_location(T.must(node.message))
110
+ return unless result
96
111
 
97
- @response_builder << Interface::Location.new(
98
- uri: URI::Generic.from_path(path: file_path).to_s,
99
- range: Interface::Range.new(
100
- start: Interface::Position.new(line: Integer(line) - 1, character: 0),
101
- end: Interface::Position.new(line: Integer(line) - 1, character: 0),
102
- ),
103
- )
112
+ @response_builder << Support::LocationBuilder.line_location_from_s(result.fetch(:location))
104
113
  end
105
114
 
106
115
  sig { params(name: String).void }
@@ -24,15 +24,15 @@ module RubyLsp
24
24
  params(
25
25
  client: RunnerClient,
26
26
  response_builder: ResponseBuilders::Hover,
27
- nesting: T::Array[String],
27
+ node_context: NodeContext,
28
28
  global_state: GlobalState,
29
29
  dispatcher: Prism::Dispatcher,
30
30
  ).void
31
31
  end
32
- def initialize(client, response_builder, nesting, global_state, dispatcher)
32
+ def initialize(client, response_builder, node_context, global_state, dispatcher)
33
33
  @client = client
34
34
  @response_builder = response_builder
35
- @nesting = nesting
35
+ @nesting = T.let(node_context.nesting, T::Array[String])
36
36
  @index = T.let(global_state.index, RubyIndexer::Index)
37
37
  dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter, :on_call_node_enter)
38
38
  end
@@ -98,6 +98,22 @@ module RubyLsp
98
98
  nil
99
99
  end
100
100
 
101
+ sig do
102
+ params(
103
+ model_name: String,
104
+ association_name: String,
105
+ ).returns(T.nilable(T::Hash[Symbol, T.untyped]))
106
+ end
107
+ def association_target_location(model_name:, association_name:)
108
+ make_request(
109
+ "association_target_location",
110
+ model_name: model_name,
111
+ association_name: association_name,
112
+ )
113
+ rescue => e
114
+ $stderr.puts("Ruby LSP Rails failed with #{e.message}: #{@stderr.read}")
115
+ end
116
+
101
117
  sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
102
118
  def route_location(name)
103
119
  make_request("route_location", name: name)
@@ -20,6 +20,10 @@ module RubyLsp
20
20
  end
21
21
 
22
22
  def start
23
+ # Load routes if they haven't been loaded yet (see https://github.com/rails/rails/pull/51614).
24
+ routes_reloader = ::Rails.application.routes_reloader
25
+ routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
26
+
23
27
  initialize_result = { result: { message: "ok" } }.to_json
24
28
  $stdout.write("Content-Length: #{initialize_result.length}\r\n\r\n#{initialize_result}")
25
29
 
@@ -43,6 +47,8 @@ module RubyLsp
43
47
  VOID
44
48
  when "model"
45
49
  resolve_database_info_from_model(params.fetch(:name))
50
+ when "association_target_location"
51
+ resolve_association_target(params)
46
52
  when "reload"
47
53
  ::Rails.application.reloader.reload!
48
54
  VOID
@@ -110,6 +116,29 @@ module RubyLsp
110
116
  { error: e.full_message(highlight: false) }
111
117
  end
112
118
 
119
+ def resolve_association_target(params)
120
+ const = ActiveSupport::Inflector.safe_constantize(params[:model_name])
121
+ unless active_record_model?(const)
122
+ return {
123
+ result: nil,
124
+ }
125
+ end
126
+
127
+ association_klass = const.reflect_on_association(params[:association_name].intern).klass
128
+
129
+ source_location = Object.const_source_location(association_klass.to_s)
130
+
131
+ {
132
+ result: {
133
+ location: source_location.first + ":" + source_location.second.to_s,
134
+ },
135
+ }
136
+ rescue NameError
137
+ {
138
+ result: nil,
139
+ }
140
+ end
141
+
113
142
  def active_record_model?(const)
114
143
  !!(
115
144
  const &&
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Rails
6
+ module Support
7
+ module Associations
8
+ ALL = T.let(
9
+ [
10
+ "belongs_to",
11
+ "has_many",
12
+ "has_one",
13
+ "has_and_belongs_to_many",
14
+ ].freeze,
15
+ T::Array[String],
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Rails
6
+ module Support
7
+ class LocationBuilder
8
+ class << self
9
+ extend T::Sig
10
+
11
+ sig { params(location_string: String).returns(Interface::Location) }
12
+ def line_location_from_s(location_string)
13
+ *file_parts, line = location_string.split(":")
14
+
15
+ raise ArgumentError, "Invalid location string given" unless file_parts
16
+
17
+ # On Windows, file paths will look something like `C:/path/to/file.rb:123`. Only the last colon is the line
18
+ # number and all other parts compose the file path
19
+ file_path = file_parts.join(":")
20
+
21
+ Interface::Location.new(
22
+ uri: URI::Generic.from_path(path: file_path).to_s,
23
+ range: Interface::Range.new(
24
+ start: Interface::Position.new(line: Integer(line) - 1, character: 0),
25
+ end: Interface::Position.new(line: Integer(line) - 1, character: 0),
26
+ ),
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.6"
6
+ VERSION = "0.3.7"
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.3.6
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-03 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-lsp
@@ -16,34 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.16.5
19
+ version: 0.17.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 0.17.0
22
+ version: 0.18.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 0.16.5
30
- - - "<"
31
28
  - !ruby/object:Gem::Version
32
29
  version: 0.17.0
33
- - !ruby/object:Gem::Dependency
34
- name: sorbet-runtime
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: 0.5.9897
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
30
+ - - "<"
45
31
  - !ruby/object:Gem::Version
46
- version: 0.5.9897
32
+ version: 0.18.0
47
33
  description: A Ruby LSP addon that adds extra editor functionality for Rails applications
48
34
  email:
49
35
  - ruby@shopify.com
@@ -63,7 +49,9 @@ files:
63
49
  - lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
64
50
  - lib/ruby_lsp/ruby_lsp_rails/server.rb
65
51
  - lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb
52
+ - lib/ruby_lsp/ruby_lsp_rails/support/associations.rb
66
53
  - lib/ruby_lsp/ruby_lsp_rails/support/callbacks.rb
54
+ - lib/ruby_lsp/ruby_lsp_rails/support/location_builder.rb
67
55
  - lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
68
56
  - lib/ruby_lsp_rails/railtie.rb
69
57
  - lib/ruby_lsp_rails/version.rb
@@ -91,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
79
  - !ruby/object:Gem::Version
92
80
  version: '0'
93
81
  requirements: []
94
- rubygems_version: 3.5.9
82
+ rubygems_version: 3.5.10
95
83
  signing_key:
96
84
  specification_version: 4
97
85
  summary: A Ruby LSP addon for Rails