Skip to content

Commit c99069c

Browse files
committed
Add concern indexing enhancement
1 parent 4eb7537 commit c99069c

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

lib/ruby_lsp/ruby_lsp_rails/addon.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require_relative "code_lens"
1414
require_relative "document_symbol"
1515
require_relative "definition"
16+
require_relative "indexing_enhancement"
1617

1718
module RubyLsp
1819
module Rails
@@ -35,6 +36,8 @@ def activate(global_state, message_queue)
3536
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
3637
Thread.new { @client = RunnerClient.create_client }
3738
register_additional_file_watchers(global_state: global_state, message_queue: message_queue)
39+
40+
T.must(@global_state).index.register_enhancement(IndexingEnhancement.new)
3841
end
3942

4043
sig { override.void }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
module RubyLsp
5+
module Rails
6+
class IndexingEnhancement
7+
extend T::Sig
8+
include RubyIndexer::Enhancement
9+
10+
sig do
11+
override.params(
12+
index: RubyIndexer::Index,
13+
owner: T.nilable(RubyIndexer::Entry::Namespace),
14+
node: Prism::CallNode,
15+
file_path: String,
16+
).void
17+
end
18+
def on_call_node(index, owner, node, file_path)
19+
return unless owner
20+
21+
name = node.name
22+
23+
case name
24+
when :extend
25+
handle_concern_extend(index, owner, node)
26+
end
27+
end
28+
29+
private
30+
31+
sig do
32+
params(
33+
index: RubyIndexer::Index,
34+
owner: RubyIndexer::Entry::Namespace,
35+
node: Prism::CallNode,
36+
).void
37+
end
38+
def handle_concern_extend(index, owner, node)
39+
arguments = node.arguments&.arguments
40+
return unless arguments
41+
42+
arguments.each do |node|
43+
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
44+
45+
module_name = node.full_name
46+
next unless module_name == "ActiveSupport::Concern"
47+
48+
index.register_included_hook(owner.name) do |index, base|
49+
class_methods_name = "#{owner.name}::ClassMethods"
50+
51+
if index.indexed?(class_methods_name)
52+
singleton = index.existing_or_new_singleton_class(base.name)
53+
singleton.mixin_operations << RubyIndexer::Entry::Include.new(class_methods_name)
54+
end
55+
end
56+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
57+
Prism::ConstantPathNode::MissingNodesInConstantPathError
58+
# Do nothing
59+
end
60+
end
61+
end
62+
end
63+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
require "test_helper"
5+
6+
module RubyLsp
7+
module Rails
8+
class IndexingEnhancementTest < ActiveSupport::TestCase
9+
class << self
10+
# For these tests, it's convenient to have the index fully populated with Rails information, but we don't have
11+
# to reindex on every single example or that will be too slow
12+
def populated_index
13+
@index ||= begin
14+
index = RubyIndexer::Index.new
15+
index.register_enhancement(IndexingEnhancement.new)
16+
index.index_all
17+
index
18+
end
19+
end
20+
end
21+
22+
def setup
23+
@index = self.class.populated_index
24+
end
25+
26+
test "ClassMethods module inside concerns are automatically extended" do
27+
@index.index_single(RubyIndexer::IndexablePath.new(nil, "/fake.rb"), <<~RUBY)
28+
class Post < ActiveRecord::Base
29+
end
30+
RUBY
31+
32+
ancestors = @index.linearized_ancestors_of("Post::<Class:Post>")
33+
assert_includes(ancestors, "ActiveRecord::Associations::ClassMethods")
34+
assert_includes(ancestors, "ActiveRecord::Store::ClassMethods")
35+
assert_includes(ancestors, "ActiveRecord::AttributeMethods::ClassMethods")
36+
end
37+
end
38+
end
39+
end

0 commit comments

Comments
 (0)