Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions lib/utopia/project/inheritance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class Inheritance
# A method inherited from another definition.
Method = Struct.new(:name, :definition)

# A group of inherited methods with the same origin.
Group = Struct.new(:definition, :methods)
# Methods inherited through a direct relationship.
Group = Struct.new(:kind, :definition, :methods)

# Initialize inheritance resolution for the given definition.
# @parameter index [Decode::Index] The index used to resolve relationships.
Expand Down Expand Up @@ -55,10 +55,10 @@ def relationships
end

# Enumerate documented public and protected methods inherited by the definition.
# @returns [Array(Group)] Methods grouped by the definition which provides them.
# @returns [Array(Group)] Methods grouped by the direct relationship which provides them.
def inherited_methods
@groups = []
@groups_by_definition = {}
@groups_by_relationship = {}

collect_instance_methods(@definition, false, {}, {})
collect_class_methods(@definition, false, {}, {})
Expand Down Expand Up @@ -113,46 +113,46 @@ def singleton_for(definition)
end
end

def collect_instance_methods(definition, include_own, seen, visited, display_prefix = "#")
def collect_instance_methods(definition, include_own, seen, visited, display_prefix: "#", kind: nil, provider: nil)
key = [definition.qualified_name, display_prefix]
return if visited[key]
visited[key] = true

relationship_definitions(definition, :prepends).reverse_each do |relationship|
collect_instance_methods(relationship, true, seen, visited, display_prefix)
collect_instance_methods(relationship, true, seen, visited, display_prefix: display_prefix, kind: kind || :prepend, provider: provider || relationship)
end

collect_methods(definition, "#", display_prefix, include_own, seen)
collect_methods(definition, "#", display_prefix, include_own, seen, kind: kind, provider: provider)

relationship_definitions(definition, :includes).reverse_each do |relationship|
collect_instance_methods(relationship, true, seen, visited, display_prefix)
collect_instance_methods(relationship, true, seen, visited, display_prefix: display_prefix, kind: kind || :include, provider: provider || relationship)
end

if display_prefix == "#" && (super_class = super_class_for(definition))
collect_instance_methods(super_class, true, seen, visited, display_prefix)
collect_instance_methods(super_class, true, seen, visited, display_prefix: display_prefix, kind: kind || :super_class, provider: provider || super_class)
end
end

def collect_class_methods(definition, include_own, seen, visited)
def collect_class_methods(definition, include_own, seen, visited, kind: nil, provider: nil)
key = definition.qualified_name
return if visited[key]
visited[key] = true

collect_methods(definition, ".", ".", include_own, seen)
collect_methods(definition, ".", ".", include_own, seen, kind: kind, provider: provider)
if singleton = singleton_for(definition)
collect_methods(singleton, "#", ".", include_own, seen, definition)
collect_methods(singleton, "#", ".", include_own, seen, kind: kind, provider: provider)
end

relationship_definitions(definition, :extends).reverse_each do |relationship|
collect_instance_methods(relationship, true, seen, {}, ".")
collect_instance_methods(relationship, true, seen, {}, display_prefix: ".", kind: kind || :extend, provider: provider || relationship)
end

if super_class = super_class_for(definition)
collect_class_methods(super_class, true, seen, visited)
collect_class_methods(super_class, true, seen, visited, kind: kind || :super_class, provider: provider || super_class)
end
end

