Skip to content

Commit fce492d

Browse files
vinistockalexcrocha
andcommitted
Allow indexing enhancements to create namespaces
Co-authored-by: Alex Rocha <[email protected]>
1 parent 7d067e5 commit fce492d

File tree

5 files changed

+288
-144
lines changed

5 files changed

+288
-144
lines changed

jekyll/add-ons.markdown

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,11 @@ This is how you could write an enhancement to teach the Ruby LSP to understand t
271271
class MyIndexingEnhancement < RubyIndexer::Enhancement
272272
# This on call node handler is invoked any time during indexing when we find a method call. It can be used to insert
273273
# more entries into the index depending on the conditions
274-
def on_call_node_enter(owner, node, file_path, code_units_cache)
275-
return unless owner
274+
def on_call_node_enter(node)
275+
return unless @listener.current_owner
276276

277-
# Get the ancestors of the current class
278-
ancestors = @index.linearized_ancestors_of(owner.name)
279-
280-
# Return early unless the method call is the one we want to handle and the class invoking the DSL inherits from
281-
# our library's parent class
282-
return unless node.name == :my_dsl_that_creates_methods && ancestors.include?("MyLibrary::ParentClass")
277+
# Return early unless the method call is the one we want to handle
278+
return unless node.name == :my_dsl_that_creates_methods
283279

284280
# Create a new entry to be inserted in the index. This entry will represent the declaration that is created via
285281
# meta-programming. All entries are defined in the `entry.rb` file.
@@ -293,24 +289,16 @@ class MyIndexingEnhancement < RubyIndexer::Enhancement
293289
RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: :a)])
294290
]
295291

