Skip to content

Commit 915b755

Browse files
authored
Add documentSymbol support to schema.rb (#660)
* Add documentSymbol support to schema.rb * don't push to the namespace stack * add support for symbol argument, sorbet checks * handle symbols and string in the same way * add additional test case, ensure we are inside define block
1 parent 74b324d commit 915b755

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed

lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ class DocumentSymbol
1616
def initialize(response_builder, dispatcher)
1717
@response_builder = response_builder
1818
@namespace_stack = [] #: Array[String]
19+
@inside_schema = false #: bool
1920

2021
dispatcher.register(
2122
self,
2223
:on_call_node_enter,
24+
:on_call_node_leave,
2325
:on_class_node_enter,
2426
:on_class_node_leave,
2527
:on_module_node_enter,
@@ -29,6 +31,13 @@ def initialize(response_builder, dispatcher)
2931

3032
#: (Prism::CallNode node) -> void
3133
def on_call_node_enter(node)
34+
message = node.message
35+
return unless message
36+
37+
@inside_schema = true if node_is_schema_define?(node)
38+
39+
handle_schema_table(node)
40+
3241
return if @namespace_stack.empty?
3342

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

47-
message = node.message
48-
return unless message
49-
5056
case message
5157
when *Support::Callbacks::ALL, "validate"
5258
handle_all_arg_types(node, message)
@@ -58,6 +64,11 @@ def on_call_node_enter(node)
5864
end
5965
end
6066

67+
#: (Prism::CallNode node) -> void
68+
def on_call_node_leave(node)
69+
@inside_schema = false if node_is_schema_define?(node)
70+
end
71+
6172
#: (Prism::ClassNode node) -> void
6273
def on_class_node_enter(node)
6374
add_to_namespace_stack(node)
@@ -213,6 +224,39 @@ def handle_class_arg_types(node, message)
213224
end
214225
end
215226

227+
#: (Prism::CallNode node) -> void
228+
def handle_schema_table(node)
229+
return unless @inside_schema
230+
return unless node.message == "create_table"
231+
232+
table_name_argument = node.arguments&.arguments&.first
233+
234+
return unless table_name_argument
235+
236+
case table_name_argument
237+
when Prism::SymbolNode
238+
name = table_name_argument.value
239+
return unless name
240+
241+
append_document_symbol(
242+
name: name,
243+
range: range_from_location(table_name_argument.location),
244+
selection_range: range_from_location(
245+
table_name_argument.value_loc, #: as !nil
246+
),
247+
)
248+
when Prism::StringNode
249+
name = table_name_argument.content
250+
return if name.empty?
251+
252+
append_document_symbol(
253+
name: name,
254+
range: range_from_location(table_name_argument.location),
255+
selection_range: range_from_location(table_name_argument.content_loc),
256+
)
257+
end
258+
end
259+
216260
#: (name: String, range: RubyLsp::Interface::Range, selection_range: RubyLsp::Interface::Range) -> void
217261
def append_document_symbol(name:, range:, selection_range:)
218262
@response_builder.last.children << RubyLsp::Interface::DocumentSymbol.new(
@@ -222,6 +266,19 @@ def append_document_symbol(name:, range:, selection_range:)
222266
selection_range: selection_range,
223267
)
224268
end
269+
270+
#: (Prism::CallNode node) -> bool
271+
def node_is_schema_define?(node)
272+
return false if node.message != "define"
273+
274+
schema_node = node.receiver
275+
return false unless schema_node.is_a?(Prism::CallNode)
276+
277+
active_record_node = schema_node.receiver
278+
return false unless active_record_node.is_a?(Prism::ConstantPathNode)
279+
280+
constant_name(active_record_node) == "ActiveRecord::Schema"
281+
end
225282
end
226283
end
227284
end

test/ruby_lsp_rails/document_symbol_test.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,53 @@ class FooModel < ApplicationRecord
439439
assert_empty(response[0].children)
440440
end
441441

442+
test "adds symbols for table names in schema.rb" do
443+
response = generate_document_symbols_for_source(<<~RUBY)
444+
ActiveRecord::Schema[8.1].define(version: 2025_10_07_010540) do
445+
create_table "action_text_rich_texts", force: :cascade do |t|
446+
t.text "body"
447+
t.datetime "created_at", null: false
448+
t.string "name", null: false
449+
t.bigint "record_id", null: false
450+
t.string "record_type", null: false
451+
t.datetime "updated_at", null: false
452+
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
453+
end
454+
455+
create_table :active_storage_attachments, force: :cascade do |t|
456+
t.bigint "blob_id", null: false
457+
t.datetime "created_at", null: false
458+
t.string "name", null: false
459+
t.bigint "record_id", null: false
460+
t.string "record_type", null: false
461+
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
462+
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
463+
end
464+
end
465+
RUBY
466+
467+
assert_equal(2, response.size)
468+
assert_equal("action_text_rich_texts", response[0].name)
469+
assert_equal("active_storage_attachments", response[1].name)
470+
end
471+
472+
test "should not add create_table symbol unless inside migration block" do
473+
response = generate_document_symbols_for_source(<<~RUBY)
474+
create_table "action_text_rich_texts", force: :cascade do |t|
475+
t.text "body"
476+
t.datetime "created_at", null: false
477+
t.string "name", null: false
478+
t.bigint "record_id", null: false
479+
t.string "record_type", null: false
480+
t.datetime "updated_at", null: false
481+
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
482+
end
483+
end
484+
RUBY
485+
486+
assert_equal(0, response.size)
487+
end
488+
442489
private
443490

444491
def generate_document_symbols_for_source(source)

0 commit comments

Comments
 (0)