Skip to content

Commit

Permalink
Nest declarations inside index entries
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Apr 24, 2024
1 parent 5746b68 commit d37657a
Show file tree
Hide file tree
Showing 17 changed files with 650 additions and 583 deletions.
209 changes: 168 additions & 41 deletions lib/ruby_indexer/lib/ruby_indexer/collector.rb
Expand Up @@ -163,25 +163,23 @@ def handle_call_node(node)
def handle_def_node(node)
method_name = node.name.to_s
comments = collect_comments(node)
declaration = Entry::MemberDeclaration.new(list_params(node.parameters), @file_path, node.location, comments)

case node.receiver
when nil
@index << Entry::InstanceMethod.new(
entry = (@current_owner && @index.resolve_method(
method_name,
@file_path,
node.location,
comments,
node.parameters,
@current_owner,
)
@current_owner.name,
)) || Entry::InstanceMethod.new(method_name, @current_owner)
@index.add(entry, @file_path)
entry.add_declaration(declaration)
when Prism::SelfNode
@index << Entry::SingletonMethod.new(
entry = (@current_owner && @index.resolve_method(
method_name,
@file_path,
node.location,
comments,
node.parameters,
@current_owner,
)
@current_owner.name,
)) || Entry::SingletonMethod.new(method_name, @current_owner)
@index.add(entry, @file_path)
entry.add_declaration(declaration)
end
end

Expand All @@ -206,8 +204,8 @@ def handle_private_constant(node)

# The private_constant method does not resolve the constant name. It always points to a constant that needs to
# exist in the current namespace
entries = @index.get_constant(fully_qualify_name(name))
entries&.each { |entry| entry.visibility = :private }
entry = @index.get_constant(fully_qualify_name(name))
entry&.visibility = :private
end

sig do
Expand All @@ -231,25 +229,31 @@ def handle_private_constant(node)
def add_constant(node, name, value = nil)
value = node.value unless node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
comments = collect_comments(node)
entry = @index.get_constant(name)

@index << case value
when Prism::ConstantReadNode, Prism::ConstantPathNode
Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
unless entry
entry = case value
when Prism::ConstantReadNode, Prism::ConstantPathNode
Entry::UnresolvedAlias.new(value.slice, @stack.dup, name)
when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
Prism::ConstantOperatorWriteNode

# If the right hand side is another constant assignment, we need to visit it because that constant has to be
# indexed too
@queue.prepend(value)
Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
# If the right hand side is another constant assignment, we need to visit it because that constant has to be
# indexed too
@queue.prepend(value)
Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name)
when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
Prism::ConstantPathAndWriteNode

@queue.prepend(value)
Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
else
Entry::Constant.new(name, @file_path, node.location, comments)
@queue.prepend(value)
Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name)
else
Entry::Constant.new(name)
end
end

@index.add(entry, @file_path)
entry.add_declaration(Entry::Declaration.new(@file_path, node.location, comments))
end

sig { params(node: Prism::ModuleNode).void }
Expand All @@ -261,8 +265,16 @@ def add_module_entry(node)
end

comments = collect_comments(node)
@current_owner = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
@index << @current_owner

fully_qualified_name = fully_qualify_name(name)
existing_entry = @index.get_constant(fully_qualified_name)

# If the user has defined the same constant as a namespace and a constant, then we end up losing the original
# definition. This is an error in Ruby, but we should still try to handle it gracefully
@current_owner = existing_entry.is_a?(Entry::Namespace) ? existing_entry : Entry::Module.new(fully_qualified_name)

@index.add(@current_owner, @file_path)
@current_owner.add_declaration(Entry::Declaration.new(@file_path, node.location, comments))
@stack << name
@queue.prepend(node.body, LEAVE_EVENT)
end
Expand All @@ -284,14 +296,22 @@ def add_class_entry(node)
superclass.slice
end

@current_owner = Entry::Class.new(
fully_qualify_name(name),
@file_path,
node.location,
comments,
parent_class,
)
@index << @current_owner
fully_qualified_name = fully_qualify_name(name)
existing_entry = @index.get_constant(fully_qualified_name)

