Skip to content

Commit

Permalink
Parse docs for lib objects
Browse files Browse the repository at this point in the history
Allow showing docs using :showdoc: directive
  • Loading branch information
nobodywasishere committed Jan 12, 2025
1 parent 639db4a commit fdd1443
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 57 deletions.
48 changes: 48 additions & 0 deletions spec/compiler/parser/parser_doc_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe "Parser doc" do
{"alias", "alias Foo = Bar"},
{"annotation", "@[Some]"},
{"private def", "private def foo\nend"},
{"lib def", "lib MyLib\nend"},
].each do |(desc, code)|
it "includes doc for #{desc}" do
parser = Parser.new(%(
Expand All @@ -29,6 +30,53 @@ describe "Parser doc" do
end
end

[
{"type def", "type Foo = Bar"},
{"cstruct def", "struct Name\nend"},
{"union def", "union Name\nend"},
{"fun def", "fun name = Name"},
{"external var", "$errno : Int32"},
].each do |(desc, code)|
it "includes doc for #{desc} inside lib def" do
parser = Parser.new(%(
lib MyLib
# This is Foo.
# Use it well.
#{code}
end
))
parser.wants_doc = true
node = parser.parse
node.as(Crystal::LibDef).body.doc.should eq("This is Foo.\nUse it well.")
end
end

it "includes doc for cstruct fields" do
parser = Parser.new(<<-CRYSTAL)
lib MyLib
struct IntOrFloat
# This is Foo.
# Use it well.
some_int : Int32
# This is Foo.
# Use it well.
some_float, other_float : Float64
end
end
CRYSTAL

parser.wants_doc = true
node = parser.parse
node.as(Crystal::LibDef)
.body.as(Crystal::CStructOrUnionDef)
.body.as(Crystal::Expressions)
.expressions.each do |exp|
exp.as(Crystal::TypeDeclaration)
.var.as(Crystal::Var)
.doc.should eq("This is Foo.\nUse it well.")
end
end

it "disables doc parsing inside defs" do
parser = Parser.new(%(
# doc 1
Expand Down
1 change: 1 addition & 0 deletions src/compiler/crystal/semantic/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ module Crystal
end

class External < Def
property? external_var : Bool = false
property real_name : String
property! fun_def : FunDef
property call_convention : LLVM::CallConvention?
Expand Down
18 changes: 13 additions & 5 deletions src/compiler/crystal/semantic/top_level_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
scope.types[name] = type
end

attach_doc type, node, annotations

node.resolved_type = type

type.private = true if node.visibility.private?
Expand Down Expand Up @@ -626,9 +628,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
type.extern = true
type.extern_union = node.union?

if location = node.location
type.add_location(location)
end
attach_doc type, node, annotations

current_type.types[node.name] = type
end
Expand All @@ -641,15 +641,22 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
end

def visit(node : TypeDef)
annotations = read_annotations
type = current_type.types[node.name]?

if type
node.raise "#{node.name} is already defined"
else
typed_def_type = lookup_type(node.type_spec)
typed_def_type = check_allowed_in_lib node.type_spec, typed_def_type
current_type.types[node.name] = TypeDefType.new @program, current_type, node.name, typed_def_type
false
type = TypeDefType.new @program, current_type, node.name, typed_def_type

attach_doc type, node, annotations

current_type.types[node.name] = type
end

false
end

def visit(node : EnumDef)
Expand Down Expand Up @@ -1000,6 +1007,7 @@ class Crystal::TopLevelVisitor < Crystal::SemanticVisitor
external.call_convention = call_convention
external.varargs = node.varargs?
external.fun_def = node
external.return_type = node.return_type
node.external = external

current_type.add_def(external)
Expand Down
36 changes: 33 additions & 3 deletions src/compiler/crystal/semantic/type_declaration_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,27 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor
var_type = check_allowed_in_lib node.type_spec, var_type

type = current_type.as(LibType)
type.add_var node.name, var_type, (node.real_name || node.name), thread_local

setter = External.new(
"#{node.name}=", [Arg.new("value", type: var_type)],
Primitive.new("external_var_set", var_type), node.real_name || node.name
).at(node.location)
setter.set_type(var_type)
setter.external_var = true
setter.thread_local = thread_local
setter.doc = node.doc || @annotations.try(&.first?).try(&.doc)

getter = External.new(
"#{node.name}", [] of Arg,
Primitive.new("external_var_get", var_type), node.real_name || node.name
).at(node.location)
getter.set_type(var_type)
getter.external_var = true
getter.thread_local = thread_local
getter.doc = node.doc || @annotations.try(&.first?).try(&.doc)

type.add_def setter
type.add_def getter

false
end
Expand Down Expand Up @@ -186,15 +206,25 @@ class Crystal::TypeDeclarationVisitor < Crystal::SemanticVisitor
if type.lookup_instance_var?(var_name)
node.raise "#{type.type_desc} #{type} already defines a field named '#{field_name}'"
end

ivar = MetaTypeVar.new(var_name, field_type)
ivar.doc = node.var.as(Var).doc
ivar.owner = type

declare_c_struct_or_union_field(type, field_name, ivar, node.location)
end

def declare_c_struct_or_union_field(type, field_name, var, location)
type.instance_vars[var.name] = var
type.add_def Def.new("#{field_name}=", [Arg.new("value")], Primitive.new("struct_or_union_set").at(location))
type.add_def Def.new(field_name, body: InstanceVar.new(var.name))

setter = Def.new("#{field_name}=", [Arg.new("value")], Primitive.new("struct_or_union_set").at(location)).at(location)
setter.doc = var.doc

getter = Def.new(field_name, body: InstanceVar.new(var.name)).at(location)
getter.doc = var.doc

type.add_def setter
type.add_def getter
end

def declare_instance_var(node, var)
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ module Crystal
include SpecialVar

property name : String
property doc : String?

def initialize(@name : String)
end
Expand Down Expand Up @@ -1630,6 +1631,7 @@ module Crystal
end

class TypeDeclaration < ASTNode
property doc : String?
property var : ASTNode
property declared_type : ASTNode
property value : ASTNode?
Expand Down Expand Up @@ -1925,6 +1927,7 @@ module Crystal

class LibDef < ASTNode
property name : Path
property doc : String?
property body : ASTNode
property name_location : Location?
property visibility = Visibility::Public
Expand Down Expand Up @@ -1980,6 +1983,7 @@ module Crystal

class TypeDef < ASTNode
property name : String
property doc : String?
property type_spec : ASTNode
property name_location : Location?

Expand All @@ -2002,6 +2006,7 @@ module Crystal
# A c struct/union definition inside a lib declaration
class CStructOrUnionDef < ASTNode
property name : String
property doc : String?
property body : ASTNode
property? union : Bool

Expand Down Expand Up @@ -2044,6 +2049,7 @@ module Crystal

class ExternalVar < ASTNode
property name : String
property doc : String?
property type_spec : ASTNode
property real_name : String?

Expand Down
14 changes: 13 additions & 1 deletion src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5633,6 +5633,7 @@ module Crystal
end

def parse_lib
doc = @token.doc
location = @token.location
next_token_skip_space_or_newline

Expand All @@ -5648,6 +5649,7 @@ module Crystal

lib_def = LibDef.new(name, body).at(location).at_end(end_location)
lib_def.name_location = name_location
lib_def.doc = doc
lib_def
end

Expand Down Expand Up @@ -5704,6 +5706,7 @@ module Crystal
skip_statement_end
Assign.new(ident, value)
when .global?
doc = @token.doc
location = @token.location
name = @token.value.to_s[1..-1]
next_token_skip_space_or_newline
Expand All @@ -5723,6 +5726,7 @@ module Crystal

skip_statement_end
ExternalVar.new(name, type, real_name)
.at(location).tap(&.doc=(doc))
when .op_lcurly_lcurly?
parse_percent_macro_expression
when .op_lcurly_percent?
Expand Down Expand Up @@ -5960,6 +5964,7 @@ module Crystal
end

def parse_type_def
doc = @token.doc
next_token_skip_space_or_newline
name = check_const
name_location = @token.location
Expand All @@ -5972,10 +5977,13 @@ module Crystal

typedef = TypeDef.new name, type
typedef.name_location = name_location
typedef.doc = doc

typedef
end

def parse_c_struct_or_union(union : Bool)
doc = @token.doc
location = @token.location
next_token_skip_space_or_newline
name = check_const
Expand All @@ -5985,7 +5993,9 @@ module Crystal
end_location = token_end_location
next_token_skip_space

CStructOrUnionDef.new(name, Expressions.from(body), union: union).at(location).at_end(end_location)
CStructOrUnionDef.new(name, Expressions.from(body), union: union)
.at(location).at_end(end_location)
.tap(&.doc=(doc))
end

def parse_c_struct_or_union_body
Expand Down Expand Up @@ -6027,6 +6037,7 @@ module Crystal
end

def parse_c_struct_or_union_fields(exps)
doc = @token.doc
vars = [Var.new(@token.value.to_s).at(@token.location).at_end(token_end_location)]

next_token_skip_space_or_newline
Expand All @@ -6045,6 +6056,7 @@ module Crystal
skip_statement_end

vars.each do |var|
var.doc = doc
exps << TypeDeclaration.new(var, type).at(var).at_end(type)
end
end
Expand Down
18 changes: 5 additions & 13 deletions src/compiler/crystal/tools/doc/generator.cr
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ class Crystal::Doc::Generator
return false if nodoc? ns
end

# Don't include lib types or types inside a lib type
return false if type.is_a?(Crystal::LibType) || type.namespace.is_a?(LibType)
# Don't include lib types or types inside a lib type unless specified with `:showdoc:`
if (type.is_a?(LibType) || type.namespace.is_a?(LibType)) && !showdoc?(type)
return false
end

!!type.locations.try &.any? do |type_location|
must_include? type_location
Expand Down Expand Up @@ -234,16 +236,6 @@ class Crystal::Doc::Generator
false
end

def showdoc?(obj)
return false if !@program.wants_doc?

if showdoc?(obj.doc.try &.strip)
return true
end

false
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 @@ -282,7 +274,7 @@ class Crystal::Doc::Generator

parent.types?.try &.each_value do |type|
case type
when Const, LibType
when Const
next
else
types << type(type) if must_include? type
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 @@ -7,7 +7,7 @@ <h2>
<div class="entry-detail" id="<%= method.html_id %>">
<div class="signature">
<%= method.abstract? ? "abstract " : "" %><%= method.visibility %>
<%= method.kind %><strong><%= method.name %></strong><%= method.args_to_html %>
<%= method.kind %><strong><%= method.name %></strong><%= method.real_name %><%= method.args_to_html %>

<a class="method-permalink" href="<%= method.anchor %>">#</a>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/doc/html/_method_summary.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h2>
<ul class="list-summary">
<% methods.each do |method| %>
<li class="entry-summary">
<a href="<%= method.anchor %>" class="signature"><strong><%= method.prefix %><%= method.name %></strong><%= method.args_to_html(:highlight) %></a>
<a href="<%= method.anchor %>" class="signature"><strong><%= method.prefix %><%= method.name %></strong><%= method.real_name %><%= method.args_to_html(:highlight) %></a>
<% if summary = method.formatted_summary %>
<div class="summary"><%= summary %></div>
<% end %>
Expand Down
Loading

0 comments on commit fdd1443

Please sign in to comment.