Skip to content
Open
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
63 changes: 60 additions & 3 deletions lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ class DocumentSymbol
def initialize(response_builder, dispatcher)
@response_builder = response_builder
@namespace_stack = [] #: Array[String]
@inside_schema = false #: bool

dispatcher.register(
self,
:on_call_node_enter,
:on_call_node_leave,
:on_class_node_enter,
:on_class_node_leave,
:on_module_node_enter,
Expand All @@ -29,6 +31,13 @@ def initialize(response_builder, dispatcher)

#: (Prism::CallNode node) -> void
def on_call_node_enter(node)
message = node.message
return unless message

@inside_schema = true if node_is_schema_define?(node)

handle_schema_table(node)

return if @namespace_stack.empty?

content = extract_test_case_name(node)
Expand All @@ -44,9 +53,6 @@ def on_call_node_enter(node)
receiver = node.receiver
return if receiver && !receiver.is_a?(Prism::SelfNode)

message = node.message
return unless message

case message
when *Support::Callbacks::ALL, "validate"
handle_all_arg_types(node, message)
Expand All @@ -58,6 +64,11 @@ def on_call_node_enter(node)
end
end

#: (Prism::CallNode node) -> void
def on_call_node_leave(node)
@inside_schema = false if node_is_schema_define?(node)
end

#: (Prism::ClassNode node) -> void
def on_class_node_enter(node)
add_to_namespace_stack(node)
Expand Down Expand Up @@ -213,6 +224,39 @@ def handle_class_arg_types(node, message)
end
end

#: (Prism::CallNode node) -> void
def handle_schema_table(node)
return unless @inside_schema
return unless node.message == "create_table"

table_name_argument = node.arguments&.arguments&.first

return unless table_name_argument

case table_name_argument
when Prism::SymbolNode
name = table_name_argument.value
return unless name

append_document_symbol(
name: name,
range: range_from_location(table_name_argument.location),
selection_range: range_from_location(
table_name_argument.value_loc, #: as !nil
),
)
when Prism::StringNode
name = table_name_argument.content
return if name.empty?

append_document_symbol(
name: name,
range: range_from_location(table_name_argument.location),
selection_range: range_from_location(table_name_argument.content_loc),
)
end
end

#: (name: String, range: RubyLsp::Interface::Range, selection_range: RubyLsp::Interface::Range) -> void
def append_document_symbol(name:, range:, selection_range:)
@response_builder.last.children << RubyLsp::Interface::DocumentSymbol.new(
Expand All @@ -222,6 +266,19 @@ def append_document_symbol(name:, range:, selection_range:)
selection_range: selection_range,
)
end

#: (Prism::CallNode node) -> bool
def node_is_schema_define?(node)
return false if node.message != "define"

schema_node = node.receiver
return false unless schema_node.is_a?(Prism::CallNode)

active_record_node = schema_node.receiver
return false unless active_record_node.is_a?(Prism::ConstantPathNode)

constant_name(active_record_node) == "ActiveRecord::Schema"
end
end
end
end
47 changes: 47 additions & 0 deletions test/ruby_lsp_rails/document_symbol_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,53 @@ class FooModel < ApplicationRecord
assert_empty(response[0].children)
end

test "adds symbols for table names in schema.rb" do
response = generate_document_symbols_for_source(<<~RUBY)
ActiveRecord::Schema[8.1].define(version: 2025_10_07_010540) do
create_table "action_text_rich_texts", force: :cascade do |t|
t.text "body"
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.datetime "updated_at", null: false
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
end

create_table :active_storage_attachments, force: :cascade do |t|
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
end
RUBY

assert_equal(2, response.size)
assert_equal("action_text_rich_texts", response[0].name)
assert_equal("active_storage_attachments", response[1].name)
end

test "should not add create_table symbol unless inside migration block" do
response = generate_document_symbols_for_source(<<~RUBY)
create_table "action_text_rich_texts", force: :cascade do |t|
t.text "body"
t.datetime "created_at", null: false
t.string "name", null: false
t.bigint "record_id", null: false
t.string "record_type", null: false
t.datetime "updated_at", null: false
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
end
end
RUBY

assert_equal(0, response.size)
end

private

def generate_document_symbols_for_source(source)
Expand Down