ruby-lsp-rails 0.3.6 → 0.3.8

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: accc5f9539ddceaa7fde3dfdb64fcc74f697e9d17178c45e9271df463cad4f18
4
+ data.tar.gz: d594ee73741270b0d6837680a5bacc6e912c1ca856aeaa5806e7d1ad9e96aeb8
5
5
  SHA512:
6
- metadata.gz: 3b527faec645912d5c49aef3d4d2bb094a789410283ddcebcc9be168f703807831a5a5f2561a88dcecf5c4e3d894c0d2ccf9fe617688f09462e88ab7ae1a2ee8
7
- data.tar.gz: b080811bd5697ae68573df2fd6d922dec47e2e16b5264cd86b707f73cab9689fffa858851da2ebed7a819930fbc148234db4de5c4a15dc2e4fffbec3e4fdfbcb
6
+ metadata.gz: 955347d73c343311571e62d7c0bb656a3cc9176f8e6ff87ab010287345d754b49f1889c436c716ce0a40c2aa45938f094124cd18315f787c854db7aed2172ee1
7
+ data.tar.gz: 65731a4414f5b68884ea6003097b5b1e67caaaaf2a954b0d25de0ed937cc7600d1b1e4e8d2c25a28b25f526c72fc05e6f9a1996874f95584c4450a4212ef9269
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,18 +35,46 @@ 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
+ @node_context = node_context
47
+ @nesting = T.let(node_context.nesting, T::Array[String])
45
48
  @index = index
46
49
 
47
- dispatcher.register(self, :on_call_node_enter)
50
+ dispatcher.register(self, :on_call_node_enter, :on_symbol_node_enter, :on_string_node_enter)
51
+ end
52
+
53
+ sig { params(node: Prism::SymbolNode).void }
54
+ def on_symbol_node_enter(node)
55
+ handle_possible_dsl(node)
56
+ end
57
+
58
+ sig { params(node: Prism::StringNode).void }
59
+ def on_string_node_enter(node)
60
+ handle_possible_dsl(node)
61
+ end
62
+
63
+ sig { params(node: T.any(Prism::SymbolNode, Prism::StringNode)).void }
64
+ def handle_possible_dsl(node)
65
+ node = @node_context.call_node
66
+ return unless node
67
+ return unless self_receiver?(node)
68
+
69
+ message = node.message
70
+
71
+ return unless message
72
+
73
+ if Support::Associations::ALL.include?(message)
74
+ handle_association(node)
75
+ elsif Support::Callbacks::ALL.include?(message)
76
+ handle_callback(node)
77
+ end
48
78
  end
49
79
 
50
80
  sig { params(node: Prism::CallNode).void }
@@ -55,9 +85,7 @@ module RubyLsp
55
85
 
56
86
  return unless message
57
87
 
58
- if Support::Callbacks::ALL.include?(message)
59
- handle_callback(node)
60
- elsif message.end_with?("_path") || message.end_with?("_url")
88
+ if message.end_with?("_path") || message.end_with?("_url")
61
89
  handle_route(node)
62
90
  end
63
91
  end
@@ -84,23 +112,28 @@ module RubyLsp
84
112
  end
85
113
 
86
114
  sig { params(node: Prism::CallNode).void }
87
- def handle_route(node)
88
- result = @client.route_location(T.must(node.message))
115
+ def handle_association(node)
116
+ first_argument = node.arguments&.arguments&.first
117
+ return unless first_argument.is_a?(Prism::SymbolNode)
118
+
119
+ association_name = first_argument.unescaped
120
+
121
+ result = @client.association_target_location(
122
+ model_name: @nesting.join("::"),
123
+ association_name: association_name,
124
+ )
125
+
89
126
  return unless result
90
127
 
91
- *file_parts, line = result.fetch(:location).split(":")
128
+ @response_builder << Support::LocationBuilder.line_location_from_s(result.fetch(:location))
129
+ end
92
130
 
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(":")
131
+ sig { params(node: Prism::CallNode).void }
132
+ def handle_route(node)
133
+ result = @client.route_location(T.must(node.message))
134
+ return unless result
96
135
 
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
- )
136
+ @response_builder << Support::LocationBuilder.line_location_from_s(result.fetch(:location))
104
137
  end
105
138
 
106
139
  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,10 +116,34 @@ 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 &&
116
145
  defined?(ActiveRecord) &&
146
+ const.is_a?(Class) &&
117
147
  ActiveRecord::Base > const && # We do this 'backwards' in case the class overwrites `<`
118
148
  !const.abstract_class?
119
149
  )
@@ -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
@@ -16,6 +16,10 @@ module RubyLsp
16
16
  "around_create",
17
17
  "after_create",
18
18
  "after_commit",
19
+ "after_create_commit",
20
+ "after_update_commit",
21
+ "after_destroy_commit",
22
+ "after_save_commit",
19
23
  "after_rollback",
20
24
  "before_update",
21
25
  "around_update",
@@ -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.8"
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.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-03 00:00:00.000000000 Z
11
+ date: 2024-07-02 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.2
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
28
  - !ruby/object:Gem::Version
29
- version: 0.16.5
29
+ version: 0.17.2
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- 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
- - - ">="
45
- - !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
@@ -76,7 +64,7 @@ metadata:
76
64
  homepage_uri: https://github.com/Shopify/ruby-lsp-rails
77
65
  source_code_uri: https://github.com/Shopify/ruby-lsp-rails
78
66
  changelog_uri: https://github.com/Shopify/ruby-lsp-rails/releases
79
- post_install_message:
67
+ post_install_message:
80
68
  rdoc_options: []
81
69
  require_paths:
82
70
  - lib
@@ -91,8 +79,8 @@ 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
95
- signing_key:
82
+ rubygems_version: 3.5.14
83
+ signing_key:
96
84
  specification_version: 4
97
85
  summary: A Ruby LSP addon for Rails
98
86
  test_files: []