Skip to content

Add definitions for Rails callbacks #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ There is no need to add the gem to your bundle.
* Hover over an ActiveRecord model to reveal its schema.
* Run or debug a test by clicking on the code lens which appears above the test class, or an individual test.
* Navigate to associations, validations, callbacks and test cases using your editor's "Go to Symbol" feature, or outline view.
* Jump to the definition of callbacks using your editor's "Go to Definition" feature.

## Documentation

Expand Down
1 change: 1 addition & 0 deletions lib/ruby-lsp-rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module RubyLsp
# - [Hover](rdoc-ref:RubyLsp::Rails::Hover)
# - [CodeLens](rdoc-ref:RubyLsp::Rails::CodeLens)
# - [DocumentSymbol](rdoc-ref:RubyLsp::Rails::DocumentSymbol)
# - [Definition](rdoc-ref:RubyLsp::Rails::Definition)
module Rails
end
end
15 changes: 15 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/addon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
require "ruby_lsp/addon"

require_relative "support/active_support_test_case_helper"
require_relative "support/callbacks"
require_relative "runner_client"
require_relative "hover"
require_relative "code_lens"
require_relative "document_symbol"
require_relative "definition"

module RubyLsp
module Rails
Expand Down Expand Up @@ -68,6 +70,19 @@ def create_document_symbol_listener(response_builder, dispatcher)
DocumentSymbol.new(response_builder, dispatcher)
end

sig do
override.params(
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
uri: URI::Generic,
nesting: T::Array[String],
index: RubyIndexer::Index,
dispatcher: Prism::Dispatcher,
).void
end
def create_definition_listener(response_builder, uri, nesting, index, dispatcher)
Definition.new(response_builder, nesting, index, dispatcher)
end

sig { params(changes: T::Array[{ uri: String, type: Integer }]).void }
def workspace_did_change_watched_files(changes)
if changes.any? do |change|
Expand Down
87 changes: 87 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/definition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Rails
# ![Definition demo](../../definition.gif)
#
# The [definition
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
# definition of the symbol under the cursor.
#
# Currently supported targets:
# - Callbacks
#
# # Example
#
# ```ruby
# before_action :foo # <- Go to definition on this symbol will jump to the method if it is defined in the same class
# ```
class Definition
extend T::Sig
include Requests::Support::Common

sig do
params(
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
nesting: T::Array[String],
index: RubyIndexer::Index,
dispatcher: Prism::Dispatcher,
).void
end
def initialize(response_builder, nesting, index, dispatcher)
@response_builder = response_builder
@nesting = nesting
@index = index

dispatcher.register(self, :on_call_node_enter)
end

sig { params(node: Prism::CallNode).void }
def on_call_node_enter(node)
return unless self_receiver?(node)

message = node.message

return unless message && Support::Callbacks::ALL.include?(message)

arguments = node.arguments&.arguments
return unless arguments&.any?

arguments.each do |argument|
name = case argument
when Prism::SymbolNode
argument.value
when Prism::StringNode
argument.content
end

next unless name

collect_definitions(name)
end
end

private

sig { params(name: String).void }
def collect_definitions(name)
methods = @index.resolve_method(name, @nesting.join("::"))
return unless methods

methods.each do |target_method|
location = target_method.location
file_path = target_method.file_path

@response_builder << Interface::Location.new(
uri: URI::Generic.from_path(path: file_path).to_s,
range: Interface::Range.new(
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
),
)
end
end
end
end
end
59 changes: 1 addition & 58 deletions lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,63 +13,6 @@ class DocumentSymbol
include Requests::Support::Common
include ActiveSupportTestCaseHelper

MODEL_CALLBACKS = T.let(
[
"before_validation",
"after_validation",
"before_save",
"around_save",
"after_save",
"before_create",
"around_create",
"after_create",
"after_commit",
"after_rollback",
"before_update",
"around_update",
"after_update",
"before_destroy",
"around_destroy",
"after_destroy",
"after_initialize",
"after_find",
"after_touch",
].freeze,
T::Array[String],
)

CONTROLLER_CALLBACKS = T.let(
[
"after_action",
"append_after_action",
"append_around_action",
"append_before_action",
"around_action",
"before_action",
"prepend_after_action",
"prepend_around_action",
"prepend_before_action",
"skip_after_action",
"skip_around_action",
"skip_before_action",
].freeze,
T::Array[String],
)

JOB_CALLBACKS = T.let(
[
"after_enqueue",
"after_perform",
"around_enqueue",
"around_perform",
"before_enqueue",
"before_perform",
].freeze,
T::Array[String],
)

CALLBACKS = T.let((MODEL_CALLBACKS + CONTROLLER_CALLBACKS + JOB_CALLBACKS).freeze, T::Array[String])

sig do
params(
response_builder: ResponseBuilders::DocumentSymbol,
Expand Down Expand Up @@ -99,7 +42,7 @@ def on_call_node_enter(node)

message = node.message
case message
when *CALLBACKS, "validate"
when *Support::Callbacks::ALL, "validate"
handle_all_arg_types(node, T.must(message))
when "validates", "validates!", "validates_each", "belongs_to", "has_one", "has_many", "has_and_belongs_to_many"
handle_symbol_and_string_arg_types(node, T.must(message))
Expand Down
67 changes: 67 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/support/callbacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Rails
module Support
module Callbacks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe CallbacksList to emphasize there's no behaviour in here?

MODELS = T.let(
[
"before_validation",
"after_validation",
"before_save",
"around_save",
"after_save",
"before_create",
"around_create",
"after_create",
"after_commit",
"after_rollback",
"before_update",
"around_update",
"after_update",
"before_destroy",
"around_destroy",
"after_destroy",
"after_initialize",
"after_find",
"after_touch",
].freeze,
T::Array[String],
)

CONTROLLERS = T.let(
[
"after_action",
"append_after_action",
"append_around_action",
"append_before_action",
"around_action",
"before_action",
"prepend_after_action",
"prepend_around_action",
"prepend_before_action",
"skip_after_action",
"skip_around_action",
"skip_before_action",
].freeze,
T::Array[String],
)

JOBS = T.let(
[
"after_enqueue",
"after_perform",
"around_enqueue",
"around_perform",
"before_enqueue",
"before_perform",
].freeze,
T::Array[String],
)

ALL = T.let((MODELS + CONTROLLERS + JOBS).freeze, T::Array[String])
end
end
end
end
Binary file added misc/definition.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion test/dummy/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
# frozen_string_literal: true

class User < ApplicationRecord
before_create :foo_arg, -> () {}
before_create :foo, -> () {}
validates :name, presence: true
has_one :profile

private

def foo
puts "test"
end
end
Loading