lib/inch/language/ruby/provider/yard/object/base.rb
require 'forwardable'
require 'inch/utils/code_location'
module Inch
module Language
module Ruby
module Provider
module YARD
module Object
# @abstract
class Base
extend Forwardable
include YARD::NodocHelper
# @return [YARD::CodeObjects::Base] the actual (YARD) code object
attr_reader :object
# @return [String] the codebase's directory
attr_accessor :base_dir
# Tags considered by wrapper methods like {#has_code_example?}
CONSIDERED_YARD_TAGS = %w(api example param private return since)
AUTO_GENERATED_TAG_NAMES = %w(raise yield)
# convenient shortcuts to (YARD) code object
def_delegators :object,
:type, :namespace, :source, :source_type, :group,
:dynamic, :visibility
# @param object [YARD::CodeObjects::Base] the actual (YARD) code
# object
def initialize(object)
@object = object
@api_tag = __api_tag
@parent = __parent
@private_tag = __private_tag
end
# Returns the fullname of the object that the current object
# is an alias for
attr_accessor :aliased_object_fullname
# Returns the fullnames of the objects that are aliases
# for the current object
def aliases_fullnames
[]
end
def api_tag?
!api_tag.nil?
end
attr_reader :api_tag
# To be overridden
# @see Proxy::NamespaceObject
# @return [CodeObject::Proxy,nil] the child inside the current
# object or +nil+
def child(_name)
nil
end
# @return [Array,nil] the full names of the children of the
# current object
def children_fullnames
[]
end
# To be overridden
# @see Proxy::NamespaceObject
def children
[]
end
RUBY_CORE = %w(
Array Bignum BasicObject Object Module Class Complex NilClass
Numeric String Float Fiber FiberError Continuation Dir File
Encoding Enumerator StopIteration Enumerator::Generator
Enumerator::Yielder Exception SystemExit SignalException
Interrupt StandardError TypeError ArgumentError IndexError
KeyError RangeError ScriptError SyntaxError LoadError
NotImplementedError NameError NoMethodError RuntimeError
SecurityError NoMemoryError EncodingError SystemCallError
Encoding::CompatibilityError
File::Stat IO Hash ENV IOError EOFError ARGF RubyVM
RubyVM::InstructionSequence Math::DomainError ZeroDivisionError
FloatDomainError Integer Fixnum Data TrueClass FalseClass Mutex
Thread Proc LocalJumpError SystemStackError Method UnboundMethod
Binding Process::Status Random Range Rational RegexpError Regexp
MatchData Symbol Struct ThreadGroup ThreadError Time
Encoding::UndefinedConversionError
Encoding::InvalidByteSequenceError
Encoding::ConverterNotFoundError Encoding::Converter RubyVM::Env
Comparable Kernel File::Constants Enumerable Errno FileTest GC
ObjectSpace GC::Profiler IO::WaitReadable IO::WaitWritable
Marshal Math Process Process::UID Process::GID Process::Sys
Signal
)
def core?
RUBY_CORE.include?(name.to_s)
end
# @return [Docstring]
def docstring
@docstring ||= Docstring.new(object.docstring)
end
# Returns all files declaring the object in the form of an Array
# of Arrays containing the location of their declaration.
#
# @return [Array<CodeLocation>]
def files
object.files.map do |(filename, line_no)|
Inch::Utils::CodeLocation.new(base_dir, filename, line_no)
end
rescue ::YARD::CodeObjects::ProxyMethodError
# this error is raised by YARD
# see broken.rb in test fixtures
[]
end
# Returns the name of the file where the object is declared first
# @return [String] a filename
def filename
# just checking the first file (which is the file where an
# object is first declared)
files.first && files.first.filename
end
def fullname
@fullname ||= object.path
end
def name
@name ||= object.name
end
def has_children?
!children.empty?
end
def has_code_example?
!tags(:example).empty? ||
docstring.contains_code_example?
end
def has_doc?
!docstring.empty?
end
def has_multiple_code_examples?
if tags(:example).size > 1 || docstring.code_examples.size > 1
true
else
if (tag = tag(:example))
multi_code_examples?(tag.text)
elsif (text = docstring.code_examples.first)
multi_code_examples?(text)
else
false
end
end
end
def has_unconsidered_tags?
!unconsidered_tags.empty?
end
def in_root?
depth == 1
end
# The depth of the following is 4:
#
# Foo::Bar::Baz#initialize
# ^ ^ ^ ^
# 1 << 2 << 3 << 4
#
# +depth+ answers the question "how many layers of code objects
# are above this one?"
#
# @note top-level counts, that's why Foo has depth 1!
#
# @return [Fixnum] the depth of the object in terms of namespace
def depth
@__depth ||= __depth
end
# @return [Boolean] +true+ if the object represents a method
def method?
false
end
# @return [Boolean] +true+ if the object represents a namespace
def namespace?
false
end
# @return [String] the documentation comments
def original_docstring
object.docstring.all.to_s
end
def parameters
[]
end
# @return [CodeObject::Base,nil] the parent of the current object or +nil+
attr_reader :parent
def parent_fullname
parent && parent.fullname
end
def __parent
YARD::Object.for(object.parent) if object.parent
end
def private?
visibility == :private
end
def tagged_as_internal_api?
private_api_tag? || docstring.describes_internal_api?
end
def tagged_as_private?
private_tag? || docstring.describes_private_object?
end
def protected?
visibility == :protected
end
def public?
visibility == :public
end
# @return [Boolean] +true+ if the object has no documentation at
# all
def undocumented?
original_docstring.empty?
end
def unconsidered_tag_count
unconsidered_tags.size
end
def inspect
"#<#{self.class}: #{fullname}>"
end
protected
def multi_code_examples?(text)
text.to_s.scan(/\b(#{Regexp.escape(name)})[^_0-9\!\?]/m).size > 1
end
# @return [Boolean]
# +true+ if the object or its parent is tagged as @private
def private_tag?
!private_tag.nil?
end
attr_reader :private_tag
def private_api_tag?
api_tag && api_tag.text == 'private'
end
def tag(name)
tags(name).first
end
def tags(name = nil)
object.tags(name)
rescue ::YARD::CodeObjects::ProxyMethodError
# this error is raised by YARD
# see broken.rb in test fixtures
[]
end
# @return [Array]
# YARD tags that are not already covered by other wrapper
# methods
def unconsidered_tags
@unconsidered_tags ||= tags.reject do |tag|
auto_generated_tag?(tag) ||
CONSIDERED_YARD_TAGS.include?(tag.tag_name)
end
end
def __depth(i = 0)
if parent
parent.__depth(i + 1)
else
i
end
end
private
def __api_tag
tag(:api) || (parent && parent.api_tag)
end
def __private_tag
tag(:private) || (parent && parent.private_tag)
end
def auto_generated_tag?(tag)
tag.text.to_s.empty? &&
AUTO_GENERATED_TAG_NAMES.include?(tag.tag_name)
end
end
end
end
end
end
end
end