Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RBS - RDoc Plugin #1048

Merged
merged 14 commits into from
Jul 27, 2022
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
lib/**/*.bundle
lib/**/*.so
lib/**/*.dll
doc/
**/*.gem
1 change: 1 addition & 0 deletions Steepfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ target :lib do
signature "stdlib/strscan/0/"
signature "stdlib/rubygems/0/"
signature "stdlib/optparse/0/"
signature "stdlib/rdoc/0/"

configure_code_diagnostics do |config|
config[D::Ruby::MethodDefinitionMissing] = :hint
Expand Down
18 changes: 18 additions & 0 deletions lib/rdoc/discover.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
begin
gem 'rdoc', '~> 6.4.0'
require 'rdoc_plugin/parser'
module RDoc
class Parser
class RBS < Parser
parse_files_matching(/\.rbs$/)
def scan
::RBS::RDocPlugin::Parser.new(@top_level, @content).scan
end
end
end
end
rescue Gem::LoadError
# Error :sad:
rescue Exception
# Exception :sad:
end
163 changes: 163 additions & 0 deletions lib/rdoc_plugin/parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# frozen_string_literal: true

require 'rbs'

module RBS
module RDocPlugin
class Parser

attr_accessor :top_level, :content

def initialize(top_level, content)
@top_level = top_level
@content = content
end

def scan
ast = ::RBS::Parser.parse_signature(@content)
ast.each do |decl|
parse_member(decl: decl, context: @top_level)
end
@top_level
end

def parse_member(decl:, context:, outer_name: nil)
case decl
when ::RBS::AST::Declarations::Class
parse_class_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Declarations::Module
parse_module_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Declarations::Constant
context = @top_level.find_class_or_module outer_name.to_s if outer_name
parse_constant_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Declarations::Interface
parse_module_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Members::MethodDefinition
context = @top_level.find_class_or_module outer_name.to_s if outer_name
parse_method_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Members::Alias
context = @top_level.find_class_or_module outer_name.to_s if outer_name
parse_method_alias_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Members::AttrReader, ::RBS::AST::Members::AttrWriter, ::RBS::AST::Members::AttrAccessor
context = @top_level.find_class_or_module outer_name.to_s if outer_name
parse_attr_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Members::Include
context = @top_level.find_class_or_module outer_name.to_s if outer_name
parse_include_decl(decl: decl, context: context, outer_name: outer_name)
when ::RBS::AST::Members::Extend
context = @top_level.find_class_or_module outer_name.to_s if outer_name
parse_extend_decl(decl: decl, context: context, outer_name: outer_name)
end
end

def parse_class_decl(decl:, context:, outer_name: nil)
full_name = fully_qualified_name(outer_name: outer_name, decl: decl)
klass = context.add_class(RDoc::NormalClass, full_name.to_s, decl.super_class&.name&.to_s || "::Object")
klass.add_comment(construct_comment(context: context, comment: comment_string(decl)), context) if decl.comment
decl.members.each { |member| parse_member(decl: member, context: context, outer_name: full_name) }
end

def parse_module_decl(decl:, context:, outer_name: nil)
full_name = fully_qualified_name(outer_name: outer_name, decl: _ = decl)
kmodule = context.add_module(RDoc::NormalModule, full_name.to_s)
kmodule.add_comment(construct_comment(context: context, comment: comment_string(decl)), context) if decl.comment
decl.members.each { |member| parse_member(decl: member, context: context, outer_name: full_name) }
end

def parse_constant_decl(decl:, context:, outer_name: nil)
comment = decl.comment ? construct_comment(context: context, comment: comment_string(decl)) : nil
constant = RDoc::Constant.new(decl.name.to_s, decl.type.to_s, comment)
context.add_constant(constant)
end

def parse_method_decl(decl:, context:, outer_name: nil)
method = RDoc::AnyMethod.new(nil, decl.name.to_s)
method.singleton = decl.singleton?
method.visibility = decl.visibility
method.call_seq = decl.types.map { |type| "#{decl.name.to_s}#{type.to_s}" }.join("\n")
if loc = decl.location
method.start_collecting_tokens
method.add_token({ line_no: 1, char_no: 1, kind: :on_comment, text: "# File #{@top_level.relative_name}, line(s) #{loc.start_line}:#{loc.end_line}\n" })
method.add_token({ line_no: 1, char_no: 1, text: loc.source })
method.line = loc.start_line
end
method.comment = construct_comment(context: context, comment: comment_string(decl)) if decl.comment
context.add_method(method)
end

def parse_method_alias_decl(decl:, context:, outer_name: nil)
alias_def = RDoc::Alias.new(nil, decl.old_name.to_s, decl.new_name.to_s, nil, decl.kind == :singleton)
alias_def.comment = construct_comment(context: context, comment: comment_string(decl)) if decl.comment
context.add_alias(alias_def)
end

def parse_attr_decl(decl:, context:, outer_name: nil)
rw = case decl
when ::RBS::AST::Members::AttrReader
'R'
when ::RBS::AST::Members::AttrWriter
'W'
when ::RBS::AST::Members::AttrAccessor
'RW'
end
attribute = RDoc::Attr.new(nil, decl.name.to_s, rw, nil, decl.kind == :singleton)
attribute.visibility = decl.visibility
attribute.comment = construct_comment(context: context, comment: comment_string(decl)) if decl.comment
context.add_attribute(attribute)
end

def parse_include_decl(decl:, context:, outer_name: nil)
name = decl.name.to_s
outer_names = outer_name ? outer_name.to_s.split("::") : []
qualified_name = ''
outer_names.each do |namespace|
qualified_name += namespace
if (module_name = @top_level.find_module_named((qualified_name += "::") + name))
name = module_name.full_name
break
end
end
include_decl = RDoc::Include.new(name, nil)
include_decl.comment = construct_comment(context: context, comment: comment_string(decl)) if decl.comment
context.add_include(include_decl)
end

def parse_extend_decl(decl:, context:, outer_name: nil)
name = decl.name.to_s
outer_names = outer_name ? outer_name.to_s.split("::") : []
qualified_name = ''
outer_names.each do |namespace|
qualified_name += namespace
if (module_name = @top_level.find_module_named((qualified_name += "::") + name))
name = module_name.full_name
break
end
end
extend_decl = RDoc::Extend.new(name, nil)
extend_decl.comment = construct_comment(context: context, comment: comment_string(decl)) if decl.comment
context.add_extend(extend_decl)
end

private

def construct_comment(context:, comment:)
comment = RDoc::Comment.new(comment, context)
comment.format = "markdown"
comment
end

def comment_string(with_comment)
comment = with_comment.comment or raise "Object with `#comment` returning a object is expected"
comment.string
end

def fully_qualified_name(outer_name:, decl:)
if outer_name
(outer_name + decl.name)
else
decl.name
end
end
end
end
end
1 change: 1 addition & 0 deletions sig/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ dependencies:
- name: optparse
- name: rubygems
- name: tsort
- name: rdoc
30 changes: 0 additions & 30 deletions sig/polyfill.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ module RDoc
end
end

class CodeObject
def comment: () -> RDoc::Markup::Document
end

class Context < CodeObject

end
Expand All @@ -55,32 +51,6 @@ module RDoc
def attributes: () -> Array[Attr]
end

class Constant < CodeObject
def name: () -> String
end

class AnyMethod < MethodAttr
def arglists: () -> String?

def callseq: () -> String?
end

class MethodAttr < CodeObject
attr_reader name: String

attr_reader singleton: bool

attr_reader is_alias_for: MethodAttr?

attr_reader call_seq: String

attr_reader arglists: String
end

class Attr < MethodAttr
attr_accessor rw: "RW" | "R" | "W"
end

module Markup
class Document
include Enumerable[Document]
Expand Down
63 changes: 63 additions & 0 deletions sig/rdoc/rbs.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module RDoc
class Parser
class RBS < Parser
end
end
end

module RBS
module RDocPlugin
class Parser
type allowed_decls = RBS::AST::Declarations::Class
| RBS::AST::Declarations::Module
| RBS::AST::Declarations::Constant

def initialize: (RDoc::TopLevel top_level, String content) -> void

def scan: () -> RDoc::TopLevel

def parse_member: (decl: RBS::AST::Declarations::t | RBS::AST::Members::t, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_class_decl: (decl: RBS::AST::Declarations::Class, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_module_decl: (decl: RBS::AST::Declarations::Module | RBS::AST::Declarations::Interface, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_constant_decl: (decl: RBS::AST::Declarations::Constant, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_method_decl: (decl: RBS::AST::Members::MethodDefinition, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_method_alias_decl: (decl: RBS::AST::Members::Alias, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_attr_decl: (decl: RBS::AST::Members::AttrReader | RBS::AST::Members::AttrAccessor | RBS::AST::Members::AttrWriter, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_include_decl: (decl: RBS::AST::Members::Include, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

def parse_extend_decl: (decl: RBS::AST::Members::Extend, context: RDoc::Context, ?outer_name: RBS::TypeName?) -> void

private

def construct_comment: (context: RDoc::Context, comment: String) -> RDoc::Comment

# _DeclWithComment is a utility interface that has `#comment`
#
interface _DeclWithComment
def comment: () -> ::RBS::AST::Comment?
end

# Extract comment string from a declaration with comment
#
# * If `#comment" returns a Comment object, it returns its `#string` value
# * If `#comment` returns `nil`, it raises an error
#
# Note that you have to confirm if the `#comment` of given declartion exists.
#
# ```ruby
# comment = construct_comment(context: context, comment: comment_string(decl)) if decl.comment
# ```
#
def comment_string: (_DeclWithComment) -> String

def fully_qualified_name: (outer_name: RBS::TypeName?, decl: allowed_decls) -> RBS::TypeName
end
end
end
Loading