# If the user has defined the same constant as a namespace and a constant, then we end up losing the original
# definition. This is an error in Ruby, but we should still try to handle it gracefully
@current_owner = if existing_entry.is_a?(Entry::Namespace)
existing_entry
else
Entry::Class.new(
fully_qualified_name,
parent_class,
)
end
@index.add(@current_owner, @file_path)
@current_owner.add_declaration(Entry::Declaration.new(@file_path, node.location, comments))

@stack << name
@queue.prepend(node.body, LEAVE_EVENT)
end
Expand Down Expand Up @@ -350,8 +370,31 @@ def handle_attribute(node, reader:, writer:)

next unless name && loc

@index << Entry::Accessor.new(name, @file_path, loc, comments, @current_owner) if reader
@index << Entry::Accessor.new("#{name}=", @file_path, loc, comments, @current_owner) if writer
if reader
entry = (@current_owner && @index.resolve_method(
name,
@current_owner.name,
)) || Entry::Accessor.new(name, @current_owner)

@index.add(entry, @file_path)
entry.add_declaration(Entry::MemberDeclaration.new([], @file_path, loc, comments))
end

next unless writer

writer_name = "#{name}="
entry = (@current_owner && @index.resolve_method(
writer_name,
@current_owner.name,
)) || Entry::Accessor.new(writer_name, @current_owner)

@index.add(entry, @file_path)
entry.add_declaration(Entry::MemberDeclaration.new(
[Entry::RequiredParameter.new(name: name.to_sym)],
@file_path,
loc,
comments,
))
end
end

Expand Down Expand Up @@ -384,5 +427,89 @@ def handle_module_operation(node, operation)
collection = operation == :included_modules ? @current_owner.included_modules : @current_owner.prepended_modules
collection.concat(names)
end

sig { params(parameters_node: T.nilable(Prism::ParametersNode)).returns(T::Array[Entry::Parameter]) }
def list_params(parameters_node)
return [] unless parameters_node

parameters = []

parameters_node.requireds.each do |required|
name = parameter_name(required)
next unless name

parameters << Entry::RequiredParameter.new(name: name)
end

parameters_node.optionals.each do |optional|
name = parameter_name(optional)
next unless name

parameters << Entry::OptionalParameter.new(name: name)
end

parameters_node.keywords.each do |keyword|
name = parameter_name(keyword)
next unless name

case keyword
when Prism::RequiredKeywordParameterNode
parameters << Entry::KeywordParameter.new(name: name)
when Prism::OptionalKeywordParameterNode
parameters << Entry::OptionalKeywordParameter.new(name: name)
end
end

rest = parameters_node.rest

if rest.is_a?(Prism::RestParameterNode)
rest_name = rest.name || Entry::RestParameter::DEFAULT_NAME
parameters << Entry::RestParameter.new(name: rest_name)
end

keyword_rest = parameters_node.keyword_rest

if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
keyword_rest_name = parameter_name(keyword_rest) || Entry::KeywordRestParameter::DEFAULT_NAME
parameters << Entry::KeywordRestParameter.new(name: keyword_rest_name)
end

parameters_node.posts.each do |post|
name = parameter_name(post)
next unless name

parameters << Entry::RequiredParameter.new(name: name)
end

block = parameters_node.block
parameters << Entry::BlockParameter.new(name: block.name || Entry::BlockParameter::DEFAULT_NAME) if block

parameters
end

sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(Symbol)) }
def parameter_name(node)
case node
when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
Prism::RestParameterNode, Prism::KeywordRestParameterNode
node.name
when Prism::MultiTargetNode
names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }

rest = node.rest
if rest.is_a?(Prism::SplatNode)
name = rest.expression&.slice
names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
end

names << nil if rest.is_a?(Prism::ImplicitRestNode)

names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })

names_with_commas = names.join(", ")
:"(#{names_with_commas})"
end
end
end
end

0 comments on commit d37657a

Please sign in to comment.