296-
new_entry = RubyIndexer::Entry::Method.new(
297-
"new_method", # The name of the method that gets created via meta-programming
298-
file_path, # The file_path where the DSL call was found. This should always just be the file_path received
299-
location, # The Prism node location where the DSL call was found
300-
location, # The Prism node location for the DSL name location. May or not be the same
301-
nil, # The documentation for this DSL call. This should always be `nil` to ensure lazy fetching of docs
302-
signatures, # All signatures for this method (every way it can be invoked)
303-
RubyIndexer::Entry::Visibility::PUBLIC, # The method's visibility
304-
owner, # The method's owner. This is almost always going to be the same owner received
292+
@listener.add_method(
293+
"new_method", # Name of the method
294+
location, # Prism location for the node defining this method
295+
signatures # Signatures available to invoke this method
305296
)
306-
307-
# Push the new entry to the index
308-
@index.add(new_entry)
309297
end
310298

311299
# This method is invoked when the parser has finished processing the method call node.
312300
# It can be used to perform cleanups like popping a stack...etc.
313-
def on_call_node_leave(owner, node, file_path, code_units_cache); end
301+
def on_call_node_leave(node); end
314302
end
315303
```
316304

lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb

Lines changed: 114 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ class DeclarationListener
1818
parse_result: Prism::ParseResult,
1919
file_path: String,
2020
collect_comments: T::Boolean,
21-
enhancements: T::Array[Enhancement],
2221
).void
2322
end
24-
def initialize(index, dispatcher, parse_result, file_path, collect_comments: false, enhancements: [])
23+
def initialize(index, dispatcher, parse_result, file_path, collect_comments: false)
2524
@index = index
2625
@file_path = file_path
27-
@enhancements = enhancements
26+
@enhancements = T.let(Enhancement.all(self), T::Array[Enhancement])
2827
@visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
2928
@comments_by_line = T.let(
3029
parse_result.comments.to_h do |c|
@@ -86,15 +85,9 @@ def initialize(index, dispatcher, parse_result, file_path, collect_comments: fal
8685

8786
sig { params(node: Prism::ClassNode).void }
8887
def on_class_node_enter(node)
89-
@visibility_stack.push(Entry::Visibility::PUBLIC)
9088
constant_path = node.constant_path
91-
name = constant_path.slice
92-
93-
comments = collect_comments(node)
94-
9589
superclass = node.superclass
96-
97-
nesting = actual_nesting(name)
90+
nesting = actual_nesting(constant_path.slice)
9891

9992
parent_class = case superclass
10093
when Prism::ConstantReadNode, Prism::ConstantPathNode
@@ -113,53 +106,29 @@ def on_class_node_enter(node)
113106
end
114107
end
115108

116-
entry = Entry::Class.new(
109+
add_class(
117110
nesting,
118-
@file_path,
119-
Location.from_prism_location(node.location, @code_units_cache),
120-
Location.from_prism_location(constant_path.location, @code_units_cache),
121-
comments,
122-
parent_class,
111+
node.location,
112+
constant_path.location,
113+
parent_class_name: parent_class,
114+
comments: collect_comments(node),
123115
)
124-
125-
@owner_stack << entry
126-
@index.add(entry)
127-
@stack << name
128116
end
129117

130118
sig { params(node: Prism::ClassNode).void }
131119
def on_class_node_leave(node)
132-
@stack.pop
133-
@owner_stack.pop
134-
@visibility_stack.pop
120+
pop_namespace_stack
135121
end
136122

137123
sig { params(node: Prism::ModuleNode).void }
138124
def on_module_node_enter(node)
139-
@visibility_stack.push(Entry::Visibility::PUBLIC)
140125
constant_path = node.constant_path
141-
name = constant_path.slice
142-
143-
comments = collect_comments(node)
144-
145-
entry = Entry::Module.new(
146-
actual_nesting(name),
147-
@file_path,
148-
Location.from_prism_location(node.location, @code_units_cache),
149-
Location.from_prism_location(constant_path.location, @code_units_cache),
150-
comments,
151-
)
152-
153-
@owner_stack << entry
154-
@index.add(entry)
155-
@stack << name
126+
add_module(constant_path.slice, node.location, constant_path.location, comments: collect_comments(node))
156127
end
157128

158129
sig { params(node: Prism::ModuleNode).void }
159130
def on_module_node_leave(node)
160-
@stack.pop
161-
@owner_stack.pop
162-
@visibility_stack.pop
131+
pop_namespace_stack
163132
end
164133

165134
sig { params(node: Prism::SingletonClassNode).void }
@@ -201,9 +170,7 @@ def on_singleton_class_node_enter(node)
201170

202171
sig { params(node: Prism::SingletonClassNode).void }
203172
def on_singleton_class_node_leave(node)
204-
@stack.pop
205-
@owner_stack.pop
206-
@visibility_stack.pop
173+
pop_namespace_stack
207174
end
208175

209176
sig { params(node: Prism::MultiWriteNode).void }
@@ -318,7 +285,7 @@ def on_call_node_enter(node)
318285
end
319286

320287
@enhancements.each do |enhancement|
321-
enhancement.on_call_node_enter(@owner_stack.last, node, @file_path, @code_units_cache)
288+
enhancement.on_call_node_enter(node)
322289
rescue StandardError => e
323290
@indexing_errors << <<~MSG
324291
Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node enter enhancement: #{e.message}
@@ -339,7 +306,7 @@ def on_call_node_leave(node)
339306
end
340307

341308
@enhancements.each do |enhancement|
342-
enhancement.on_call_node_leave(@owner_stack.last, node, @file_path, @code_units_cache)
309+
enhancement.on_call_node_leave(node)
343310
rescue StandardError => e
344311
@indexing_errors << <<~MSG
345312
Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node leave enhancement: #{e.message}
@@ -464,6 +431,98 @@ def on_alias_method_node_enter(node)
464431
)
465432
end
466433

434+
sig do
435+
params(
436+
name: String,
437+
node_location: Prism::Location,
438+
signatures: T::Array[Entry::Signature],
439+
visibility: Entry::Visibility,
440+
comments: T.nilable(String),
441+
).void
442+
end
443+
def add_method(name, node_location, signatures, visibility: Entry::Visibility::PUBLIC, comments: nil)
444+
location = Location.from_prism_location(node_location, @code_units_cache)
445+
446+
@index.add(Entry::Method.new(
447+
name,
448+
@file_path,
449+
location,
450+
location,
451+
comments,
452+
signatures,
453+
visibility,
454+
@owner_stack.last,
455+
))
456+
end
457+
458+
sig do
459+
params(
460+
name: String,
461+
full_location: Prism::Location,
462+
name_location: Prism::Location,
463+
comments: T.nilable(String),
464+
).void
465+
end
466+
def add_module(name, full_location, name_location, comments: nil)
467+
location = Location.from_prism_location(full_location, @code_units_cache)
468+
name_loc = Location.from_prism_location(name_location, @code_units_cache)
469+
470+
entry = Entry::Module.new(
471+
actual_nesting(name),
472+
@file_path,
473+
location,
474+
name_loc,
475+
comments,
476+
)
477+
478+
advance_namespace_stack(name, entry)
479+
end
480+
481+
sig do
482+
params(
483+
name_or_nesting: T.any(String, T::Array[String]),
484+
full_location: Prism::Location,
485+
name_location: Prism::Location,
486+
parent_class_name: T.nilable(String),
487+
comments: T.nilable(String),
488+
).void
489+
end
490+
def add_class(name_or_nesting, full_location, name_location, parent_class_name: nil, comments: nil)
491+
nesting = name_or_nesting.is_a?(Array) ? name_or_nesting : actual_nesting(name_or_nesting)
492+
entry = Entry::Class.new(
493+
nesting,
494+
@file_path,
495+
Location.from_prism_location(full_location, @code_units_cache),
496+
Location.from_prism_location(name_location, @code_units_cache),
497+
comments,
498+
parent_class_name,
499+
)
500+
501+
advance_namespace_stack(T.must(nesting.last), entry)
502+
end
503+
504+
sig { params(block: T.proc.params(index: Index, base: Entry::Namespace).void).void }
505+
def register_included_hook(&block)
506+
owner = @owner_stack.last
507+
return unless owner
508+
509+
@index.register_included_hook(owner.name) do |index, base|
510+
block.call(index, base)
511+
end
512+
end
513+
514+
sig { void }
515+
def pop_namespace_stack
516+
@stack.pop
517+
@owner_stack.pop
518+
@visibility_stack.pop
519+
end
520+
521+
sig { returns(T.nilable(Entry::Namespace)) }
522+
def current_owner
523+
@owner_stack.last
524+
end
525+
467526
private
468527

469528
sig do
@@ -921,5 +980,13 @@ def actual_nesting(name)
921980

922981
corrected_nesting
923982
end
983+
984+
sig { params(short_name: String, entry: Entry::Namespace).void }
985+
def advance_namespace_stack(short_name, entry)
986+
@visibility_stack.push(Entry::Visibility::PUBLIC)
987+
@owner_stack << entry
988+
@index.add(entry)
989+
@stack << short_name
990+
end
924991
end
925992
end

lib/ruby_indexer/lib/ruby_indexer/enhancement.rb

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,41 @@ class Enhancement
88

99
abstract!
1010

11-
sig { params(index: Index).void }
12-
def initialize(index)
13-
@index = index
11+
@enhancements = T.let([], T::Array[T::Class[Enhancement]])
12+
13+
class << self
14+
extend T::Sig
15+
16+
sig { params(child: T::Class[Enhancement]).void }
17+
def inherited(child)
18+
@enhancements << child
19+
super
20+
end
21+
22+
sig { params(listener: DeclarationListener).returns(T::Array[Enhancement]) }
23+
def all(listener)
24+
@enhancements.map { |enhancement| enhancement.new(listener) }
25+
end
26+
27+
# Only available for testing purposes
28+
sig { void }
29+
def clear
30+
@enhancements.clear
31+
end
32+
end
33+
34+
sig { params(listener: DeclarationListener).void }
35+
def initialize(listener)
36+
@listener = listener
1437
end
1538

1639
# The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
1740
# register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
1841
# `ClassMethods` modules
19-
sig do
20-
overridable.params(
21-
owner: T.nilable(Entry::Namespace),
22-
node: Prism::CallNode,
23-
file_path: String,
24-
code_units_cache: T.any(
25-
T.proc.params(arg0: Integer).returns(Integer),
26-
Prism::CodeUnitsCache,
27-
),
28-
).void
29-
end
30-
def on_call_node_enter(owner, node, file_path, code_units_cache); end
31-
32-
sig do
33-
overridable.params(
34-
owner: T.nilable(Entry::Namespace),
35-
node: Prism::CallNode,
36-
file_path: String,
37-
code_units_cache: T.any(
38-
T.proc.params(arg0: Integer).returns(Integer),
39-
Prism::CodeUnitsCache,
40-
),
41-
).void
42-
end
43-
def on_call_node_leave(owner, node, file_path, code_units_cache); end
42+
sig { overridable.params(node: Prism::CallNode).void }
43+
def on_call_node_enter(node); end # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
44+
45+
sig { overridable.params(node: Prism::CallNode).void }
46+
def on_call_node_leave(node); end # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
4447
end
4548
end

lib/ruby_indexer/lib/ruby_indexer/index.rb

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ def initialize
4040
# Holds the linearized ancestors list for every namespace
4141
@ancestors = T.let({}, T::Hash[String, T::Array[String]])
4242

43-
# List of classes that are enhancing the index
44-
@enhancements = T.let([], T::Array[Enhancement])
45-
4643
# Map of module name to included hooks that have to be executed when we include the given module
4744
@included_hooks = T.let(
4845
{},
@@ -52,12 +49,6 @@ def initialize
5249
@configuration = T.let(RubyIndexer::Configuration.new, Configuration)
5350
end
5451

55-
# Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
56-
sig { params(enhancement: Enhancement).void }
57-
def register_enhancement(enhancement)
58-
@enhancements << enhancement
59-
end
60-
6152
# Register an included `hook` that will be executed when `module_name` is included into any namespace
6253
sig { params(module_name: String, hook: T.proc.params(index: Index, base: Entry::Namespace).void).void }
6354
def register_included_hook(module_name, &hook)
@@ -396,7 +387,6 @@ def index_single(indexable_path, source = nil, collect_comments: true)
396387
result,
397388
indexable_path.full_path,
398389
collect_comments: collect_comments,
399-
enhancements: @enhancements,
400390
)
401391
dispatcher.dispatch(result.value)
402392

0 commit comments

Comments
 (0)