ruby-lsp-rails 0.3.6 → 0.3.8

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: 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: []