diff --git a/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb b/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb index bdad1445..e21026f1 100644 --- a/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb +++ b/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb @@ -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, @@ -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) @@ -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) @@ -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) @@ -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( @@ -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 diff --git a/test/ruby_lsp_rails/document_symbol_test.rb b/test/ruby_lsp_rails/document_symbol_test.rb index 9896519f..15864ab2 100644 --- a/test/ruby_lsp_rails/document_symbol_test.rb +++ b/test/ruby_lsp_rails/document_symbol_test.rb @@ -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)