def collect_methods(definition, source_prefix, display_prefix, include_own, seen, origin = definition)
def collect_methods(definition, source_prefix, display_prefix, include_own, seen, kind:, provider:)
methods_for(definition, source_prefix).each do |method|
name = "#{display_prefix}#{method.name}"
next if seen[name]
Expand All @@ -168,13 +168,14 @@ def collect_methods(definition, source_prefix, display_prefix, include_own, seen
next unless method.documented?
next if method.respond_to?(:private?) && method.private?

add_method(origin, name, method)
add_method(kind, provider, name, method)
end
end

def add_method(definition, name, method)
group = @groups_by_definition[definition.qualified_name] ||= begin
group = Group.new(definition, [])
def add_method(kind, provider, name, method)
key = [kind, provider.qualified_name]
group = @groups_by_relationship[key] ||= begin
group = Group.new(kind, provider, [])
@groups << group
group
end
Expand Down
38 changes: 22 additions & 16 deletions pages/reference/_relationships.xnode
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ inheritance = base.inheritance_for(symbol)

relationships = inheritance.relationships
inherited_methods = inheritance.inherited_methods
inherited_methods_by_relationship = inherited_methods.to_h do |group|
[[group.kind, group.definition.qualified_name], group.methods]
end

relationship_labels = {
super_class: "Inherits from",
Expand All @@ -14,32 +17,35 @@ relationship_labels = {
}
?>
<?r if relationships.any? ?>
<dl class="relationships"><?r
<dl class="relationships" data-pagefind-ignore><?r
relationships.group_by(&:kind).each do |kind, entries|
?><dt>#{relationship_labels.fetch(kind)}</dt>
<dd><?r
entries.each_with_index do |relationship, index|
if index > 0
?>, <?r
?>; <?r
end

?>#{base.linkify(relationship.name, symbol)}<?r

methods = if relationship.definition
key = [kind, relationship.definition.qualified_name]
inherited_methods_by_relationship[key]
end

if methods&.any?
?><code>: <?r
methods.each_with_index do |method, method_index|
if method_index > 0
?>, <?r
end

?><a href="#{base.link_for(method.definition)}">#{method.name}</a><?r
end
?></code><?r
end
end
?></dd><?r
end
?></dl>
<?r end ?>

<?r if inherited_methods.any? ?>
<section>
<h2>Inherited Methods</h2><?r
inherited_methods.each do |group|
?><p>From <a href="#{base.link_for(group.definition)}"><code>#{group.definition.qualified_name}</code></a>:</p>
<ul class="index"><?r
group.methods.each do |method|
?><li><a href="#{base.link_for(method.definition)}"><code>#{method.name}</code></a></li><?r
end
?></ul><?r
end
?></section>
<?r end ?>
16 changes: 15 additions & 1 deletion test/utopia/project/.fixtures/inheritance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,21 @@ def extended_method
end
end

class Parent
module ParentIncluded
# A method included by the parent.
def parent_included_method
end
end

class Grandparent
# An inherited grandparent method.
def grandparent_method
end
end

class Parent < Grandparent
include ParentIncluded

# Initialize the parent.
def initialize
end
Expand Down
19 changes: 12 additions & 7 deletions test/utopia/project/inheritance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@
}
end

it "groups inherited methods by their origin" do
methods = inheritance.inherited_methods.to_h do |group|
[group.definition.qualified_name, group.methods.map(&:name)]
it "groups inherited methods by their direct relationship" do
groups = inheritance.inherited_methods
methods = groups.to_h do |group|
[[group.kind, group.definition.qualified_name], group.methods.map(&:name)]
end

expect(methods).to be == {
"Example::Prepended" => ["#prepended_method"],
"Example::Included" => ["#included_method"],
"Example::Parent" => ["#parent_method", ".parent_class_method", ".singleton_class_method"],
"Example::Extended" => [".extended_method"],
[:prepend, "Example::Prepended"] => ["#prepended_method"],
[:include, "Example::Included"] => ["#included_method"],
[:super_class, "Example::Parent"] => ["#parent_method", "#parent_included_method", "#grandparent_method", ".parent_class_method", ".singleton_class_method"],
[:extend, "Example::Extended"] => [".extended_method"],
}

parent = groups.find{|group| group.definition.qualified_name == "Example::Parent"}
parent_included_method = parent.methods.find{|method| method.name == "#parent_included_method"}
expect(parent_included_method.definition.qualified_name).to be == "Example::ParentIncluded#parent_included_method"
end
end
8 changes: 6 additions & 2 deletions test/utopia/project/serve.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,13 @@

expect(body).to be(:include?, "def release_names")
expect(body).to be(:include?, 'class ReleasesDocument &lt; <a href="/reference/Utopia/Project/Document/index" title="Utopia::Project::Document">Document</a>')
expect(body).to be(:include?, '<dl class="relationships">')
expect(body).to be(:include?, '<dl class="relationships" data-pagefind-ignore>')
expect(body).to be(:include?, "<dt>Inherits from</dt>")
expect(body).to be(:include?, "/reference/Utopia/Project/Document/index#Utopia%3A%3AProject%3A%3ADocument%23root")

relationships = body[/<dl class="relationships" data-pagefind-ignore>.*?<\/dl>/m]
expect(relationships).to be(:include?, "/reference/Utopia/Project/Document/index#Utopia%3A%3AProject%3A%3ADocument%23root")
expect(relationships).to be(:include?, "<code>: <a href=")
expect(body).not.to be(:include?, "<h2>Inherited Methods</h2>")
end

it "summarizes nested definitions" do
Expand Down
Loading