Skip to content

Commit

Permalink
Add :showdoc: directive for private and protected objects (RFC #11
Browse files Browse the repository at this point in the history
) (#15337)
  • Loading branch information
nobodywasishere authored Feb 5, 2025
1 parent 1c03dfa commit 0da1746
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 9 deletions.
109 changes: 109 additions & 0 deletions spec/compiler/crystal/tools/doc/directives_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
require "../../../spec_helper"

describe Crystal::Doc::Generator do
context ":nodoc:" do
it "hides documentation from being generated for methods" do
program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program
class Foo
# :nodoc:
#
# Some docs
def foo
end
end
CRYSTAL

generator = Doc::Generator.new program, [""]
generator.type(program.types["Foo"]).lookup_method("foo").should be_nil
end

it "hides documentation from being generated for classes" do
program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program
# :nodoc:
class Foo
end
CRYSTAL

generator = Doc::Generator.new program, [""]
generator.must_include?(program.types["Foo"]).should be_false
end
end

context ":showdoc:" do
it "shows documentation for private methods" do
program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program
class Foo
# :showdoc:
#
# Some docs
private def foo
end
end
CRYSTAL

generator = Doc::Generator.new program, [""]
a_def = generator.type(program.types["Foo"]).lookup_method("foo").not_nil!
a_def.doc.should eq("Some docs")
a_def.visibility.should eq("private")
end

it "does not include documentation for methods within a :nodoc: namespace" do
program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program
# :nodoc:
class Foo
# :showdoc:
#
# Some docs
private def foo
end
end
CRYSTAL

generator = Doc::Generator.new program, [""]

# If namespace isn't included, don't need to check if the method is included
generator.must_include?(program.types["Foo"]).should be_false
end

it "does not include documentation for private and protected methods and objects in a :showdoc: namespace" do
program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program
# :showdoc:
class Foo
# Some docs for `foo`
private def foo
end
# Some docs for `bar`
protected def bar
end
# Some docs for `Baz`
private class Baz
end
end
CRYSTAL

generator = Doc::Generator.new program, [""]

generator.type(program.types["Foo"]).lookup_method("foo").should be_nil
generator.type(program.types["Foo"]).lookup_method("bar").should be_nil

generator.must_include?(generator.type(program.types["Foo"]).lookup_path("Baz")).should be_false
end

it "doesn't show a method marked :nodoc: within a :showdoc: namespace" do
program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program
# :showdoc:
class Foo
# :nodoc:
# Some docs for `foo`
def foo
end
end
CRYSTAL

generator = Doc::Generator.new program, [""]
generator.type(program.types["Foo"]).lookup_method("foo").should be_nil
end
end
end
15 changes: 12 additions & 3 deletions src/compiler/crystal/tools/doc/generator.cr
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class Crystal::Doc::Generator
end

def must_include?(type : Crystal::Type)
return false if type.private?
return false if type.private? && !showdoc?(type)
return false if nodoc? type
return true if crystal_builtin?(type)

Expand Down Expand Up @@ -215,6 +215,15 @@ class Crystal::Doc::Generator
nodoc? obj.doc.try &.strip
end

def showdoc?(str : String?) : Bool
return false if !str || !@program.wants_doc?
str.starts_with?(":showdoc:")
end

def showdoc?(obj : Crystal::Type)
showdoc?(obj.doc.try &.strip)
end

def crystal_builtin?(type)
return false unless project_info.crystal_stdlib?
# TODO: Enabling this allows links to `NoReturn` to work, but has two `NoReturn`s show up in the sidebar
Expand Down Expand Up @@ -267,7 +276,7 @@ class Crystal::Doc::Generator
types = [] of Constant

parent.type.types?.try &.each_value do |type|
if type.is_a?(Const) && must_include?(type) && !type.private?
if type.is_a?(Const) && must_include?(type) && (!type.private? || showdoc?(type))
types << Constant.new(self, parent, type)
end
end
Expand Down Expand Up @@ -296,7 +305,7 @@ class Crystal::Doc::Generator
end

def doc(obj : Type | Method | Macro | Constant)
doc = obj.doc
doc = obj.doc.try &.strip.lchop(":showdoc:").strip

return if !doc && !has_doc_annotations?(obj)

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/doc/html/_method_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h2>
<% methods.each do |method| %>
<div class="entry-detail" id="<%= method.html_id %>">
<div class="signature">
<%= method.abstract? ? "abstract " : "" %>
<%= method.abstract? ? "abstract " : "" %><%= method.visibility.try(&.+(" ")) %>
<%= method.kind %><strong><%= method.name %></strong><%= method.args_to_html %>

<a class="method-permalink" href="<%= method.anchor %>">#</a>
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/crystal/tools/doc/html/type.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ <h1 class="type-name">
<% if type.program? %>
<%= type.full_name.gsub("::", "::<wbr>") %>
<% else %>
<span class="kind"><%= type.abstract? ? "abstract " : ""%><%= type.kind %></span> <%= type.full_name.gsub("::", "::<wbr>") %>
<span class="kind">
<%= type.abstract? ? "abstract " : ""%><%= type.visibility.try(&.+(" ")) %><%= type.kind %>
</span> <%= type.full_name.gsub("::", "::<wbr>") %>
<% end %>
</h1>

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/crystal/tools/doc/macro.cr
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class Crystal::Doc::Macro
false
end

def visibility
@type.visibility
end

def kind
"macro "
end
Expand Down
13 changes: 12 additions & 1 deletion src/compiler/crystal/tools/doc/method.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Crystal::Doc::Method
# This docs not include the "Description copied from ..." banner
# in case it's needed.
def doc
doc_info.doc
doc_info.doc.try &.strip.lchop(":showdoc:").strip
end

# Returns the type this method's docs are copied from, but
Expand Down Expand Up @@ -135,6 +135,16 @@ class Crystal::Doc::Method
end
end

def visibility
case @def.visibility
in .public?
in .protected?
"protected"
in .private?
"private"
end
end

def constructor?
return false unless @class_method
return true if name == "new"
Expand Down Expand Up @@ -323,6 +333,7 @@ class Crystal::Doc::Method
builder.field "doc", doc unless doc.nil?
builder.field "summary", formatted_summary unless formatted_summary.nil?
builder.field "abstract", abstract?
builder.field "visibility", visibility if visibility
builder.field "args", args unless args.empty?
builder.field "args_string", args_to_s unless args.empty?
builder.field "args_html", args_to_html unless args.empty?
Expand Down
16 changes: 13 additions & 3 deletions src/compiler/crystal/tools/doc/type.cr
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class Crystal::Doc::Type
@type.abstract?
end

def visibility
@type.private? ? "private" : nil
end

def parents_of?(type)
return false unless type

Expand Down Expand Up @@ -181,7 +185,7 @@ class Crystal::Doc::Type
defs = [] of Method
@type.defs.try &.each do |def_name, defs_with_metadata|
defs_with_metadata.each do |def_with_metadata|
next unless def_with_metadata.def.visibility.public?
next if !def_with_metadata.def.visibility.public? && !showdoc?(def_with_metadata.def)
next unless @generator.must_include? def_with_metadata.def

defs << method(def_with_metadata.def, false)
Expand All @@ -192,6 +196,10 @@ class Crystal::Doc::Type
end
end

private def showdoc?(adef)
@generator.showdoc?(adef.doc.try &.strip)
end

private def sort_order(item)
# Sort operators first, then alphanumeric (case-insensitive).
{item.name[0].alphanumeric? ? 1 : 0, item.name.downcase}
Expand All @@ -205,7 +213,7 @@ class Crystal::Doc::Type
@type.metaclass.defs.try &.each_value do |defs_with_metadata|
defs_with_metadata.each do |def_with_metadata|
a_def = def_with_metadata.def
next unless a_def.visibility.public?
next if !def_with_metadata.def.visibility.public? && !showdoc?(def_with_metadata.def)

body = a_def.body

Expand Down Expand Up @@ -236,7 +244,9 @@ class Crystal::Doc::Type
macros = [] of Macro
@type.metaclass.macros.try &.each_value do |the_macros|
the_macros.each do |a_macro|
if a_macro.visibility.public? && @generator.must_include? a_macro
next if !a_macro.visibility.public? && !showdoc?(a_macro)

if @generator.must_include? a_macro
macros << self.macro(a_macro)
end
end
Expand Down

0 comments on commit 0da1746

Please sign in to comment.