Merge pull request #12307 from Homebrew/dependabot/bundler/Library/Homebrew/tapioca-0.5.3

build(deps-dev): bump tapioca from 0.5.2 to 0.5.3 in /Library/Homebrew
This commit is contained in:
Nanda H Krishna 2021-10-22 17:37:29 -04:00 committed by GitHub
commit 49031f02f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
231 changed files with 30010 additions and 111 deletions

View File

@ -166,15 +166,15 @@ GEM
sorbet (>= 0.5.9204)
sorbet-runtime (>= 0.5.9204)
thor (>= 0.19.2)
tapioca (0.5.2)
tapioca (0.5.3)
bundler (>= 1.17.3)
pry (>= 0.12.2)
rbi
sorbet-runtime
sorbet-static (>= 0.4.4471)
sorbet-static (>= 0.5.6200)
spoom
thor (>= 0.19.2)
unparser
yard-sorbet
thor (1.1.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
@ -189,6 +189,10 @@ GEM
warning (1.2.1)
webrick (1.7.0)
webrobots (0.1.2)
yard (0.9.26)
yard-sorbet (0.6.0)
sorbet-runtime (>= 0.5)
yard (>= 0.9)
zeitwerk (2.5.1)
PLATFORMS

View File

@ -1,9 +1,9 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `tapioca` gem.
# Please instead update this file by running `bin/tapioca gem tapioca`.
# typed: true
class ActiveRecordColumnTypeHelper
def initialize(*args, &blk); end
@ -22,8 +22,86 @@ class ActiveRecordColumnTypeHelper
def lookup_return_type_of_method(*args, &blk); end
end
class DynamicMixinCompiler
include ::Tapioca::Reflection
sig { params(constant: Module).void }
def initialize(constant); end
def class_attribute_predicates; end
sig { returns(T::Array[Symbol]) }
def class_attribute_readers; end
def class_attribute_writers; end
sig { params(tree: RBI::Tree).void }
def compile_class_attributes(tree); end
sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
def compile_mixes_in_class_methods(tree); end
sig { returns(T::Array[Module]) }
def dynamic_extends; end
def dynamic_includes; end
sig { returns(T::Boolean) }
def empty_attributes?; end
def instance_attribute_predicates; end
sig { returns(T::Array[Symbol]) }
def instance_attribute_readers; end
def instance_attribute_writers; end
end
module RBI; end
class RBI::File
sig { params(strictness: T.nilable(String), comments: T::Array[RBI::Comment], block: T.nilable(T.proc.params(file: RBI::File).void)).void }
def initialize(strictness: T.unsafe(nil), comments: T.unsafe(nil), &block); end
sig { params(node: RBI::Node).void }
def <<(node); end
sig { params(v: RBI::Printer).void }
def accept_printer(v); end
sig { returns(T::Array[RBI::Comment]) }
def comments; end
def comments=(_arg0); end
sig { returns(T::Boolean) }
def empty?; end
sig { params(out: T.any(IO, StringIO), indent: Integer, print_locs: T::Boolean).void }
def print(out: T.unsafe(nil), indent: T.unsafe(nil), print_locs: T.unsafe(nil)); end
sig { returns(RBI::Tree) }
def root; end
sig { void }
def set_empty_body_content; end
sig { params(command: String, reason: T.nilable(String), display_heading: T::Boolean).void }
def set_file_header(command, reason: T.unsafe(nil), display_heading: T.unsafe(nil)); end
sig { returns(T.nilable(String)) }
def strictness; end
sig { params(indent: Integer, print_locs: T::Boolean).returns(String) }
def string(indent: T.unsafe(nil), print_locs: T.unsafe(nil)); end
sig { void }
def transform_rbi!; end
sig { returns(String) }
def transformed_string; end
end
class RBI::Tree < ::RBI::NodeWithComments
sig { params(loc: T.nilable(RBI::Loc), comments: T::Array[RBI::Comment], block: T.nilable(T.proc.params(node: RBI::Tree).void)).void }
def initialize(loc: T.unsafe(nil), comments: T.unsafe(nil), &block); end
@ -137,25 +215,13 @@ module Tapioca
end
class Tapioca::Cli < ::Thor
include ::Thor::Actions
extend ::Thor::Actions::ClassMethods
def __print_version; end
def dsl(*constants); end
def gem(*gems); end
def generate(*gems); end
def init; end
def require; end
def sync; end
def todo; end
private
def create_config; end
def create_post_require; end
def generate_binstub; end
def generator; end
class << self
def exit_on_failure?; end
end
@ -172,9 +238,15 @@ class Tapioca::Compilers::Dsl::Base
sig { void }
def initialize; end
sig { params(error: String).void }
def add_error(error); end
sig { abstract.type_parameters(:T).params(tree: RBI::Tree, constant: T.type_parameter(:T)).void }
def decorate(tree, constant); end
sig { returns(T::Array[String]) }
def errors; end
sig { abstract.returns(T::Enumerable[Module]) }
def gather_constants; end
@ -229,6 +301,8 @@ class Tapioca::Compilers::Dsl::Base
def parameters_types_from_signature(method_def, signature); end
end
Tapioca::Compilers::Dsl::COMPILERS_PATH = T.let(T.unsafe(nil), String)
class Tapioca::Compilers::DslCompiler
sig { params(requested_constants: T::Array[Module], requested_generators: T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)], excluded_generators: T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)], error_handler: T.nilable(T.proc.params(error: String).void)).void }
def initialize(requested_constants:, requested_generators: T.unsafe(nil), excluded_generators: T.unsafe(nil), error_handler: T.unsafe(nil)); end
@ -242,7 +316,7 @@ class Tapioca::Compilers::DslCompiler
sig { returns(T::Array[Module]) }
def requested_constants; end
sig { params(blk: T.proc.params(constant: Module, rbi: String).void).void }
sig { params(blk: T.proc.params(constant: Module, rbi: RBI::File).void).void }
def run(&blk); end
private
@ -253,7 +327,7 @@ class Tapioca::Compilers::DslCompiler
sig { params(requested_generators: T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)], excluded_generators: T::Array[T.class_of(Tapioca::Compilers::Dsl::Base)]).returns(T::Enumerable[Tapioca::Compilers::Dsl::Base]) }
def gather_generators(requested_generators, excluded_generators); end
sig { params(constant: Module).returns(T.nilable(String)) }
sig { params(constant: Module).returns(T.nilable(RBI::File)) }
def rbi_for_constant(constant); end
sig { params(error: String).returns(T.noreturn) }
@ -307,14 +381,16 @@ module Tapioca::Compilers::SymbolTable; end
class Tapioca::Compilers::SymbolTable::SymbolGenerator
include ::Tapioca::Reflection
sig { params(gem: Tapioca::Gemfile::GemSpec, indent: Integer).void }
def initialize(gem, indent = T.unsafe(nil)); end
sig { params(gem: Tapioca::Gemfile::GemSpec, indent: Integer, include_doc: T::Boolean).void }
def initialize(gem, indent = T.unsafe(nil), include_doc = T.unsafe(nil)); end
sig { returns(Tapioca::Gemfile::GemSpec) }
def gem; end
sig { returns(String) }
def generate; end
sig { params(rbi: RBI::File).void }
def generate(rbi); end
sig { returns(Integer) }
def indent; end
private
@ -322,17 +398,12 @@ class Tapioca::Compilers::SymbolTable::SymbolGenerator
sig { params(name: String).void }
def add_to_alias_namespace(name); end
sig { params(name: T.nilable(String)).void }
def add_to_symbol_queue(name); end
sig { params(name: String).returns(T::Boolean) }
def alias_namespaced?(name); end
sig { params(constant: Module).returns([T::Array[Module], T::Array[Module]]) }
def collect_dynamic_mixins_of(constant); end
sig { params(constant: Module, dynamic_extends: T::Array[Module]).returns(T::Array[Module]) }
def collect_mixed_in_class_methods(constant, dynamic_extends); end
sig { params(tree: RBI::Tree, name: T.nilable(String), constant: BasicObject).void }
def compile(tree, name, constant); end
@ -348,6 +419,9 @@ class Tapioca::Compilers::SymbolTable::SymbolGenerator
sig { params(tree: RBI::Tree, module_name: String, mod: Module, for_visibility: T::Array[Symbol]).void }
def compile_directly_owned_methods(tree, module_name, mod, for_visibility = T.unsafe(nil)); end
sig { params(tree: RBI::Tree, constant: Module).void }
def compile_dynamic_mixins(tree, constant); end
sig { params(tree: RBI::Tree, constant: Module).void }
def compile_enums(tree, constant); end
@ -357,9 +431,6 @@ class Tapioca::Compilers::SymbolTable::SymbolGenerator
sig { params(tree: RBI::Tree, name: String, constant: Module).void }
def compile_methods(tree, name, constant); end
sig { params(tree: RBI::Tree, constant: Module).void }
def compile_mixes_in_class_methods(tree, constant); end
sig { params(tree: RBI::Tree, constant: Module).void }
def compile_mixins(tree, constant); end
@ -393,6 +464,9 @@ class Tapioca::Compilers::SymbolTable::SymbolGenerator
sig { params(constant: Module, strict: T::Boolean).returns(T::Boolean) }
def defined_in_gem?(constant, strict: T.unsafe(nil)); end
sig { params(name: String).returns(T::Array[RBI::Comment]) }
def documentation_comments(name); end
sig { params(symbols: T::Set[String]).returns(T::Set[String]) }
def engine_symbols(symbols); end
@ -405,6 +479,7 @@ class Tapioca::Compilers::SymbolTable::SymbolGenerator
sig { params(constant: Module).returns(T::Array[String]) }
def get_file_candidates(constant); end
sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
def initialize_method_for(constant); end
sig { params(constant: Module).returns(T::Array[Module]) }
@ -447,6 +522,7 @@ class Tapioca::Compilers::SymbolTable::SymbolGenerator
def valid_method_name?(name); end
end
Tapioca::Compilers::SymbolTable::SymbolGenerator::IGNORED_COMMENTS = T.let(T.unsafe(nil), Array)
Tapioca::Compilers::SymbolTable::SymbolGenerator::IGNORED_SYMBOLS = T.let(T.unsafe(nil), Array)
Tapioca::Compilers::SymbolTable::SymbolGenerator::SPECIAL_METHOD_NAMES = T.let(T.unsafe(nil), Array)
Tapioca::Compilers::SymbolTable::SymbolGenerator::TYPE_PARAMETER_MATCHER = T.let(T.unsafe(nil), Regexp)
@ -476,8 +552,8 @@ class Tapioca::Compilers::SymbolTable::SymbolLoader::SymbolTableParser
end
class Tapioca::Compilers::SymbolTableCompiler
sig { params(gem: Tapioca::Gemfile::GemSpec, indent: Integer).returns(String) }
def compile(gem, indent = T.unsafe(nil)); end
sig { params(gem: Tapioca::Gemfile::GemSpec, rbi: RBI::File, indent: Integer, include_docs: T::Boolean).void }
def compile(gem, rbi, indent = T.unsafe(nil), include_docs = T.unsafe(nil)); end
end
class Tapioca::Compilers::TodosCompiler
@ -491,6 +567,7 @@ class Tapioca::Compilers::TodosCompiler
end
class Tapioca::Config < ::T::Struct
const :doc, T::Boolean, default: T.unsafe(nil)
const :exclude, T::Array[String]
const :exclude_generators, T::Array[String]
const :file_header, T::Boolean, default: T.unsafe(nil)
@ -586,7 +663,7 @@ class Tapioca::Gemfile
def lockfile; end
sig { returns([T::Enumerable[T.any(Gem::Specification, T.all(Bundler::RemoteSpecification, Bundler::StubSpecification))], T::Array[String]]) }
sig { returns([T::Enumerable[T.any(Bundler::StubSpecification, Gem::Specification)], T::Array[String]]) }
def materialize_deps; end
sig { returns(Bundler::Runtime) }
@ -594,7 +671,7 @@ class Tapioca::Gemfile
end
class Tapioca::Gemfile::GemSpec
sig { params(spec: T.any(Gem::Specification, T.all(Bundler::RemoteSpecification, Bundler::StubSpecification))).void }
sig { params(spec: T.any(Bundler::StubSpecification, Gem::Specification)).void }
def initialize(spec); end
sig { params(path: String).returns(T::Boolean) }
@ -612,6 +689,9 @@ class Tapioca::Gemfile::GemSpec
sig { returns(String) }
def name; end
sig { void }
def parse_yard_docs; end
sig { returns(String) }
def rbi_file_name; end
@ -645,55 +725,55 @@ class Tapioca::Gemfile::GemSpec
end
Tapioca::Gemfile::GemSpec::IGNORED_GEMS = T.let(T.unsafe(nil), Array)
Tapioca::Gemfile::Spec = T.type_alias { T.any(Gem::Specification, T.all(Bundler::RemoteSpecification, Bundler::StubSpecification)) }
Tapioca::Gemfile::Spec = T.type_alias { T.any(Bundler::StubSpecification, Gem::Specification) }
module Tapioca::Generators; end
class Tapioca::Generator < ::Thor::Shell::Color
sig { params(config: Tapioca::Config).void }
def initialize(config); end
class Tapioca::Generators::Base
include ::Thor::Base
include ::Thor::Invocation
include ::Thor::Shell
extend ::Thor::Base::ClassMethods
extend ::Thor::Invocation::ClassMethods
sig { params(requested_constants: T::Array[String], should_verify: T::Boolean, quiet: T::Boolean, verbose: T::Boolean).void }
def build_dsl(requested_constants, should_verify: T.unsafe(nil), quiet: T.unsafe(nil), verbose: T.unsafe(nil)); end
abstract!
sig { params(gem_names: T::Array[String]).void }
def build_gem_rbis(gem_names); end
sig { params(default_command: String, file_writer: Thor::Actions).void }
def initialize(default_command:, file_writer: T.unsafe(nil)); end
sig { void }
def build_requires; end
sig { abstract.void }
def generate; end
sig { void }
def build_todos; end
private
sig { returns(Tapioca::Config) }
def config; end
sig { params(path: T.any(Pathname, String), content: String, force: T::Boolean, skip: T::Boolean, verbose: T::Boolean).void }
def create_file(path, content, force: T.unsafe(nil), skip: T.unsafe(nil), verbose: T.unsafe(nil)); end
sig { params(should_verify: T::Boolean).void }
def sync_rbis_with_gemfile(should_verify: T.unsafe(nil)); end
sig { params(message: String, color: T.any(Symbol, T::Array[Symbol])).void }
def say_error(message = T.unsafe(nil), *color); end
end
class Tapioca::Generators::Base::FileWriter < ::Thor
include ::Thor::Actions
extend ::Thor::Actions::ClassMethods
end
class Tapioca::Generators::Dsl < ::Tapioca::Generators::Base
sig { params(requested_constants: T::Array[String], outpath: Pathname, generators: T::Array[String], exclude_generators: T::Array[String], file_header: T::Boolean, compiler_path: String, tapioca_path: String, default_command: String, file_writer: Thor::Actions, should_verify: T::Boolean, quiet: T::Boolean, verbose: T::Boolean).void }
def initialize(requested_constants:, outpath:, generators:, exclude_generators:, file_header:, compiler_path:, tapioca_path:, default_command:, file_writer: T.unsafe(nil), should_verify: T.unsafe(nil), quiet: T.unsafe(nil), verbose: T.unsafe(nil)); end
sig { override.void }
def generate; end
private
sig { void }
def abort_if_pending_migrations!; end
sig { params(filename: Pathname).void }
def add(filename); end
sig { returns(T::Array[String]) }
def added_rbis; end
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
def build_error_for_files(cause, files); end
sig { returns(Tapioca::Gemfile) }
def bundle; end
sig { params(constant_name: String, contents: String, outpath: Pathname, quiet: T::Boolean).returns(T.nilable(Pathname)) }
def compile_dsl_rbi(constant_name, contents, outpath: T.unsafe(nil), quiet: T.unsafe(nil)); end
sig { params(gem: Tapioca::Gemfile::GemSpec).void }
def compile_gem_rbi(gem); end
sig { returns(Tapioca::Compilers::SymbolTableCompiler) }
def compiler; end
sig { params(constant_name: String, rbi: RBI::File, outpath: Pathname, quiet: T::Boolean).returns(T.nilable(Pathname)) }
def compile_dsl_rbi(constant_name, rbi, outpath: T.unsafe(nil), quiet: T.unsafe(nil)); end
sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
def constantize(constant_names); end
@ -704,12 +784,73 @@ class Tapioca::Generator < ::Thor::Shell::Color
sig { params(constant_name: String).returns(Pathname) }
def dsl_rbi_filename(constant_name); end
sig { params(gem_name: String).returns(Pathname) }
def existing_rbi(gem_name); end
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
def existing_rbi_filenames(requested_constants, path: T.unsafe(nil)); end
sig { params(constant: String).returns(String) }
def generate_command_for(constant); end
sig { params(eager_load: T::Boolean).void }
def load_application(eager_load:); end
sig { void }
def load_dsl_generators; end
sig { returns(Tapioca::Loader) }
def loader; end
sig { params(dir: Pathname).void }
def perform_dsl_verification(dir); end
sig { params(files: T::Set[Pathname]).void }
def purge_stale_dsl_rbi_files(files); end
sig { params(constant: String).returns(String) }
def rbi_filename_for(constant); end
sig { params(path: Pathname).returns(T::Array[Pathname]) }
def rbi_files_in(path); end
sig { params(filename: Pathname).void }
def remove(filename); end
sig { params(diff: T::Hash[String, Symbol], command: String).void }
def report_diff_and_exit_if_out_of_date(diff, command); end
sig { params(class_name: String).returns(String) }
def underscore(class_name); end
sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
def verify_dsl_rbi(tmp_dir:); end
end
class Tapioca::Generators::Gem < ::Tapioca::Generators::Base
sig { params(gem_names: T::Array[String], gem_excludes: T::Array[String], prerequire: T.nilable(String), postrequire: String, typed_overrides: T::Hash[String, String], default_command: String, outpath: Pathname, file_header: T::Boolean, doc: T::Boolean, file_writer: Thor::Actions).void }
def initialize(gem_names:, gem_excludes:, prerequire:, postrequire:, typed_overrides:, default_command:, outpath:, file_header:, doc:, file_writer: T.unsafe(nil)); end
sig { override.void }
def generate; end
sig { params(should_verify: T::Boolean).void }
def sync(should_verify: T.unsafe(nil)); end
private
sig { returns(T::Array[String]) }
def added_rbis; end
sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
def build_error_for_files(cause, files); end
sig { returns(Tapioca::Gemfile) }
def bundle; end
sig { params(gem: Tapioca::Gemfile::GemSpec).void }
def compile_gem_rbi(gem); end
sig { params(gem_name: String).returns(Pathname) }
def existing_rbi(gem_name); end
sig { returns(T::Hash[String, String]) }
def existing_rbis; end
@ -731,12 +872,6 @@ class Tapioca::Generator < ::Thor::Shell::Color
sig { params(gem_names: T::Array[String]).returns(T::Array[Tapioca::Gemfile::GemSpec]) }
def gems_to_generate(gem_names); end
sig { params(eager_load: T::Boolean).void }
def load_application(eager_load:); end
sig { void }
def load_dsl_generators; end
sig { returns(Tapioca::Loader) }
def loader; end
@ -746,24 +881,12 @@ class Tapioca::Generator < ::Thor::Shell::Color
sig { void }
def perform_additions; end
sig { params(dir: Pathname).void }
def perform_dsl_verification(dir); end
sig { void }
def perform_removals; end
sig { void }
def perform_sync_verification; end
sig { params(files: T::Set[Pathname]).void }
def purge_stale_dsl_rbi_files(files); end
sig { params(path: Pathname).returns(T::Array[Pathname]) }
def rbi_files_in(path); end
sig { params(command: String, reason: T.nilable(String), strictness: T.nilable(String)).returns(String) }
def rbi_header(command, reason: T.unsafe(nil), strictness: T.unsafe(nil)); end
sig { params(filename: Pathname).void }
def remove(filename); end
@ -775,18 +898,54 @@ class Tapioca::Generator < ::Thor::Shell::Color
sig { void }
def require_gem_file; end
sig { params(message: String, color: T.any(Symbol, T::Array[Symbol])).void }
def say_error(message = T.unsafe(nil), *color); end
sig { params(class_name: String).returns(String) }
def underscore(class_name); end
sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
def verify_dsl_rbi(tmp_dir:); end
end
Tapioca::Generator::EMPTY_RBI_COMMENT = T.let(T.unsafe(nil), String)
class Tapioca::Generators::Init < ::Tapioca::Generators::Base
sig { params(sorbet_config: String, default_postrequire: String, default_command: String, file_writer: Thor::Actions).void }
def initialize(sorbet_config:, default_postrequire:, default_command:, file_writer: T.unsafe(nil)); end
sig { override.void }
def generate; end
private
sig { void }
def create_config; end
sig { void }
def create_post_require; end
sig { void }
def generate_binstub; end
sig { void }
def generate_binstub!; end
sig { returns(Bundler::Installer) }
def installer; end
sig { returns(Bundler::StubSpecification) }
def spec; end
end
class Tapioca::Generators::Require < ::Tapioca::Generators::Base
sig { params(requires_path: String, sorbet_config_path: String, default_command: String, file_writer: Thor::Actions).void }
def initialize(requires_path:, sorbet_config_path:, default_command:, file_writer: T.unsafe(nil)); end
sig { override.void }
def generate; end
end
class Tapioca::Generators::Todo < ::Tapioca::Generators::Base
sig { params(todos_path: String, file_header: T::Boolean, default_command: String, file_writer: Thor::Actions).void }
def initialize(todos_path:, file_header:, default_command:, file_writer: T.unsafe(nil)); end
sig { override.void }
def generate; end
sig { params(command: String, reason: T.nilable(String), strictness: T.nilable(String)).returns(String) }
def rbi_header(command, reason: T.unsafe(nil), strictness: T.unsafe(nil)); end
end
module Tapioca::GenericTypeRegistry
class << self
@ -863,6 +1022,9 @@ module Tapioca::Reflection
sig { params(constant: Module).returns(T::Array[Module]) }
def inherited_ancestors_of(constant); end
sig { params(constant: Module, method: Symbol).returns(Method) }
def method_of(constant, method); end
sig { params(constant: Module).returns(T.nilable(String)) }
def name_of(constant); end
@ -898,6 +1060,7 @@ Tapioca::Reflection::ANCESTORS_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::CLASS_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::CONSTANTS_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::EQUAL_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::METHOD_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::NAME_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::OBJECT_ID_METHOD = T.let(T.unsafe(nil), UnboundMethod)
Tapioca::Reflection::PRIVATE_INSTANCE_METHODS_METHOD = T.let(T.unsafe(nil), UnboundMethod)

View File

@ -0,0 +1,194 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `yard-sorbet` gem.
# Please instead update this file by running `bin/tapioca gem yard-sorbet`.
module YARDSorbet; end
module YARDSorbet::Directives
class << self
sig { params(docstring: String, directives: T::Array[String]).void }
def add_directives(docstring, directives); end
sig { params(docstring: T.nilable(String)).returns([YARD::Docstring, T::Array[String]]) }
def extract_directives(docstring); end
end
end
module YARDSorbet::Handlers; end
class YARDSorbet::Handlers::AbstractDSLHandler < ::YARD::Handlers::Ruby::Base
sig { void }
def process; end
end
YARDSorbet::Handlers::AbstractDSLHandler::CLASS_TAG_TEXT = T.let(T.unsafe(nil), String)
YARDSorbet::Handlers::AbstractDSLHandler::TAG_TEXT = T.let(T.unsafe(nil), String)
class YARDSorbet::Handlers::EnumsHandler < ::YARD::Handlers::Ruby::Base
sig { void }
def process; end
private
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Boolean) }
def const_assign_node?(node); end
end
class YARDSorbet::Handlers::IncludeHandler < ::YARD::Handlers::Ruby::Base
sig { void }
def process; end
private
sig { returns(YARD::CodeObjects::NamespaceObject) }
def included_in; end
end
class YARDSorbet::Handlers::MixesInClassMethodsHandler < ::YARD::Handlers::Ruby::Base
sig { void }
def process; end
end
class YARDSorbet::Handlers::SigHandler < ::YARD::Handlers::Ruby::Base
sig { void }
def process; end
private
sig { params(method_node: YARD::Parser::Ruby::AstNode, node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
def parse_params(method_node, node, docstring); end
sig { params(node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
def parse_return(node, docstring); end
sig { params(method_node: YARD::Parser::Ruby::AstNode, docstring: YARD::Docstring).void }
def parse_sig(method_node, docstring); end
end
YARDSorbet::Handlers::SigHandler::ATTR_NODE_TYPES = T.let(T.unsafe(nil), Array)
module YARDSorbet::Handlers::StructClassHandler
sig { void }
def process; end
private
sig { params(object: YARD::CodeObjects::MethodObject, props: T::Array[YARDSorbet::TStructProp], docstring: YARD::Docstring, directives: T::Array[String]).void }
def decorate_t_struct_init(object, props, docstring, directives); end
sig { params(props: T::Array[YARDSorbet::TStructProp], class_ns: YARD::CodeObjects::ClassObject).void }
def process_t_struct_props(props, class_ns); end
sig { params(props: T::Array[YARDSorbet::TStructProp]).returns(T::Array[[String, T.nilable(String)]]) }
def to_object_parameters(props); end
end
class YARDSorbet::Handlers::StructPropHandler < ::YARD::Handlers::Ruby::Base
sig { void }
def process; end
private
sig { params(object: YARD::CodeObjects::MethodObject, prop: YARDSorbet::TStructProp).void }
def decorate_object(object, prop); end
sig { returns(T.nilable(String)) }
def default_value; end
sig { params(name: String).returns(YARDSorbet::TStructProp) }
def make_prop(name); end
sig { params(object: YARD::CodeObjects::MethodObject, name: String).void }
def register_attrs(object, name); end
sig { params(prop: YARDSorbet::TStructProp).void }
def update_state(prop); end
end
module YARDSorbet::NodeUtils
class << self
sig { params(node: YARD::Parser::Ruby::AstNode, _blk: T.proc.params(n: YARD::Parser::Ruby::AstNode).void).void }
def bfs_traverse(node, &_blk); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T.any(YARD::Parser::Ruby::MethodCallNode, YARD::Parser::Ruby::MethodDefinitionNode)) }
def get_method_node(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(YARD::Parser::Ruby::AstNode) }
def sibling_node(node); end
end
end
YARDSorbet::NodeUtils::ATTRIBUTE_METHODS = T.let(T.unsafe(nil), Array)
YARDSorbet::NodeUtils::SIGABLE_NODE = T.type_alias { T.any(YARD::Parser::Ruby::MethodCallNode, YARD::Parser::Ruby::MethodDefinitionNode) }
YARDSorbet::NodeUtils::SKIP_METHOD_CONTENTS = T.let(T.unsafe(nil), Array)
module YARDSorbet::SigToYARD
class << self
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert(node); end
private
sig { params(node: YARD::Parser::Ruby::AstNode).returns(String) }
def build_generic_type(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_aref(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_array(node); end
sig { params(node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
def convert_call(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_collection(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_hash(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_list(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_node(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_node_type(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_ref(node); end
sig { params(node: YARD::Parser::Ruby::MethodCallNode).returns(T::Array[String]) }
def convert_t_method(node); end
sig { params(node: YARD::Parser::Ruby::AstNode).returns(T::Array[String]) }
def convert_unknown(node); end
end
end
class YARDSorbet::TStructProp < ::T::Struct
const :default, T.nilable(String)
const :doc, String
const :prop_name, String
const :source, String
const :types, T::Array[String]
class << self
def inherited(s); end
end
end
module YARDSorbet::TagUtils
class << self
sig { params(docstring: YARD::Docstring, tag_name: String, name: T.nilable(String)).returns(T.nilable(YARD::Tags::Tag)) }
def find_tag(docstring, tag_name, name); end
sig { params(docstring: YARD::Docstring, tag_name: String, types: T.nilable(T::Array[String]), name: T.nilable(String), text: String).void }
def upsert_tag(docstring, tag_name, types = T.unsafe(nil), name = T.unsafe(nil), text = T.unsafe(nil)); end
end
end
YARDSorbet::VERSION = T.let(T.unsafe(nil), String)

File diff suppressed because it is too large Load Diff

View File

@ -3243,6 +3243,12 @@ module DiskUsageExtension
extend ::T::Private::Methods::SingletonMethodHooks
end
class DynamicMixinCompiler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class ELFTools::Structs::ELF32_PhdrBe
end
@ -3796,14 +3802,6 @@ module GC
def self.verify_transient_heap_internal_consistency(); end
end
module Gem
ConfigMap = ::T.let(nil, ::T.untyped)
RbConfigPriorities = ::T.let(nil, ::T.untyped)
RubyGemsPackageVersion = ::T.let(nil, ::T.untyped)
RubyGemsVersion = ::T.let(nil, ::T.untyped)
USE_BUNDLER_FOR_GEMDEPS = ::T.let(nil, ::T.untyped)
end
class Gem::DependencyInstaller
def _deprecated_add_found_dependencies(to_do, dependency_list); end
@ -4348,8 +4346,6 @@ end
class Gem::UninstallError
end
Gem::UnsatisfiableDepedencyError = Gem::UnsatisfiableDependencyError
Gem::Version::Requirement = Gem::Requirement
module Gem
@ -5825,6 +5821,8 @@ class Object
PATCH_B_SHA256 = ::T.let(nil, ::T.untyped)
REQUIRED_RUBY_X = ::T.let(nil, ::T.untyped)
REQUIRED_RUBY_Y = ::T.let(nil, ::T.untyped)
RUBY18 = ::T.let(nil, ::T.untyped)
RUBY19 = ::T.let(nil, ::T.untyped)
RUBY_BIN = ::T.let(nil, ::T.untyped)
RUBY_COPYRIGHT = ::T.let(nil, ::T.untyped)
RUBY_DESCRIPTION = ::T.let(nil, ::T.untyped)
@ -7340,6 +7338,14 @@ end
class Racc::CparseParams
end
class Rack::Request
def query(); end
def version_supplied(); end
def version_supplied=(version_supplied); end
end
class Random
def self.bytes(arg); end
end
@ -7453,6 +7459,545 @@ class ResourceStageContext
extend ::T::Private::Methods::SingletonMethodHooks
end
class Ripper
def column(); end
def encoding(); end
def end_seen?(); end
def error?(); end
def filename(); end
def initialize(*arg); end
def lineno(); end
def parse(); end
def state(); end
def yydebug(); end
def yydebug=(yydebug); end
EXPR_ARG = ::T.let(nil, ::T.untyped)
EXPR_ARG_ANY = ::T.let(nil, ::T.untyped)
EXPR_BEG = ::T.let(nil, ::T.untyped)
EXPR_BEG_ANY = ::T.let(nil, ::T.untyped)
EXPR_CLASS = ::T.let(nil, ::T.untyped)
EXPR_CMDARG = ::T.let(nil, ::T.untyped)
EXPR_DOT = ::T.let(nil, ::T.untyped)
EXPR_END = ::T.let(nil, ::T.untyped)
EXPR_ENDARG = ::T.let(nil, ::T.untyped)
EXPR_ENDFN = ::T.let(nil, ::T.untyped)
EXPR_END_ANY = ::T.let(nil, ::T.untyped)
EXPR_FITEM = ::T.let(nil, ::T.untyped)
EXPR_FNAME = ::T.let(nil, ::T.untyped)
EXPR_LABEL = ::T.let(nil, ::T.untyped)
EXPR_LABELED = ::T.let(nil, ::T.untyped)
EXPR_MID = ::T.let(nil, ::T.untyped)
EXPR_NONE = ::T.let(nil, ::T.untyped)
EXPR_VALUE = ::T.let(nil, ::T.untyped)
PARSER_EVENT_TABLE = ::T.let(nil, ::T.untyped)
SCANNER_EVENT_TABLE = ::T.let(nil, ::T.untyped)
Version = ::T.let(nil, ::T.untyped)
end
class Ripper::Filter
def initialize(src, filename=T.unsafe(nil), lineno=T.unsafe(nil)); end
end
class Ripper::Lexer
def lex(); end
def tokenize(); end
end
class Ripper::Lexer::Elem
def event(); end
def event=(_); end
def initialize(pos, event, tok, state); end
def pos(); end
def pos=(_); end
def state(); end
def state=(_); end
def tok(); end
def tok=(_); end
end
class Ripper::Lexer::Elem
def self.[](*arg); end
def self.members(); end
end
class Ripper::Lexer::State
def &(i); end
def ==(i); end
def allbits?(i); end
def anybits?(i); end
def initialize(i); end
def nobits?(i); end
def to_i(); end
def to_int(); end
def to_int=(_); end
def to_s=(_); end
def |(i); end
end
class Ripper::Lexer::State
def self.[](*arg); end
def self.members(); end
end
class Ripper::Lexer
end
class Ripper::SexpBuilder
def on_BEGIN(*args); end
def on_CHAR(tok); end
def on_END(*args); end
def on___end__(tok); end
def on_alias(*args); end
def on_alias_error(*args); end
def on_aref(*args); end
def on_aref_field(*args); end
def on_arg_ambiguous(*args); end
def on_arg_paren(*args); end
def on_args_add(*args); end
def on_args_add_block(*args); end
def on_args_add_star(*args); end
def on_args_new(*args); end
def on_array(*args); end
def on_assign(*args); end
def on_assign_error(*args); end
def on_assoc_new(*args); end
def on_assoc_splat(*args); end
def on_assoclist_from_args(*args); end
def on_backref(tok); end
def on_backtick(tok); end
def on_bare_assoc_hash(*args); end
def on_begin(*args); end
def on_binary(*args); end
def on_block_var(*args); end
def on_blockarg(*args); end
def on_bodystmt(*args); end
def on_brace_block(*args); end
def on_break(*args); end
def on_call(*args); end
def on_case(*args); end
def on_class(*args); end
def on_class_name_error(*args); end
def on_comma(tok); end
def on_command(*args); end
def on_command_call(*args); end
def on_comment(tok); end
def on_const(tok); end
def on_const_path_field(*args); end
def on_const_path_ref(*args); end
def on_const_ref(*args); end
def on_cvar(tok); end
def on_def(*args); end
def on_defined(*args); end
def on_defs(*args); end
def on_do_block(*args); end
def on_dot2(*args); end
def on_dot3(*args); end
def on_dyna_symbol(*args); end
def on_else(*args); end
def on_elsif(*args); end
def on_embdoc(tok); end
def on_embdoc_beg(tok); end
def on_embdoc_end(tok); end
def on_embexpr_beg(tok); end
def on_embexpr_end(tok); end
def on_embvar(tok); end
def on_ensure(*args); end
def on_excessed_comma(*args); end
def on_fcall(*args); end
def on_field(*args); end
def on_float(tok); end
def on_for(*args); end
def on_gvar(tok); end
def on_hash(*args); end
def on_heredoc_beg(tok); end
def on_heredoc_end(tok); end
def on_ident(tok); end
def on_if(*args); end
def on_if_mod(*args); end
def on_ifop(*args); end
def on_ignored_nl(tok); end
def on_ignored_sp(tok); end
def on_imaginary(tok); end
def on_int(tok); end
def on_ivar(tok); end
def on_kw(tok); end
def on_kwrest_param(*args); end
def on_label(tok); end
def on_label_end(tok); end
def on_lambda(*args); end
def on_lbrace(tok); end
def on_lbracket(tok); end
def on_lparen(tok); end
def on_magic_comment(*args); end
def on_massign(*args); end
def on_method_add_arg(*args); end
def on_method_add_block(*args); end
def on_mlhs_add(*args); end
def on_mlhs_add_post(*args); end
def on_mlhs_add_star(*args); end
def on_mlhs_new(*args); end
def on_mlhs_paren(*args); end
def on_module(*args); end
def on_mrhs_add(*args); end
def on_mrhs_add_star(*args); end
def on_mrhs_new(*args); end
def on_mrhs_new_from_args(*args); end
def on_next(*args); end
def on_nl(tok); end
def on_op(tok); end
def on_opassign(*args); end
def on_operator_ambiguous(*args); end
def on_param_error(*args); end
def on_params(*args); end
def on_paren(*args); end
def on_parse_error(*args); end
def on_period(tok); end
def on_program(*args); end
def on_qsymbols_add(*args); end
def on_qsymbols_beg(tok); end
def on_qsymbols_new(*args); end
def on_qwords_add(*args); end
def on_qwords_beg(tok); end
def on_qwords_new(*args); end
def on_rational(tok); end
def on_rbrace(tok); end
def on_rbracket(tok); end
def on_redo(*args); end
def on_regexp_add(*args); end
def on_regexp_beg(tok); end
def on_regexp_end(tok); end
def on_regexp_literal(*args); end
def on_regexp_new(*args); end
def on_rescue(*args); end
def on_rescue_mod(*args); end
def on_rest_param(*args); end
def on_retry(*args); end
def on_return(*args); end
def on_return0(*args); end
def on_rparen(tok); end
def on_sclass(*args); end
def on_semicolon(tok); end
def on_sp(tok); end
def on_stmts_add(*args); end
def on_stmts_new(*args); end
def on_string_add(*args); end
def on_string_concat(*args); end
def on_string_content(*args); end
def on_string_dvar(*args); end
def on_string_embexpr(*args); end
def on_string_literal(*args); end
def on_super(*args); end
def on_symbeg(tok); end
def on_symbol(*args); end
def on_symbol_literal(*args); end
def on_symbols_add(*args); end
def on_symbols_beg(tok); end
def on_symbols_new(*args); end
def on_tlambda(tok); end
def on_tlambeg(tok); end
def on_top_const_field(*args); end
def on_top_const_ref(*args); end
def on_tstring_beg(tok); end
def on_tstring_content(tok); end
def on_tstring_end(tok); end
def on_unary(*args); end
def on_undef(*args); end
def on_unless(*args); end
def on_unless_mod(*args); end
def on_until(*args); end
def on_until_mod(*args); end
def on_var_alias(*args); end
def on_var_field(*args); end
def on_var_ref(*args); end
def on_vcall(*args); end
def on_void_stmt(*args); end
def on_when(*args); end
def on_while(*args); end
def on_while_mod(*args); end
def on_word_add(*args); end
def on_word_new(*args); end
def on_words_add(*args); end
def on_words_beg(tok); end
def on_words_new(*args); end
def on_words_sep(tok); end
def on_xstring_add(*args); end
def on_xstring_literal(*args); end
def on_xstring_new(*args); end
def on_yield(*args); end
def on_yield0(*args); end
def on_zsuper(*args); end
end
class Ripper::SexpBuilder
end
class Ripper::SexpBuilderPP
end
class Ripper::SexpBuilderPP
end
class Ripper::TokenPattern
def initialize(pattern); end
def match(str); end
def match_list(tokens); end
MAP = ::T.let(nil, ::T.untyped)
end
class Ripper::TokenPattern::CompileError
end
class Ripper::TokenPattern::CompileError
end
class Ripper::TokenPattern::Error
end
class Ripper::TokenPattern::Error
end
class Ripper::TokenPattern::MatchData
def initialize(tokens, match); end
def string(n=T.unsafe(nil)); end
end
class Ripper::TokenPattern::MatchData
end
class Ripper::TokenPattern::MatchError
end
class Ripper::TokenPattern::MatchError
end
class Ripper::TokenPattern
def self.compile(*arg); end
end
class Ripper
def self.dedent_string(arg, arg1); end
def self.lex_state_name(arg); end
def self.token_match(src, pattern); end
end
module RuboCop::AST::CollectionNode
def abbrev(*args, &block); end
@ -9688,6 +10233,14 @@ class Version
extend ::T::Private::Methods::SingletonMethodHooks
end
class WEBrick::HTTPRequest
def version_supplied(); end
def version_supplied=(version_supplied); end
def xhr?(); end
end
class WeakRef
def initialize(orig); end
end
@ -9899,6 +10452,76 @@ end
module Webrobots
end
module YARD::CodeObjects
extend ::YARD::CodeObjects::NamespaceMapper
end
module YARDSorbet::Directives
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class YARDSorbet::Handlers::AbstractDSLHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class YARDSorbet::Handlers::EnumsHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class YARDSorbet::Handlers::IncludeHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class YARDSorbet::Handlers::MixesInClassMethodsHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class YARDSorbet::Handlers::SigHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module YARDSorbet::Handlers::StructClassHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class YARDSorbet::Handlers::StructPropHandler
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module YARDSorbet::NodeUtils
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module YARDSorbet::SigToYARD
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module YARDSorbet::TagUtils
extend ::T::Sig
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class Zlib::Deflate
def initialize(*arg); end
end

View File

@ -99,5 +99,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-cobertura-1
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-runtime-stub-0.2.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thor-1.1.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/spoom-1.1.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tapioca-0.5.2/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/yard-0.9.26/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/yard-sorbet-0.6.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tapioca-0.5.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/warning-1.2.1/lib"

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
if defined?(Gem::VERSION) && Gem::VERSION >= "2.0."
require File.expand_path(File.dirname(__FILE__) + '/yard/rubygems/hook')
else
unless defined?(Gem::DocManager.load_yardoc)
require File.expand_path(File.dirname(__FILE__) + '/yard/rubygems/specification')
require File.expand_path(File.dirname(__FILE__) + '/yard/rubygems/doc_manager')
end
end

View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
module YARD
# The root path for YARD source libraries
ROOT = File.expand_path(File.dirname(__FILE__))
require File.join(YARD::ROOT, 'yard', 'version')
require File.join(YARD::ROOT, 'yard', 'autoload')
# The root path for YARD builtin templates
TEMPLATE_ROOT = File.join(ROOT, '..', 'templates')
# @deprecated Use {Config::CONFIG_DIR}
CONFIG_DIR = Config::CONFIG_DIR
# An alias to {Parser::SourceParser}'s parsing method
#
# @example Parse a glob of files
# YARD.parse('lib/**/*.rb')
# @see Parser::SourceParser.parse
def self.parse(*args) Parser::SourceParser.parse(*args) end
# An alias to {Parser::SourceParser}'s parsing method
#
# @example Parse a string of input
# YARD.parse_string('class Foo; end')
# @see Parser::SourceParser.parse_string
def self.parse_string(*args) Parser::SourceParser.parse_string(*args) end
# (see YARD::Config.load_plugins)
# @deprecated Use {Config.load_plugins}
def self.load_plugins; YARD::Config.load_plugins end
# @return [Boolean] whether YARD is being run inside of Windows
def self.windows?
return @windows if defined? @windows
require 'rbconfig'
@windows =
::RbConfig::CONFIG['host_os'] =~ /mingw|win32|cygwin/ ? true : false
ensure
@windows ||= false
end
# @return [Boolean] whether YARD is being run in Ruby 1.8 mode
def self.ruby18?; !ruby19? end
# @return [Boolean] whether YARD is being run in Ruby 1.9 mode
def self.ruby19?; @ruby19 ||= (RUBY_VERSION >= "1.9.1") end
# @return [Boolean] whether YARD is being run in Ruby 2.0
def self.ruby2?; @ruby2 ||= (RUBY_VERSION >= '2.0.0') end
end
# Keep track of Ruby version for compatibility code
# @deprecated Use {YARD.ruby18?} or {YARD.ruby19?} instead.
RUBY18 = YARD.ruby18?
RUBY19 = YARD.ruby19?
# Load Ruby core extension classes
Dir.glob(File.join(YARD::ROOT, 'yard', 'core_ext', '*.rb')).each do |file|
require file
end
# Backport RubyGems SourceIndex and other classes
require File.join(YARD::ROOT, 'yard', 'rubygems', 'backports')
require File.join(YARD::ROOT, 'yard', 'globals')
# Load YARD configuration options (and plugins)
YARD::Config.load

View File

@ -0,0 +1,308 @@
# frozen_string_literal: true
# @private
def __p(path) File.join(YARD::ROOT, 'yard', *path.split('/')); end
module YARD
module CLI # Namespace for command-line interface components
autoload :Command, __p('cli/command')
autoload :CommandParser, __p('cli/command_parser')
autoload :Config, __p('cli/config')
autoload :Diff, __p('cli/diff')
autoload :Display, __p('cli/display')
autoload :Gems, __p('cli/gems')
autoload :Graph, __p('cli/graph')
autoload :Help, __p('cli/help')
autoload :List, __p('cli/list')
autoload :MarkupTypes, __p('cli/markup_types')
autoload :Server, __p('cli/server')
autoload :Stats, __p('cli/stats')
autoload :Yardoc, __p('cli/yardoc')
autoload :YardoptsCommand, __p('cli/yardopts_command')
autoload :YRI, __p('cli/yri')
autoload :I18n, __p('cli/i18n')
end
# A "code object" is defined as any entity in the Ruby language.
# Classes, modules, methods, class variables and constants are the
# major objects, but DSL languages can create their own by inheriting
# from {CodeObjects::Base}.
module CodeObjects
autoload :Base, __p('code_objects/base')
autoload :CodeObjectList, __p('code_objects/base')
autoload :ClassObject, __p('code_objects/class_object')
autoload :ClassVariableObject, __p('code_objects/class_variable_object')
autoload :ConstantObject, __p('code_objects/constant_object')
autoload :ExtendedMethodObject, __p('code_objects/extended_method_object')
autoload :ExtraFileObject, __p('code_objects/extra_file_object')
autoload :MacroObject, __p('code_objects/macro_object')
autoload :MethodObject, __p('code_objects/method_object')
autoload :ModuleObject, __p('code_objects/module_object')
autoload :NamespaceMapper, __p('code_objects/namespace_mapper')
autoload :NamespaceObject, __p('code_objects/namespace_object')
autoload :Proxy, __p('code_objects/proxy')
autoload :ProxyMethodError, __p('code_objects/proxy')
autoload :RootObject, __p('code_objects/root_object')
autoload :BUILTIN_ALL, __p('code_objects/base')
autoload :BUILTIN_CLASSES, __p('code_objects/base')
autoload :BUILTIN_MODULES, __p('code_objects/base')
autoload :BUILTIN_EXCEPTIONS, __p('code_objects/base')
autoload :CONSTANTMATCH, __p('code_objects/base')
autoload :CONSTANTSTART, __p('code_objects/base')
autoload :METHODMATCH, __p('code_objects/base')
autoload :METHODNAMEMATCH, __p('code_objects/base')
autoload :NAMESPACEMATCH, __p('code_objects/base')
autoload :NSEP, __p('code_objects/base')
autoload :NSEPQ, __p('code_objects/base')
autoload :ISEP, __p('code_objects/base')
autoload :ISEPQ, __p('code_objects/base')
autoload :CSEP, __p('code_objects/base')
autoload :CSEPQ, __p('code_objects/base')
end
# Handlers are called during the data processing part of YARD's
# parsing phase. This allows YARD as well as any custom extension to
# analyze source and generate {CodeObjects} to be stored for later use.
module Handlers
# Shared logic between C and Ruby handlers.
module Common
autoload :MethodHandler, __p('handlers/common/method_handler')
end
# CRuby Handlers
# @since 0.8.0
module C
autoload :Base, __p('handlers/c/base')
autoload :AliasHandler, __p('handlers/c/alias_handler')
autoload :AttributeHandler, __p('handlers/c/attribute_handler')
autoload :ClassHandler, __p('handlers/c/class_handler')
autoload :ConstantHandler, __p('handlers/c/constant_handler')
autoload :HandlerMethods, __p('handlers/c/handler_methods')
autoload :InitHandler, __p('handlers/c/init_handler')
autoload :MethodHandler, __p('handlers/c/method_handler')
autoload :MixinHandler, __p('handlers/c/mixin_handler')
autoload :ModuleHandler, __p('handlers/c/module_handler')
autoload :OverrideCommentHandler, __p('handlers/c/override_comment_handler')
autoload :PathHandler, __p('handlers/c/path_handler')
autoload :StructHandler, __p('handlers/c/struct_handler')
autoload :SymbolHandler, __p('handlers/c/symbol_handler')
end
module Ruby # All Ruby handlers
module Legacy # Handlers for old Ruby 1.8 parser
autoload :Base, __p('handlers/ruby/legacy/base')
autoload :AliasHandler, __p('handlers/ruby/legacy/alias_handler')
autoload :AttributeHandler, __p('handlers/ruby/legacy/attribute_handler')
autoload :ClassHandler, __p('handlers/ruby/legacy/class_handler')
autoload :ClassConditionHandler, __p('handlers/ruby/legacy/class_condition_handler')
autoload :ClassVariableHandler, __p('handlers/ruby/legacy/class_variable_handler')
autoload :CommentHandler, __p('handlers/ruby/legacy/comment_handler')
autoload :ConstantHandler, __p('handlers/ruby/legacy/constant_handler')
autoload :DSLHandler, __p('handlers/ruby/legacy/dsl_handler')
autoload :ExceptionHandler, __p('handlers/ruby/legacy/exception_handler')
autoload :ExtendHandler, __p('handlers/ruby/legacy/extend_handler')
autoload :MethodHandler, __p('handlers/ruby/legacy/method_handler')
autoload :MixinHandler, __p('handlers/ruby/legacy/mixin_handler')
autoload :ModuleHandler, __p('handlers/ruby/legacy/module_handler')
autoload :ModuleFunctionHandler, __p('handlers/ruby/legacy/module_function_handler')
autoload :PrivateClassMethodHandler, __p('handlers/ruby/legacy/private_class_method_handler')
autoload :PrivateConstantHandler, __p('handlers/ruby/legacy/private_constant_handler')
autoload :VisibilityHandler, __p('handlers/ruby/legacy/visibility_handler')
autoload :YieldHandler, __p('handlers/ruby/legacy/yield_handler')
end
autoload :Base, __p('handlers/ruby/base')
autoload :AliasHandler, __p('handlers/ruby/alias_handler')
autoload :AttributeHandler, __p('handlers/ruby/attribute_handler')
autoload :ClassHandler, __p('handlers/ruby/class_handler')
autoload :ClassConditionHandler, __p('handlers/ruby/class_condition_handler')
autoload :ClassVariableHandler, __p('handlers/ruby/class_variable_handler')
autoload :CommentHandler, __p('handlers/ruby/comment_handler')
autoload :ConstantHandler, __p('handlers/ruby/constant_handler')
autoload :DecoratorHandlerMethods, __p('handlers/ruby/decorator_handler_methods')
autoload :DSLHandler, __p('handlers/ruby/dsl_handler')
autoload :DSLHandlerMethods, __p('handlers/ruby/dsl_handler_methods')
autoload :ExceptionHandler, __p('handlers/ruby/exception_handler')
autoload :ExtendHandler, __p('handlers/ruby/extend_handler')
autoload :MethodHandler, __p('handlers/ruby/method_handler')
autoload :MethodConditionHandler, __p('handlers/ruby/method_condition_handler')
autoload :MixinHandler, __p('handlers/ruby/mixin_handler')
autoload :ModuleHandler, __p('handlers/ruby/module_handler')
autoload :ModuleFunctionHandler, __p('handlers/ruby/module_function_handler')
autoload :PrivateClassMethodHandler, __p('handlers/ruby/private_class_method_handler')
autoload :PrivateConstantHandler, __p('handlers/ruby/private_constant_handler')
autoload :PublicClassMethodHandler, __p('handlers/ruby/public_class_method_handler')
autoload :StructHandlerMethods, __p('handlers/ruby/struct_handler_methods')
autoload :VisibilityHandler, __p('handlers/ruby/visibility_handler')
autoload :YieldHandler, __p('handlers/ruby/yield_handler')
end
autoload :Base, __p('handlers/base')
autoload :HandlerAborted, __p('handlers/base')
autoload :NamespaceMissingError, __p('handlers/base')
autoload :Processor, __p('handlers/processor')
end
# Namespace for internationalization (i18n)
# @since 0.8.0
module I18n
autoload :Locale, __p('i18n/locale')
autoload :Message, __p('i18n/message')
autoload :Messages, __p('i18n/messages')
autoload :PotGenerator, __p('i18n/pot_generator')
autoload :Text, __p('i18n/text')
end
# The parser namespace holds all parsing engines used by YARD.
# Currently only Ruby and C (Ruby) parsers are implemented.
module Parser
module C # CRuby Parsing components
autoload :BodyStatement, __p('parser/c/statement')
autoload :Comment, __p('parser/c/statement')
autoload :CommentParser, __p('parser/c/comment_parser')
autoload :CParser, __p('parser/c/c_parser')
autoload :Statement, __p('parser/c/statement')
autoload :ToplevelStatement, __p('parser/c/statement')
end
module Ruby # Ruby parsing components.
module Legacy # Handles Ruby parsing in Ruby 1.8.
autoload :RipperParser, __p('parser/ruby/legacy/ruby_parser')
autoload :RubyParser, __p('parser/ruby/legacy/ruby_parser')
autoload :RubyToken, __p('parser/ruby/legacy/ruby_lex')
autoload :Statement, __p('parser/ruby/legacy/statement')
autoload :StatementList, __p('parser/ruby/legacy/statement_list')
autoload :TokenList, __p('parser/ruby/legacy/token_list')
end
autoload :AstNode, __p('parser/ruby/ast_node')
autoload :RubyParser, __p('parser/ruby/ruby_parser')
autoload :TokenResolver, __p('parser/ruby/token_resolver')
end
autoload :Base, __p('parser/base')
autoload :ParserSyntaxError, __p('parser/source_parser')
autoload :SourceParser, __p('parser/source_parser')
autoload :UndocumentableError, __p('parser/source_parser')
end
module Rake # Holds Rake tasks used by YARD
autoload :YardocTask, __p('rake/yardoc_task')
end
module Serializers # Namespace for components that serialize to various endpoints
autoload :Base, __p('serializers/base')
autoload :FileSystemSerializer, __p('serializers/file_system_serializer')
autoload :ProcessSerializer, __p('serializers/process_serializer')
autoload :StdoutSerializer, __p('serializers/stdout_serializer')
autoload :YardocSerializer, __p('serializers/yardoc_serializer')
end
# Namespace for classes and modules that handle serving documentation over HTTP
#
# == Implementing a Custom Server
# To customize the YARD server, see the {Adapter} and {Router} classes.
#
# == Rack Middleware
# If you want to use the YARD server as a Rack middleware, see the documentation
# in {RackMiddleware}.
#
# @since 0.6.0
module Server
require __p('server')
# Commands implement specific kinds of server responses which are routed
# to by the {Router} class. To implement a custom command, subclass {Commands::Base}.
module Commands
autoload :Base, __p('server/commands/base')
autoload :DisplayFileCommand, __p('server/commands/display_file_command')
autoload :DisplayObjectCommand, __p('server/commands/display_object_command')
autoload :FramesCommand, __p('server/commands/frames_command')
autoload :ListCommand, __p('server/commands/list_command')
autoload :LibraryCommand, __p('server/commands/library_command')
autoload :LibraryIndexCommand, __p('server/commands/library_index_command')
autoload :RootRequestCommand, __p('server/commands/root_request_command')
autoload :SearchCommand, __p('server/commands/search_command')
autoload :StaticFileCommand, __p('server/commands/static_file_command')
autoload :StaticFileHelpers, __p('server/commands/static_file_helpers')
end
autoload :Adapter, __p('server/adapter')
autoload :DocServerSerializer, __p('server/doc_server_serializer')
autoload :DocServerHelper, __p('server/doc_server_helper')
autoload :FinishRequest, __p('server/adapter')
autoload :LibraryVersion, __p('server/library_version')
autoload :NotFoundError, __p('server/adapter')
autoload :RackAdapter, __p('server/rack_adapter')
autoload :RackMiddleware, __p('server/rack_adapter')
autoload :Router, __p('server/router')
autoload :StaticCaching, __p('server/static_caching')
autoload :WebrickAdapter, __p('server/webrick_adapter')
autoload :WebrickServlet, __p('server/webrick_adapter')
end
module Tags # Namespace for Tag components
autoload :AttributeDirective, __p('tags/directives')
autoload :DefaultFactory, __p('tags/default_factory')
autoload :DefaultTag, __p('tags/default_tag')
autoload :Directive, __p('tags/directives')
autoload :EndGroupDirective, __p('tags/directives')
autoload :GroupDirective, __p('tags/directives')
autoload :Library, __p('tags/library')
autoload :MacroDirective, __p('tags/directives')
autoload :MethodDirective, __p('tags/directives')
autoload :OptionTag, __p('tags/option_tag')
autoload :OverloadTag, __p('tags/overload_tag')
autoload :ParseDirective, __p('tags/directives')
autoload :RefTag, __p('tags/ref_tag')
autoload :RefTagList, __p('tags/ref_tag_list')
autoload :ScopeDirective, __p('tags/directives')
autoload :Tag, __p('tags/tag')
autoload :TagFormatError, __p('tags/tag_format_error')
autoload :TypesExplainer, __p('tags/types_explainer')
autoload :VisibilityDirective, __p('tags/directives')
end
# Namespace for templating system
module Templates
module Helpers # Namespace for template helpers
module Markup # Namespace for markup providers
autoload :RDocMarkup, __p('templates/helpers/markup/rdoc_markup')
autoload :RDocMarkdown, __p('templates/helpers/markup/rdoc_markdown')
end
autoload :BaseHelper, __p('templates/helpers/base_helper')
autoload :FilterHelper, __p('templates/helpers/filter_helper')
autoload :HtmlHelper, __p('templates/helpers/html_helper')
autoload :HtmlSyntaxHighlightHelper, __p('templates/helpers/html_syntax_highlight_helper')
autoload :MarkupHelper, __p('templates/helpers/markup_helper')
autoload :MethodHelper, __p('templates/helpers/method_helper')
autoload :ModuleHelper, __p('templates/helpers/module_helper')
autoload :TextHelper, __p('templates/helpers/text_helper')
autoload :UMLHelper, __p('templates/helpers/uml_helper')
end
autoload :Engine, __p('templates/engine')
autoload :ErbCache, __p('templates/erb_cache')
autoload :Section, __p('templates/section')
autoload :Template, __p('templates/template')
autoload :TemplateOptions, __p('templates/template_options')
end
autoload :Config, __p('config')
autoload :Docstring, __p('docstring')
autoload :DocstringParser, __p('docstring_parser')
autoload :GemIndex, __p('gem_index')
autoload :Logger, __p('logging')
autoload :Options, __p('options')
autoload :Registry, __p('registry')
autoload :RegistryResolver, __p('registry_resolver')
autoload :RegistryStore, __p('registry_store')
autoload :StubProxy, __p('serializers/yardoc_serializer')
autoload :Verifier, __p('verifier')
end
undef __p

View File

@ -0,0 +1,85 @@
# frozen_string_literal: true
require 'optparse'
module YARD
module CLI
# Abstract base class for CLI utilities. Provides some helper methods for
# the option parser
#
# @abstract
# @since 0.6.0
class Command
# Helper method to run the utility on an instance.
# @see #run
def self.run(*args) new.run(*args) end
def description; '' end
protected
# Adds a set of common options to the tail of the OptionParser
#
# @param [OptionParser] opts the option parser object
# @return [void]
def common_options(opts)
opts.separator ""
opts.separator "Other options:"
opts.on('-e', '--load FILE', 'A Ruby script to load before running command.') do |file|
load_script(file)
end
opts.on('--plugin PLUGIN', 'Load a YARD plugin (gem with `yard-\' prefix)') do |name|
# Not actually necessary to load here, this is done at boot in YARD::Config.load_plugins
# YARD::Config.load_plugin(name)
end
opts.on('--legacy', 'Use old style Ruby parser and handlers. ',
' Always on in 1.8.x.') do
YARD::Parser::SourceParser.parser_type = :ruby18
end
opts.on('--safe', 'Enable safe mode for this instance') do
# Parsed in YARD::Config.load
end
opts.on_tail('-q', '--quiet', 'Show no warnings.') { log.level = Logger::ERROR }
opts.on_tail('--verbose', 'Show more information.') { log.level = Logger::INFO }
opts.on_tail('--debug', 'Show debugging information.') { log.level = Logger::DEBUG }
opts.on_tail('--backtrace', 'Show stack traces') { log.show_backtraces = true }
opts.on_tail('-v', '--version', 'Show version.') { log.puts "yard #{YARD::VERSION}"; exit }
opts.on_tail('-h', '--help', 'Show this help.') { log.puts opts; exit }
end
# Parses the option and gracefully handles invalid switches
#
# @param [OptionParser] opts the option parser object
# @param [Array<String>] args the arguments passed from input. This
# array will be modified.
# @return [void]
def parse_options(opts, args)
opts.parse!(args)
rescue OptionParser::ParseError => err
unrecognized_option(err)
args.shift if args.first && args.first[0, 1] != '-'
retry
end
# Loads a Ruby script. If <tt>Config.options[:safe_mode]</tt> is enabled,
# this method will do nothing.
#
# @param [String] file the path to the script to load
# @since 0.6.2
def load_script(file)
return if YARD::Config.options[:safe_mode]
load(file)
rescue LoadError => load_exception
log.error "The file `#{file}' could not be loaded:\n#{load_exception}"
exit
end
# Callback when an unrecognize option is parsed
#
# @param [OptionParser::ParseError] err the exception raised by the
# option parser
def unrecognized_option(err)
log.warn "Unrecognized/#{err.message}"
end
end
end
end

View File

@ -0,0 +1,93 @@
# frozen_string_literal: true
module YARD
module CLI
# This class parses a command name out of the +yard+ CLI command and calls
# that command in the form:
#
# $ yard command_name [options]
#
# If no command or arguments are specified, or if the arguments immediately
# begin with a +--opt+ (not +--help+), the {default_command} will be used
# (which itself defaults to +:doc+).
#
# == Adding a Command
#
# To add a custom command via plugin, create a mapping in {commands} from
# the Symbolic command name to the {Command} class that implements the
# command. To implement a command, see the documentation for the {Command}
# class.
#
# @see Command
# @see commands
# @see default_command
class CommandParser
class << self
# @return [Hash{Symbol => Command}] the mapping of command names to
# command classes to parse the user command.
attr_accessor :commands
# @return [Symbol] the default command name to use when no options
# are specified or
attr_accessor :default_command
end
self.commands = SymbolHash[
:config => Config,
:diff => Diff,
:display => Display,
:doc => Yardoc,
:gems => Gems,
:graph => Graph,
:help => Help,
:list => List,
:markups => MarkupTypes,
:ri => YRI,
:server => Server,
:stats => Stats,
:i18n => I18n
]
self.default_command = :doc
# Convenience method to create a new CommandParser and call {#run}
# @return (see #run)
def self.run(*args) new.run(*args) end
def initialize
log.show_backtraces = false
end
# Runs the {Command} object matching the command name of the first
# argument.
# @return [void]
def run(*args)
unless args == ['--help']
if args.empty? || args.first =~ /^-/
command_name = self.class.default_command
else
command_name = args.first.to_sym
args.shift
end
if commands.key?(command_name)
return commands[command_name].run(*args)
end
end
list_commands
end
private
def commands; self.class.commands end
def list_commands
log.puts "Usage: yard <command> [options]"
log.puts
log.puts "Commands:"
commands.keys.sort_by(&:to_s).each do |command_name|
command = commands[command_name].new
log.puts "%-8s %s" % [command_name, command.description]
end
end
end
end
end

View File

@ -0,0 +1,198 @@
# frozen_string_literal: true
module YARD
module CLI
# CLI command to view or edit configuration options
# @since 0.6.2
class Config < Command
# @return [Symbol, nil] the key to view/edit, if any
attr_accessor :key
# @return [Array, nil] the list of values to set (or single value), if modifying
attr_accessor :values
# @return [Boolean] whether to reset the {#key}
attr_accessor :reset
# @return [Boolean] whether the value being set should be inside a list
attr_accessor :as_list
# @return [Boolean] whether to append values to existing key
attr_accessor :append
# @return [String, nil] command to use when configuring ~/.gemrc file.
# If the string is nil, configuration should not occur.
attr_accessor :gem_install_cmd
def initialize
super
self.key = nil
self.values = []
self.reset = false
self.append = false
self.as_list = false
self.gem_install_cmd = nil
end
def description
'Views or edits current global configuration'
end
def run(*args)
optparse(*args)
if gem_install_cmd
configure_gemrc
elsif key
if reset || !values.empty?
modify_item
else
view_item
end
else
list_configuration
end
end
private
def configure_gemrc
return unless gem_install_cmd
require 'rubygems'
['install', :install, 'gem', :gem].find do |cmd|
conf = Gem.configuration[cmd] || ""
next if conf.empty? && cmd != :gem
conf = conf.split(/\s+/)
conf.delete_if {|c| c =~ /^--(no-)?document\b/ } # scrub doc args
conf |= ["--document=#{gem_install_cmd}"]
conf = conf.join(' ')
Gem.configuration[cmd] = conf
Gem.configuration.write
log.puts "Updated #{Gem.configuration.path || '~/.gemrc'}: '#{cmd}: #{conf}'"
true
end
end
def modify_item
if reset
log.debug "Resetting #{key}"
YARD::Config.options[key] = YARD::Config::DEFAULT_CONFIG_OPTIONS[key]
else
log.debug "Setting #{key} to #{values.inspect}"
items = encode_values
current_items = YARD::Config.options[key]
items = [current_items].flatten + [items].flatten if append
YARD::Config.options[key] = items
end
YARD::Config.save
end
def view_item
log.debug "Viewing #{key}"
log.puts YARD::Config.options[key].inspect
end
def list_configuration
log.debug "Listing configuration"
require 'yaml'
log.puts YAML.dump(YARD::Config.options).sub(/\A--.*\n/, '').gsub(/\n\n/, "\n")
end
def encode_values
if values.size == 1 && !as_list
encode_value(values.first)
else
values.map {|v| encode_value(v) }
end
end
def encode_value(value)
case value
when /^-?\d+/; value.to_i
when "true"; true
when "false"; false
else value
end
end
def optparse(*args)
list = false
self.as_list = false
self.append = false
opts = OptionParser.new
opts.banner = "Usage: yard config [options] [item [value ...]]"
opts.separator ""
opts.separator "Example: yard config load_plugins true"
opts.separator ""
opts.separator "Views and sets configuration items. If an item is provided"
opts.separator "With no value, the item is viewed. If a value is provided,"
opts.separator "the item is modified. Specifying no item is equivalent to --list."
opts.separator "If you specify multiple space delimited values, these are"
opts.separator "parsed as an array of values."
opts.separator ""
opts.separator "Note that `true` and `false` are reserved words."
opts.separator ""
opts.separator "---------------------------------------------------------"
opts.separator ""
opts.separator "Configuring RubyGems support:"
opts.separator ""
opts.separator "YARD can automatically generate the YRI index or HTML"
opts.separator "documentation in a `gem install` by adding the following"
opts.separator "to your ~/.gemrc file:"
opts.separator ""
opts.separator " gem: \"--document=yri\""
opts.separator ""
opts.separator "Note: you can add 'yard' to also generate HTML docs."
opts.separator " You can also add 'ri' to continue generating RDoc."
opts.separator ""
opts.separator "You can also run the following command to configure this"
opts.separator "behavior automatically:"
opts.separator ""
opts.separator " $ yard config --gem-install-yri"
opts.separator ""
opts.separator "Add --gem-install-yard to also generate HTML."
opts.separator ""
opts.separator "---------------------------------------------------------"
opts.separator ""
opts.separator "General options:"
opts.on('-l', '--list', 'List current configuration') do
list = true
end
opts.on('-r', '--reset', 'Resets the specific item to default') do
self.reset = true
end
opts.separator ""
opts.separator "Modifying keys:"
opts.on('-a', '--append', 'Appends items to existing key values') do
self.append = true
end
opts.on('--as-list', 'Forces the value(s) to be wrapped in an array') do
self.as_list = true
end
opts.separator ""
opts.separator "Add RubyGems install hook:"
opts.on('--gem-install-yri', 'Configures ~/.gemrc to run yri on a gem install') do
self.gem_install_cmd = 'yri' if gem_install_cmd != 'yard'
end
opts.on('--gem-install-yard', 'Configures ~/.gemrc to run yard on a gem install') do
self.gem_install_cmd = 'yard'
end
common_options(opts)
parse_options(opts, args)
args = [] if list
self.key = args.shift.to_sym if args.size >= 1
self.values = args if args.size >= 1
args
end
end
end
end

View File

@ -0,0 +1,273 @@
# frozen_string_literal: true
require 'tmpdir'
require 'fileutils'
require 'open-uri'
module YARD
module CLI
# CLI command to return the objects that were added/removed from 2 versions
# of a project (library, gem, working copy).
# @since 0.6.0
class Diff < Command
def initialize
super
@list_all = false
@use_git = false
@compact = false
@modified = true
@verifier = Verifier.new
@old_git_commit = nil
@old_path = Dir.pwd
log.show_backtraces = true
end
def description
'Returns the object diff of two gems or .yardoc files'
end
def run(*args)
registry = optparse(*args).map do |gemfile|
if @use_git
load_git_commit(gemfile)
all_objects
elsif load_gem_data(gemfile)
log.info "Found #{gemfile}"
all_objects
else
log.error "Cannot find gem #{gemfile}"
nil
end
end.compact
return if registry.size != 2
first_object = nil
[["Added objects", "A", added_objects(*registry)],
["Modified objects", "M", modified_objects(*registry)],
["Removed objects", "D", removed_objects(*registry)]].each do |name, short, objects|
next if short == "M" && @modified == false
next if objects.empty?
last_object = nil
all_objects_notice = false
log.puts name + ":" unless @compact
objects.sort_by(&:path).each do |object|
if !@list_all && last_object && object.parent == last_object
log.print " (...)" unless all_objects_notice
all_objects_notice = true
next
elsif @compact
log.puts if first_object
else
log.puts
end
all_objects_notice = false
log.print "" + (@compact ? "#{short} " : " ") +
object.path + " (#{object.file}:#{object.line})"
last_object = object
first_object = true
end
unless @compact
log.puts; log.puts
end
end
log.puts if @compact
end
private
def all_objects
return Registry.all if @verifier.expressions.empty?
@verifier.run(Registry.all)
end
def added_objects(registry1, registry2)
registry2.reject {|o| registry1.find {|o2| o2.path == o.path } }
end
def modified_objects(registry1, registry2)
registry1.select do |obj|
case obj
when CodeObjects::MethodObject
registry2.find {|o| obj == o && o.source != obj.source }
when CodeObjects::ConstantObject
registry2.find {|o| obj == o && o.value != obj.value }
end
end.compact
end
def removed_objects(registry1, registry2)
registry1.reject {|o| registry2.find {|o2| o2.path == o.path } }
end
def load_git_commit(commit)
Registry.clear
commit_path = 'git_commit' + commit.gsub(/\W/, '_')
tmpdir = File.join(Dir.tmpdir, commit_path)
log.info "Expanding #{commit} to #{tmpdir}..."
Dir.chdir(@old_path)
FileUtils.mkdir_p(tmpdir)
FileUtils.cp_r('.', tmpdir)
Dir.chdir(tmpdir)
log.info("git says: " + `git reset --hard #{commit}`.chomp)
generate_yardoc(tmpdir)
ensure
Dir.chdir(@old_path)
cleanup(commit_path)
end
def load_gem_data(gemfile)
require_rubygems
Registry.clear
# First check for argument as .yardoc file
[File.join(gemfile, '.yardoc'), gemfile].each do |yardoc|
log.info "Searching for .yardoc db at #{yardoc}"
next unless File.directory?(yardoc)
Registry.load_yardoc(yardoc)
Registry.load_all
return true
end
# Next check installed RubyGems
gemfile_without_ext = gemfile.sub(/\.gem$/, '')
log.info "Searching for installed gem #{gemfile_without_ext}"
YARD::GemIndex.each.find do |spec|
next unless spec.full_name == gemfile_without_ext
yardoc = Registry.yardoc_file_for_gem(spec.name, "= #{spec.version}")
if yardoc
Registry.load_yardoc(yardoc)
Registry.load_all
else
log.enter_level(Logger::ERROR) do
olddir = Dir.pwd
Gems.run(spec.name, spec.version.to_s)
Dir.chdir(olddir)
end
end
return true
end
# Look for local .gem file
gemfile += '.gem' unless gemfile =~ /\.gem$/
log.info "Searching for local gem file #{gemfile}"
if File.exist?(gemfile)
File.open(gemfile, 'rb') do |io|
expand_and_parse(gemfile, io)
end
return true
end
# Remote gemfile from rubygems.org
url = "http://rubygems.org/downloads/#{gemfile}"
log.info "Searching for remote gem file #{url}"
begin
# Note: In Ruby 2.4.x, URI.open is a private method. After
# 2.5, URI.open behaves much like Kernel#open once you've
# required 'open-uri'
OpenURI.open_uri(url) {|io| expand_and_parse(gemfile, io) }
return true
rescue OpenURI::HTTPError
nil # noop
end
false
end
def expand_and_parse(gemfile, io)
dir = expand_gem(gemfile, io)
generate_yardoc(dir)
cleanup(gemfile)
end
def generate_yardoc(dir)
Dir.chdir(dir) do
log.enter_level(Logger::ERROR) { Yardoc.run('-n', '--no-save') }
end
end
def expand_gem(gemfile, io)
tmpdir = File.join(Dir.tmpdir, gemfile)
FileUtils.mkdir_p(tmpdir)
log.info "Expanding #{gemfile} to #{tmpdir}..."
if Gem::VERSION >= '2.0.0'
require 'rubygems/package/tar_reader'
reader = Gem::Package::TarReader.new(io)
reader.each do |pkg|
next unless pkg.full_name == 'data.tar.gz'
Zlib::GzipReader.wrap(pkg) do |gzio|
tar = Gem::Package::TarReader.new(gzio)
tar.each do |entry|
file = File.join(tmpdir, entry.full_name)
FileUtils.mkdir_p(File.dirname(file))
File.open(file, 'wb') do |out|
out.write(entry.read)
begin
out.fsync
rescue NotImplementedError
nil # noop
end
end
end
end
break
end
else
Gem::Package.open(io) do |pkg|
pkg.each do |entry|
pkg.extract_entry(tmpdir, entry)
end
end
end
tmpdir
end
def require_rubygems
require 'rubygems'
require 'rubygems/package'
rescue LoadError => e
log.error "Missing RubyGems, cannot run this command."
raise(e)
end
def cleanup(gemfile)
dir = File.join(Dir.tmpdir, gemfile)
log.info "Cleaning up #{dir}..."
FileUtils.rm_rf(dir)
end
def optparse(*args)
opts = OptionParser.new
opts.banner = "Usage: yard diff [options] oldgem newgem"
opts.separator ""
opts.separator "Example: yard diff yard-0.5.6 yard-0.5.8"
opts.separator ""
opts.separator "If the files don't exist locally, they will be grabbed using the `gem fetch`"
opts.separator "command. If the gem is a .yardoc directory, it will be used. Finally, if the"
opts.separator "gem name matches an installed gem (full name-version syntax), that gem will be used."
opts.on('-a', '--all', 'List all objects, even if they are inside added/removed module/class') do
@list_all = true
end
opts.on('--compact', 'Show compact results') { @compact = true }
opts.on('--git', 'Compare versions from two git commit/branches') do
@use_git = true
end
opts.on('--query QUERY', 'Only diff filtered objects') do |query|
@verifier.add_expressions(query)
end
opts.on('--no-modified', 'Ignore modified objects') do
@modified = false
end
common_options(opts)
parse_options(opts, args)
unless args.size == 2
log.puts opts.banner
exit(0)
end
args
end
end
end
end

View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
module YARD
module CLI
# Display one object
# @since 0.8.6
class Display < Yardoc
def description; 'Displays a formatted object' end
def initialize(*args)
super
options.format = :text # default for this command
@layout = nil
@objects = []
end
# Runs the commandline utility, parsing arguments and displaying an object
# from the {Registry}.
#
# @param [Array<String>] args the list of arguments.
# @return [void]
def run(*args)
return unless parse_arguments(*args)
log.puts wrap_layout(format_objects)
end
# @return [String] the output data for all formatted objects
def format_objects
@objects.inject([]) do |arr, obj|
arr.push obj.format(options)
end.join("\n")
end
def wrap_layout(contents)
return contents unless @layout
opts = options.merge(
:contents => contents,
:object => @objects.first,
:objects => @objects
)
args = [options.template, @layout, options.format]
Templates::Engine.template(*args).run(opts)
end
# Parses commandline options.
# @param [Array<String>] args each tokenized argument
def parse_arguments(*args)
opts = OptionParser.new
opts.banner = "Usage: yard display [options] OBJECT [OTHER OBJECTS]"
general_options(opts)
output_options(opts)
parse_options(opts, args)
Registry.load
@objects = args.map {|o| Registry.at(o) }
# validation
return false if @objects.any?(&:nil?)
verify_markup_options
end
def output_options(opts)
super(opts)
opts.on('-l', '--layout [LAYOUT]', 'Wraps output in layout template (good for HTML)') do |layout|
@layout = layout || 'layout'
end
end
end
end
end

View File

@ -0,0 +1,84 @@
# frozen_string_literal: true
module YARD
module CLI
# @since 0.6.0
class Gems < Command
def initialize
@rebuild = false
@gems = []
end
def description; "Builds YARD index for gems" end
# Runs the commandline utility, parsing arguments and generating
# YARD indexes for gems.
#
# @param [Array<String>] args the list of arguments
# @return [void]
def run(*args)
require 'rubygems'
optparse(*args)
build_gems
end
private
# Builds .yardoc files for all non-existing gems
def build_gems
require 'rubygems'
@gems.each do |spec|
ver = "= #{spec.version}"
dir = Registry.yardoc_file_for_gem(spec.name, ver)
if dir && File.directory?(dir) && !@rebuild
log.debug "#{spec.name} index already exists at '#{dir}'"
else
yfile = Registry.yardoc_file_for_gem(spec.name, ver, true)
next unless yfile
next unless File.directory?(spec.full_gem_path)
Registry.clear
Dir.chdir(spec.full_gem_path) do
log.info "Building yardoc index for gem: #{spec.full_name}"
Yardoc.run('--no-stats', '-n', '-b', yfile)
end
end
end
end
def add_gems(gems)
0.step(gems.size - 1, 2) do |index|
gem = gems[index]
ver_require = gems[index + 1] || ">= 0"
specs = YARD::GemIndex.find_all_by_name(gem, ver_require)
if specs.empty?
log.warn "#{gem} #{ver_require} could not be found in RubyGems index"
else
@gems += specs
end
end
end
# Parses options
def optparse(*args)
opts = OptionParser.new
opts.banner = 'Usage: yard gems [options] [gem_name [version]]'
opts.separator ""
opts.separator "#{description}. If no gem_name is given,"
opts.separator "all gems are built."
opts.separator ""
opts.on('--rebuild', 'Rebuilds index') do
@rebuild = true
end
common_options(opts)
parse_options(opts, args)
add_gems(args)
if !args.empty? && @gems.empty?
log.error "No specified gems could be found for command"
elsif @gems.empty?
@gems += YARD::GemIndex.all if @gems.empty?
end
end
end
end
end

View File

@ -0,0 +1,125 @@
# frozen_string_literal: true
module YARD
module CLI
# Options to pass to the {Graph} CLI.
class GraphOptions < Templates::TemplateOptions
# @return [:dot] the default output format
default_attr :format, :dot
# @return [Boolean] whether to list the full class diagram
attr_accessor :full
# @return [Boolean] whether to show the object dependencies
attr_accessor :dependencies
# @return [String] any contents to pass to the digraph
attr_accessor :contents
end
# A command-line utility to generate Graphviz graphs from
# a set of objects
#
# @see Graph#run
# @since 0.6.0
class Graph < YardoptsCommand
# The options parsed out of the commandline.
# Default options are:
# :format => :dot
attr_reader :options
# The set of objects to include in the graph.
attr_reader :objects
# Creates a new instance of the command-line utility
def initialize
super
@use_document_file = false
@options = GraphOptions.new
options.reset_defaults
options.serializer = YARD::Serializers::StdoutSerializer.new
end
def description
"Graphs class diagram using Graphviz"
end
# Runs the command-line utility.
#
# @example
# grapher = Graph.new
# grapher.run('--private')
# @param [Array<String>] args each tokenized argument
def run(*args)
parse_arguments(*args)
contents = objects.map do |o|
o.format(options.merge(:serialize => false))
end.join("\n")
opts = {:type => :layout, :contents => contents}
options.update(opts)
Templates::Engine.render(options)
end
private
def unrecognized_option(err) end
# Parses commandline options.
# @param [Array<String>] args each tokenized argument
def optparse(*args)
visibilities = [:public]
opts = OptionParser.new
opts.separator ""
opts.separator "General Options:"
opts.on('-b', '--db FILE', 'Use a specified .yardoc db to load from or save to. (defaults to .yardoc)') do |yfile|
YARD::Registry.yardoc_file = yfile
end
opts.on('--full', 'Full class diagrams (show methods and attributes).') do
options[:full] = true
end
opts.on('-d', '--dependencies', 'Show mixins in dependency graph.') do
options[:dependencies] = true
end
opts.on('--no-public', "Don't show public methods. (default shows public)") do
visibilities.delete(:public)
end
opts.on('--protected', "Show or don't show protected methods. (default hides protected)") do
visibilities.push(:protected)
end
opts.on('--private', "Show or don't show private methods. (default hides private)") do
visibilities.push(:private)
end
opts.separator ""
opts.separator "Output options:"
opts.on('--dot [OPTIONS]', 'Send the results directly to `dot` with optional arguments.') do |dotopts|
options.serializer = Serializers::ProcessSerializer.new('dot ' + dotopts.to_s)
end
opts.on('-f', '--file [FILE]', 'Writes output to a file instead of stdout.') do |file|
options.serializer = Serializers::FileSystemSerializer.new(:basepath => '.', :extension => nil)
options.serializer.instance_eval "def serialized_path(object) #{file.inspect} end"
end
common_options(opts)
parse_options(opts, args)
Registry.load
expression = "#{visibilities.uniq.inspect}.include?(object.visibility)"
options.verifier = Verifier.new(expression)
@objects = args.first ?
args.map {|o| Registry.at(o) }.compact :
[Registry.root]
end
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module YARD
module CLI
# Handles help for commands
# @since 0.6.0
class Help < Command
def description; "Retrieves help for a command" end
def run(*args)
cmd = args.first && CommandParser.commands[args.first.to_sym]
if cmd
cmd.run('--help')
else
log.puts "Command #{args.first} not found." if args.first
CommandParser.run('--help')
end
end
end
end
end

View File

@ -0,0 +1,70 @@
# frozen_string_literal: true
require "pathname"
module YARD
module CLI
# CLI command to support internationalization (a.k.a. i18n).
# I18n feature is based on gettext technology.
# This command generates .pot file from docstring and extra
# documentation.
#
# @since 0.8.0
# @todo Support msgminit and msgmerge features?
class I18n < Yardoc
def initialize
super
@options.serializer.basepath = "po/yard.pot"
end
def description
'Generates .pot file from source code and extra documentation'
end
def run(*args)
if args.empty? || !args.first.nil?
# fail early if arguments are not valid
return unless parse_arguments(*args)
end
YARD.parse(files, excluded)
serializer = options.serializer
pot_file_path = Pathname.new(serializer.basepath).expand_path
pot_file_dir_path, pot_file_basename = pot_file_path.split
relative_base_path = Pathname.pwd.relative_path_from(pot_file_dir_path)
serializer.basepath = pot_file_dir_path.to_s
serializer.serialize(pot_file_basename.to_s,
generate_pot(relative_base_path.to_s))
true
end
private
def general_options(opts)
opts.banner = "Usage: yard i18n [options] [source_files [- extra_files]]"
opts.top.list.clear
opts.separator "(if a list of source files is omitted, "
opts.separator " {lib,app}/**/*.rb ext/**/*.{c,rb} is used.)"
opts.separator ""
opts.separator "Example: yard i18n -o yard.pot - FAQ LICENSE"
opts.separator " The above example outputs .pot file for files in"
opts.separator " lib/**/*.rb to yard.pot including the extra files"
opts.separator " FAQ and LICENSE."
opts.separator ""
opts.separator "A base set of options can be specified by adding a .yardopts"
opts.separator "file to your base path containing all extra options separated"
opts.separator "by whitespace."
super(opts)
end
def generate_pot(relative_base_path)
generator = YARD::I18n::PotGenerator.new(relative_base_path)
objects = run_verifier(all_objects)
generator.parse_objects(objects)
generator.parse_files(options.files || [])
generator.generate
end
end
end
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module YARD
module CLI
# Lists all constant and method names in the codebase. Uses {Yardoc} --list.
class List < Command
def description; 'Lists all constant and methods. Uses `yard doc --list`' end
# Runs the commandline utility, parsing arguments and displaying a
# list of objects
#
# @param [Array<String>] args the list of arguments.
# @return [void]
def run(*args)
if args.include?('--help')
log.puts "Usage: yard list [yardoc_options]"
log.puts "Takes the same arguments as yardoc. See yardoc --help"
else
Yardoc.run('-c', '--list', *args)
end
end
end
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module YARD
module CLI
# Lists all markup types
# @since 0.8.6
class MarkupTypes < Command
def description; 'Lists all available markup types and libraries' end
# Runs the commandline utility, parsing arguments and displaying a
# list of markup types
#
# @param [Array<String>] args the list of arguments.
# @return [void]
def run(*args) # rubocop:disable Lint/UnusedMethodArgument
log.puts "Available markup types for `doc' command:"
log.puts
types = Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS
exts = Templates::Helpers::MarkupHelper::MARKUP_EXTENSIONS
types.sort_by {|name, _| name.to_s }.each do |name, providers|
log.puts "[#{name}]"
libs = providers.map {|p| p[:lib] }.compact
log.puts " Providers: #{libs.join(" ")}" unless libs.empty?
if exts[name]
log.puts " Extensions: #{exts[name].map {|e| ".#{e}" }.join(" ")}"
end
log.puts
end
end
end
end
end

View File

@ -0,0 +1,266 @@
# frozen_string_literal: true
module YARD
module CLI
# A local documentation server
# @since 0.6.0
class Server < Command
# @return [Hash] a list of options to pass to the doc server
attr_accessor :options
# @return [Hash] a list of options to pass to the web server
attr_accessor :server_options
# @return [Hash] a list of library names and yardoc files to serve
attr_accessor :libraries
# @return [YARD::Server::Adapter] the adapter to use for loading the web server
attr_accessor :adapter
# @return [Array<String>] a list of scripts to load
# @since 0.6.2
attr_accessor :scripts
# @return [Array<String>] a list of template paths to register
# @since 0.6.2
attr_accessor :template_paths
# Creates a new instance of the Server command line utility
def initialize
super
self.scripts = []
self.template_paths = []
self.libraries = {}
self.options = SymbolHash.new(false).update(
:single_library => true,
:caching => false
)
self.server_options = {:Port => 8808}
end
def description
"Runs a local documentation server"
end
def run(*args)
optparse(*args)
select_adapter.setup
load_scripts
load_template_paths
adapter.new(libraries, options, server_options).start
end
private
def load_scripts
scripts.each {|file| load_script(file) }
end
def load_template_paths
return if YARD::Config.options[:safe_mode]
Templates::Engine.template_paths |= template_paths
end
def select_adapter
return adapter if adapter
require 'rubygems'
require 'rack'
self.adapter = YARD::Server::RackAdapter
rescue LoadError
self.adapter = YARD::Server::WebrickAdapter
end
def add_libraries(args)
(0...args.size).step(2) do |index|
library = args[index]
dir = args[index + 1]
libver = nil
if dir
if File.exist?(dir)
# Provided dir contains a .yardopts file
libver = create_library_version_if_yardopts_exist(library, dir)
libver ||= YARD::Server::LibraryVersion.new(library, nil, dir)
end
else
# Check if this dir contains a .yardopts file
pwd = Dir.pwd
libver = create_library_version_if_yardopts_exist(library, pwd)
# Check default location
yfile = File.join(pwd, Registry::DEFAULT_YARDOC_FILE)
libver ||= YARD::Server::LibraryVersion.new(library, nil, yfile)
end
# Register library
if libver
libver.yardoc_file = File.expand_path(libver.yardoc_file) if libver.yardoc_file
libver.source_path = File.expand_path(libver.source_path) if libver.source_path
libraries[library] ||= []
libraries[library] |= [libver]
else
log.warn "Cannot find yardoc db for #{library}: #{dir.inspect}"
end
end
end
# @param [String] library The library name.
# @param [String, nil] dir The argument provided on the CLI after the
# library name. Is supposed to point to either a project directory
# with a Yard options file, or a yardoc db.
# @return [LibraryVersion, nil]
def create_library_version_if_yardopts_exist(library, dir)
if dir
options_file = File.join(dir, Yardoc::DEFAULT_YARDOPTS_FILE)
if File.exist?(options_file)
# Found yardopts, extract db path
yfile = extract_db_from_options_file(options_file)
db = File.expand_path(yfile, dir)
# Create libver
libver = YARD::Server::LibraryVersion.new(library, nil, db)
libver.source_path = dir
libver
end
end
end
def add_gems
require 'rubygems'
YARD::GemIndex.each do |spec|
libraries[spec.name] ||= []
libraries[spec.name] |= [YARD::Server::LibraryVersion.new(spec.name, spec.version.to_s,
nil, :gem)]
end
end
def add_gems_from_gemfile(gemfile = nil)
require 'bundler'
gemfile ||= "Gemfile"
if File.exist?("#{gemfile}.lock")
Bundler::LockfileParser.new(File.read("#{gemfile}.lock")).specs.each do |spec|
libraries[spec.name] ||= []
libraries[spec.name] |= [YARD::Server::LibraryVersion.new(spec.name,
spec.version.to_s, nil, :gem)]
end
else
log.warn "Cannot find #{gemfile}.lock, ignoring --gemfile option"
end
rescue LoadError
log.error "Bundler not available, ignoring --gemfile option"
end
def optparse(*args)
opts = OptionParser.new
opts.banner = 'Usage: yard server [options] [[library yardoc_file] ...]'
opts.separator ''
opts.separator 'Example: yard server -m yard .yardoc ruby-core ../ruby/.yardoc'
opts.separator 'The above example serves documentation for YARD and Ruby-core'
opts.separator ''
opts.separator 'If no library/yardoc_file is specified, the server uses'
opts.separator 'the name of the current directory and `.yardoc` respectively'
opts.separator ''
opts.separator "General Options:"
opts.on('-m', '--multi-library', 'Serves documentation for multiple libraries') do
options[:single_library] = false
end
opts.on('-c', '--cache', 'Caches all documentation to document root (see --docroot)') do
options[:caching] = true
end
opts.on('-r', '--reload', 'Reparses the library code on each request') do
options[:incremental] = true
end
opts.on('-g', '--gems', 'Serves documentation for installed gems') do
add_gems
end
opts.on('-G', '--gemfile [GEMFILE]',
'Serves documentation for gems from Gemfile') do |gemfile|
add_gems_from_gemfile(gemfile)
end
opts.on('-t', '--template-path PATH',
'The template path to look for templates in. (used with -t).') do |path|
template_paths << path
end
opts.separator ''
opts.separator "Web Server Options:"
opts.on('-d', '--daemon', 'Daemonizes the server process') do
server_options[:daemonize] = true
end
opts.on('-B HOST', '--bind', 'The host address to bind to') do |host|
server_options[:Host] = host.to_s
end
opts.on('-p PORT', '--port', 'Serves documentation on PORT') do |port|
server_options[:Port] = port.to_i
end
opts.on('--docroot DOCROOT', 'Uses DOCROOT as document root') do |docroot|
server_options[:DocumentRoot] = File.expand_path(docroot)
end
opts.on('-a', '--adapter ADAPTER',
'Use the ADAPTER (full Ruby class) for web server') do |adapter|
self.adapter = if adapter.casecmp('webrick') == 0
YARD::Server::WebrickAdapter
elsif adapter.casecmp('rack') == 0
YARD::Server::RackAdapter
else
eval(adapter) # rubocop:disable Security/Eval
end
end
opts.on('-s', '--server TYPE',
'Use a specific server type eg. thin,mongrel,cgi (Rack specific)') do |type|
server_options[:server] = type
end
opts.on('--fork', 'Use process forking when serving requests') do
options[:use_fork] = true
end
common_options(opts)
opts.on('-e', '--load FILE',
'A Ruby script to load before the source tree is parsed.') do |file|
scripts << file
end
parse_options(opts, args)
if args.empty? && libraries.empty?
# No args - try to use current dir
add_libraries([File.basename(Dir.pwd), nil])
# Generate doc for first time
# This is not necessary but makes for a better first-run experience
libver = libraries.empty? ? nil : libraries.values.first.first
generate_doc_for_first_time(libver) if libver && !libver.ready?
else
add_libraries(args)
options[:single_library] = false if libraries.size > 1
end
end
def generate_doc_for_first_time(libver)
log.enter_level(Logger::INFO) do
yardoc_file = libver.yardoc_file.sub(%r{^#{Regexp.quote Dir.pwd}[\\/]+}, '')
log.info "No yardoc db found in #{yardoc_file}, parsing source before starting server..."
end
Dir.chdir(libver.source_path) do
Yardoc.run('-n')
end
end
def extract_db_from_options_file(options_file)
args = File.read_binary(options_file).shell_split
db = YARD::Registry.yardoc_file
opts = OptionParser.new
opts.on('-b', '--db FILE') {|file| db = file }
begin
opts.parse!(args)
rescue OptionParser::ParseError
args.shift if args.first && args.first[0, 1] != '-'
retry
end
db
end
end
end
end

View File

@ -0,0 +1,231 @@
# frozen_string_literal: true
module YARD
module CLI
# @since 0.6.0
class Stats < Yardoc
include Templates::Helpers::BaseHelper
# Maintains the order in which +stats_for_+ statistics methods should be
# printed.
#
# @see #print_statistics
STATS_ORDER = [:files, :modules, :classes, :constants, :attributes, :methods]
# @return [Boolean] whether to parse and load registry
attr_accessor :parse
# @param [Boolean] parse whether to parse and load registry (see {#parse})
def initialize(parse = true)
super()
@parse = parse
@undoc_list = nil
@compact = false
end
def description
"Prints documentation statistics on a set of files"
end
# Runs the commandline utility, parsing arguments and generating
# output if set.
#
# @param [Array<String>] args the list of arguments
# @return [void]
def run(*args)
parse_arguments(*args)
if use_cache
Registry.load!
elsif parse
YARD.parse(files, excluded)
Registry.save(use_cache) if save_yardoc
end
print_statistics
print_undocumented_objects
end
# Prints statistics for different object types
#
# To add statistics for a specific type, add a method +#stats_for_TYPE+
# to this class that calls {#output}.
def print_statistics
@total = 0
@undocumented = 0
meths = methods.map(&:to_s).grep(/^stats_for_/)
STATS_ORDER.each do |meth|
mname = "stats_for_#{meth}"
if meths.include?(mname)
send(mname)
meths.delete(mname)
end
end
meths.each {|m| send(m) }
total =
if @undocumented == 0
100
elsif @total == 0
0
else
(@total - @undocumented).to_f / @total.to_f * 100
end
log.puts("% 3.2f%% documented" % total)
end
# Prints list of undocumented objects
def print_undocumented_objects
return if !@undoc_list || @undoc_list.empty?
log.puts
log.puts "Undocumented Objects:"
# array needed for sort due to unstable sort
objects = @undoc_list.sort_by {|o| [o.file.to_s, o.path] }
max = objects.max {|a, b| a.path.length <=> b.path.length }.path.length
if @compact
objects.each do |object|
log.puts("%-#{max}s (%s)" % [object.path,
[object.file || "-unknown-", object.line].compact.join(":")])
end
else
last_file = nil
objects.each do |object|
if object.file != last_file
log.puts
log.puts "(in file: #{object.file || "-unknown-"})"
end
log.puts object.path
last_file = object.file
end
end
end
# @return [Array<CodeObjects::Base>] all the parsed objects in the registry,
# removing any objects that are not visible (private, protected) depending
# on the arguments passed to the command.
def all_objects
@all_objects ||= run_verifier Registry.all
end
# Statistics for files
def stats_for_files
files = []
all_objects.each {|o| files |= [o.file] }
output "Files", files.size
end
# Statistics for modules
def stats_for_modules
output "Modules", *type_statistics(:module)
end
# Statistics for classes
def stats_for_classes
output "Classes", *type_statistics(:class)
end
# Statistics for constants
def stats_for_constants
output "Constants", *type_statistics(:constant)
end
# Statistics for attributes
def stats_for_attributes
objs = all_objects.select {|m| m.type == :method && m.is_attribute? }
objs.uniq! {|m| m.name.to_s.gsub(/=$/, '') }
undoc = objs.select {|m| m.docstring.blank? }
@undoc_list |= undoc if @undoc_list
output "Attributes", objs.size, undoc.size
end
# Statistics for methods
def stats_for_methods
objs = all_objects.select {|m| m.type == :method }
objs.reject!(&:is_alias?)
objs.reject!(&:is_attribute?)
undoc = objs.select {|m| m.docstring.blank? }
@undoc_list |= undoc if @undoc_list
output "Methods", objs.size, undoc.size
end
# Prints a statistic to standard out. This method is optimized for
# getting Integer values, though it allows any data to be printed.
#
# @param [String] name the statistic name
# @param [Integer, String] data the numeric (or any) data representing
# the statistic. If +data+ is an Integer, it should represent the
# total objects of a type.
# @param [Integer, nil] undoc number of undocumented objects for the type
# @return [void]
def output(name, data, undoc = nil)
@total += data if data.is_a?(Integer) && undoc
@undocumented += undoc if undoc.is_a?(Integer)
data =
if undoc
("%5s (% 5d undocumented)" % [data, undoc])
else
"%5s" % data
end
log.puts("%-12s %s" % [name + ":", data])
end
private
def type_statistics(type)
objs = all_objects.select {|m| m.type == type }
undoc = objs.find_all {|m| m.docstring.blank? }
@undoc_list |= undoc if @undoc_list
[objs.size, undoc.size]
end
# Parses commandline options.
# @param [Array<String>] args each tokenized argument
def optparse(*args)
opts = OptionParser.new
opts.banner = "Usage: yard stats [options] [source_files]"
opts.separator "(if a list of source files is omitted, lib/**/*.rb ext/**/*.{c,rb} is used.)"
general_options(opts)
output_options(opts)
tag_options(opts)
common_options(opts)
parse_options(opts, args)
parse_files(*args) unless args.empty?
end
def general_options(opts)
super(opts)
opts.on('--list-undoc', 'List all undocumented objects') do
@undoc_list = []
end
opts.on('--compact', 'Compact undocumented objects listing') do
@compact = true
end
opts.on('--no-public', "Don't include public methods in statistics.") do
visibilities.delete(:public)
end
opts.on('--protected', "Include protected methods in statistics.") do
visibilities.push(:protected)
end
opts.on('--private', "Include private methods in statistics.") do
visibilities.push(:private)
end
opts.on('--no-private', "Don't include objects with @private tag in statistics.") do
options[:verifier].add_expressions '!object.tag(:private) &&
(object.namespace.type == :proxy || !object.namespace.tag(:private))'
end
opts.on('--query QUERY', "Only includes objects that match a specific query") do |query|
options[:verifier].add_expressions(query.taint)
end
end
end
end
end

View File

@ -0,0 +1,789 @@
# frozen_string_literal: true
require 'digest/sha1'
require 'fileutils'
module YARD
module CLI
# Default options used in +yard doc+ command.
class YardocOptions < Templates::TemplateOptions
# @return [Array<CodeObjects::ExtraFileObject>]
# the list of extra files rendered along with objects
default_attr :files, lambda { [] }
# @return [String] the default title appended to each generated page
default_attr :title, "Documentation by YARD #{YARD::VERSION}"
# @return [Verifier] the default verifier object to filter queries
default_attr :verifier, lambda { Verifier.new }
# @return [Serializers::Base] the default serializer for generating output
# to disk.
default_attr :serializer, lambda { Serializers::FileSystemSerializer.new }
# @return [Symbol] the default output format (:html).
default_attr :format, :html
# @return [Boolean] whether the data should be rendered in a single page,
# if the template supports it.
default_attr :onefile, false
# @return [CodeObjects::ExtraFileObject] the README file object rendered
# along with objects
attr_accessor :readme
# @return [Array<CodeObjects::Base>] the list of code objects to render
# the templates with.
attr_accessor :objects
# @return [Numeric] An index value for rendering sequentially related templates
attr_accessor :index
# @return [CodeObjects::Base] an extra item to send to a template that is not
# the main rendered object
attr_accessor :item
# @return [CodeObjects::ExtraFileObject] the file object being rendered.
# The +object+ key is not used so that a file may be rendered in the context
# of an object's namespace (for generating links).
attr_accessor :file
# @return [String] the current locale
attr_accessor :locale
end
# Yardoc is the default YARD CLI command (+yard doc+ and historic +yardoc+
# executable) used to generate and output (mainly) HTML documentation given
# a set of source files.
#
# == Usage
#
# Main usage for this command is:
#
# $ yardoc [options] [source_files [- extra_files]]
#
# See +yardoc --help+ for details on valid options.
#
# == Options File (+.yardopts+)
#
# If a +.yardopts+ file is found in the source directory being processed,
# YARD will use the contents of the file as arguments to the command,
# treating newlines as spaces. You can use shell-style quotations to
# group space delimited arguments, just like on the command line.
#
# A valid +.yardopts+ file might look like:
#
# --no-private
# --title "My Title"
# --exclude foo --exclude bar
# lib/**/*.erb
# lib/**/*.rb -
# HACKING.rdoc LEGAL COPYRIGHT
#
# Note that Yardoc also supports the legacy RDoc style +.document+ file,
# though this file can only specify source globs to parse, not options.
#
# == Queries (+--query+)
#
# Yardoc supports queries to select specific code objects for which to
# generate documentation. For example, you might want to generate
# documentation only for your public API. If you've documented your public
# methods with +@api public+, you can use the following query to select
# all of these objects:
#
# --query '@api.text == "public"'
#
# Note that the syntax for queries is mostly Ruby with a few syntactic
# simplifications for meta-data tags. See the {Verifier} class for an
# overview of this syntax.
#
# == Adding Custom Ad-Hoc Meta-data Tags (+--tag+)
#
# YARD allows specification of {file:docs/Tags.md meta-data tags}
# programmatically via the {YARD::Tags::Library} class, but often this is not
# practical for users writing documentation. To make adding custom tags
# easier, Yardoc has a few command-line switches for creating basic tags
# and displaying them in generated HTML output.
#
# To specify a custom tag to be displayed in output, use any of the
# following:
#
# * +--tag+ TAG:TITLE
# * +--name-tag+ TAG:TITLE
# * +--type-tag+ TAG:TITLE
# * +--type-name-tag+ TAG:TITLE
# * +--title-tag+ TAG:TITLE
#
# "TAG:TITLE" is of the form: name:"Display Title", for example:
#
# --tag overload:"Overloaded Method"
#
# See +yard help doc+ for a description of the various options.
#
# Tags added in this way are automatically displayed in output. To add
# a meta-data tag that does not show up in output, use +--hide-tag TAG+.
# Note that you can also use this option on existing tags to hide
# builtin tags, for instance.
#
# == Processed Data Storage (+.yardoc+ directory)
#
# When Yardoc parses a source directory, it creates a +.yardoc+ directory
# (by default, override with +-b+) at the root of the project. This directory
# contains marshal dumps for all raw object data in the source, so that
# you can access it later for various commands (+stats+, +graph+, etc.).
# This directory is also used as a cache for any future calls to +yardoc+
# so as to process only the files which have changed since the last call.
#
# When Yardoc uses the cache in subsequent calls to +yardoc+, methods
# or classes that have been deleted from source since the last parsing
# will not be erased from the cache (YARD never deletes objects). In such
# a case, you should wipe the cache and do a clean parsing of the source tree.
# You can do this by deleting the +.yardoc+ directory manually, or running
# Yardoc without +--use-cache+ (+-c+).
#
# @since 0.2.1
# @see Verifier
class Yardoc < YardoptsCommand
# @return [Hash] the hash of options passed to the template.
# @see Templates::Engine#render
attr_reader :options
# @return [Array<String>] list of Ruby source files to process
attr_accessor :files
# @return [Array<String>] list of excluded paths (regexp matches)
# @since 0.5.3
attr_accessor :excluded
# @return [Boolean] whether to use the existing yardoc db if the
# .yardoc already exists. Also makes use of file checksums to
# parse only changed files.
attr_accessor :use_cache
# @return [Boolean] whether objects should be serialized to .yardoc db
attr_accessor :save_yardoc
# @return [Boolean] whether to generate output
attr_accessor :generate
# @return [Boolean] whether to print a list of objects
# @since 0.5.5
attr_accessor :list
# Keep track of which visibilities are to be shown
# @return [Array<Symbol>] a list of visibilities
# @since 0.5.6
attr_accessor :visibilities
# Keep track of which APIs are to be shown
# @return [Array<String>] a list of APIs
# @since 0.8.1
attr_accessor :apis
# Keep track of which APIs are to be hidden
# @return [Array<String>] a list of APIs to be hidden
# @since 0.8.7
attr_accessor :hidden_apis
# @return [Array<Symbol>] a list of tags to hide from templates
# @since 0.6.0
attr_accessor :hidden_tags
# @return [Boolean] whether to print statistics after parsing
# @since 0.6.0
attr_accessor :statistics
# @return [Array<String>] a list of assets to copy after generation
# @since 0.6.0
attr_accessor :assets
# @return [Boolean] whether markup option was specified
# @since 0.7.0
attr_accessor :has_markup
# @return [Boolean] whether yard exits with error status code if a warning occurs
attr_accessor :fail_on_warning
# Creates a new instance of the commandline utility
def initialize
super
@options = YardocOptions.new
@options.reset_defaults
@visibilities = [:public]
@apis = []
@hidden_apis = []
@assets = {}
@excluded = []
@files = []
@hidden_tags = []
@use_cache = false
@generate = true
@statistics = true
@list = false
@save_yardoc = true
@has_markup = false
@fail_on_warning = false
if defined?(::Encoding) && ::Encoding.respond_to?(:default_external=)
utf8 = ::Encoding.find('utf-8')
::Encoding.default_external = utf8 unless ::Encoding.default_external == utf8
::Encoding.default_internal = utf8 unless ::Encoding.default_internal == utf8
end
end
def description
"Generates documentation"
end
# Runs the commandline utility, parsing arguments and generating
# output if set.
#
# @param [Array<String>] args the list of arguments. If the list only
# contains a single nil value, skip calling of {#parse_arguments}
# @return [void]
def run(*args)
log.show_progress = true
if args.empty? || !args.first.nil?
# fail early if arguments are not valid
return unless parse_arguments(*args)
end
checksums = nil
if use_cache
Registry.load
checksums = Registry.checksums.dup
end
if save_yardoc
Registry.lock_for_writing do
YARD.parse(files, excluded)
Registry.save(use_cache)
end
else
YARD.parse(files, excluded)
end
if generate
run_generate(checksums)
copy_assets
elsif list
print_list
end
if !list && statistics && log.level < Logger::ERROR
Registry.load_all
log.enter_level(Logger::ERROR) do
Stats.new(false).run(*args)
end
end
abort if fail_on_warning && log.warned
true
ensure
log.show_progress = false
end
# Parses commandline arguments
# @param [Array<String>] args the list of arguments
# @return [Boolean] whether or not arguments are valid
# @since 0.5.6
def parse_arguments(*args)
super(*args)
# Last minute modifications
self.files = Parser::SourceParser::DEFAULT_PATH_GLOB if files.empty?
files.delete_if {|x| x =~ /\A\s*\Z/ } # remove empty ones
readme = Dir.glob('README{,*[^~]}').
sort_by {|r| [r.count('.'), r.index('.'), r] }.first
readme ||= Dir.glob(files.first).first if options.onefile && !files.empty?
options.readme ||= CodeObjects::ExtraFileObject.new(readme) if readme
options.files.unshift(options.readme).uniq! if options.readme
Tags::Library.visible_tags -= hidden_tags
add_visibility_verifier
add_api_verifier
apply_locale
# US-ASCII is invalid encoding for onefile
if defined?(::Encoding) && options.onefile
if ::Encoding.default_internal == ::Encoding::US_ASCII
log.warn "--one-file is not compatible with US-ASCII encoding, using ASCII-8BIT"
::Encoding.default_external, ::Encoding.default_internal = ['ascii-8bit'] * 2
end
end
if generate && !verify_markup_options
false
else
true
end
end
# The list of all objects to process. Override this method to change
# which objects YARD should generate documentation for.
#
# @deprecated To hide methods use the +@private+ tag instead.
# @return [Array<CodeObjects::Base>] a list of code objects to process
def all_objects
Registry.all(:root, :module, :class)
end
private
# Generates output for objects
# @param [Hash, nil] checksums if supplied, a list of checkums for files.
# @return [void]
# @since 0.5.1
def run_generate(checksums)
if checksums
changed_files = []
Registry.checksums.each do |file, hash|
changed_files << file if checksums[file] != hash
end
end
Registry.load_all if use_cache
objects = run_verifier(all_objects).reject do |object|
serialized = !options.serializer || options.serializer.exists?(object)
if checksums && serialized && !object.files.any? {|f, _line| changed_files.include?(f) }
true
else
log.debug "Re-generating object #{object.path}..."
false
end
end
Templates::Engine.generate(objects, options)
end
# Verifies that the markup options are valid before parsing any code.
# Failing early is better than failing late.
#
# @return (see YARD::Templates::Helpers::MarkupHelper#load_markup_provider)
def verify_markup_options
result = false
lvl = has_markup ? log.level : Logger::FATAL
obj = Struct.new(:options).new(options)
obj.extend(Templates::Helpers::MarkupHelper)
options.files.each do |file|
markup = file.attributes[:markup] || obj.markup_for_file('', file.filename)
result = obj.load_markup_provider(markup)
return false if !result && markup != :rdoc
end
options.markup = :rdoc unless has_markup
log.enter_level(lvl) { result = obj.load_markup_provider }
if !result && !has_markup
log.warn "Could not load default RDoc formatter, " \
"ignoring any markup (install RDoc to get default formatting)."
options.markup = :none
true
else
result
end
end
# Copies any assets to the output directory
# @return [void]
# @since 0.6.0
def copy_assets
return unless options.serializer
outpath = options.serializer.basepath
assets.each do |from, to|
to = File.join(outpath, to)
log.debug "Copying asset '#{from}' to '#{to}'"
from += '/.' if File.directory?(from)
FileUtils.cp_r(from, to)
end
end
# Prints a list of all objects
# @return [void]
# @since 0.5.5
def print_list
Registry.load_all
run_verifier(Registry.all).
sort_by {|item| [item.file || '', item.line || 0] }.each do |item|
log.puts "#{item.file}:#{item.line}: #{item.path}"
end
end
# Adds a set of extra documentation files to be processed
# @param [Array<String>] files the set of documentation files
def add_extra_files(*files)
files.map! {|f| f.include?("*") ? Dir.glob(f) : f }.flatten!
files.each do |file|
if extra_file_valid?(file)
options.files << CodeObjects::ExtraFileObject.new(file)
end
end
end
# @param file [String] the filename to validate
# @param check_exists [Boolean] whether the file should exist on disk
# @return [Boolean] whether the file is allowed to be used
def extra_file_valid?(file, check_exists = true)
if file =~ %r{^(?:\.\./|/)}
log.warn "Invalid file: #{file}"
false
elsif check_exists && !File.file?(file)
log.warn "Could not find file: #{file}"
false
else
true
end
end
# Parses the file arguments into Ruby files and extra files, which are
# separated by a '-' element.
#
# @example Parses a set of Ruby source files
# parse_files %w(file1 file2 file3)
# @example Parses a set of Ruby files with a separator and extra files
# parse_files %w(file1 file2 - extrafile1 extrafile2)
# @param [Array<String>] files the list of files to parse
# @return [void]
def parse_files(*files)
seen_extra_files_marker = false
files.each do |file|
if file == "-"
seen_extra_files_marker = true
next
end
if seen_extra_files_marker
add_extra_files(file)
else
self.files << file
end
end
end
# Adds verifier rule for visibilities
# @return [void]
# @since 0.5.6
def add_visibility_verifier
vis_expr = "#{visibilities.uniq.inspect}.include?(object.visibility)"
options.verifier.add_expressions(vis_expr)
end
# Adds verifier rule for APIs
# @return [void]
# @since 0.8.1
def add_api_verifier
no_api = true if apis.delete('')
exprs = []
exprs << "#{apis.uniq.inspect}.include?(@api.text)" unless apis.empty?
unless hidden_apis.empty?
exprs << "!#{hidden_apis.uniq.inspect}.include?(@api.text)"
end
exprs = !exprs.empty? ? [exprs.join(' && ')] : []
exprs << "!@api" if no_api
expr = exprs.join(' || ')
options.verifier.add_expressions(expr) unless expr.empty?
end
# Applies the specified locale to collected objects
# @return [void]
# @since 0.8.3
def apply_locale
YARD::I18n::Locale.default = options.locale
options.files.each do |file|
file.locale = options.locale
end
end
# (see Templates::Helpers::BaseHelper#run_verifier)
def run_verifier(list)
options.verifier ? options.verifier.run(list) : list
end
# @since 0.6.0
def add_tag(tag_data, factory_method = nil)
tag, title = *tag_data.split(':')
title ||= tag.capitalize
Tags::Library.define_tag(title, tag.to_sym, factory_method)
Tags::Library.visible_tags |= [tag.to_sym]
end
# Parses commandline options.
# @param [Array<String>] args each tokenized argument
def optparse(*args)
opts = OptionParser.new
opts.banner = "Usage: yard doc [options] [source_files [- extra_files]]"
opts.separator "(if a list of source files is omitted, "
opts.separator " {lib,app}/**/*.rb ext/**/*.{c,rb} is used.)"
opts.separator ""
opts.separator "Example: yardoc -o documentation/ - FAQ LICENSE"
opts.separator " The above example outputs documentation for files in"
opts.separator " lib/**/*.rb to documentation/ including the extra files"
opts.separator " FAQ and LICENSE."
opts.separator ""
opts.separator "A base set of options can be specified by adding a .yardopts"
opts.separator "file to your base path containing all extra options separated"
opts.separator "by whitespace."
general_options(opts)
output_options(opts)
tag_options(opts)
common_options(opts)
parse_options(opts, args)
parse_files(*args) unless args.empty?
end
# Adds general options
def general_options(opts)
opts.separator ""
opts.separator "General Options:"
opts.on('-b', '--db FILE', 'Use a specified .yardoc db to load from or save to',
' (defaults to .yardoc)') do |yfile|
YARD::Registry.yardoc_file = yfile
end
opts.on('--[no-]single-db', 'Whether code objects should be stored to single',
' database file (advanced)') do |use_single_db|
Registry.single_object_db = use_single_db
end
opts.on('-n', '--no-output', 'Only generate .yardoc database, no documentation.') do
self.generate = false
end
opts.on('-c', '--use-cache [FILE]',
"Use the cached .yardoc db to generate documentation.",
" (defaults to no cache)") do |file|
YARD::Registry.yardoc_file = file if file
self.use_cache = true
end
opts.on('--no-cache', "Clear .yardoc db before parsing source.") do
self.use_cache = false
end
yardopts_options(opts)
opts.on('--no-save', 'Do not save the parsed data to the yardoc db') do
self.save_yardoc = false
end
opts.on('--exclude REGEXP', 'Ignores a file if it matches path match (regexp)') do |path|
excluded << path
end
opts.on('--fail-on-warning', 'Exit with error status code if a warning occurs') do
self.fail_on_warning = true
end
end
# Adds output options
def output_options(opts)
opts.separator ""
opts.separator "Output options:"
opts.on('--one-file', 'Generates output as a single file') do
options.onefile = true
end
opts.on('--list', 'List objects to standard out (implies -n)') do |_format|
self.generate = false
self.list = true
end
opts.on('--no-public', "Don't show public methods. (default shows public)") do
visibilities.delete(:public)
end
opts.on('--protected', "Show protected methods. (default hides protected)") do
visibilities.push(:protected)
end
opts.on('--private', "Show private methods. (default hides private)") do
visibilities.push(:private)
end
opts.on('--no-private', "Hide objects with @private tag") do
options.verifier.add_expressions '!object.tag(:private) &&
(object.namespace.is_a?(CodeObjects::Proxy) || !object.namespace.tag(:private))'
end
opts.on('--[no-]api API', 'Generates documentation for a given API',
'(objects which define the correct @api tag).',
'If --no-api is given, displays objects with',
'no @api tag.') do |api|
api = '' if api == false
apis.push(api)
end
opts.on('--hide-api API', 'Hides given @api tag from documentation') do |api|
hidden_apis.push(api)
end
opts.on('--embed-mixins', "Embeds mixin methods into class documentation") do
options.embed_mixins << '*'
end
opts.on('--embed-mixin [MODULE]', "Embeds mixin methods from a particular",
" module into class documentation") do |mod|
options.embed_mixins << mod
end
opts.on('--no-highlight', "Don't highlight code blocks in output.") do
options.highlight = false
end
opts.on('--default-return TYPE', "Shown if method has no return type. ",
" (defaults to 'Object')") do |type|
options.default_return = type
end
opts.on('--hide-void-return', "Hides return types specified as 'void'. ",
" (default is shown)") do
options.hide_void_return = true
end
opts.on('--query QUERY', "Only show objects that match a specific query") do |query|
next if YARD::Config.options[:safe_mode]
options.verifier.add_expressions(query.taint)
end
opts.on('--title TITLE', 'Add a specific title to HTML documents') do |title|
options.title = title
end
opts.on('-r', '--readme FILE', '--main FILE', 'The readme file used as the title page',
' of documentation.') do |readme|
if extra_file_valid?(readme)
options.readme = CodeObjects::ExtraFileObject.new(readme)
end
end
opts.on('--files FILE1,FILE2,...', 'Any extra comma separated static files to be ',
' included (eg. FAQ)') do |files|
add_extra_files(*files.split(","))
end
opts.on('--asset FROM[:TO]', 'A file or directory to copy over to output ',
' directory after generating') do |asset|
from, to = *asset.split(':').map {|f| File.cleanpath(f, true) }
to ||= from
if extra_file_valid?(from, false) && extra_file_valid?(to, false)
assets[from] = to
end
end
opts.on('-o', '--output-dir PATH',
'The output directory. (defaults to ./doc)') do |dir|
options.serializer.basepath = dir
end
opts.on('-m', '--markup MARKUP',
'Markup style used in documentation, like textile, ',
' markdown or rdoc. (defaults to rdoc)') do |markup|
self.has_markup = true
options.markup = markup.to_sym
end
opts.on('-M', '--markup-provider MARKUP_PROVIDER',
'Overrides the library used to process markup ',
' formatting (specify the gem name)') do |markup_provider|
options.markup_provider = markup_provider.to_sym
end
opts.on('--charset ENC', 'Character set to use when parsing files ',
' (default is system locale)') do |encoding|
begin
if defined?(Encoding) && Encoding.respond_to?(:default_external=)
Encoding.default_external = encoding
Encoding.default_internal = encoding
end
rescue ArgumentError => e
raise OptionParser::InvalidOption, e
end
end
opts.on('-t', '--template TEMPLATE',
'The template to use. (defaults to "default")') do |template|
options.template = template.to_sym
end
opts.on('-p', '--template-path PATH',
'The template path to look for templates in.',
' (used with -t).') do |path|
next if YARD::Config.options[:safe_mode]
YARD::Templates::Engine.register_template_path(File.expand_path(path))
end
opts.on('-f', '--format FORMAT',
'The output format for the template.',
' (defaults to html)') do |format|
options.format = format.to_sym
end
opts.on('--no-stats', 'Don\'t print statistics') do
self.statistics = false
end
opts.on('--no-progress', 'Don\'t show progress bar') do
log.show_progress = false
end
opts.on('--locale LOCALE',
'The locale for generated documentation.',
' (defaults to en)') do |locale|
options.locale = locale
end
opts.on('--po-dir DIR',
'The directory that has .po files.',
" (defaults to #{YARD::Registry.po_dir})") do |dir|
YARD::Registry.po_dir = dir
end
end
# Adds tag options
# @since 0.6.0
def tag_options(opts)
opts.separator ""
opts.separator "Tag options: (TAG:TITLE looks like: 'overload:Overloaded Method')"
opts.on('--tag TAG:TITLE', 'Registers a new free-form metadata @tag') do |tag|
add_tag(tag)
end
opts.on('--type-tag TAG:TITLE', 'Tag with an optional types field') do |tag|
add_tag(tag, :with_types)
end
opts.on('--type-name-tag TAG:TITLE', 'Tag with optional types and a name field') do |tag|
add_tag(tag, :with_types_and_name)
end
opts.on('--name-tag TAG:TITLE', 'Tag with a name field') do |tag|
add_tag(tag, :with_name)
end
opts.on('--title-tag TAG:TITLE', 'Tag with first line as title field') do |tag|
add_tag(tag, :with_title_and_text)
end
opts.on('--hide-tag TAG', 'Hides a previously defined tag from templates') do |tag|
self.hidden_tags |= [tag.to_sym]
end
opts.on('--transitive-tag TAG', 'Marks a tag as transitive') do |tag|
Tags::Library.transitive_tags |= [tag.to_sym]
end
opts.on('--non-transitive-tag TAG', 'Marks a tag as not transitive') do |tag|
Tags::Library.transitive_tags -= [tag.to_sym]
end
end
end
end
end

View File

@ -0,0 +1,110 @@
# frozen_string_literal: true
require 'optparse'
module YARD
module CLI
# Abstract base class for command that reads .yardopts file
#
# @abstract
# @since 0.8.3
class YardoptsCommand < Command
# The configuration filename to load extra options from
DEFAULT_YARDOPTS_FILE = ".yardopts"
# @return [Boolean] whether to parse options from .yardopts
attr_accessor :use_yardopts_file
# @return [Boolean] whether to parse options from .document
attr_accessor :use_document_file
# The options file name (defaults to {DEFAULT_YARDOPTS_FILE})
# @return [String] the filename to load extra options from
attr_accessor :options_file
# Creates a new command that reads .yardopts
def initialize
super
@options_file = DEFAULT_YARDOPTS_FILE
@use_yardopts_file = true
@use_document_file = true
end
# Parses commandline arguments
# @param [Array<String>] args the list of arguments
# @return [Boolean] whether or not arguments are valid
# @since 0.5.6
def parse_arguments(*args)
parse_yardopts_options(*args)
# Parse files and then command line arguments
parse_rdoc_document_file
parse_yardopts
optparse(*args)
end
protected
# Adds --[no-]yardopts / --[no-]document
def yardopts_options(opts)
opts.on('--[no-]yardopts [FILE]',
"If arguments should be read from FILE",
" (defaults to yes, FILE defaults to .yardopts)") do |use_yardopts|
if use_yardopts.is_a?(String)
self.options_file = use_yardopts
self.use_yardopts_file = true
else
self.use_yardopts_file = (use_yardopts != false)
end
end
opts.on('--[no-]document', "If arguments should be read from .document file. ",
" (defaults to yes)") do |use_document|
self.use_document_file = use_document
end
end
private
# Parses the .yardopts file for default yard options
# @return [Array<String>] an array of options parsed from .yardopts
def yardopts(file = options_file)
return [] unless use_yardopts_file
File.read_binary(file).shell_split
rescue Errno::ENOENT
[]
end
# Parses out the yardopts/document options
def parse_yardopts_options(*args)
opts = OptionParser.new
opts.base.long.clear # HACK: why are --help and --version defined?
yardopts_options(opts)
begin
opts.parse(args)
rescue OptionParser::ParseError => err
idx = args.index(err.args.first)
args = args[(idx + 1)..-1]
args.shift while args.first && args.first[0, 1] != '-'
retry
end
end
def parse_rdoc_document_file(file = '.document')
optparse(*support_rdoc_document_file!(file)) if use_document_file
end
def parse_yardopts(file = options_file)
optparse(*yardopts(file)) if use_yardopts_file
end
# Reads a .document file in the directory to get source file globs
# @return [Array<String>] an array of files parsed from .document
def support_rdoc_document_file!(file = '.document')
return [] unless use_document_file
File.read(file).gsub(/^[ \t]*#.+/m, '').split(/\s+/)
rescue Errno::ENOENT
[]
end
end
end
end

View File

@ -0,0 +1,215 @@
# frozen_string_literal: true
require 'rbconfig'
module YARD
module CLI
# A tool to view documentation in the console like `ri`
class YRI < Command
# The location in {YARD::CONFIG_DIR} where the YRI cache file is loaded
# from.
CACHE_FILE = File.expand_path(File.join(YARD::Config::CONFIG_DIR, 'yri_cache'))
# A file containing all paths, delimited by newlines, to search for
# yardoc databases.
# @since 0.5.1
SEARCH_PATHS_FILE = File.expand_path(File.join(YARD::Config::CONFIG_DIR, 'yri_search_paths'))
# Default search paths that should be loaded dynamically into YRI. These paths
# take precedence over all other paths ({SEARCH_PATHS_FILE} and RubyGems
# paths). To add a path, call:
#
# DEFAULT_SEARCH_PATHS.push("/path/to/.yardoc")
#
# @return [Array<String>] a list of extra search paths
# @since 0.6.0
DEFAULT_SEARCH_PATHS = []
# Helper method to run the utility on an instance.
# @see #run
def self.run(*args) new.run(*args) end
def initialize
super
@cache = {}
@search_paths = []
add_default_paths
add_gem_paths
load_cache
@search_paths.uniq!
end
def description
"A tool to view documentation in the console like `ri`"
end
# Runs the command-line utility.
#
# @example
# YRI.new.run('String#reverse')
# @param [Array<String>] args each tokenized argument
def run(*args)
optparse(*args)
if ::RbConfig::CONFIG['host_os'] =~ /mingw|win32/
@serializer ||= YARD::Serializers::StdoutSerializer.new
else
@serializer ||= YARD::Serializers::ProcessSerializer.new('less')
end
if @name.nil? || @name.strip.empty?
print_usage
return exit(1)
end
object = find_object(@name)
if object
print_object(object)
else
STDERR.puts "No documentation for `#{@name}'"
return exit(1)
end
end
protected
# Prints the command usage
# @return [void]
# @since 0.5.6
def print_usage
log.puts "Usage: yri [options] <Path to object>"
log.puts "See yri --help for more options."
end
# Caches the .yardoc file where an object can be found in the {CACHE_FILE}
# @return [void]
def cache_object(name, path)
return if path == Registry.yardoc_file
@cache[name] = path
File.open!(CACHE_FILE, 'w') do |file|
@cache.each do |key, value|
file.puts("#{key} #{value}")
end
end
end
# @param [CodeObjects::Base] object the object to print.
# @return [String] the formatted output for an object.
def print_object(object)
if object.type == :method && object.is_alias?
tmp = P(object.namespace, (object.scope == :instance ? "#" : "") +
object.namespace.aliases[object].to_s)
object = tmp unless YARD::CodeObjects::Proxy === tmp
end
object.format(:serializer => @serializer)
end
# Locates an object by name starting in the cached paths and then
# searching through any search paths.
#
# @param [String] name the full name of the object
# @return [CodeObjects::Base] an object if found
# @return [nil] if no object is found
def find_object(name)
@search_paths.unshift(@cache[name]) if @cache[name]
@search_paths.unshift(Registry.yardoc_file)
# Try to load it from in memory cache
log.debug "Searching for #{name} in memory"
obj = try_load_object(name, nil)
return obj if obj
log.debug "Searching for #{name} in search paths"
@search_paths.each do |path|
next unless File.exist?(path)
log.debug "Searching for #{name} in #{path}..."
Registry.load(path)
obj = try_load_object(name, path)
return obj if obj
end
nil
end
private
# Tries to load the object with name. If successful, caches the object
# with the cache_path
#
# @param [String] name the object path
# @param [String] cache_path the location of the yardoc
# db containing the object to cache for future lookups.
# No caching is done if this is nil.
# @return [void]
def try_load_object(name, cache_path)
obj = Registry.at(name)
cache_object(name, cache_path) if obj && cache_path
obj
end
# Loads {CACHE_FILE}
# @return [void]
def load_cache
return unless File.file?(CACHE_FILE)
File.readlines(CACHE_FILE).each do |line|
line = line.strip.split(/\s+/)
@cache[line[0]] = line[1]
end
end
# Adds all RubyGems yardoc files to search paths
# @return [void]
def add_gem_paths
require 'rubygems'
gem_paths = []
YARD::GemIndex.each do |spec|
yfile = Registry.yardoc_file_for_gem(spec.name)
next if yfile.nil?
if spec.name =~ /^yard-doc-/
gem_paths.unshift(yfile)
else
gem_paths.push(yfile)
end
end
@search_paths += gem_paths
rescue LoadError
nil # noop
end
# Adds paths in {SEARCH_PATHS_FILE}
# @since 0.5.1
def add_default_paths
@search_paths.concat(DEFAULT_SEARCH_PATHS)
return unless File.file?(SEARCH_PATHS_FILE)
paths = File.readlines(SEARCH_PATHS_FILE).map(&:strip)
@search_paths.concat(paths)
end
# Parses commandline options.
# @param [Array<String>] args each tokenized argument
def optparse(*args)
opts = OptionParser.new
opts.banner = "Usage: yri [options] <Path to object>"
opts.separator "Example: yri String#gsub"
opts.separator ""
opts.separator "General Options:"
opts.on('-b', '--db FILE', 'Use a specified .yardoc db to search in') do |yfile|
@search_paths.unshift(yfile)
end
opts.on('-T', '--no-pager', 'No pager') do
@serializer = YARD::Serializers::StdoutSerializer.new
end
opts.on('-p PAGER', '--pager') do |pager|
@serializer = YARD::Serializers::ProcessSerializer.new(pager)
end
common_options(opts)
parse_options(opts, args)
@name = args.first
end
end
end
end

View File

@ -0,0 +1,622 @@
# frozen_string_literal: true
module YARD
module CodeObjects
# A list of code objects. This array acts like a set (no unique items)
# but also disallows any {Proxy} objects from being added.
class CodeObjectList < Array
# Creates a new object list associated with a namespace
#
# @param [NamespaceObject] owner the namespace the list should be associated with
# @return [CodeObjectList]
def initialize(owner = Registry.root)
@owner = owner
end
# Adds a new value to the list
#
# @param [Base] value a code object to add
# @return [CodeObjectList] self
def push(value)
value = Proxy.new(@owner, value) if value.is_a?(String) || value.is_a?(Symbol)
if value.is_a?(CodeObjects::Base) || value.is_a?(Proxy)
super(value) unless include?(value)
else
raise ArgumentError, "#{value.class} is not a valid CodeObject"
end
self
end
alias << push
end
extend NamespaceMapper
# Namespace separator
NSEP = '::'
# Regex-quoted namespace separator
NSEPQ = NSEP
# Instance method separator
ISEP = '#'
# Regex-quoted instance method separator
ISEPQ = ISEP
# Class method separator
CSEP = '.'
# Regex-quoted class method separator
CSEPQ = Regexp.quote CSEP
# Regular expression to match constant name
CONSTANTMATCH = /[A-Z]\w*/
# Regular expression to match the beginning of a constant
CONSTANTSTART = /^[A-Z]/
# Regular expression to match namespaces (const A or complex path A::B)
NAMESPACEMATCH = /(?:(?:#{NSEPQ}\s*)?#{CONSTANTMATCH})+/
# Regular expression to match a method name
METHODNAMEMATCH = %r{[a-zA-Z_]\w*[!?=]?|[-+~]\@|<<|>>|=~|===?|![=~]?|<=>|[<>]=?|\*\*|[-/+%^&*~`|]|\[\]=?}
# Regular expression to match a fully qualified method def (self.foo, Class.foo).
METHODMATCH = /(?:(?:#{NAMESPACEMATCH}|[a-z]\w*)\s*(?:#{CSEPQ}|#{NSEPQ})\s*)?#{METHODNAMEMATCH}/
# All builtin Ruby exception classes for inheritance tree.
BUILTIN_EXCEPTIONS = ["ArgumentError", "ClosedQueueError", "EncodingError",
"EOFError", "Exception", "FiberError", "FloatDomainError", "IndexError",
"Interrupt", "IOError", "KeyError", "LoadError", "LocalJumpError",
"NameError", "NoMemoryError", "NoMethodError", "NotImplementedError",
"RangeError", "RegexpError", "RuntimeError", "ScriptError", "SecurityError",
"SignalException", "StandardError", "StopIteration", "SyntaxError",
"SystemCallError", "SystemExit", "SystemStackError", "ThreadError",
"TypeError", "UncaughtThrowError", "ZeroDivisionError"]
# All builtin Ruby classes for inheritance tree.
# @note MatchingData is a 1.8.x legacy class
BUILTIN_CLASSES = ["Array", "Bignum", "Binding", "Class", "Complex",
"ConditionVariable", "Data", "Dir", "Encoding", "Enumerator", "FalseClass",
"Fiber", "File", "Fixnum", "Float", "Hash", "IO", "Integer", "MatchData",
"Method", "Module", "NilClass", "Numeric", "Object", "Proc", "Queue",
"Random", "Range", "Rational", "Regexp", "RubyVM", "SizedQueue", "String",
"Struct", "Symbol", "Thread", "ThreadGroup", "Time", "TracePoint",
"TrueClass", "UnboundMethod"] + BUILTIN_EXCEPTIONS
# All builtin Ruby modules for mixin handling.
BUILTIN_MODULES = ["Comparable", "Enumerable", "Errno", "FileTest", "GC",
"Kernel", "Marshal", "Math", "ObjectSpace", "Precision", "Process", "Signal"]
# All builtin Ruby classes and modules.
BUILTIN_ALL = BUILTIN_CLASSES + BUILTIN_MODULES
# Hash of {BUILTIN_EXCEPTIONS} as keys and true as value (for O(1) lookups)
BUILTIN_EXCEPTIONS_HASH = BUILTIN_EXCEPTIONS.inject({}) {|h, n| h.update(n => true) }
# +Base+ is the superclass of all code objects recognized by YARD. A code
# object is any entity in the Ruby language (class, method, module). A
# DSL might subclass +Base+ to create a new custom object representing
# a new entity type.
#
# == Registry Integration
# Any created object associated with a namespace is immediately registered
# with the registry. This allows the Registry to act as an identity map
# to ensure that no object is represented by more than one Ruby object
# in memory. A unique {#path} is essential for this identity map to work
# correctly.
#
# == Custom Attributes
# Code objects allow arbitrary custom attributes to be set using the
# {#[]=} assignment method.
#
# == Namespaces
# There is a special type of object called a "namespace". These are subclasses
# of the {NamespaceObject} and represent Ruby entities that can have
# objects defined within them. Classically these are modules and classes,
# though a DSL might create a custom {NamespaceObject} to describe a
# specific set of objects.
#
# == Separators
# Custom classes with different separator tokens should define their own
# separators using the {NamespaceMapper.register_separator} method. The
# standard Ruby separators have already been defined ('::', '#', '.', etc).
#
# @abstract This class should not be used directly. Instead, create a
# subclass that implements {#path}, {#sep} or {#type}. You might also
# need to register custom separators if {#sep} uses alternate separator
# tokens.
# @see Registry
# @see #path
# @see #[]=
# @see NamespaceObject
# @see NamespaceMapper.register_separator
class Base
# The files the object was defined in. To add a file, use {#add_file}.
# @return [Array<Array(String, Integer)>] a list of files
# @see #add_file
attr_reader :files
# The namespace the object is defined in. If the object is in the
# top level namespace, this is {Registry.root}
# @return [NamespaceObject] the namespace object
attr_reader :namespace
# The source code associated with the object
# @return [String, nil] source, if present, or nil
attr_reader :source
# Language of the source code associated with the object. Defaults to
# +:ruby+.
#
# @return [Symbol] the language type
attr_accessor :source_type
# The one line signature representing an object. For a method, this will
# be of the form "def meth(arguments...)". This is usually the first
# source line.
#
# @return [String] a line of source
attr_accessor :signature
# The non-localized documentation string associated with the object
# @return [Docstring] the documentation string
# @since 0.8.4
attr_reader :base_docstring
undef base_docstring
def base_docstring; @docstring end
# Marks whether or not the method is conditionally defined at runtime
# @return [Boolean] true if the method is conditionally defined at runtime
attr_accessor :dynamic
# @return [String] the group this object is associated with
# @since 0.6.0
attr_accessor :group
# Is the object defined conditionally at runtime?
# @see #dynamic
def dynamic?; @dynamic end
# @return [Symbol] the visibility of an object (:public, :private, :protected)
attr_accessor :visibility
undef visibility=
def visibility=(v) @visibility = v.to_sym end
class << self
# Allocates a new code object
# @return [Base]
# @see #initialize
def new(namespace, name, *args, &block)
raise ArgumentError, "invalid empty object name" if name.to_s.empty?
if namespace.is_a?(ConstantObject)
unless namespace.value =~ /\A#{NAMESPACEMATCH}\Z/
raise Parser::UndocumentableError, "constant mapping"
end
namespace = Proxy.new(namespace.namespace, namespace.value)
end
if name.to_s[0, 2] == NSEP
name = name.to_s[2..-1]
namespace = Registry.root
end
if name =~ /(?:#{NSEPQ})([^:]+)$/
return new(Proxy.new(namespace, $`), $1, *args, &block)
end
obj = super(namespace, name, *args)
existing_obj = Registry.at(obj.path)
obj = existing_obj if existing_obj && existing_obj.class == self
yield(obj) if block_given?
obj
end
# Compares the class with subclasses
#
# @param [Object] other the other object to compare classes with
# @return [Boolean] true if other is a subclass of self
def ===(other)
other.is_a?(self)
end
end
# Creates a new code object
#
# @example Create a method in the root namespace
# CodeObjects::Base.new(:root, '#method') # => #<yardoc method #method>
# @example Create class Z inside namespace X::Y
# CodeObjects::Base.new(P("X::Y"), :Z) # or
# CodeObjects::Base.new(Registry.root, "X::Y")
# @param [NamespaceObject] namespace the namespace the object belongs in,
# {Registry.root} or :root should be provided if it is associated with
# the top level namespace.
# @param [Symbol, String] name the name (or complex path) of the object.
# @yield [self] a block to perform any extra initialization on the object
# @yieldparam [Base] self the newly initialized code object
# @return [Base] the newly created object
def initialize(namespace, name, *)
if namespace && namespace != :root &&
!namespace.is_a?(NamespaceObject) && !namespace.is_a?(Proxy)
raise ArgumentError, "Invalid namespace object: #{namespace}"
end
@files = []
@current_file_has_comments = false
@name = name.to_sym
@source_type = :ruby
@visibility = :public
@tags = []
@docstrings = {}
@docstring = Docstring.new!('', [], self)
@namespace = nil
self.namespace = namespace
yield(self) if block_given?
end
# Copies all data in this object to another code object, except for
# uniquely identifying information (path, namespace, name, scope).
#
# @param [Base] other the object to copy data to
# @return [Base] the other object
# @since 0.8.0
def copy_to(other)
copyable_attributes.each do |ivar|
ivar = "@#{ivar}"
other.instance_variable_set(ivar, instance_variable_get(ivar))
end
other.docstring = @docstring.to_raw
other
end
# The name of the object
# @param [Boolean] prefix whether to show a prefix. Implement
# this in a subclass to define how the prefix is showed.
# @return [Symbol] if prefix is false, the symbolized name
# @return [String] if prefix is true, prefix + the name as a String.
# This must be implemented by the subclass.
def name(prefix = false)
prefix ? @name.to_s : (defined?(@name) && @name)
end
# Associates a file with a code object, optionally adding the line where it was defined.
# By convention, '<stdin>' should be used to associate code that comes form standard input.
#
# @param [String] file the filename ('<stdin>' for standard input)
# @param [Fixnum, nil] line the line number where the object lies in the file
# @param [Boolean] has_comments whether or not the definition has comments associated. This
# will allow {#file} to return the definition where the comments were made instead
# of any empty definitions that might have been parsed before (module namespaces for instance).
def add_file(file, line = nil, has_comments = false)
raise(ArgumentError, "file cannot be nil or empty") if file.nil? || file == ''
obj = [file.to_s, line]
return if files.include?(obj)
if has_comments && !@current_file_has_comments
@current_file_has_comments = true
@files.unshift(obj)
else
@files << obj # back of the line
end
end
# Returns the filename the object was first parsed at, taking
# definitions with docstrings first.
#
# @return [String] a filename
def file
@files.first ? @files.first[0] : nil
end
# Returns the line the object was first parsed at (or nil)
#
# @return [Fixnum] the line where the object was first defined.
# @return [nil] if there is no line associated with the object
def line
@files.first ? @files.first[1] : nil
end
# Tests if another object is equal to this, including a proxy
# @param [Base, Proxy] other if other is a {Proxy}, tests if
# the paths are equal
# @return [Boolean] whether or not the objects are considered the same
def equal?(other)
if other.is_a?(Base) || other.is_a?(Proxy)
path == other.path
else
super
end
end
alias == equal?
alias eql? equal?
# @return [Integer] the object's hash value (for equality checking)
def hash; path.hash end
# @return [nil] this object does not turn into an array
def to_ary; nil end
# Accesses a custom attribute on the object
# @param [#to_s] key the name of the custom attribute
# @return [Object, nil] the custom attribute or nil if not found.
# @see #[]=
def [](key)
if respond_to?(key)
send(key)
elsif instance_variable_defined?("@#{key}")
instance_variable_get("@#{key}")
end
end
# Sets a custom attribute on the object
# @param [#to_s] key the name of the custom attribute
# @param [Object] value the value to associate
# @return [void]
# @see #[]
def []=(key, value)
if respond_to?("#{key}=")
send("#{key}=", value)
else
instance_variable_set("@#{key}", value)
end
end
# @overload dynamic_attr_name
# @return the value of attribute named by the method attribute name
# @raise [NoMethodError] if no method or custom attribute exists by
# the attribute name
# @see #[]
# @overload dynamic_attr_name=(value)
# @param value a value to set
# @return +value+
# @see #[]=
def method_missing(meth, *args, &block)
if meth.to_s =~ /=$/
self[meth.to_s[0..-2]] = args.first
elsif instance_variable_get("@#{meth}")
self[meth]
else
super
end
end
# Attaches source code to a code object with an optional file location
#
# @param [#source, String] statement
# the +Parser::Statement+ holding the source code or the raw source
# as a +String+ for the definition of the code object only (not the block)
def source=(statement)
if statement.respond_to?(:source)
@source = format_source(statement.source.strip)
else
@source = format_source(statement.to_s)
end
if statement.respond_to?(:signature)
self.signature = statement.signature
end
end
# The documentation string associated with the object
#
# @param [String, I18n::Locale] locale (I18n::Locale.default)
# the locale of the documentation string.
# @return [Docstring] the documentation string
def docstring(locale = I18n::Locale.default)
if locale.nil?
@docstring.resolve_reference
return @docstring
end
if locale.is_a?(String)
locale_name = locale
locale = nil
else
locale_name = locale.name
end
@docstrings[locale_name] ||=
translate_docstring(locale || Registry.locale(locale_name))
end
# Attaches a docstring to a code object by parsing the comments attached to the statement
# and filling the {#tags} and {#docstring} methods with the parsed information.
#
# @param [String, Array<String>, Docstring] comments
# the comments attached to the code object to be parsed
# into a docstring and meta tags.
def docstring=(comments)
@docstrings.clear
@docstring = Docstring === comments ?
comments : Docstring.new(comments, self)
end
# Default type is the lowercase class name without the "Object" suffix.
# Override this method to provide a custom object type
#
# @return [Symbol] the type of code object this represents
def type
self.class.name.split('::').last.gsub(/Object$/, '').downcase.to_sym
end
# Represents the unique path of the object. The default implementation
# joins the path of {#namespace} with {#name} via the value of {#sep}.
# Custom code objects should ensure that the path is unique to the code
# object by either overriding {#sep} or this method.
#
# @example The path of an instance method
# MethodObject.new(P("A::B"), :c).path # => "A::B#c"
# @return [String] the unique path of the object
# @see #sep
def path
@path ||= if parent && !parent.root?
[parent.path, name.to_s].join(sep)
else
name.to_s
end
end
alias to_s path
# @note
# Override this method if your object has a special title that does
# not match the {#path} attribute value. This title will be used
# when linking or displaying the object.
# @return [String] the display title for an object
# @see 0.8.4
def title
path
end
# @param [Base, String] other another code object (or object path)
# @return [String] the shortest relative path from this object to +other+
# @since 0.5.3
def relative_path(other)
other = Registry.at(other) if String === other && Registry.at(other)
same_parent = false
if other.respond_to?(:path)
same_parent = other.parent == parent
other = other.path
end
return other unless namespace
common = [path, other].join(" ").match(/^(\S*)\S*(?: \1\S*)*$/)[1]
common = path unless common =~ /(\.|::|#)$/
common = common.sub(/(\.|::|#)[^:#\.]*?$/, '') if same_parent
suffix = %w(. :).include?(common[-1, 1]) || other[common.size, 1] == '#' ?
'' : '(::|\.)'
result = other.sub(/^#{Regexp.quote common}#{suffix}/, '')
result.empty? ? other : result
end
# Renders the object using the {Templates::Engine templating system}.
#
# @example Formats a class in plaintext
# puts P('MyClass').format
# @example Formats a method in html with rdoc markup
# puts P('MyClass#meth').format(:format => :html, :markup => :rdoc)
# @param [Hash] options a set of options to pass to the template
# @option options [Symbol] :format (:text) :html, :text or another output format
# @option options [Symbol] :template (:default) a specific template to use
# @option options [Symbol] :markup (nil) the markup type (:rdoc, :markdown, :textile)
# @option options [Serializers::Base] :serializer (nil) see Serializers
# @return [String] the rendered template
# @see Templates::Engine#render
def format(options = {})
options = options.merge(:object => self)
options = options.merge(:type => type) unless options[:type]
Templates::Engine.render(options)
end
# Inspects the object, returning the type and path
# @return [String] a string describing the object
def inspect
"#<yardoc #{type} #{path}>"
end
# Sets the namespace the object is defined in.
#
# @param [NamespaceObject, :root, nil] obj the new namespace (:root
# for {Registry.root}). If obj is nil, the object is unregistered
# from the Registry.
def namespace=(obj)
if @namespace
@namespace.children.delete(self)
Registry.delete(self)
end
@namespace = (obj == :root ? Registry.root : obj)
if @namespace
reg_obj = Registry.at(path)
return if reg_obj && reg_obj.class == self.class
unless @namespace.is_a?(Proxy)
# remove prior objects from obj's children that match this one
@namespace.children.delete_if {|o| o.path == path }
@namespace.children << self
end
Registry.register(self)
end
end
alias parent namespace
alias parent= namespace=
# Gets a tag from the {#docstring}
# @see Docstring#tag
def tag(name); docstring.tag(name) end
# Gets a list of tags from the {#docstring}
# @see Docstring#tags
def tags(name = nil); docstring.tags(name) end
# Tests if the {#docstring} has a tag
# @see Docstring#has_tag?
def has_tag?(name); docstring.has_tag?(name) end
# Add tags to the {#docstring}
# @see Docstring#add_tag
# @since 0.8.4
def add_tag(*tags)
@docstrings.clear
@docstring.add_tag(*tags)
end
# @return whether or not this object is a RootObject
def root?; false end
# Override this method with a custom component separator. For instance,
# {MethodObject} implements sep as '#' or '.' (depending on if the
# method is instance or class respectively). {#path} depends on this
# value to generate the full path in the form: namespace.path + sep + name
#
# @return [String] the component that separates the namespace path
# and the name (default is {NSEP})
def sep; NSEP end
protected
# Override this method if your code object subclass does not allow
# copying of certain attributes.
#
# @return [Array<String>] the list of instance variable names (without
# "@" prefix) that should be copied when {#copy_to} is called
# @see #copy_to
# @since 0.8.0
def copyable_attributes
vars = instance_variables.map {|ivar| ivar.to_s[1..-1] }
vars -= %w(docstring docstrings namespace name path)
vars
end
private
# Formats source code by removing leading indentation
#
# @param [String] source the source code to format
# @return [String] formatted source
def format_source(source)
source = source.chomp
last = source.split(/\r?\n/).last
indent = last ? last[/^([ \t]*)/, 1].length : 0
source.gsub(/^[ \t]{#{indent}}/, '')
end
def translate_docstring(locale)
@docstring.resolve_reference
return @docstring if locale.nil?
text = I18n::Text.new(@docstring)
localized_text = text.translate(locale)
docstring = Docstring.new(localized_text, self)
@docstring.tags.each do |tag|
if tag.is_a?(Tags::Tag)
localized_tag = tag.clone
localized_tag.text = I18n::Text.new(tag.text).translate(locale)
docstring.add_tag(localized_tag)
else
docstring.add_tag(tag)
end
end
docstring
end
end
end
end

View File

@ -0,0 +1,146 @@
# frozen_string_literal: true
module YARD::CodeObjects
register_separator NSEP, :class
# A ClassObject represents a Ruby class in source code. It is a {ModuleObject}
# with extra inheritance semantics through the superclass.
class ClassObject < NamespaceObject
# The {ClassObject} that this class object inherits from in Ruby source.
# @return [ClassObject] a class object that is the superclass of this one
attr_reader :superclass
# Creates a new class object in +namespace+ with +name+
#
# @see Base.new
def initialize(namespace, name, *args, &block)
super
if is_exception?
self.superclass ||= "::Exception" unless P(namespace, name) == P(:Exception)
else
case P(namespace, name).path
when "BasicObject"
nil
when "Object"
self.superclass ||= "::BasicObject"
else
self.superclass ||= "::Object"
end
end
end
# Whether or not the class is a Ruby Exception
#
# @return [Boolean] whether the object represents a Ruby exception
def is_exception?
inheritance_tree.reverse.any? {|o| BUILTIN_EXCEPTIONS_HASH.key? o.path }
end
# Returns the inheritance tree of the object including self.
#
# @param [Boolean] include_mods whether or not to include mixins in the
# inheritance tree.
# @return [Array<NamespaceObject>] the list of code objects that make up
# the inheritance tree.
def inheritance_tree(include_mods = false)
list = (include_mods ? mixins(:instance, :class) : [])
if superclass.is_a?(Proxy) || superclass.respond_to?(:inheritance_tree)
list += [superclass] unless superclass == P(:Object) || superclass == P(:BasicObject)
end
[self] + list.map do |m|
next m if m == self
next m unless m.respond_to?(:inheritance_tree)
m.inheritance_tree(include_mods)
end.flatten.uniq
end
# Returns the list of methods matching the options hash. Returns
# all methods if hash is empty.
#
# @param [Hash] opts the options hash to match
# @option opts [Boolean] :inherited (true) whether inherited methods should be
# included in the list
# @option opts [Boolean] :included (true) whether mixed in methods should be
# included in the list
# @return [Array<MethodObject>] the list of methods that matched
def meths(opts = {})
opts = SymbolHash[:inherited => true].update(opts)
list = super(opts)
list += inherited_meths(opts).reject do |o|
next(false) if opts[:all]
list.find {|o2| o2.name == o.name && o2.scope == o.scope }
end if opts[:inherited]
list
end
# Returns only the methods that were inherited.
#
# @return [Array<MethodObject>] the list of inherited method objects
def inherited_meths(opts = {})
inheritance_tree[1..-1].inject([]) do |list, superclass|
if superclass.is_a?(Proxy)
list
else
list += superclass.meths(opts).reject do |o|
next(false) if opts[:all]
child(:name => o.name, :scope => o.scope) ||
list.find {|o2| o2.name == o.name && o2.scope == o.scope }
end
end
end
end
# Returns the list of constants matching the options hash.
#
# @param [Hash] opts the options hash to match
# @option opts [Boolean] :inherited (true) whether inherited constant should be
# included in the list
# @option opts [Boolean] :included (true) whether mixed in constant should be
# included in the list
# @return [Array<ConstantObject>] the list of constant that matched
def constants(opts = {})
opts = SymbolHash[:inherited => true].update(opts)
super(opts) + (opts[:inherited] ? inherited_constants : [])
end
# Returns only the constants that were inherited.
#
# @return [Array<ConstantObject>] the list of inherited constant objects
def inherited_constants
inheritance_tree[1..-1].inject([]) do |list, superclass|
if superclass.is_a?(Proxy)
list
else
list += superclass.constants.reject do |o|
child(:name => o.name) || list.find {|o2| o2.name == o.name }
end
end
end
end
# Sets the superclass of the object
#
# @param [Base, Proxy, String, Symbol, nil] object the superclass value
# @return [void]
def superclass=(object)
case object
when Base, Proxy, NilClass
@superclass = object
when String, Symbol
@superclass = Proxy.new(namespace, object)
else
raise ArgumentError, "superclass must be CodeObject, Proxy, String or Symbol"
end
if name == @superclass.name && namespace != YARD::Registry.root && !object.is_a?(Base)
@superclass = Proxy.new(namespace.namespace, object)
end
if @superclass == self
msg = "superclass #{@superclass.inspect} cannot be the same as the declared class #{inspect}"
@superclass = P("::Object")
raise ArgumentError, msg
end
end
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module YARD::CodeObjects
register_separator NSEP, :class_variable
# Represents a class variable inside a namespace. The path is expressed
# in the form "A::B::@@classvariable"
class ClassVariableObject < Base
# @return [String] the class variable's value
attr_accessor :value
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module YARD::CodeObjects
register_separator NSEP, :constant
# A +ConstantObject+ represents a Ruby constant (not a module or class).
# To access the constant's (source code) value, use {#value}.
class ConstantObject < Base
# The source code representing the constant's value
# @return [String] the value the constant is set to
attr_reader :value
def value=(value)
@value = format_source(value)
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module YARD::CodeObjects
# Represents an instance method of a module that was mixed into the class
# scope of another namespace.
#
# @see MethodObject
class ExtendedMethodObject
instance_methods.each {|m| undef_method(m) unless m =~ /^__/ || m.to_sym == :object_id }
# @return [Symbol] always +:class+
def scope; :class end
# Sets up a delegate for {MethodObject} obj.
#
# @param [MethodObject] obj the instance method to treat as a mixed in
# class method on another namespace.
def initialize(obj) @del = obj end
# Sends all methods to the {MethodObject} assigned in {#initialize}
# @see #initialize
# @see MethodObject
def method_missing(sym, *args, &block) @del.__send__(sym, *args, &block) end
end
end

View File

@ -0,0 +1,134 @@
# frozen_string_literal: true
module YARD::CodeObjects
# An ExtraFileObject represents an extra documentation file (README or other
# file). It is not strictly a CodeObject (does not inherit from `Base`) although
# it implements `path`, `name` and `type`, and therefore should be structurally
# compatible with most CodeObject interfaces.
class ExtraFileObject
attr_accessor :filename
attr_writer :attributes
attr_accessor :name
# @since 0.8.3
attr_reader :locale
# Creates a new extra file object.
# @param [String] filename the location on disk of the file
# @param [String] contents the file contents. If not set, the contents
# will be read from disk using the +filename+.
def initialize(filename, contents = nil)
self.filename = filename
self.name = File.basename(filename).gsub(/\.[^.]+$/, '')
self.attributes = SymbolHash.new(false)
@original_contents = contents
@parsed = false
@locale = nil
ensure_parsed
end
alias path name
def attributes
ensure_parsed
@attributes
end
def title
attributes[:title] || name
end
def contents
ensure_parsed
@contents
end
def contents=(contents)
@original_contents = contents
@parsed = false
end
# @param [String] locale the locale name to be translated.
# @return [void]
# @since 0.8.3
def locale=(locale)
@locale = locale
@parsed = false
end
def inspect
"#<yardoc #{type} #{filename} attrs=#{attributes.inspect}>"
end
alias to_s inspect
def type; :extra_file end
def ==(other)
return false unless self.class === other
other.filename == filename
end
alias eql? ==
alias equal? ==
def hash; filename.hash end
private
def ensure_parsed
return if @parsed
@parsed = true
@contents = parse_contents(@original_contents || File.read(@filename))
end
# @param [String] data the file contents
def parse_contents(data)
retried = false
cut_index = 0
data = translate(data)
data = data.split("\n")
data.each_with_index do |line, index|
case line
when /^#!(\S+)\s*$/
if index == 0
attributes[:markup] = $1
else
cut_index = index
break
end
when /^\s*#\s*@(\S+)\s*(.+?)\s*$/
attributes[$1] = $2
when /^\s*<!--\s*$/, /^\s*-->\s*$/
# Ignore HTML comments
else
cut_index = index
break
end
end
data = data[cut_index..-1] if cut_index > 0
contents = data.join("\n")
if contents.respond_to?(:force_encoding) && attributes[:encoding]
begin
contents.force_encoding(attributes[:encoding])
rescue ArgumentError
log.warn "Invalid encoding `#{attributes[:encoding]}' in #{filename}"
end
end
contents
rescue ArgumentError => e
raise unless e.message =~ /invalid byte sequence/
if retried
# This should never happen.
log.warn "Could not read #{filename}, #{e.message}. You probably want to set `--charset`."
return ''
else
data.force_encoding('binary') if data.respond_to?(:force_encoding)
retried = true
retry
end
end
def translate(data)
text = YARD::I18n::Text.new(data, :have_header => true)
text.translate(YARD::Registry.locale(locale))
end
end
end

View File

@ -0,0 +1,172 @@
# frozen_string_literal: true
require 'ostruct'
module YARD
module CodeObjects
# A MacroObject represents a docstring defined through +@!macro NAME+ and can be
# reused by specifying the tag +@!macro NAME+. You can also provide the
# +attached+ type flag to the macro definition to have it attached to the
# specific DSL method so it will be implicitly reused.
#
# Macros are fully described in the {file:docs/Tags.md#macro Tags Overview}
# document.
#
# @example Creating a basic named macro
# # @!macro prop
# # @!method $1(${3-})
# # @return [$2] the value of the $0
# property :foo, String, :a, :b
#
# # @!macro prop
# property :bar, Numeric, :value
#
# @example Creating a macro that is attached to the method call
# # @!macro [attach] prop2
# # @!method $1(value)
# property :foo
#
# # Extra data added to docstring
# property :bar
class MacroObject < Base
MACRO_MATCH = /(\\)?\$(?:\{(-?\d+|\*)(-)?(-?\d+)?\}|(-?\d+|\*))/
class << self
# Creates a new macro and fills in the relevant properties.
# @param [String] macro_name the name of the macro, must be unique.
# @param [String] data the data the macro should expand when re-used
# @param [CodeObjects::Base] method_object an object to attach this
# macro to. If supplied, {#attached?} will be true
# @return [MacroObject] the newly created object
def create(macro_name, data, method_object = nil)
obj = new(:root, macro_name)
obj.macro_data = data
obj.method_object = method_object
obj
end
# Finds a macro using +macro_name+
# @param [#to_s] macro_name the name of the macro
# @return [MacroObject] if a macro is found
# @return [nil] if there is no registered macro by that name
def find(macro_name)
Registry.at('.macro.' + macro_name.to_s)
end
# Parses a given docstring and determines if the macro is "new" or
# not. If the macro has $variable names or if it has a @!macro tag
# with the [new] or [attached] flag, it is considered new.
#
# If a new macro is found, the macro is created and registered. Otherwise
# the macro name is searched and returned. If a macro is not found,
# nil is returned.
#
# @param [#to_s] macro_name the name of the macro
# @param [CodeObjects::Base] method_object an optional method to attach
# the macro to. Only used if the macro is being created, otherwise
# this argument is ignored.
# @return [MacroObject] the newly created or existing macro, depending
# on whether the @!macro tag was a new tag or not.
# @return [nil] if the +data+ has no macro tag or if the macro is
# not new and no macro by the macro name is found.
def find_or_create(macro_name, data, method_object = nil)
find(name) || create(macro_name, data, method_object)
end
alias create_docstring find_or_create
# Expands +macro_data+ using the interpolation parameters.
#
# Interpolation rules:
# * $0, $1, $2, ... = the Nth parameter in +call_params+
# * $* = the full statement source (excluding block)
# * Also supports $!{N-M} ranges, as well as negative indexes on N or M
# * Use \$ to escape the variable name in a macro.
#
# @!macro [new] macro.expand
# @param [Array<String>] call_params the method name and parameters
# to the method call. These arguments will fill \$0-N
# @param [String] full_source the full source line (excluding block)
# interpolated as \$*
# @param [String] block_source Currently unused. Will support
# interpolating the block data as a variable.
# @return [String] the expanded macro data
# @param [String] macro_data the macro data to expand (taken from {#macro_data})
def expand(macro_data, call_params = [], full_source = '', block_source = '') # rubocop:disable Lint/UnusedMethodArgument
macro_data = macro_data.all if macro_data.is_a?(Docstring)
macro_data.gsub(MACRO_MATCH) do
escape = $1
first = $2 || $5
last = $4
rng = $3 ? true : false
next $&[1..-1] if escape
if first == '*'
last ? $& : full_source
else
first_i = first.to_i
last_i = (last ? last.to_i : call_params.size)
last_i = first_i unless rng
params = call_params[first_i..last_i]
params ? params.join(", ") : ''
end
end
end
# Applies a macro on a docstring by creating any macro data inside of
# the docstring first. Equivalent to calling {find_or_create} and {apply_macro}
# on the new macro object.
#
# @param [Docstring] docstring the docstring to create a macro out of
# @!macro macro.expand
# @see find_or_create
def apply(docstring, call_params = [], full_source = '', block_source = '', _method_object = nil) # rubocop:disable Lint/UnusedMethodArgument
docstring = docstring.all if Docstring === docstring
parser = Docstring.parser
handler = OpenStruct.new
handler.call_params = call_params[1..-1]
handler.caller_method = call_params.first
handler.statement = OpenStruct.new(:source => full_source)
parser.parse(docstring, nil, handler).to_docstring.to_raw
end
# Applies a macro to a docstring, interpolating the macro's data on the
# docstring and appending any extra local docstring data that was in
# the original +docstring+ object.
#
# @param [MacroObject] macro the macro object
# @!macro macro.expand
def apply_macro(macro, docstring, call_params = [], full_source = '', block_source = '') # rubocop:disable Lint/UnusedMethodArgument
apply(docstring, call_params, full_source, block_source)
end
end
# @return [String] the macro data stored on the object
attr_accessor :macro_data
# @return [CodeObjects::Base] the method object that this macro is
# attached to.
attr_accessor :method_object
# @return [Boolean] whether this macro is attached to a method
def attached?; method_object ? true : false end
# Overrides {Base#path} so the macro path is ".macro.MACRONAME"
def path; '.macro.' + name.to_s end
# Overrides the separator to be '.'
def sep; '.' end
# Expands the macro using
# @param [Array<String>] call_params a list of tokens that are passed
# to the method call
# @param [String] full_source the full method call (not including the block)
# @param [String] block_source the source passed in the block of the method
# call, if there is a block.
# @example Expanding a Macro
# macro.expand(%w(property foo bar), 'property :foo, :bar', '') #=>
# "...macro data interpolating this line of code..."
# @see expand
def expand(call_params = [], full_source = '', block_source = '')
self.class.expand(macro_data, call_params, full_source, block_source)
end
end
end
end

View File

@ -0,0 +1,196 @@
# frozen_string_literal: true
module YARD::CodeObjects
register_separator CSEP, :method
register_separator ISEP, :method
# Represents a Ruby method in source
class MethodObject < Base
# The scope of the method (+:class+ or +:instance+)
#
# @return [Symbol] the scope
attr_reader :scope
# Whether the object is explicitly defined in source or whether it was
# inferred by a handler. For instance, attribute methods are generally
# inferred and therefore not explicitly defined in source.
#
# @return [Boolean] whether the object is explicitly defined in source.
attr_accessor :explicit
# Returns the list of parameters parsed out of the method signature
# with their default values.
#
# @return [Array<Array(String, String)>] a list of parameter names followed
# by their default values (or nil)
attr_accessor :parameters
# Creates a new method object in +namespace+ with +name+ and an instance
# or class +scope+
#
# If scope is +:module+, this object is instantiated as a public
# method in +:class+ scope, but also creates a new (empty) method
# as a private +:instance+ method on the same class or module.
#
# @param [NamespaceObject] namespace the namespace
# @param [String, Symbol] name the method name
# @param [Symbol] scope +:instance+, +:class+, or +:module+
def initialize(namespace, name, scope = :instance, &block)
@module_function = false
@scope = nil
# handle module function
if scope == :module
other = self.class.new(namespace, name, &block)
other.visibility = :private
scope = :class
@module_function = true
end
@visibility = :public
self.scope = scope
self.parameters = []
super
end
# Changes the scope of an object from :instance or :class
# @param [Symbol] v the new scope
def scope=(v)
reregister = @scope ? true : false
# handle module function
if v == :module
other = self.class.new(namespace, name)
other.visibility = :private
@visibility = :public
@module_function = true
@path = nil
end
YARD::Registry.delete(self)
@path = nil
@scope = v.to_sym
@scope = :class if @scope == :module
YARD::Registry.register(self) if reregister
end
# @return whether or not the method is the #initialize constructor method
def constructor?
name == :initialize && scope == :instance && namespace.is_a?(ClassObject)
end
# @return [Boolean] whether or not this method was created as a module
# function
# @since 0.8.0
def module_function?
@module_function
end
# Returns the read/writer info for the attribute if it is one
# @return [SymbolHash] if there is information about the attribute
# @return [nil] if the method is not an attribute
# @since 0.5.3
def attr_info
return nil unless namespace.is_a?(NamespaceObject)
namespace.attributes[scope][name.to_s.gsub(/=$/, '')]
end
# @return [Boolean] whether the method is a writer attribute
# @since 0.5.3
def writer?
info = attr_info
info && info[:write] == self ? true : false
end
# @return [Boolean] whether the method is a reader attribute
# @since 0.5.3
def reader?
info = attr_info
info && info[:read] == self ? true : false
end
# Tests if the object is defined as an attribute in the namespace
# @return [Boolean] whether the object is an attribute
def is_attribute?
info = attr_info
if info
read_or_write = name.to_s =~ /=$/ ? :write : :read
info[read_or_write] ? true : false
else
false
end
end
# Tests if the object is defined as an alias of another method
# @return [Boolean] whether the object is an alias
def is_alias?
return false unless namespace.is_a?(NamespaceObject)
namespace.aliases.key? self
end
# Tests boolean {#explicit} value.
#
# @return [Boolean] whether the method is explicitly defined in source
def is_explicit?
explicit ? true : false
end
# @return [MethodObject] the object that this method overrides
# @return [nil] if it does not override a method
# @since 0.6.0
def overridden_method
return nil if namespace.is_a?(Proxy)
meths = namespace.meths(:all => true)
meths.find {|m| m.path != path && m.name == name && m.scope == scope }
end
# Returns all alias names of the object
# @return [Array<MethodObject>] the alias names
def aliases
list = []
return list unless namespace.is_a?(NamespaceObject)
namespace.aliases.each do |o, aname|
list << o if aname == name && o.scope == scope
end
list
end
# Override path handling for instance methods in the root namespace
# (they should still have a separator as a prefix).
# @return [String] the path of a method
def path
@path ||= !namespace || namespace.path == "" ? sep + super : super
end
# Returns the name of the object.
#
# @example The name of an instance method (with prefix)
# an_instance_method.name(true) # => "#mymethod"
# @example The name of a class method (with prefix)
# a_class_method.name(true) # => "mymethod"
# @param [Boolean] prefix whether or not to show the prefix
# @return [String] returns {#sep} + +name+ for an instance method if
# prefix is true
# @return [Symbol] the name without {#sep} if prefix is set to false
def name(prefix = false)
prefix ? (sep == ISEP ? "#{sep}#{super}" : super.to_s) : super
end
# Override separator to differentiate between class and instance
# methods.
# @return [String] "#" for an instance method, "." for class
def sep
if scope == :class
namespace && namespace != YARD::Registry.root ? CSEP : NSEP
else
ISEP
end
end
protected
def copyable_attributes
super - %w(scope module_function)
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module YARD::CodeObjects
register_separator NSEP, :module
# Represents a Ruby module.
class ModuleObject < NamespaceObject
# Returns the inheritance tree of mixins.
#
# @param [Boolean] include_mods if true, will include mixed in
# modules (which is likely what is wanted).
# @return [Array<NamespaceObject>] a list of namespace objects
def inheritance_tree(include_mods = false)
return [self] unless include_mods
[self] + mixins(:instance, :class).map do |m|
next if m == self
next m unless m.respond_to?(:inheritance_tree)
m.inheritance_tree(true)
end.compact.flatten.uniq
end
end
end

View File

@ -0,0 +1,141 @@
# frozen_string_literal: true
module YARD
module CodeObjects
# This module controls registration and accessing of namespace separators
# for {Registry} lookup.
#
# @since 0.9.1
module NamespaceMapper
# @!group Registering a Separator for a Namespace
# Registers a separator with an optional set of valid types that
# must follow the separator lexically.
#
# Calls all callbacks defined by {NamespaceMapper.on_invalidate} after
# the separator is registered.
#
# @param sep [String] the separator string for the namespace
# @param valid_types [Array<Symbol>] a list of object types that
# must follow the separator. If the list is empty, any type can
# follow the separator.
# @example Registering separators for a method object
# # Anything after a "#" denotes a method object
# register_separator "#", :method
# # Anything after a "." denotes a method object
# register_separator ".", :method
# @see .on_invalidate
def register_separator(sep, *valid_types)
NamespaceMapper.invalidate
valid_types.each do |t|
NamespaceMapper.rev_map[t] ||= []
NamespaceMapper.rev_map[t] << sep
end
NamespaceMapper.map[sep] ||= []
NamespaceMapper.map[sep] += valid_types
end
# Unregisters a separator by a type.
#
# @param type [Symbol] the type to unregister
# @see #register_separator
def unregister_separator_by_type(type)
seps = NamespaceMapper.rev_map[type]
return unless seps
seps.each {|s| NamespaceMapper.map.delete(s) }
NamespaceMapper.rev_map.delete(type)
NamespaceMapper.invalidate
end
# Clears the map of separators.
#
# @return [void]
def clear_separators
NamespaceMapper.invalidate
NamespaceMapper.map = {}
NamespaceMapper.rev_map = {}
end
# Gets or sets the default separator value to use when no
# separator for the namespace can be determined.
#
# @param value [String, nil] the default separator, or nil to return the
# value
# @example
# default_separator "::"
def default_separator(value = nil)
if value
NamespaceMapper.invalidate
NamespaceMapper.default_separator = Regexp.quote value
else
NamespaceMapper.default_separator
end
end
# @!group Separator and Type Lookup Helpers
# @return [Array<String>] all of the registered separators
def separators
NamespaceMapper.map.keys
end
# @return [Regexp] the regexp match of all separators
def separators_match
NamespaceMapper.map_match
end
# @param sep [String] the separator to return types for
# @return [Array<Symbol>] a list of types registered to a separator
def types_for_separator(sep)
NamespaceMapper.map[sep] || []
end
# @param type [String] the type to return separators for
# @return [Array<Symbol>] a list of separators registered to a type
def separators_for_type(type)
NamespaceMapper.rev_map[type] || []
end
# Internal methods to act as a singleton registry
class << self
# @!group Invalidation callbacks
# Adds a callback that triggers when a new separator is registered or
# the cache is cleared by invalidation.
def on_invalidate(&block)
(@invalidation_callbacks ||= []).push(block)
end
# @!visibility private
# @return [Hash] a mapping of types to separators
def map
@map ||= {}
end
# @return [Hash] a reverse mapping of separators to types
def rev_map
@rev_map ||= {}
end
# Invalidates all separators
# @return [void]
def invalidate
@map_match = nil
(@invalidation_callbacks || []).each(&:call)
end
# @return [Regexp] the full list of separators as a regexp match
def map_match
@map_match ||= map.keys.map {|k| Regexp.quote k }.join('|')
end
# @return [String] the default separator when no separator can begin
# determined.
attr_accessor :default_separator
end
end
end
end

View File

@ -0,0 +1,200 @@
# frozen_string_literal: true
module YARD::CodeObjects
register_separator NSEP, :namespace
default_separator NSEP
# A "namespace" is any object that can store other objects within itself.
# The two main Ruby objects that can act as namespaces are modules
# ({ModuleObject}) and classes ({ClassObject}).
class NamespaceObject < Base
# @return [Array<String>] a list of ordered group names inside the namespace
# @since 0.6.0
attr_accessor :groups
# The list of objects defined in this namespace
# @return [Array<Base>] a list of objects
attr_reader :children
# A hash containing two keys, class and instance, each containing
# the attribute name with a { :read, :write } hash for the read and
# write objects respectively.
#
# @example The attributes of an object
# >> Registry.at('YARD::Docstring').attributes
# => {
# :class => { },
# :instance => {
# :ref_tags => {
# :read => #<yardoc method YARD::Docstring#ref_tags>,
# :write => nil
# },
# :object => {
# :read => #<yardoc method YARD::Docstring#object>,
# :write => #<yardoc method YARD::Docstring#object=>
# },
# ...
# }
# }
# @return [Hash] a list of methods
attr_reader :attributes
# A hash containing two keys, :class and :instance, each containing
# a hash of objects and their alias names.
# @return [Hash] a list of methods
attr_reader :aliases
# Class mixins
# @return [Array<ModuleObject>] a list of mixins
attr_reader :class_mixins
# Instance mixins
# @return [Array<ModuleObject>] a list of mixins
attr_reader :instance_mixins
# Creates a new namespace object inside +namespace+ with +name+.
# @see Base#initialize
def initialize(namespace, name, *args, &block)
@children = CodeObjectList.new(self)
@class_mixins = CodeObjectList.new(self)
@instance_mixins = CodeObjectList.new(self)
@attributes = SymbolHash[:class => SymbolHash.new, :instance => SymbolHash.new]
@aliases = {}
@groups = []
super
end
# Only the class attributes
# @return [Hash] a list of method names and their read/write objects
# @see #attributes
def class_attributes
attributes[:class]
end
# Only the instance attributes
# @return [Hash] a list of method names and their read/write objects
# @see #attributes
def instance_attributes
attributes[:instance]
end
# Looks for a child that matches the attributes specified by +opts+.
#
# @example Finds a child by name and scope
# namespace.child(:name => :to_s, :scope => :instance)
# # => #<yardoc method MyClass#to_s>
# @return [Base, nil] the first matched child object, or nil
def child(opts = {})
if !opts.is_a?(Hash)
children.find {|o| o.name == opts.to_sym }
else
opts = SymbolHash[opts]
children.find do |obj|
opts.each do |meth, value|
break false unless value.is_a?(Array) ? value.include?(obj[meth]) : obj[meth] == value
end
end
end
end
# Returns all methods that match the attributes specified by +opts+. If
# no options are provided, returns all methods.
#
# @example Finds all private and protected class methods
# namespace.meths(:visibility => [:private, :protected], :scope => :class)
# # => [#<yardoc method MyClass.privmeth>, #<yardoc method MyClass.protmeth>]
# @option opts [Array<Symbol>, Symbol] :visibility ([:public, :private,
# :protected]) the visibility of the methods to list. Can be an array or
# single value.
# @option opts [Array<Symbol>, Symbol] :scope ([:class, :instance]) the
# scope of the methods to list. Can be an array or single value.
# @option opts [Boolean] :included (true) whether to include mixed in
# methods in the list.
# @return [Array<MethodObject>] a list of method objects
def meths(opts = {})
opts = SymbolHash[
:visibility => [:public, :private, :protected],
:scope => [:class, :instance],
:included => true
].update(opts)
opts[:visibility] = [opts[:visibility]].flatten
opts[:scope] = [opts[:scope]].flatten
ourmeths = children.select do |o|
o.is_a?(MethodObject) &&
opts[:visibility].include?(o.visibility) &&
opts[:scope].include?(o.scope)
end
ourmeths + (opts[:included] ? included_meths(opts) : [])
end
# Returns methods included from any mixins that match the attributes
# specified by +opts+. If no options are specified, returns all included
# methods.
#
# @option opts [Array<Symbol>, Symbol] :visibility ([:public, :private,
# :protected]) the visibility of the methods to list. Can be an array or
# single value.
# @option opts [Array<Symbol>, Symbol] :scope ([:class, :instance]) the
# scope of the methods to list. Can be an array or single value.
# @option opts [Boolean] :included (true) whether to include mixed in
# methods in the list.
# @see #meths
def included_meths(opts = {})
opts = SymbolHash[:scope => [:instance, :class]].update(opts)
[opts[:scope]].flatten.map do |scope|
mixins(scope).inject([]) do |list, mixin|
next list if mixin.is_a?(Proxy)
arr = mixin.meths(opts.merge(:scope => :instance)).reject do |o|
next false if opts[:all]
child(:name => o.name, :scope => scope) || list.find {|o2| o2.name == o.name }
end
arr.map! {|o| ExtendedMethodObject.new(o) } if scope == :class
list + arr
end
end.flatten
end
# Returns all constants in the namespace
#
# @option opts [Boolean] :included (true) whether or not to include
# mixed in constants in list
# @return [Array<ConstantObject>] a list of constant objects
def constants(opts = {})
opts = SymbolHash[:included => true].update(opts)
consts = children.select {|o| o.is_a? ConstantObject }
consts + (opts[:included] ? included_constants : [])
end
# Returns constants included from any mixins
# @return [Array<ConstantObject>] a list of constant objects
def included_constants
instance_mixins.inject([]) do |list, mixin|
if mixin.respond_to? :constants
list += mixin.constants.reject do |o|
child(:name => o.name) || list.find {|o2| o2.name == o.name }
end
else
list
end
end
end
# Returns class variables defined in this namespace.
# @return [Array<ClassVariableObject>] a list of class variable objects
def cvars
children.select {|o| o.is_a? ClassVariableObject }
end
# Returns for specific scopes. If no scopes are provided, returns all mixins.
# @param [Array<Symbol>] scopes a list of scopes (:class, :instance) to
# return mixins for. If this is empty, all scopes will be returned.
# @return [Array<ModuleObject>] a list of mixins
def mixins(*scopes)
return class_mixins if scopes == [:class]
return instance_mixins if scopes == [:instance]
class_mixins | instance_mixins
end
end
end

View File

@ -0,0 +1,245 @@
# frozen_string_literal: true
module YARD
module CodeObjects
# A special type of +NoMethodError+ when raised from a {Proxy}
class ProxyMethodError < NoMethodError; end
# @private
PROXY_MATCH = /(?:#{NSEPQ}|#{ISEPQ}|#{CSEPQ})([^#{Regexp.quote(
(NSEP + ISEP + CSEP).split('').uniq.join
)}]+)$/
# The Proxy class is a way to lazily resolve code objects in
# cases where the object may not yet exist. A proxy simply stores
# an unresolved path until a method is called on the object, at which
# point it does a lookup using {Registry.resolve}. If the object is
# not found, a warning is raised and {ProxyMethodError} might be raised.
#
# @example Creates a Proxy to the String class from a module
# # When the String class is parsed this method will
# # begin to act like the String ClassObject.
# Proxy.new(mymoduleobj, "String")
# @see Registry.resolve
# @see ProxyMethodError
class Proxy
def self.===(other) other.is_a?(self) end
attr_reader :namespace
alias parent namespace
# Creates a new Proxy
#
# @raise [ArgumentError] if namespace is not a NamespaceObject
# @return [Proxy] self
def initialize(namespace, name, type = nil)
namespace = Registry.root if !namespace || namespace == :root
if name =~ /^#{NSEPQ}/
namespace = Registry.root
name = name[2..-1]
end
if name =~ PROXY_MATCH
@orignamespace = namespace
@origname = name
@imethod = true if name.include? ISEP
namespace = Proxy.new(namespace, $`) unless $`.empty?
name = $1
else
@orignamespace = nil
@origname = nil
@imethod = nil
end
@name = name.to_sym
@namespace = namespace
@obj = nil
@imethod ||= nil
self.type = type
if @namespace.is_a?(ConstantObject)
unless @namespace.value =~ /\A#{NAMESPACEMATCH}\Z/
raise Parser::UndocumentableError, "constant mapping for " +
"#{@origname} (type=#{type.inspect})"
end
@origname = nil # forget these for a constant
@orignamespace = nil
@namespace = Proxy.new(@namespace.namespace, @namespace.value)
end
unless @namespace.is_a?(NamespaceObject) || @namespace.is_a?(Proxy)
raise ArgumentError, "Invalid namespace object: #{namespace}"
end
# If the name begins with "::" (like "::String")
# this is definitely a root level object, so
# remove the namespace and attach it to the root
if @name =~ /^#{NSEPQ}/
@name.gsub!(/^#{NSEPQ}/, '')
@namespace = Registry.root
end
end
# (see Base#name)
def name(prefix = false)
prefix ? "#{@imethod && ISEP}#{@name}" : @name
end
# Returns a text representation of the Proxy
# @return [String] the object's #inspect method or P(OBJECTPATH)
def inspect
to_obj ? to_obj.inspect : "P(#{path})"
end
# If the proxy resolves to an object, returns its path, otherwise
# guesses at the correct path using the original namespace and name.
#
# @return [String] the assumed path of the proxy (or the real path
# of the resolved object)
def path
to_obj ? to_obj.path : proxy_path
end
alias to_s path
alias to_str path
alias title path
# @return [Boolean]
def is_a?(klass)
to_obj ? to_obj.is_a?(klass) : self.class <= klass
end
# @return [Boolean]
def ===(other)
to_obj ? to_obj === other : self.class <= other.class
end
# @return [Boolean]
def <=>(other)
if other.respond_to? :path
path <=> other.path
else
false
end
end
# @return [Boolean]
def equal?(other)
if other.respond_to? :path
path == other.path
else
false
end
end
alias == equal?
# @return [Integer] the object's hash value (for equality checking)
def hash; path.hash end
# Returns the class name of the object the proxy is mimicking, if
# resolved. Otherwise returns +Proxy+.
# @return [Class] the resolved object's class or +Proxy+
def class
to_obj ? to_obj.class : Proxy
end
# Returns the type of the proxy. If it cannot be resolved at the
# time of the call, it will either return the inferred proxy type
# (see {#type=}) or +:proxy+
# @return [Symbol] the Proxy's type
# @see #type=
def type
to_obj ? to_obj.type : @type || :proxy
end
# Allows a parser to infer the type of the proxy by its path.
# @param [#to_sym] type the proxy's inferred type
# @return [void]
def type=(type) @type = type ? type.to_sym : nil end
# @return [Boolean]
def instance_of?(klass)
self.class == klass
end
# @return [Boolean]
def kind_of?(klass)
self.class <= klass
end
# @return [Boolean]
def respond_to?(meth, include_private = false)
to_obj ? to_obj.respond_to?(meth, include_private) : super
end
# Dispatches the method to the resolved object.
#
# @raise [ProxyMethodError] if the proxy cannot find the real object
def method_missing(meth, *args, &block)
if to_obj
to_obj.__send__(meth, *args, &block)
else
log.warn "Load Order / Name Resolution Problem on #{path}:\n" \
"-\n" \
"Something is trying to call #{meth} on object #{path} before it has been recognized.\n" \
"This error usually means that you need to modify the order in which you parse files\n" \
"so that #{path} is parsed before methods or other objects attempt to access it.\n" \
"-\n" \
"YARD will recover from this error and continue to parse but you *may* have problems\n" \
"with your generated documentation. You should probably fix this.\n" \
"-\n"
begin
super
rescue NoMethodError
raise ProxyMethodError, "Proxy cannot call method ##{meth} on object '#{path}'"
end
end
end
# This class is never a root object
def root?; false end
private
# @note this method fixes a bug in 1.9.2: http://gist.github.com/437136
def to_ary; nil end
# Attempts to find the object that this unresolved object
# references by checking if any objects by this name are
# registered all the way up the namespace tree.
#
# @return [Base, nil] the registered code object or nil
def to_obj
return @obj if @obj
@obj = Registry.resolve(@namespace, (@imethod ? ISEP : '') + @name.to_s, false, false, @type)
if @obj
if @origname && @origname.include?("::") && !@obj.path.include?(@origname)
# the object's path should include the original proxy namespace,
# otherwise it's (probably) not the right object.
@obj = nil
else
@namespace = @obj.namespace
@name = @obj.name
end
end
@obj
end
def proxy_path
if @namespace.root?
(@imethod ? ISEP : "") + name.to_s
elsif @origname
if @origname =~ CONSTANTSTART
@origname
else
[namespace.path, @origname].join
end
elsif name.to_s =~ CONSTANTSTART
name.to_s
else # class meth?
[namespace.path, name.to_s].join(CSEP)
end
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module YARD
module CodeObjects
# Represents the root namespace object (the invisible Ruby module that
# holds all top level modules, class and other objects).
class RootObject < ModuleObject
def path; @path ||= "" end
def inspect; @inspect ||= "#<yardoc root>" end
def root?; true end
def title; 'Top Level Namespace' end
def equal?(other)
other == :root ? true : super(other)
end
def hash; :root.hash end
end
end
end

View File

@ -0,0 +1,270 @@
# frozen_string_literal: true
module YARD
# This class maintains all system-wide configuration for YARD and handles
# the loading of plugins. To access options call {options}, and to load
# a plugin use {load_plugin}. All other public methods are used by YARD
# during load time.
#
# == User Configuration Files
#
# Persistent user configuration files can be stored in the file
# +~/.yard/config+, which is read when YARD first loads. The file should
# be formatted as YAML, and should contain a map of keys and values.
#
# Although you can specify any key-value mapping in the configuration file,
# YARD defines special keys specified in {DEFAULT_CONFIG_OPTIONS}.
#
# An example of a configuration file is listed below:
#
# !!!yaml
# load_plugins: true # Auto-load plugins when YARD starts
# ignored_plugins:
# - yard-broken
# - broken2 # yard- prefix not necessary
# autoload_plugins:
# - yard-rspec
#
# == Automatic Loading of Plugins
#
# YARD 0.6.2 will no longer automatically load all plugins by default. This
# option can be reset by setting 'load_plugins' to true in the configuration
# file. In addition, you can specify a set of specific plugins to load on
# load through the 'autoload_plugins' list setting. This setting is
# independent of the 'load_plugins' value and will always be processed.
#
# == Ignored Plugins File
#
# YARD 0.5 and below used a +~/.yard/ignored_plugins+ file to specify
# plugins to be ignored at load time. Ignored plugins in 0.6.2 and above
# should now be specified in the main configuration file, though YARD
# will support the +ignored_plugins+ file until 0.7.x.
#
# == Safe Mode
#
# YARD supports running in safe-mode. By doing this, it will avoid executing
# any user code such as require files or queries. Plugins will still be
# loaded with safe mode on, because plugins are properly namespaced with
# a 'yard-' prefix, must be installed as a gem, and therefore cannot be
# touched by the user. To specify safe mode, use the +safe_mode+ key.
#
# == Plugin Specific Configuration
#
# Additional settings can be defined within the configuration file
# specifically to provide configuration for a plugin. A plugin that utilizes
# the YARD configuration is strongly encouraged to utilize namespacing of
# their configuration content.
#
# !!!yaml
# load_plugins: true # Auto-load plugins when YARD starts
# ignored_plugins:
# - yard-broken
# - broken2 # yard- prefix not necessary
# autoload_plugins:
# - yard-rspec
# # Plugin Specific Configuration
# yard-sample-plugin:
# show-results-inline: true
#
# As the configuration is available system wide, it can be
# accessed within the plugin code.
#
#
# if YARD::Config.options['yard-sample-plugin'] and
# YARD::Config.options['yard-sample-plugin']['show-results-inline']
# # ... perform the action that places the results inline ...
# else
# # ... do the default behavior of not showing the results inline ...
# end
#
# When accessing the configuration, be aware that this file is user managed
# so configuration keys and values may not be present. Make no assumptions and
# instead ensure that you check for the existence of keys before proceeding to
# retrieve values.
#
# @since 0.6.2
# @see options
class Config
class << self
# The system-wide configuration options for YARD
# @return [SymbolHash] a map a key-value pair settings.
# @see DEFAULT_CONFIG_OPTIONS
attr_accessor :options
end
# The location where YARD stores user-specific settings
CONFIG_DIR = File.expand_path('~/.yard')
# The main configuration YAML file.
CONFIG_FILE = File.join(CONFIG_DIR, 'config')
# File listing all ignored plugins
# @deprecated Set `ignored_plugins` in the {CONFIG_FILE} instead.
IGNORED_PLUGINS = File.join(CONFIG_DIR, 'ignored_plugins')
# Default configuration options
DEFAULT_CONFIG_OPTIONS = {
:load_plugins => false, # Whether to load plugins automatically with YARD
:ignored_plugins => [], # A list of ignored plugins by name
:autoload_plugins => [], # A list of plugins to be automatically loaded
:safe_mode => false # Does not execute or eval any user-level code
}
# The prefix used for YARD plugins. Name your gem with this prefix
# to allow it to be used as a plugin.
YARD_PLUGIN_PREFIX = /^yard[-_]/
# Loads settings from {CONFIG_FILE}. This method is called by YARD at
# load time and should not be called by the user.
# @return [void]
def self.load
self.options = SymbolHash.new(false)
options.update(DEFAULT_CONFIG_OPTIONS)
options.update(read_config_file)
load_commandline_safemode
add_ignored_plugins_file
translate_plugin_names
load_plugins
rescue => e
log.error "Invalid configuration file, using default options."
log.backtrace(e)
options.update(DEFAULT_CONFIG_OPTIONS)
end
# Saves settings to {CONFIG_FILE}.
# @return [void]
def self.save
require 'yaml'
Dir.mkdir(CONFIG_DIR) unless File.directory?(CONFIG_DIR)
File.open(CONFIG_FILE, 'w') {|f| f.write(YAML.dump(options)) }
end
# Loads gems that match the name 'yard-*' (recommended) or 'yard_*' except
# those listed in +~/.yard/ignored_plugins+. This is called immediately
# after YARD is loaded to allow plugin support.
#
# @return [Boolean] true if all plugins loaded successfully, false otherwise.
def self.load_plugins
load_gem_plugins &&
load_autoload_plugins &&
load_commandline_plugins ? true : false
end
# Loads an individual plugin by name. It is not necessary to include the
# +yard-+ plugin prefix here.
#
# @param [String] name the name of the plugin (with or without +yard-+ prefix)
# @return [Boolean] whether the plugin was successfully loaded
def self.load_plugin(name)
name = translate_plugin_name(name)
return false if options[:ignored_plugins].include?(name)
return false if name =~ /^yard-doc-/
log.debug "Loading plugin '#{name}'..."
require name
true
rescue LoadError => e
load_plugin_failed(name, e)
end
# Load gem plugins if :load_plugins is true
def self.load_gem_plugins
return true unless options[:load_plugins]
require 'rubygems'
result = true
YARD::GemIndex.each do |gem|
begin
next true unless gem.name =~ YARD_PLUGIN_PREFIX
load_plugin(gem.name)
rescue Gem::LoadError => e
tmp = load_plugin_failed(gem.name, e)
result = tmp unless tmp
end
end
result
rescue LoadError
log.debug "RubyGems is not present, skipping plugin loading"
false
end
# Load plugins set in :autoload_plugins
def self.load_autoload_plugins
options[:autoload_plugins].each {|name| load_plugin(name) }
end
# Load plugins from {arguments}
def self.load_commandline_plugins
with_yardopts do
arguments.each_with_index do |arg, i|
next unless arg == '--plugin'
load_plugin(arguments[i + 1])
end
end
end
# Check for command-line safe_mode switch in {arguments}
def self.load_commandline_safemode
with_yardopts do
arguments.each_with_index do |arg, _i|
options[:safe_mode] = true if arg == '--safe'
end
end
end
# Print a warning if the plugin failed to load
# @return [false]
def self.load_plugin_failed(name, exception)
log.error "Error loading plugin '#{name}'"
log.backtrace(exception) if $DEBUG
false
end
# Legacy support for {IGNORED_PLUGINS}
def self.add_ignored_plugins_file
if File.file?(IGNORED_PLUGINS)
options[:ignored_plugins] += File.read(IGNORED_PLUGINS).split(/\s+/)
end
end
# Translates plugin names to add yard- prefix.
def self.translate_plugin_names
options[:ignored_plugins].map! {|name| translate_plugin_name(name) }
options[:autoload_plugins].map! {|name| translate_plugin_name(name) }
end
# Loads the YAML configuration file into memory
# @return [Hash] the contents of the YAML file from disk
# @see CONFIG_FILE
def self.read_config_file
if File.file?(CONFIG_FILE)
require 'yaml'
YAML.load_file(CONFIG_FILE)
else
{}
end
end
# Sanitizes and normalizes a plugin name to include the 'yard-' prefix.
# @param [String] name the plugin name
# @return [String] the sanitized and normalized plugin name.
def self.translate_plugin_name(name)
name = name.delete('/') # Security sanitization
name = "yard-" + name unless name =~ YARD_PLUGIN_PREFIX
name
end
# Temporarily loads .yardopts file into @yardopts
def self.with_yardopts
yfile = CLI::Yardoc::DEFAULT_YARDOPTS_FILE
@yardopts = File.file?(yfile) ? File.read_binary(yfile).shell_split : []
result = yield
@yardopts = nil
result
end
# @return [Array<String>] arguments from commandline and yardopts file
def self.arguments
ARGV + @yardopts
end
end
Config.options = Config::DEFAULT_CONFIG_OPTIONS
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class Array
# Places values before or after another object (by value) in
# an array. This is used in tandem with the before and after
# methods of the {Insertion} class.
#
# @example Places an item before another
# [1, 2, 3].place(4).before(3) # => [1, 2, 4, 3]
# @example Places an item after another
# [:a, :b, :c].place(:x).after(:a) # => [:a, :x, :b, :c]
# @param [Array] values value to insert
# @return [Insertion] an insertion object to
# @see Insertion#before
# @see Insertion#after
def place(*values) Insertion.new(self, values) end
end

View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
require 'fileutils'
class File
RELATIVE_PARENTDIR = '..'
RELATIVE_SAMEDIR = '.'
# @group Manipulating Paths
# Turns a path +to+ into a relative path from starting
# point +from+. The argument +from+ is assumed to be
# a filename. To treat it as a directory, make sure it
# ends in +File::SEPARATOR+ ('/' on UNIX filesystems).
#
# @param [String] from the starting filename
# (or directory with +from_isdir+ set to +true+).
# @param [String] to the final path that should be made relative.
# @return [String] the relative path from +from+ to +to+.
def self.relative_path(from, to)
from = expand_path(from).split(SEPARATOR)
to = expand_path(to).split(SEPARATOR)
from.length.times do
break if from[0] != to[0]
from.shift; to.shift
end
from.pop
join(*(from.map { RELATIVE_PARENTDIR } + to))
end
# Cleans a path by removing extraneous '..', '.' and '/' characters
#
# @example Clean a path
# File.cleanpath('a/b//./c/../e') # => "a/b/e"
# @param [String] path the path to clean
# @param [Boolean] rel_root allows relative path above root value
# @return [String] the sanitized path
def self.cleanpath(path, rel_root = false)
path = path.split(SEPARATOR)
path = path.inject([]) do |acc, comp|
next acc if comp == RELATIVE_SAMEDIR
if comp == RELATIVE_PARENTDIR && !acc.empty? && acc.last != RELATIVE_PARENTDIR
acc.pop
next acc
elsif !rel_root && comp == RELATIVE_PARENTDIR && acc.empty?
next acc
end
acc << comp
end
File.join(*path)
end
# @group Reading Files
# Forces opening a file (for writing) by first creating the file's directory
# @param [String] file the filename to open
# @since 0.5.2
def self.open!(file, *args, &block)
dir = dirname(file)
FileUtils.mkdir_p(dir) unless directory?(dir)
open(file, *args, &block)
end
# Reads a file with binary encoding
# @return [String] the ascii-8bit encoded data
# @since 0.5.3
def self.read_binary(file)
File.open(file, 'rb', &:read)
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class Hash
class << self
def create(*args)
if args.first.is_a?(Array) && args.size == 1
obj = new
args.first.each {|k, v| obj[k] = v }
obj
else
create_186(*args)
end
end
alias create_186 []
alias [] create
end
end if RUBY_VERSION < "1.8.7"

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
# The Insertion class inserts a value before or after another
# value in a list.
#
# @example
# Insertion.new([1, 2, 3], 4).before(3) # => [1, 2, 4, 3]
class Insertion
# Creates an insertion object on a list with a value to be
# inserted. To finalize the insertion, call {#before} or
# {#after} on the object.
#
# @param [Array] list the list to perform the insertion on
# @param [Object] value the value to insert
def initialize(list, value)
@list = list
@values = (Array === value ? value : [value])
end
# Inserts the value before +val+
# @param [Object] val the object the value will be inserted before
# @param [Boolean] recursive look inside sublists
def before(val, recursive = false) insertion(val, 0, recursive) end
# Inserts the value after +val+.
#
# @example If subsections are ignored
# Insertion.new([1, [2], 3], :X).after(1) # => [1, [2], :X, 3]
# @param [Object] val the object the value will be inserted after
# @param [Boolean] recursive look inside sublists
def after(val, recursive = false) insertion(val, 1, recursive) end
# Alias for {#before} with +recursive+ set to true
# @since 0.6.0
def before_any(val) insertion(val, 0, true) end
# Alias for {#after} with +recursive+ set to true
# @since 0.6.0
def after_any(val) insertion(val, 1, true) end
private
# This method performs the actual insertion
#
# @param [Object] val the value to insert
# @param [Fixnum] rel the relative index (0 or 1) of where the object
# should be placed
# @param [Boolean] recursive look inside sublists
# @param [Array] list the list to place objects into
def insertion(val, rel, recursive = false, list = @list)
if recursive
list.each do |item|
next unless item.is_a?(Array)
tmp = item.dup
insertion(val, rel, recursive, item)
return(list) unless item == tmp
end
end
index = list.index(val)
list[index + rel, 0] = @values if index
list
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class Module
# Returns the class name of a full module namespace path
#
# @example
# module A::B::C; class_name end # => "C"
# @return [String] the last part of a module path
def class_name
name.split("::").last
end
end

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
class String
# Splits text into tokens the way a shell would, handling quoted
# text as a single token. Use '\"' and "\'" to escape quotes and
# '\\' to escape a backslash.
#
# @return [Array] an array representing the tokens
def shell_split
out = [String.new("")]
state = :none
escape_next = false
quote = String.new("")
strip.split(//).each do |char|
case state
when :none, :space
case char
when /\s/
out << String.new("") unless state == :space
state = :space
escape_next = false
when "\\"
if escape_next
out.last << char
escape_next = false
else
escape_next = true
end
when '"', "'"
if escape_next
out.last << char
escape_next = false
else
state = char
quote = String.new("")
end
else
state = :none
out.last << char
escape_next = false
end
when '"', "'"
case char
when '"', "'"
if escape_next
quote << char
escape_next = false
elsif char == state
out.last << quote
state = :none
else
quote << char
end
when '\\'
if escape_next
quote << char
escape_next = false
else
escape_next = true
end
else
quote << char
escape_next = false
end
end
end
out
end
end

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
# A subclass of Hash where all keys are converted into Symbols, and
# optionally, all String values are converted into Symbols.
class SymbolHash < Hash
# Creates a new SymbolHash object
#
# @param [Boolean] symbolize_value converts any String values into Symbols
# if this is set to +true+.
def initialize(symbolize_value = true)
@symbolize_value = symbolize_value
end
# @overload [](hash)
# Creates a SymbolHash object from an existing Hash
#
# @example
# SymbolHash['x' => 1, :y => 2] # => #<SymbolHash:0x...>
# @param [Hash] hash the hash object
# @return [SymbolHash] a new SymbolHash from a hash object
#
# @overload [](*list)
# Creates a SymbolHash from an even list of keys and values
#
# @example
# SymbolHash[key1, value1, key2, value2, ...]
# @param [Array] list an even list of key followed by value
# @return [SymbolHash] a new SymbolHash object
def self.[](*hsh)
obj = new
if hsh.size == 1 && hsh.first.is_a?(Hash)
hsh.first.each {|k, v| obj[k] = v }
else
0.step(hsh.size, 2) {|n| obj[hsh[n]] = hsh[n + 1] }
end
obj
end
# Assigns a value to a symbolized key
# @param [#to_sym] key the key
# @param [Object] value the value to be assigned. If this is a String and
# values are set to be symbolized, it will be converted into a Symbol.
def []=(key, value)
super(key.to_sym, value.instance_of?(String) && @symbolize_value ? value.to_sym : value)
end
# Accessed a symbolized key
# @param [#to_sym] key the key to access
# @return [Object] the value associated with the key
def [](key) super(key.to_sym) end
# Deleted a key and value associated with it
# @param [#to_sym] key the key to delete
# @return [void]
def delete(key) super(key.to_sym) end
# Tests if a symbolized key exists
# @param [#to_sym] key the key to test
# @return [Boolean] whether the key exists
def key?(key) super(key.to_sym) end
alias has_key? key?
# Updates the object with the contents of another Hash object.
# This method modifies the original SymbolHash object
#
# @param [Hash] hash the hash object to copy the values from
# @return [SymbolHash] self
def update(hash) hash.each {|k, v| self[k] = v }; self end
alias merge! update
# Merges the contents of another hash into a new SymbolHash object
#
# @param [Hash] hash the hash of objects to copy
# @return [SymbolHash] a new SymbolHash containing the merged data
def merge(hash) dup.merge!(hash) end
end

View File

@ -0,0 +1,386 @@
# frozen_string_literal: true
module YARD
# A documentation string, or "docstring" for short, encapsulates the
# comments and metadata, or "tags", of an object. Meta-data is expressed
# in the form +@tag VALUE+, where VALUE can span over multiple lines as
# long as they are indented. The following +@example+ tag shows how tags
# can be indented:
#
# # @example My example
# # a = "hello world"
# # a.reverse
# # @version 1.0
#
# Tags can be nested in a documentation string, though the {Tags::Tag}
# itself is responsible for parsing the inner tags.
class Docstring < String
class << self
# @note Plugin developers should make sure to reset this value
# after parsing finishes. This can be done via the
# {Parser::SourceParser.after_parse_list} callback. This will
# ensure that YARD can properly parse multiple projects in
# the same process.
# @return [Class<DocstringParser>] the parser class used to parse
# text and optional meta-data from docstrings. Defaults to
# {DocstringParser}.
# @see DocstringParser
# @see Parser::SourceParser.after_parse_list
attr_accessor :default_parser
# Creates a parser object using the current {default_parser}.
# Equivalent to:
# Docstring.default_parser.new(*args)
# @param args arguments are passed to the {DocstringParser}
# class. See {DocstringParser#initialize} for details on
# arguments.
# @return [DocstringParser] the parser object used to parse a
# docstring.
def parser(*args) default_parser.new(*args) end
end
self.default_parser = DocstringParser
# @return [Array<Tags::RefTag>] the list of reference tags
attr_reader :ref_tags
# @return [CodeObjects::Base] the object that owns the docstring.
attr_accessor :object
# @return [Range] line range in the {#object}'s file where the docstring was parsed from
attr_accessor :line_range
# @return [String] the raw documentation (including raw tag text)
attr_reader :all
# @return [Boolean] whether the docstring was started with "##"
attr_reader :hash_flag
def hash_flag=(v) @hash_flag = v.nil? ? false : v end
# Matches a tag at the start of a comment line
# @deprecated Use {DocstringParser::META_MATCH}
META_MATCH = DocstringParser::META_MATCH
# @group Creating a Docstring Object
# Creates a new docstring without performing any parsing through
# a {DocstringParser}. This method is called by +DocstringParser+
# when creating the new docstring object.
#
# @param [String] text the textual portion of the docstring
# @param [Array<Tags::Tag>] tags the list of tag objects in the docstring
# @param [CodeObjects::Base, nil] object the object associated with the
# docstring. May be nil.
# @param [String] raw_data the complete docstring, including all
# original formatting and any unparsed tags/directives.
# @param [CodeObjects::Base, nil] ref_object a reference object used for
# the base set of documentation / tag information.
def self.new!(text, tags = [], object = nil, raw_data = nil, ref_object = nil)
docstring = allocate
docstring.replace(text, false)
docstring.object = object
docstring.add_tag(*tags)
docstring.instance_variable_set("@unresolved_reference", ref_object)
docstring.instance_variable_set("@all", raw_data) if raw_data
docstring
end
# Creates a new docstring with the raw contents attached to an optional
# object. Parsing will be done by the {DocstringParser} class.
#
# @note To properly parse directives with proper parser context within
# handlers, you should not use this method to create a Docstring.
# Instead, use the {parser}, which takes a handler object that
# can pass parser state onto directives. If a Docstring is created
# with this method, directives do not have access to any parser
# state, and may not function as expected.
# @example
# Docstring.new("hello world\n@return Object return", someobj)
#
# @param [String] content the raw comments to be parsed into a docstring
# and associated meta-data.
# @param [CodeObjects::Base] object an object to associate the docstring
# with.
def initialize(content = '', object = nil)
@object = object
@summary = nil
@hash_flag = false
self.all = content
end
# Adds another {Docstring}, copying over tags.
#
# @param [Docstring, String] other the other docstring (or string) to
# add.
# @return [Docstring] a new docstring with both docstrings combines
def +(other)
case other
when Docstring
Docstring.new([all, other.all].join("\n"), object)
else
super
end
end
def to_s
resolve_reference
super
end
# Replaces the docstring with new raw content. Called by {#all=}.
# @param [String] content the raw comments to be parsed
def replace(content, parse = true)
content = content.join("\n") if content.is_a?(Array)
@tags = []
@ref_tags = []
if parse
super(parse_comments(content))
else
@all = content
@unresolved_reference = nil
super(content)
end
end
alias all= replace
# Deep-copies a docstring
#
# @note This method creates a new docstring with new tag lists, but does
# not create new individual tags. Modifying the tag objects will still
# affect the original tags.
# @return [Docstring] a new copied docstring
# @since 0.7.0
def dup
resolve_reference
obj = super
%w(all summary tags ref_tags).each do |name|
val = instance_variable_defined?("@#{name}") && instance_variable_get("@#{name}")
obj.instance_variable_set("@#{name}", val ? val.dup : nil)
end
obj
end
# @endgroup
# @return [Fixnum] the first line of the {#line_range}
# @return [nil] if there is no associated {#line_range}
def line
line_range ? line_range.first : nil
end
# Gets the first line of a docstring to the period or the first paragraph.
# @return [String] The first line or paragraph of the docstring; always ends with a period.
def summary
resolve_reference
return @summary if defined?(@summary) && @summary
stripped = gsub(/[\r\n](?![\r\n])/, ' ').strip
num_parens = 0
idx = length.times do |index|
case stripped[index, 1]
when "."
next_char = stripped[index + 1, 1].to_s
break index - 1 if num_parens <= 0 && next_char =~ /^\s*$/
when "\r", "\n"
next_char = stripped[index + 1, 1].to_s
if next_char =~ /^\s*$/
break stripped[index - 1, 1] == '.' ? index - 2 : index - 1
end
when "{", "(", "["
num_parens += 1
when "}", ")", "]"
num_parens -= 1
end
end
@summary = stripped[0..idx]
if !@summary.empty? && @summary !~ /\A\s*\{include:.+\}\s*\Z/
@summary += '.'
end
@summary
end
# Reformats and returns a raw representation of the tag data using the
# current tag and docstring data, not the original text.
#
# @return [String] the updated raw formatted docstring data
# @since 0.7.0
# @todo Add Tags::Tag#to_raw and refactor
def to_raw
tag_data = tags.map do |tag|
case tag
when Tags::OverloadTag
tag_text = "@#{tag.tag_name} #{tag.signature}\n"
unless tag.docstring.blank?
tag_text += "\n " + tag.docstring.all.gsub(/\r?\n/, "\n ")
end
when Tags::OptionTag
tag_text = "@#{tag.tag_name} #{tag.name}"
tag_text += ' [' + tag.pair.types.join(', ') + ']' if tag.pair.types
tag_text += ' ' + tag.pair.name.to_s if tag.pair.name
tag_text += "\n " if tag.name && tag.text
tag_text += ' (' + tag.pair.defaults.join(', ') + ')' if tag.pair.defaults
tag_text += " " + tag.pair.text.strip.gsub(/\n/, "\n ") if tag.pair.text
else
tag_text = '@' + tag.tag_name
tag_text += ' [' + tag.types.join(', ') + ']' if tag.types
tag_text += ' ' + tag.name.to_s if tag.name
tag_text += "\n " if tag.name && tag.text
tag_text += ' ' + tag.text.strip.gsub(/\n/, "\n ") if tag.text
end
tag_text
end
[strip, tag_data.join("\n")].reject(&:empty?).compact.join("\n")
end
# @group Creating and Accessing Meta-data
# Adds a tag or reftag object to the tag list. If you want to parse
# tag data based on the {Tags::DefaultFactory} tag factory, use
# {DocstringParser} instead.
#
# @param [Tags::Tag, Tags::RefTag] tags list of tag objects to add
# @return [void]
def add_tag(*tags)
tags.each_with_index do |tag, i|
case tag
when Tags::Tag
tag.object = object
@tags << tag
when Tags::RefTag, Tags::RefTagList
@ref_tags << tag
else
raise ArgumentError, "expected Tag or RefTag, got #{tag.class} (at index #{i})"
end
end
end
# Convenience method to return the first tag
# object in the list of tag objects of that name
#
# @example
# doc = Docstring.new("@return zero when nil")
# doc.tag(:return).text # => "zero when nil"
#
# @param [#to_s] name the tag name to return data for
# @return [Tags::Tag] the first tag in the list of {#tags}
def tag(name)
tags.find {|tag| tag.tag_name.to_s == name.to_s }
end
# Returns a list of tags specified by +name+ or all tags if +name+ is not specified.
#
# @param [#to_s] name the tag name to return data for, or nil for all tags
# @return [Array<Tags::Tag>] the list of tags by the specified tag name
def tags(name = nil)
list = stable_sort_by(@tags + convert_ref_tags, &:tag_name)
return list unless name
list.select {|tag| tag.tag_name.to_s == name.to_s }
end
# Returns true if at least one tag by the name +name+ was declared
#
# @param [String] name the tag name to search for
# @return [Boolean] whether or not the tag +name+ was declared
def has_tag?(name)
tags.any? {|tag| tag.tag_name.to_s == name.to_s }
end
# Delete all tags with +name+
# @param [String] name the tag name
# @return [void]
# @since 0.7.0
def delete_tags(name)
delete_tag_if {|tag| tag.tag_name.to_s == name.to_s }
end
# Deletes all tags where the block returns true
# @yieldparam [Tags::Tag] tag the tag that is being tested
# @yieldreturn [Boolean] true if the tag should be deleted
# @return [void]
# @since 0.7.0
def delete_tag_if(&block)
@tags.delete_if(&block)
@ref_tags.delete_if(&block)
end
# Returns true if the docstring has no content that is visible to a template.
#
# @param [Boolean] only_visible_tags whether only {Tags::Library.visible_tags}
# should be checked, or if all tags should be considered.
# @return [Boolean] whether or not the docstring has content
def blank?(only_visible_tags = true)
if only_visible_tags
empty? && !tags.any? {|tag| Tags::Library.visible_tags.include?(tag.tag_name.to_sym) }
else
empty? && @tags.empty? && @ref_tags.empty?
end
end
# @endgroup
# Resolves unresolved other docstring reference if there is
# unresolved reference. Does nothing if there is no unresolved
# reference.
#
# Normally, you don't need to call this method
# explicitly. Resolving unresolved reference is done implicitly.
#
# @return [void]
def resolve_reference
loop do
return if defined?(@unresolved_reference).nil? || @unresolved_reference.nil?
return if CodeObjects::Proxy === @unresolved_reference
reference = @unresolved_reference
@unresolved_reference = nil
self.all = [reference.docstring.all, @all].join("\n")
end
end
private
# Maps valid reference tags
#
# @return [Array<Tags::RefTag>] the list of valid reference tags
def convert_ref_tags
list = @ref_tags.reject {|t| CodeObjects::Proxy === t.owner }
@ref_tag_recurse_count ||= 0
@ref_tag_recurse_count += 1
if @ref_tag_recurse_count > 2
log.error "#{@object.file}:#{@object.line}: Detected circular reference tag in " \
"`#{@object}', ignoring all reference tags for this object " \
"(#{@ref_tags.map {|t| "@#{t.tag_name}" }.join(", ")})."
@ref_tags = []
return @ref_tags
end
list = list.map(&:tags).flatten
@ref_tag_recurse_count -= 1
list
end
# Parses out comments split by newlines into a new code object
#
# @param [String] comments
# the newline delimited array of comments. If the comments
# are passed as a String, they will be split by newlines.
#
# @return [String] the non-metadata portion of the comments to
# be used as a docstring
def parse_comments(comments)
parser = self.class.parser
parser.parse(comments, object)
@all = parser.raw_text
@unresolved_reference = parser.reference
add_tag(*parser.tags)
parser.text
end
# A stable sort_by method.
#
# @param list [Enumerable] the list to sort.
# @return [Array] a stable sorted list.
def stable_sort_by(list)
list.each_with_index.sort_by {|tag, i| [yield(tag), i] }.map(&:first)
end
end
end

View File

@ -0,0 +1,345 @@
# frozen_string_literal: true
require 'ostruct'
module YARD
# Parses text and creates a {Docstring} object to represent documentation
# for a {CodeObjects::Base}. To create a new docstring, you should initialize
# the parser and call {#parse} followed by {#to_docstring}.
#
# == Subclassing Notes
#
# The DocstringParser can be subclassed and subtituted during parsing by
# setting the {Docstring.default_parser} attribute with the name of the
# subclass. This allows developers to change the way docstrings are
# parsed, allowing for completely different docstring syntaxes.
#
# @example Creating a Docstring with a DocstringParser
# DocstringParser.new.parse("text here").to_docstring
# @example Creating a Custom DocstringParser
# # Parses docstrings backwards!
# class ReverseDocstringParser
# def parse_content(content)
# super(content.reverse)
# end
# end
#
# # Set the parser as default when parsing
# YARD::Docstring.default_parser = ReverseDocstringParser
# @see #parse_content
# @since 0.8.0
class DocstringParser
# @return [String] the parsed text portion of the docstring,
# with tags removed.
attr_accessor :text
# @return [String] the complete input string to the parser.
attr_accessor :raw_text
# @return [Array<Tags::Tag>] the list of meta-data tags identified
# by the parser
attr_accessor :tags
# @return [Array<Tags::Directive>] a list of directives identified
# by the parser. This list will not be passed on to the
# Docstring object.
attr_accessor :directives
# @return [OpenStruct] any arbitrary state to be passed between
# tags during parsing. Mainly used by directives to coordinate
# behaviour (so that directives can be aware of other directives
# used in a docstring).
attr_accessor :state
# @return [CodeObjects::Base, nil] the object associated with
# the docstring being parsed. May be nil if the docstring is
# not attached to any object.
attr_accessor :object
# @return [CodeObjects::Base, nil] the object referenced by
# the docstring being parsed. May be nil if the docstring doesn't
# refer to any object.
attr_accessor :reference
# @return [Handlers::Base, nil] the handler parsing this
# docstring. May be nil if this docstring parser is not
# initialized through
attr_accessor :handler
# @return [Tags::Library] the tag library being used to
# identify registered tags in the docstring.
attr_accessor :library
# The regular expression to match the tag syntax
META_MATCH = /^@(!)?((?:\w\.?)+)(?:\s+(.*))?$/i
# @!group Creation and Conversion Methods
# Creates a new parser to parse docstring data
#
# @param [Tags::Library] library a tag library for recognizing
# tags.
def initialize(library = Tags::Library.instance)
@text = ""
@raw_text = ""
@tags = []
@directives = []
@library = library
@object = nil
@reference = nil
@handler = nil
@state = OpenStruct.new
end
# @return [Docstring] translates parsed text into
# a Docstring object.
def to_docstring
Docstring.new!(text, tags, object, raw_text, reference)
end
# @!group Parsing Methods
# Parses all content and returns itself.
#
# @param [String] content the docstring text to parse
# @param [CodeObjects::Base] object the object that the docstring
# is attached to. Will be passed to directives to act on
# this object.
# @param [Handlers::Base, nil] handler the handler object that is
# parsing this object. May be nil if this parser is not being
# called from a {Parser::SourceParser} context.
# @return [self] the parser object. To get the docstring,
# call {#to_docstring}.
# @see #to_docstring
def parse(content, object = nil, handler = nil)
@object = object
@handler = handler
@reference, @raw_text = detect_reference(content)
text = parse_content(@raw_text)
@text = text.strip
call_directives_after_parse
post_process
self
end
# Parses a given block of text.
#
# @param [String] content the content to parse
# @note Subclasses can override this method to perform custom
# parsing of content data.
def parse_content(content)
content = content.split(/\r?\n/) if content.is_a?(String)
return '' if !content || content.empty?
docstring = String.new("")
indent = content.first[/^\s*/].length
last_indent = 0
orig_indent = 0
directive = false
last_line = ""
tag_name = nil
tag_buf = []
(content + ['']).each_with_index do |line, index|
indent = line[/^\s*/].length
empty = (line =~ /^\s*$/ ? true : false)
done = content.size == index
if tag_name && (((indent < orig_indent && !empty) || done ||
(indent == 0 && !empty)) || (indent <= last_indent && line =~ META_MATCH))
buf = tag_buf.join("\n")
if directive || tag_is_directive?(tag_name)
directive = create_directive(tag_name, buf)
if directive
docstring << parse_content(directive.expanded_text).chomp
end
else
create_tag(tag_name, buf)
end
tag_name = nil
tag_buf = []
directive = false
orig_indent = 0
end
# Found a meta tag
if line =~ META_MATCH
directive = $1
tag_name = $2
tag_buf = [($3 || '')]
elsif tag_name && indent >= orig_indent && !empty
orig_indent = indent if orig_indent == 0
# Extra data added to the tag on the next line
last_empty = last_line =~ /^[ \t]*$/ ? true : false
tag_buf << '' if last_empty
tag_buf << line.gsub(/^[ \t]{#{orig_indent}}/, '')
elsif !tag_name
# Regular docstring text
docstring << line
docstring << "\n"
end
last_indent = indent
last_line = line
end
docstring
end
# @!group Parser Callback Methods
# Call post processing callbacks on parser.
# This is called implicitly by parser. Use this when
# manually configuring a {Docstring} object.
#
# @return [void]
def post_process
call_after_parse_callbacks
end
# @!group Tag Manipulation Methods
# Creates a tag from the {Tags::DefaultFactory tag factory}.
#
# To add an already created tag object, append it to {#tags}.
#
# @param [String] tag_name the tag name
# @param [String] tag_buf the text attached to the tag with newlines removed.
# @return [Tags::Tag, Tags::RefTag] a tag
def create_tag(tag_name, tag_buf = '')
if tag_buf =~ /\A\s*(?:(\S+)\s+)?\(\s*see\s+(\S+)\s*\)\s*\Z/
return create_ref_tag(tag_name, $1, $2)
end
if library.has_tag?(tag_name)
@tags += [library.tag_create(tag_name, tag_buf)].flatten
else
log.warn "Unknown tag @#{tag_name}" +
(object ? " in file `#{object.file}` near line #{object.line}" : "")
end
rescue Tags::TagFormatError
log.warn "Invalid tag format for @#{tag_name}" +
(object ? " in file `#{object.file}` near line #{object.line}" : "")
end
# Creates a {Tags::RefTag}
def create_ref_tag(tag_name, name, object_name)
@tags << Tags::RefTagList.new(tag_name, P(object, object_name), name)
end
# Creates a new directive using the registered {#library}
# @return [Tags::Directive] the directive object that is created
def create_directive(tag_name, tag_buf)
if library.has_directive?(tag_name)
dir = library.directive_create(tag_name, tag_buf, self)
if dir.is_a?(Tags::Directive)
@directives << dir
dir
end
else
log.warn "Unknown directive @!#{tag_name}" +
(object ? " in file `#{object.file}` near line #{object.line}" : "")
nil
end
rescue Tags::TagFormatError
log.warn "Invalid directive format for @!#{tag_name}" +
(object ? " in file `#{object.file}` near line #{object.line}" : "")
nil
end
# Backward compatibility to detect old tags that should be specified
# as directives in 0.8 and onward.
def tag_is_directive?(tag_name)
list = %w(attribute endgroup group macro method scope visibility)
list.include?(tag_name)
end
# Creates a callback that is called after a docstring is successfully
# parsed. Use this method to perform sanity checks on a docstring's
# tag data, or add any extra tags automatically to a docstring.
#
# @yield [parser] a block to be called after a docstring is parsed
# @yieldparam [DocstringParser] parser the docstring parser object
# with all directives and tags created.
# @yieldreturn [void]
# @return [void]
def self.after_parse(&block)
after_parse_callbacks << block
end
# @return [Array<Proc>] the {after_parse} callback proc objects
def self.after_parse_callbacks
@after_parse_callbacks ||= []
end
# Define a callback to check that @param tags are properly named
after_parse do |parser|
next unless parser.object
next unless parser.object.is_a?(CodeObjects::MethodObject)
next if parser.object.is_alias?
names = parser.object.parameters.map {|l| l.first.gsub(/\W/, '') }
seen_names = []
infile_info = "\n in file `#{parser.object.file}' " \
"near line #{parser.object.line}"
parser.tags.each do |tag|
next if tag.is_a?(Tags::RefTagList) # we don't handle this yet
next unless tag.tag_name == "param"
if seen_names.include?(tag.name)
log.warn "@param tag has duplicate parameter name: " \
"#{tag.name} #{infile_info}"
elsif names.include?(tag.name)
seen_names << tag.name
else
log.warn "@param tag has unknown parameter name: " \
"#{tag.name} #{infile_info}"
end
end
end
private
def namespace
object && object.namespace
end
def detect_reference(content)
if content =~ /\A\s*\(see (\S+)\s*\)(?:\s|$)/
path = $1
extra = $'
[CodeObjects::Proxy.new(namespace, path), extra]
else
[nil, content]
end
end
# @!group Parser Callback Methods
# Calls the {Tags::Directive#after_parse} callback on all the
# created directives.
def call_directives_after_parse
directives.each(&:after_parse)
end
# Calls all {after_parse} callbacks
def call_after_parse_callbacks
self.class.after_parse_callbacks.each do |cb|
cb.call(self)
end
end
# Define a callback to check that @see tags do not use {}.
after_parse do |parser|
next unless parser.object
parser.tags.each_with_index do |tag, i|
next if tag.is_a?(Tags::RefTagList) # we don't handle this yet
next unless tag.tag_name == "see"
next unless "#{tag.name}#{tag.text}" =~ /\A\{.*\}\Z/
infile_info = "\n in file `#{parser.object.file}' " \
"near line #{parser.object.line}"
log.warn "@see tag (##{i + 1}) should not be wrapped in {} " \
"(causes rendering issues): #{infile_info}"
end
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
# Backward compatibility for gem specification lookup
# @see Gem::SourceIndex
module YARD
module GemIndex
module_function
def find_all_by_name(*args)
if defined?(Gem::Specification) && Gem::Specification.respond_to?(:find_all_by_name)
Gem::Specification.find_all_by_name(*args)
else
Gem.source_index.find_name(*args)
end
end
def each(&block)
if defined?(Gem::Specification) && Gem::Specification.respond_to?(:each)
Gem::Specification.each(&block)
else
Gem.source_index.find_name('').each(&block)
end
end
def all
each.to_a
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
# @group Global Convenience Methods
# Shortcut for creating a YARD::CodeObjects::Proxy via a path
#
# @see YARD::CodeObjects::Proxy
# @see YARD::Registry.resolve
def P(namespace, name = nil, type = nil) # rubocop:disable Naming/MethodName
if name.nil?
name = namespace
namespace = nil
end
YARD::Registry.resolve(namespace, name, false, true, type)
end
# The global {YARD::Logger} instance
#
# @return [YARD::Logger] the global {YARD::Logger} instance
# @see YARD::Logger
def log
YARD::Logger.instance
end

View File

@ -0,0 +1,595 @@
# frozen_string_literal: true
module YARD
module Handlers
# Raise this error when a handler should exit before completing.
# The exception will be silenced, allowing the next handler(s) in the
# queue to be executed.
# @since 0.8.4
class HandlerAborted < ::RuntimeError; end
# Raised during processing phase when a handler needs to perform
# an operation on an object's namespace but the namespace could
# not be resolved.
class NamespaceMissingError < Parser::UndocumentableError
# The object the error occurred on
# @return [CodeObjects::Base] a code object
attr_accessor :object
def initialize(object) @object = object end
end
# Handlers are pluggable semantic parsers for YARD's code generation
# phase. They allow developers to control what information gets
# generated by YARD, giving them the ability to, for instance, document
# any Ruby DSLs that a customized framework may use. A good example
# of this would be the ability to document and generate meta data for
# the 'describe' declaration of the RSpec testing framework by simply
# adding a handler for such a keyword. Similarly, any Ruby API that
# takes advantage of class level declarations could add these to the
# documentation in a very explicit format by treating them as first-
# class objects in any outputted documentation.
#
# == Overview of a Typical Handler Scenario
#
# Generally, a handler class will declare a set of statements which
# it will handle using the {handles} class declaration. It will then
# implement the {#process} method to do the work. The processing would
# usually involve the manipulation of the {#namespace}, {#owner}
# {CodeObjects::Base code objects} or the creation of new ones, in
# which case they should be registered by {#register}, a method that
# sets some basic attributes for the new objects.
#
# Handlers are usually simple and take up to a page of code to process
# and register a new object or add new attributes to the current +namespace+.
#
# == Setting up a Handler for Use
#
# A Handler is automatically registered when it is subclassed from the
# base class. The only other thing that needs to be done is to specify
# which statement the handler will process. This is done with the +handles+
# declaration, taking either a {Parser::Ruby::Legacy::RubyToken}, {String} or `Regexp`.
# Here is a simple example which processes module statements.
#
# class MyModuleHandler < YARD::Handlers::Base
# handles TkMODULE
#
# def process
# # do something
# end
# end
#
# == Processing Handler Data
#
# The goal of a specific handler is really up to the developer, and as
# such there is no real guideline on how to process the data. However,
# it is important to know where the data is coming from to be able to use
# it.
#
# === +statement+ Attribute
#
# The +statement+ attribute pertains to the {Parser::Ruby::Legacy::Statement} object
# containing a set of tokens parsed in by the parser. This is the main set
# of data to be analyzed and processed. The comments attached to the statement
# can be accessed by the {Parser::Ruby::Legacy::Statement#comments} method, but generally
# the data to be processed will live in the +tokens+ attribute. This list
# can be converted to a +String+ using +#to_s+ to parse the data with
# regular expressions (or other text processing mechanisms), if needed.
#
# === +namespace+ Attribute
#
# The +namespace+ attribute is a {CodeObjects::NamespaceObject namespace object}
# which represents the current namespace that the parser is in. For instance:
#
# module SomeModule
# class MyClass
# def mymethod; end
# end
# end
#
# If a handler was to parse the 'class MyClass' statement, it would
# be necessary to know that it belonged inside the SomeModule module.
# This is the value that +namespace+ would return when processing such
# a statement. If the class was then entered and another handler was
# called on the method, the +namespace+ would be set to the 'MyClass'
# code object.
#
# === +owner+ Attribute
#
# The +owner+ attribute is similar to the +namespace+ attribute in that
# it also follows the scope of the code during parsing. However, a namespace
# object is loosely defined as a module or class and YARD has the ability
# to parse beyond module and class blocks (inside methods, for instance),
# so the +owner+ attribute would not be limited to modules and classes.
#
# To put this into context, the example from above will be used. If a method
# handler was added to the mix and decided to parse inside the method body,
# the +owner+ would be set to the method object but the namespace would remain
# set to the class. This would allow the developer to process any method
# definitions set inside a method (def x; def y; 2 end end) by adding them
# to the correct namespace (the class, not the method).
#
# In summary, the distinction between +namespace+ and +owner+ can be thought
# of as the difference between first-class Ruby objects (namespaces) and
# second-class Ruby objects (methods).
#
# === +visibility+ and +scope+ Attributes
#
# Mainly needed for parsing methods, the +visibility+ and +scope+ attributes
# refer to the public/protected/private and class/instance values (respectively)
# of the current parsing position.
#
# == Parsing Blocks in Statements
#
# In addition to parsing a statement and creating new objects, some
# handlers may wish to continue parsing the code inside the statement's
# block (if there is one). In this context, a block means the inside
# of any statement, be it class definition, module definition, if
# statement or classic 'Ruby block'.
#
# For example, a class statement would be "class MyClass" and the block
# would be a list of statements including the method definitions inside
# the class. For a class handler, the programmer would execute the
# {#parse_block} method to continue parsing code inside the block, with
# the +namespace+ now pointing to the class object the handler created.
#
# YARD has the ability to continue into any block: class, module, method,
# even if statements. For this reason, the block parsing method must be
# invoked explicitly out of efficiency sake.
#
# @abstract Subclass this class to provide a handler for YARD to use
# during the processing phase.
#
# @see CodeObjects::Base
# @see CodeObjects::NamespaceObject
# @see handles
# @see #namespace
# @see #owner
# @see #register
# @see #parse_block
class Base
# For accessing convenience, eg. "MethodObject"
# instead of the full qualified namespace
include YARD::CodeObjects
include Parser
class << self
# Clear all registered subclasses. Testing purposes only
# @return [void]
def clear_subclasses
@@subclasses = []
end
# Returns all registered handler subclasses.
# @return [Array<Base>] a list of handlers
def subclasses
@@subclasses ||= []
end
def inherited(subclass)
@@subclasses ||= []
@@subclasses << subclass
end
# Declares the statement type which will be processed
# by this handler.
#
# A match need not be unique to a handler. Multiple
# handlers can process the same statement. However,
# in this case, care should be taken to make sure that
# {#parse_block} would only be executed by one of
# the handlers, otherwise the same code will be parsed
# multiple times and slow YARD down.
#
# @param [Parser::Ruby::Legacy::RubyToken, Symbol, String, Regexp] matches
# statements that match the declaration will be
# processed by this handler. A {String} match is
# equivalent to a +/\Astring/+ regular expression
# (match from the beginning of the line), and all
# token matches match only the first token of the
# statement.
#
def handles(*matches)
(@handlers ||= []).concat(matches)
end
# This class is implemented by {Ruby::Base} and {Ruby::Legacy::Base}.
# To implement a base handler class for another language, implement
# this method to return true if the handler should process the given
# statement object. Use {handlers} to enumerate the matchers declared
# for the handler class.
#
# @param statement a statement object or node (depends on language type)
# @return [Boolean] whether or not this handler object should process
# the given statement
def handles?(statement) # rubocop:disable Lint/UnusedMethodArgument
raise NotImplementedError, "override #handles? in a subclass"
end
# @return [Array] a list of matchers for the handler object.
# @see handles?
def handlers
@handlers ||= []
end
# Declares that the handler should only be called when inside a
# {CodeObjects::NamespaceObject}, not a method body.
#
# @return [void]
def namespace_only
@namespace_only = true
end
# @return [Boolean] whether the handler should only be processed inside
# a namespace.
def namespace_only?
@namespace_only ||= false
end
# Declares that a handler should only be called when inside a filename
# by its basename or a regex match for the full path.
#
# @param [String, Regexp] filename a matching filename or regex
# @return [void]
# @since 0.6.2
def in_file(filename)
(@in_files ||= []) << filename
end
# @return [Boolean] whether the filename matches the declared file
# match for a handler. If no file match is specified, returns true.
# @since 0.6.2
def matches_file?(filename)
@in_files ||= nil # avoid ruby warnings
return true unless @in_files
@in_files.any? do |in_file|
case in_file
when String
File.basename(filename) == in_file
when Regexp
filename =~ in_file
else
true
end
end
end
# Generates a +process+ method, equivalent to +def process; ... end+.
# Blocks defined with this syntax will be wrapped inside an anonymous
# module so that the handler class can be extended with mixins that
# override the +process+ method without alias chaining.
#
# @!macro yard.handlers.process
# @!method process
# Main processing callback
# @return [void]
# @see #process
# @return [void]
# @since 0.5.4
def process(&block)
mod = Module.new
mod.send(:define_method, :process, &block)
include mod
end
end
def initialize(source_parser, stmt)
@parser = source_parser
@statement = stmt
end
# The main handler method called by the parser on a statement
# that matches the {handles} declaration.
#
# Subclasses should override this method to provide the handling
# functionality for the class.
#
# @return [Array<CodeObjects::Base>, CodeObjects::Base, Object]
# If this method returns a code object (or a list of them),
# they are passed to the +#register+ method which adds basic
# attributes. It is not necessary to return any objects and in
# some cases you may want to explicitly avoid the returning of
# any objects for post-processing by the register method.
#
# @see handles
# @see #register
#
def process
raise NotImplementedError, "#{self} did not implement a #process method for handling."
end
# Parses the semantic "block" contained in the statement node.
#
# @abstract Subclasses should call {Processor#process parser.process}
def parse_block(*)
raise NotImplementedError, "#{self} did not implement a #parse_block method for handling"
end
# @return [Processor] the processor object that manages all global state
# during handling.
attr_reader :parser
# @return [Object] the statement object currently being processed. Usually
# refers to one semantic language statement, though the strict definition
# depends on the parser used.
attr_reader :statement
# (see Processor#owner)
attr_accessor :owner
# (see Processor#namespace)
attr_accessor :namespace
# (see Processor#visibility)
attr_accessor :visibility
# (see Processor#scope)
attr_accessor :scope
# (see Processor#globals)
attr_reader :globals
# (see Processor#extra_state)
attr_reader :extra_state
undef owner, owner=, namespace, namespace=
undef visibility, visibility=, scope, scope=
undef globals, extra_state
def owner; parser.owner end
def owner=(v) parser.owner = v end
def namespace; parser.namespace end
def namespace=(v); parser.namespace = v end
def visibility; parser.visibility end
def visibility=(v); parser.visibility = v end
def scope; parser.scope end
def scope=(v); parser.scope = v end
def globals; parser.globals end
def extra_state; parser.extra_state end
# Aborts a handler by raising {Handlers::HandlerAborted}.
# An exception will only be logged in debugging mode for
# this kind of handler exit.
#
# @since 0.8.4
def abort!
raise Handlers::HandlerAborted
end
# Executes a given block with specific state values for {#owner},
# {#namespace} and {#scope}.
#
# @option opts [CodeObjects::NamespaceObject] :namespace (value of #namespace)
# the namespace object that {#namespace} will be equal to for the
# duration of the block.
# @option opts [Symbol] :scope (:instance)
# the scope for the duration of the block.
# @option opts [CodeObjects::Base] :owner (value of #owner)
# the owner object (method) for the duration of the block
# @yield a block to execute with the given state values.
def push_state(opts = {})
opts = {
:namespace => namespace,
:scope => :instance,
:owner => owner || namespace,
:visibility => nil
}.update(opts)
ns = namespace
vis = visibility
sc = scope
oo = owner
self.namespace = opts[:namespace]
self.visibility = opts[:visibility] || :public
self.scope = opts[:scope]
self.owner = opts[:owner]
yield
self.namespace = ns
self.visibility = vis
self.scope = sc
self.owner = oo
end
# Do some post processing on a list of code objects.
# Adds basic attributes to the list of objects like
# the filename, line number, {CodeObjects::Base#dynamic},
# source code and {CodeObjects::Base#docstring},
# but only if they don't exist.
#
# @param [Array<CodeObjects::Base>] objects
# the list of objects to post-process.
#
# @return [CodeObjects::Base, Array<CodeObjects::Base>]
# returns whatever is passed in, for chainability.
#
def register(*objects)
objects.flatten.each do |object|
next unless object.is_a?(CodeObjects::Base)
register_ensure_loaded(object)
yield(object) if block_given?
register_file_info(object)
register_source(object)
register_visibility(object)
register_docstring(object)
register_group(object)
register_dynamic(object)
register_module_function(object)
end
objects.size == 1 ? objects.first : objects
end
# Ensures that the object's namespace is loaded before attaching it
# to the namespace.
#
# @param [CodeObjects::Base] object the object to register
# @return [void]
# @since 0.8.0
def register_ensure_loaded(object)
ensure_loaded!(object.namespace)
object.namespace.children << object
rescue NamespaceMissingError
nil # noop
end
# Registers the file/line of the declaration with the object
#
# @param [CodeObjects::Base] object the object to register
# @return [void]
# @since 0.8.0
def register_file_info(object, file = parser.file, line = statement.line, comments = statement.comments)
object.add_file(file, line, comments)
end
# Registers any docstring found for the object and expands macros
#
# @param [CodeObjects::Base] object the object to register
# @return [void]
# @since 0.8.0
def register_docstring(object, docstring = statement.comments, stmt = statement)
docstring = docstring.join("\n") if Array === docstring
parser = Docstring.parser
parser.parse(docstring || "", object, self)
if object && docstring
object.docstring = parser.to_docstring
# Add hash_flag/line_range
if stmt
object.docstring.hash_flag = stmt.comments_hash_flag
object.docstring.line_range = stmt.comments_range
end
end
register_transitive_tags(object)
end
# Registers the object as being inside a specific group
#
# @param [CodeObjects::Base] object the object to register
# @return [void]
# @since 0.8.0
def register_group(object, group = extra_state.group)
if group
unless object.namespace.is_a?(Proxy)
object.namespace.groups |= [group]
end
object.group = group
end
end
# Registers any transitive tags from the namespace on the object
#
# @param [CodeObjects::Base, nil] object the object to register
# @return [void]
# @since 0.8.0
def register_transitive_tags(object)
return unless object && !object.namespace.is_a?(Proxy)
Tags::Library.transitive_tags.each do |tag|
next unless object.namespace.has_tag?(tag)
next if object.has_tag?(tag)
object.add_tag(*object.namespace.tags(tag))
end
end
# @param [CodeObjects::Base] object the object to register
# @return [void]
# @since 0.8.0
def register_source(object, source = statement, type = parser.parser_type)
return unless object.is_a?(MethodObject)
object.source ||= source
object.source_type = type
end
# Registers visibility on a method object. If the object does not
# respond to setting visibility, nothing is done.
#
# @param [#visibility=] object the object to register
# @param [Symbol] visibility the visibility to set on the object
# @since 0.8.0
def register_visibility(object, visibility = self.visibility)
return unless object.respond_to?(:visibility=)
return if object.is_a?(NamespaceObject)
object.visibility = visibility
end
# Registers the same method information on the module function, if
# the object was defined as a module function.
#
# @param [CodeObjects::Base] object the possible module function object
# to copy data for
# @since 0.8.0
def register_module_function(object)
return unless object.is_a?(MethodObject)
return unless object.module_function?
modobj = MethodObject.new(object.namespace, object.name)
object.copy_to(modobj)
modobj.visibility = :private # rubocop:disable Lint/UselessSetterCall
end
# Registers the object as dynamic if the object is defined inside
# a method or block (owner != namespace)
#
# @param [CodeObjects::Base] object the object to register
# @return [void]
# @since 0.8.0
def register_dynamic(object)
object.dynamic = true if owner != namespace
end
# Ensures that a specific +object+ has been parsed and loaded into the
# registry. This is necessary when adding data to a namespace, for instance,
# since the namespace may not have been processed yet (it can be located
# in a file that has not been handled).
#
# Calling this method defers the handler until all other files have been
# processed. If the object gets resolved, the rest of the handler continues,
# otherwise an exception is raised.
#
# @example Adding a mixin to the String class programmatically
# ensure_loaded! P('String')
# # "String" is now guaranteed to be loaded
# P('String').mixins << P('MyMixin')
#
# @param [Proxy, CodeObjects::Base] object the object to resolve.
# @param [Integer] max_retries the number of times to defer the handler
# before raising a +NamespaceMissingError+.
# @raise [NamespaceMissingError] if the object is not resolved within
# +max_retries+ attempts, this exception is raised and the handler
# finishes processing.
def ensure_loaded!(object, max_retries = 1)
return if object.root?
return object unless object.is_a?(Proxy)
retries = 0
while object.is_a?(Proxy)
raise NamespaceMissingError, object if retries > max_retries
log.debug "Missing object #{object} in file `#{parser.file}', moving it to the back of the line."
parser.parse_remaining_files
retries += 1
end
object
end
# @group Macro Support
# @abstract Implement this method to return the parameters in a method call
# statement. It should return an empty list if the statement is not a
# method call.
# @return [Array<String>] a list of argument names
def call_params
raise NotImplementedError
end
# @abstract Implement this method to return the method being called in
# a method call. It should return nil if the statement is not a method
# call.
# @return [String] the method name being called
# @return [nil] if the statement is not a method call
def caller_method
raise NotImplementedError
end
end
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class YARD::Handlers::C::AliasHandler < YARD::Handlers::C::Base
MATCH = /rb_define_alias
\s*\(\s*([\w\.]+),
\s*"([^"]+)",
\s*"([^"]+)"\s*\)/xm
handles MATCH
statement_class BodyStatement
process do
statement.source.scan(MATCH) do |var_name, new_name, old_name|
var_name = "rb_cObject" if var_name == "rb_mKernel"
handle_alias(var_name, new_name, old_name)
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class YARD::Handlers::C::AttributeHandler < YARD::Handlers::C::Base
MATCH = /rb_define_attr\s*\(\s*([\w\.]+),\s*"([^"]+)",\s*(0|1)\s*,\s*(0|1)\s*\)/
handles MATCH
process do
return if ToplevelStatement == statement
return if Comment === statement && statement.type != :multi
statement.source.scan(MATCH) do |var_name, name, read, write|
handle_attribute(var_name, name, read, write)
end
end
end

View File

@ -0,0 +1,164 @@
# frozen_string_literal: true
module YARD
module Handlers
module C
class Base < Handlers::Base
include YARD::Parser::C
include HandlerMethods
# @return [Boolean] whether the handler handles this statement
def self.handles?(statement, processor)
processor.globals.cruby_processed_files ||= {}
processor.globals.cruby_processed_files[processor.file] = true
src = statement.respond_to?(:declaration) ?
statement.declaration : statement.source
handlers.any? do |a_handler|
statement_class >= statement.class &&
case a_handler
when String
src == a_handler
when Regexp
src =~ a_handler
end
end
end
def self.statement_class(type = nil)
if type
@statement_class = type
else
(defined?(@statement_class) && @statement_class) || Statement
end
end
# @group Registering objects
def register_docstring(object, docstring = nil, stmt = nil)
super(object, docstring, stmt) if docstring
end
def register_file_info(object, file = nil, line = nil, comments = nil)
super(object, file, line, comments) if file
end
def register_source(object, source = nil, type = nil)
super(object, source, type) if source
end
def register_visibility(object, visibility = nil)
super(object, visibility) if visibility
end
# @group Looking up Symbol and Var Values
def symbols
globals.cruby_symbols ||= {}
end
def override_comments
globals.cruby_override_comments ||= []
end
def namespace_for_variable(var)
return namespaces[var] if namespaces[var]
# The global variables for Ruby's core error classes does not
# represent their Ruby name. So we need to look up these names.
name = ERROR_CLASS_NAMES[var]
return P(name) if name
# Otherwise the name is inferred from the C variable name.
var = remove_var_prefix(var)
var.empty? ? nil : P(var)
end
def ensure_variable_defined!(var, max_retries = 1)
retries = 0
object = nil
loop do
object = namespace_for_variable(var)
break unless object.is_a?(Proxy)
raise NamespaceMissingError, object if retries > max_retries
log.debug "Missing namespace variable #{var} in file `#{parser.file}', moving it to the back of the line."
parser.parse_remaining_files
retries += 1
end
object
end
def namespaces
globals.cruby_namespaces ||= {}
end
def processed_files
globals.cruby_processed_files ||= {}
end
# @group Parsing an Inner Block
def parse_block(opts = {})
return if !statement.block || statement.block.empty?
push_state(opts) do
parser.process(statement.block)
end
end
# @group Processing other files
def process_file(file, object)
file = File.cleanpath(file)
return if processed_files[file]
processed_files[file] = file
begin
log.debug "Processing embedded call to C source #{file}..."
globals.ordered_parser.files.delete(file) if globals.ordered_parser
parser.process(Parser::C::CParser.new(File.read(file), file).parse)
rescue Errno::ENOENT
log.warn "Missing source file `#{file}' when parsing #{object}"
end
end
# @endgroup
private
# Generated by update_error_map.rb (Copy+past results)
ERROR_CLASS_NAMES = {
'rb_eArgError' => 'ArgumentError',
'rb_eEncodingError' => 'EncodingError',
'rb_eException' => 'Exception',
'rb_eFatal' => 'fatal',
'rb_eFrozenError' => 'FrozenError',
'rb_eIndexError' => 'IndexError',
'rb_eInterrupt' => 'Interrupt',
'rb_eKeyError' => 'KeyError',
'rb_eLoadError' => 'LoadError',
'rb_eNameError' => 'NameError',
'rb_eNoMatchingPatternError' => 'NoMatchingPatternError',
'rb_eNoMemError' => 'NoMemoryError',
'rb_eNoMethodError' => 'NoMethodError',
'rb_eNotImpError' => 'NotImplementedError',
'rb_eRangeError' => 'RangeError',
'rb_eRuntimeError' => 'RuntimeError',
'rb_eScriptError' => 'ScriptError',
'rb_eSecurityError' => 'SecurityError',
'rb_eSignal' => 'SignalException',
'rb_eStandardError' => 'StandardError',
'rb_eSyntaxError' => 'SyntaxError',
'rb_eSystemCallError' => 'SystemCallError',
'rb_eSystemExit' => 'SystemExit',
'rb_eTypeError' => 'TypeError',
}
def remove_var_prefix(var)
var.gsub(/^rb_[mc]|^[a-z_]+/, '')
end
end
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class YARD::Handlers::C::ClassHandler < YARD::Handlers::C::Base
MATCH1 = /([\w\.]+)\s* = \s*(?:rb_define_class|boot_defclass)\s*
\(
\s*"([\w:]+)",
\s*(\w+|0)\s*
\)/mx
MATCH2 = /([\w\.]+)\s* = \s*rb_define_class_under\s*
\(
\s*(\w+),
\s*"(\w+)"(?:,
\s*([\w\*\s\(\)\.\->]+)\s*)? # for SWIG
\s*\)/mx
handles MATCH1
handles MATCH2
statement_class BodyStatement
process do
statement.source.scan(MATCH1) do |var_name, class_name, parent|
handle_class(var_name, class_name, parent)
end
statement.source.scan(MATCH2) do |var_name, in_module, class_name, parent|
handle_class(var_name, class_name, parent.strip, in_module)
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class YARD::Handlers::C::ConstantHandler < YARD::Handlers::C::Base
MATCH = /\brb_define_((?:readonly_)?variable|(?:global_)?const)
\s*\((?:\s*(\w+),)?\s*"(\w+)",\s*(.*?)\s*\)\s*;/xm
handles MATCH
statement_class BodyStatement
process do
statement.source.scan(MATCH) do |type, var_name, const_name, value|
handle_constants(type, var_name, const_name, value)
end
end
end

View File

@ -0,0 +1,212 @@
# frozen_string_literal: true
module YARD
module Handlers
module C
module HandlerMethods
include Parser::C
include CodeObjects
include Common::MethodHandler
def handle_class(var_name, class_name, parent, in_module = nil)
parent = nil if parent == "0"
namespace = in_module ? ensure_variable_defined!(in_module) : Registry.root
if namespace.nil?
raise Parser::UndocumentableError,
"class #{class_name}. Cannot find definition for parent namespace."
end
register ClassObject.new(namespace, class_name) do |obj|
if parent
parent_class = namespace_for_variable(parent)
if parent_class.is_a?(Proxy)
obj.superclass = "::#{parent_class.path}"
obj.superclass.type = :class
else
obj.superclass = parent_class
end
end
namespaces[var_name] = obj
register_file_info(obj, statement.file, statement.line)
end
end
def handle_module(var_name, module_name, in_module = nil)
namespace = in_module ? ensure_variable_defined!(in_module) : Registry.root
if namespace.nil?
raise Parser::UndocumentableError,
"module #{module_name}. Cannot find definition for parent namespace."
end
register ModuleObject.new(namespace, module_name) do |obj|
namespaces[var_name] = obj
register_file_info(obj, statement.file, statement.line)
end
end
def handle_method(scope, var_name, name, func_name, _source_file = nil)
visibility = :public
case scope
when "singleton_method"; scope = :class
when "module_function"; scope = :module
when "private_method"; scope = :instance; visibility = :private
else; scope = :instance
end
namespace = namespace_for_variable(var_name)
# Is this method being defined on a core Ruby class or module?
if namespace.is_a?(Proxy)
if var_name =~ /^rb_c(\w+)/ && YARD::CodeObjects::BUILTIN_CLASSES.include?($1)
namespace = namespaces[var_name] = YARD::CodeObjects::ClassObject.new(:root, $1)
elsif var_name =~ /^rb_m(\w+)/ && YARD::CodeObjects::BUILTIN_MODULES.include?($1)
namespace = namespaces[var_name] = YARD::CodeObjects::ModuleObject.new(:root, $1)
end
end
return if namespace.nil? # XXX: raise UndocumentableError might be too noisy.
register MethodObject.new(namespace, name, scope) do |obj|
register_visibility(obj, visibility)
find_method_body(obj, func_name)
obj.explicit = true
add_predicate_return_tag(obj) if name =~ /\?$/
end
end
def handle_attribute(var_name, name, read, write)
values = {:read => read.to_i, :write => write.to_i}
{:read => name, :write => "#{name}="}.each do |type, meth_name|
next unless values[type] > 0
obj = handle_method(:instance, var_name, meth_name, nil)
register_file_info(obj, statement.file, statement.line)
obj.namespace.attributes[:instance][name] ||= SymbolHash[:read => nil, :write => nil]
obj.namespace.attributes[:instance][name][type] = obj
end
end
def handle_alias(var_name, new_name, old_name)
namespace = namespace_for_variable(var_name)
return if namespace.nil?
new_meth = new_name.to_sym
old_meth = old_name.to_sym
old_obj = namespace.child(:name => old_meth, :scope => :instance)
new_obj = register MethodObject.new(namespace, new_meth, :instance) do |o|
register_visibility(o, visibility)
register_file_info(o, statement.file, statement.line)
end
if old_obj
new_obj.signature = old_obj.signature
new_obj.source = old_obj.source
new_obj.docstring = old_obj.docstring
new_obj.docstring.object = new_obj
else
new_obj.signature = "def #{new_meth}" # this is all we know.
end
namespace.aliases[new_obj] = old_meth
end
def handle_constants(type, var_name, const_name, value)
return unless type =~ /^const$|^global_const$/
namespace = type == 'global_const' ?
:root : namespace_for_variable(var_name)
register ConstantObject.new(namespace, const_name) do |obj|
obj.source_type = :c
obj.value = value
register_file_info(obj, statement.file, statement.line)
find_constant_docstring(obj)
end
end
private
def find_constant_docstring(object)
comment = nil
# look inside overrides for declaration value
override_comments.each do |name, override_comment|
next unless override_comment.file == statement.file
just_const_name = name.gsub(/\A.+::/, '')
if object.path == name || object.name.to_s == just_const_name
comment = override_comment.source
break
end
end
# use any comments on this statement as a last resort
if comment.nil? && statement.comments && statement.comments.source =~ /\S/
comment = statement.comments.source
stmt = statement.comments
end
# In the case of rb_define_const, the definition and comment are in
# "/* definition: comment */" form. The literal ':' and '\' characters
# can be escaped with a backslash.
if comment
comment.scan(/\A\s*(.*?[^\s\\]):\s*(.+)/m) do |new_value, new_comment|
object.value = new_value.gsub(/\\:/, ':')
comment = new_comment
end
register_docstring(object, comment, stmt)
end
end
def find_method_body(object, symbol)
file = statement.file
in_file = false
if statement.comments && statement.comments.source =~ /\A\s*in (\S+)\Z/
file = $1
in_file = true
process_file(file, object)
end
src_stmt = symbols[symbol]
if src_stmt
register_file_info(object, src_stmt.file, src_stmt.line, true)
register_source(object, src_stmt)
record_parameters(object, symbol, src_stmt)
unless src_stmt.comments.nil? || src_stmt.comments.source.empty?
register_docstring(object, src_stmt.comments.source, src_stmt)
return # found docstring
end
end
# found source (possibly) but no docstring
# so look in overrides
return if override_comments.any? do |name, override_comment|
next unless override_comment.file == file
name = name.gsub(/::([^:\.#]+?)\Z/, '.\1')
# explicit namespace in override comment
path = (name =~ /\.|#/ ? object.path : object.name.to_s)
if path == name || path == name.sub(/new$/, 'initialize') || path == name.sub('.', '#')
register_docstring(object, override_comment.source, override_comment)
true
else
false
end
end
# use any comments on this statement as a last resort
if !in_file && statement.comments && statement.comments.source =~ /\S/
register_docstring(object, statement.comments.source, statement)
end
end
def record_parameters(object, symbol, src)
# use regex to extract comma-delimited list of parameters from cfunc definition
if src.source =~ /VALUE\s+#{symbol}\(([^)]*)\)\s*\{/m
params = $~[1].split(/\s*,\s*/) # rubocop:disable Style/SpecialGlobalVars
# cfunc for a "varargs" method has params "int argc, VALUE *argv"
if params[0] =~ /int\s+argc/ && params[1] =~ /VALUE\s*\*\s*argv/
object.parameters = [['*args', nil]]
else
# the first cfunc argument is the 'self' argument, we don't need that
object.parameters = params.drop(1).map {|s| [s[/VALUE\s+(\S+)/, 1], nil] }
end
end
end
end
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
# Handles the Init_Libname() method
class YARD::Handlers::C::InitHandler < YARD::Handlers::C::Base
MATCH = /\A\s*(?:\S+\s+)*void\s+(?:[Ii]nit_)?(\w+)\s*/
handles MATCH
statement_class ToplevelStatement
process do
parse_block
decl = statement.declaration[MATCH, 1]
if decl
ns = namespace_for_variable(decl)
if ns.is_a?(YARD::CodeObjects::NamespaceObject) && ns.docstring.blank?
if statement.comments
register_docstring(ns, statement.comments.source, statement)
end
end
end
end
end

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
class YARD::Handlers::C::MethodHandler < YARD::Handlers::C::Base
MATCH1 = /rb_define_
(
singleton_method |
method |
module_function |
private_method
)
\s*\(\s*([\w\.]+)\s*,
\s*"([^"]+)"\s*,
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(\w+\))?(\w+)\)?\s*,
\s*(-?\w+)\s*\)/xm
MATCH2 = /rb_define_global_function\s*\(
\s*"([^"]+)",
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(\w+\))?(\w+)\)?,
\s*(-?\w+)\s*\)/xm
MATCH3 = /define_filetest_function\s*\(
\s*"([^"]+)",
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(\w+\))?(\w+)\)?,
\s*(-?\w+)\s*\)/xm
handles MATCH1
handles MATCH2
handles MATCH3
statement_class BodyStatement
process do
statement.source.scan(MATCH1) do |type, var_name, name, func_name, _param_count|
break if var_name == "ruby_top_self"
break if var_name == "nstr"
break if var_name == "envtbl"
var_name = "rb_cObject" if var_name == "rb_mKernel"
handle_method(type, var_name, name, func_name)
end
statement.source.scan(MATCH2) do |name, func_name, _param_count|
handle_method("method", "rb_mKernel", name, func_name)
end
statement.source.scan(MATCH3) do |name, func_name, _param_count|
handle_method("singleton_method", "rb_cFile", name, func_name)
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class YARD::Handlers::C::MixinHandler < YARD::Handlers::C::Base
MATCH = /rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/
handles MATCH
statement_class BodyStatement
process do
statement.source.scan(MATCH) do |klass_var, mixin_var|
namespace = namespace_for_variable(klass_var)
ensure_loaded!(namespace)
var = namespace_for_variable(mixin_var)
if var
namespace.mixins(:instance) << var
else
raise YARD::Parser::UndocumentableError,
"CRuby mixin for unrecognized variable '#{mixin_var}'"
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class YARD::Handlers::C::ModuleHandler < YARD::Handlers::C::Base
MATCH1 = /([\w\.]+)\s* = \s*rb_define_module\s*\(\s*"([\w:]+)"\s*\)/mx
MATCH2 = /([\w\.]+)\s* = \s*rb_define_module_under\s*\(\s*(\w+),\s*"(\w+)"\s*\)/mx
handles MATCH1
handles MATCH2
statement_class BodyStatement
process do
statement.source.scan(MATCH1) do |var_name, module_name|
handle_module(var_name, module_name)
end
statement.source.scan(MATCH2) do |var_name, in_module, module_name|
handle_module(var_name, module_name, in_module)
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
# Parses comments
class YARD::Handlers::C::OverrideCommentHandler < YARD::Handlers::C::Base
handles(/./)
statement_class Comment
process do
return if statement.overrides.empty?
statement.overrides.each do |type, name|
override_comments << [name, statement]
obj = nil
case type
when :class
name, superclass = *name.split(/\s*<\s*/)
obj = YARD::CodeObjects::ClassObject.new(:root, name)
obj.superclass = "::#{superclass}" if superclass
when :module
obj = YARD::CodeObjects::ModuleObject.new(:root, name)
end
register(obj)
end
end
def register_docstring(object, docstring = statement.source, stmt = statement)
super
end
def register_file_info(object, file = parser.file, line = statement.line, comments = statement.comments)
super
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class YARD::Handlers::C::PathHandler < YARD::Handlers::C::Base
MATCH = /([\w\.]+)\s* = \s*rb_path2class\s*\(\s*"([\w:]+)"\)/mx
handles MATCH
process do
statement.source.scan(MATCH) do |var_name, path|
namespaces[var_name] = P(path)
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class YARD::Handlers::C::StructHandler < YARD::Handlers::C::Base
MATCH = /([\w\.]+)\s*=\s*(?:rb_struct_define_without_accessor)\s*
\(\s*"([\w:]+)"\s*,\s*(\w+)\s*/mx
handles MATCH
statement_class BodyStatement
process do
statement.source.scan(MATCH) do |var_name, class_name, parent|
handle_class(var_name, class_name, parent)
end
end
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
# Keeps track of function bodies for symbol lookup during Ruby method declarations
class YARD::Handlers::C::SymbolHandler < YARD::Handlers::C::Base
MATCH = /\A\s*(?:(?:\w+)\s+)?(?:intern\s+)?VALUE\s+(\w+)\s*\(/
handles MATCH
statement_class ToplevelStatement
process { symbols[statement.source[MATCH, 1]] = statement }
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module YARD::Handlers
module Common
# Shared functionality between Ruby and C method handlers.
module MethodHandler
# @param [MethodObject] obj
def add_predicate_return_tag(obj)
if obj.tag(:return) && (obj.tag(:return).types || []).empty?
obj.tag(:return).types = ['Boolean']
elsif obj.tag(:return).nil?
unless obj.tags(:overload).any? {|overload| overload.tag(:return) }
obj.add_tag(YARD::Tags::Tag.new(:return, "", "Boolean"))
end
end
end
end
end
end

View File

@ -0,0 +1,200 @@
# frozen_string_literal: true
require 'ostruct'
module YARD
module Handlers
# Iterates over all statements in a file and delegates them to the
# {Handlers::Base} objects that are registered to handle the statement.
#
# This class is passed to each handler and keeps overall processing state.
# For example, if the {#visibility} is set in a handler, all following
# statements will have access to this state. This allows "public",
# "protected" and "private" statements to be handled in classes and modules.
# In addition, the {#namespace} can be set during parsing to control
# where objects are being created from. You can also access extra stateful
# properties that any handler can set during the duration of the post
# processing of a file from {#extra_state}. If you need to access state
# across different files, look at {#globals}.
#
# @see Handlers::Base
class Processor
class << self
# Registers a new namespace for handlers of the given type.
# @since 0.6.0
def register_handler_namespace(type, ns)
namespace_for_handler[type] = ns
end
# @return [Hash] a list of registered parser type extensions
# @private
# @since 0.6.0
attr_reader :namespace_for_handler
undef namespace_for_handler
def namespace_for_handler; @@parser_type_extensions ||= {} end
end
register_handler_namespace :ruby, Ruby
register_handler_namespace :ruby18, Ruby::Legacy
register_handler_namespace :c, C
# @return [String] the filename
attr_accessor :file
# @return [CodeObjects::NamespaceObject] the current namespace
attr_accessor :namespace
# @return [Symbol] the current visibility (public, private, protected)
attr_accessor :visibility
# @return [Symbol] the current scope (class, instance)
attr_accessor :scope
# @return [CodeObjects::Base, nil] unlike the namespace, the owner
# is a non-namespace object that should be stored between statements.
# For instance, when parsing a method body, the {CodeObjects::MethodObject}
# is set as the owner, in case any extra method information is processed.
attr_accessor :owner
# @return [Symbol] the parser type (:ruby, :ruby18, :c)
attr_accessor :parser_type
# Handlers can share state for the entire post processing stage through
# this attribute. Note that post processing stage spans multiple files.
# To share state only within a single file, use {#extra_state}
#
# @example Sharing state among two handlers
# class Handler1 < YARD::Handlers::Ruby::Base
# handles :class
# process { globals.foo = :bar }
# end
#
# class Handler2 < YARD::Handlers::Ruby::Base
# handles :method
# process { puts globals.foo }
# end
# @return [OpenStruct] global shared state for post-processing stage
# @see #extra_state
attr_accessor :globals
# Share state across different handlers inside of a file.
# This attribute is similar to {#visibility}, {#scope}, {#namespace}
# and {#owner}, in that they all maintain state across all handlers
# for the entire source file. Use this attribute to store any data
# your handler might need to save during the parsing of a file. If
# you need to save state across files, see {#globals}.
#
# @return [OpenStruct] an open structure that can store arbitrary data
# @see #globals
attr_accessor :extra_state
# Creates a new Processor for a +file+.
# @param [Parser::SourceParser] parser the parser used to initialize the processor
def initialize(parser)
@file = parser.file || "(stdin)"
@namespace = YARD::Registry.root
@visibility = :public
@scope = :instance
@owner = @namespace
@parser_type = parser.parser_type
@handlers_loaded = {}
@globals = parser.globals || OpenStruct.new
@extra_state = OpenStruct.new
load_handlers
end
# Processes a list of statements by finding handlers to process each
# one.
#
# @param [Array] statements a list of statements
# @return [void]
def process(statements)
statements.each_with_index do |stmt, _index|
find_handlers(stmt).each do |handler|
begin
handler.new(self, stmt).process
rescue HandlerAborted
log.debug "#{handler} cancelled from #{caller.last}"
log.debug "\tin file '#{file}':#{stmt.line}:\n\n" + stmt.show + "\n"
rescue NamespaceMissingError => missingerr
log.warn "The #{missingerr.object.type} #{missingerr.object.path} has not yet been recognized.\n" \
"If this class/method is part of your source tree, this will affect your documentation results.\n" \
"You can correct this issue by loading the source file for this object before `#{file}'\n"
rescue Parser::UndocumentableError => undocerr
log.warn "in #{handler}: Undocumentable #{undocerr.message}\n" \
"\tin file '#{file}':#{stmt.line}:\n\n" + stmt.show + "\n"
rescue => e
log.error "Unhandled exception in #{handler}:\n" \
" in `#{file}`:#{stmt.line}:\n\n#{stmt.show}\n"
log.backtrace(e)
end
end
end
end
# Continue parsing the remainder of the files in the +globals.ordered_parser+
# object. After the remainder of files are parsed, processing will continue
# on the current file.
#
# @return [void]
# @see Parser::OrderedParser
def parse_remaining_files
if globals.ordered_parser
globals.ordered_parser.parse
log.debug("Re-processing #{@file}...")
end
end
# Searches for all handlers in {Base.subclasses} that match the +statement+
#
# @param statement the statement object to match.
# @return [Array<Base>] a list of handlers to process the statement with.
def find_handlers(statement)
Base.subclasses.find_all do |handler|
handler_base_class > handler &&
(handler.namespace_only? ? owner.is_a?(CodeObjects::NamespaceObject) : true) &&
handles?(handler, statement)
end
end
private
def handles?(handler, statement)
return false unless handler.matches_file?(file)
if handler.method(:handles?).arity == 1
handler.handles?(statement)
elsif [-1, 2].include?(handler.method(:handles?).arity)
handler.handles?(statement, self)
end
end
# Returns the handler base class
# @return [Base] the base class
def handler_base_class
handler_base_namespace.const_get(:Base)
end
# The module holding the handlers to be loaded
#
# @return [Module] the module containing the handlers depending on
# {#parser_type}.
def handler_base_namespace
self.class.namespace_for_handler[parser_type]
end
# Loads handlers from {#handler_base_namespace}. This ensures that
# Ruby1.9 handlers are never loaded into 1.8; also lowers the amount
# of modules that are loaded
# @return [void]
def load_handlers
return if @handlers_loaded[parser_type]
handler_base_namespace.constants.each do |c|
const = handler_base_namespace.const_get(c)
unless Handlers::Base.subclasses.include?(const)
Handlers::Base.subclasses << const
end
end
@handlers_loaded[parser_type] = true
end
end
end
end

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
# Handles alias and alias_method calls
class YARD::Handlers::Ruby::AliasHandler < YARD::Handlers::Ruby::Base
handles :alias, method_call(:alias_method)
namespace_only
process do
names = []
if statement.type == :alias
nodes = [:ident, :op, :kw, :const, :tstring_content, :string_content]
names = statement.map {|o| o.jump(*nodes).source }
elsif statement.call?
statement.parameters(false).each do |obj|
case obj.type
when :symbol_literal
names << obj.jump(:ident, :op, :kw, :const).source
when :string_literal, :dyna_symbol
names << obj.jump(:string_content, :tstring_content).source
end
end
end
raise YARD::Parser::UndocumentableError, "alias/alias_method" if names.size != 2
new_meth = names[0].to_sym
old_meth = names[1].to_sym
old_obj = namespace.child(:name => old_meth, :scope => scope)
new_obj = register MethodObject.new(namespace, new_meth, scope) do |o|
o.add_file(parser.file, statement.line)
end
namespace.aliases[new_obj] = old_meth
if old_obj
new_obj.signature = old_obj.signature
new_obj.source = old_obj.source
comments = [old_obj.docstring.to_raw, statement.comments].join("\n")
doc = YARD::Docstring.parser.parse(comments, new_obj, self)
new_obj.docstring = doc.to_docstring
new_obj.docstring.line_range = statement.comments_range
new_obj.docstring.hash_flag = statement.comments_hash_flag
new_obj.docstring.object = new_obj
else
new_obj.signature = "def #{new_meth}" # this is all we know.
end
end
end

View File

@ -0,0 +1,87 @@
# frozen_string_literal: true
# Handles +attr_*+ statements in modules/classes
class YARD::Handlers::Ruby::AttributeHandler < YARD::Handlers::Ruby::Base
handles method_call(:attr)
handles method_call(:attr_reader)
handles method_call(:attr_writer)
handles method_call(:attr_accessor)
namespace_only
process do
return if statement.type == :var_ref || statement.type == :vcall
read = true
write = false
params = statement.parameters(false).dup
# Change read/write based on attr_reader/writer/accessor
case statement.method_name(true)
when :attr
# In the case of 'attr', the second parameter (if given) isn't a symbol.
if params.size == 2
write = true if params.pop == s(:var_ref, s(:kw, "true"))
end
when :attr_accessor
write = true
when :attr_reader
# change nothing
when :attr_writer
read = false
write = true
end
# Add all attributes
validated_attribute_names(params).each do |name|
namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
# Show their methods as well
{:read => name, :write => "#{name}="}.each do |type, meth|
if type == :read ? read : write
o = MethodObject.new(namespace, meth, scope)
if type == :write
o.parameters = [['value', nil]]
src = "def #{meth}(value)"
full_src = "#{src}\n @#{name} = value\nend"
doc = "Sets the attribute #{name}\n@param value the value to set the attribute #{name} to."
else
src = "def #{meth}"
full_src = "#{src}\n @#{name}\nend"
doc = "Returns the value of attribute #{name}."
end
o.source ||= full_src
o.signature ||= src
register(o)
o.docstring = doc if o.docstring.blank?(false)
# Regsiter the object explicitly
namespace.attributes[scope][name][type] = o
else
obj = namespace.children.find {|other| other.name == meth.to_sym && other.scope == scope }
# register an existing method as attribute
namespace.attributes[scope][name][type] = obj if obj
end
end
end
end
protected
# Strips out any non-essential arguments from the attr statement.
#
# @param [Array<Parser::Ruby::AstNode>] params a list of the parameters
# in the attr call.
# @return [Array<String>] the validated attribute names
# @raise [Parser::UndocumentableError] if the arguments are not valid.
def validated_attribute_names(params)
params.map do |obj|
case obj.type
when :symbol_literal
obj.jump(:ident, :op, :kw, :const).source
when :string_literal
obj.jump(:string_content).source
else
raise YARD::Parser::UndocumentableError, obj.source
end
end
end
end

View File

@ -0,0 +1,165 @@
# frozen_string_literal: true
module YARD
module Handlers
module Ruby
# To implement a custom handler matcher, subclass this class and implement
# {#matches?} to return whether a node matches the handler.
#
# @example A Custom Handler Matcher Extension
# # Implements a handler that checks for a specific string
# # in the node's source.
# class MyExtension < HandlesExtension
# def matches?(node) node.source.include?(name) end
# end
#
# # This handler will handle any node where the source includes 'foo'
# class MyHandler < Handlers::Ruby::Base
# handles MyExtension.new('foo')
# end
class HandlesExtension
# Creates a new extension with a specific matcher value +name+
# @param [Object] name the matcher value to check against {#matches?}
def initialize(name) @name = name end
# Tests if the node matches the handler
# @param [Parser::Ruby::AstNode] node a Ruby node
# @return [Boolean] whether the +node+ matches the handler
def matches?(node) # rubocop:disable Lint/UnusedMethodArgument
raise NotImplementedError
end
protected
# @return [String] the extension matcher value
attr_reader :name
end
class MethodCallWrapper < HandlesExtension
def matches?(node)
case node.type
when :var_ref
if !node.parent || node.parent.type == :list
return true if node[0].type == :ident && (name.nil? || node[0][0] == name)
end
when :fcall, :command, :vcall
return true if name.nil? || node[0][0] == name
when :call, :command_call
return true if name.nil? || node[2][0] == name
end
false
end
end
class TestNodeWrapper < HandlesExtension
def matches?(node) !node.send(name).is_a?(FalseClass) end
end
# This is the base handler class for the new-style (1.9) Ruby parser.
# All handlers that subclass this base class will be used when the
# new-style parser is used. For implementing legacy handlers, see
# {Legacy::Base}.
#
# @abstract See {Handlers::Base} for subclassing information.
# @see Handlers::Base
# @see Legacy::Base
class Base < Handlers::Base
class << self
include Parser::Ruby
# @group Statement Matcher Extensions
# Matcher for handling any type of method call. Method calls can
# be expressed by many {AstNode} types depending on the syntax
# with which it is called, so YARD allows you to use this matcher
# to simplify matching a method call.
#
# @example Match the "describe" method call
# handles method_call(:describe)
#
# # The following will be matched:
# # describe(...)
# # object.describe(...)
# # describe "argument" do ... end
#
# @param [#to_s] name matches the method call of this name
# @return [void]
def method_call(name = nil)
MethodCallWrapper.new(name ? name.to_s : nil)
end
# Matcher for handling a node with a specific meta-type. An {AstNode}
# has a {AstNode#type} to define its type but can also be associated
# with a set of types. For instance, +:if+ and +:unless+ are both
# of the meta-type +:condition+.
#
# A meta-type is any method on the {AstNode} class ending in "?",
# though you should not include the "?" suffix in your declaration.
# Some examples are: "condition", "call", "literal", "kw", "token",
# "ref".
#
# @example Handling any conditional statement (if, unless)
# handles meta_type(:condition)
# @param [Symbol] type the meta-type to match. A meta-type can be
# any method name + "?" that {AstNode} responds to.
# @return [void]
def meta_type(type)
TestNodeWrapper.new(type.to_s + "?")
end
# @group Testing for a Handler
# @return [Boolean] whether or not an {AstNode} object should be
# handled by this handler
def handles?(node)
handlers.any? do |a_handler|
case a_handler
when Symbol
a_handler == node.type
when String
node.source == a_handler
when Regexp
node.source =~ a_handler
when Parser::Ruby::AstNode
a_handler == node
when HandlesExtension
a_handler.matches?(node)
end
end
end
end
include Parser::Ruby
# @group Parsing an Inner Block
def parse_block(inner_node, opts = {})
push_state(opts) do
nodes = inner_node.type == :list ? inner_node.children : [inner_node]
parser.process(nodes)
end
end
# @group Macro Handling
def call_params
return [] unless statement.respond_to?(:parameters)
statement.parameters(false).compact.map do |param|
if param.type == :list
param.map {|n| n.jump(:ident, :kw, :tstring_content).source }
else
param.jump(:ident, :kw, :tstring_content).source
end
end.flatten
end
def caller_method
if statement.call? || statement.def?
statement.method_name(true).to_s
elsif statement.type == :var_ref || statement.type == :vcall
statement[0].jump(:ident, :kw).source
end
end
end
end
end
end

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
# Matches if/unless conditions inside classes and attempts to process only
# one branch (by evaluating the condition if possible).
#
# @example A simple class conditional
# class Foo
# if 0
# # This method is ignored
# def xyz; end
# end
# end
class YARD::Handlers::Ruby::ClassConditionHandler < YARD::Handlers::Ruby::Base
handles meta_type(:condition)
namespace_only
process do
condition = parse_condition
if condition.nil?
# Parse both blocks if we're unsure of the condition
parse_then_block
parse_else_block
elsif condition
parse_then_block
else
parse_else_block
end
end
protected
# Parses the condition part of the if/unless statement
#
# @return [true, false, nil] true if the condition can be definitely
# parsed to true, false if not, and nil if the condition cannot be
# parsed with certainty (it's dynamic)
def parse_condition
condition = nil
# Right now we can handle very simple unary conditions like:
# if true
# if false
# if 0
# if 100 (not 0)
# if defined? SOME_CONSTANT
#
# The last case will do a lookup in the registry and then one
# in the Ruby world (using eval).
case statement.condition.type
when :int
condition = statement.condition[0] != "0"
when :defined
# defined? keyword used, let's see if we can look up the name
# in the registry, then we'll try using Ruby's powers. eval() is not
# *too* dangerous here since code is not actually executed.
arg = statement.condition.first
if arg.type == :var_ref
name = arg.source
obj = YARD::Registry.resolve(namespace, name, true)
begin
condition = true if obj || (name && Object.instance_eval("defined? #{name}"))
rescue SyntaxError, NameError
condition = false
end
end
when :var_ref
var = statement.condition[0]
if var == s(:kw, "true")
condition = true
elsif var == s(:kw, "false")
condition = false
end
end
# Invert an unless condition
if statement.type == :unless || statement.type == :unless_mod
condition = !condition unless condition.nil?
end
condition
end
def parse_then_block
parse_block(statement.then_block, :visibility => visibility)
end
def parse_else_block
if statement.else_block
parse_block(statement.else_block, :visibility => visibility)
end
end
end

View File

@ -0,0 +1,119 @@
# frozen_string_literal: true
# Handles class declarations
class YARD::Handlers::Ruby::ClassHandler < YARD::Handlers::Ruby::Base
include YARD::Handlers::Ruby::StructHandlerMethods
handles :class, :sclass
namespace_only
process do
classname = statement[0].source.gsub(/\s/, '')
if statement.type == :class
superclass = parse_superclass(statement[1])
if superclass == "Struct"
is_a_struct = true
superclass = struct_superclass_name(statement[1]) # refine the superclass if possible
create_struct_superclass(superclass, statement[1])
end
undocsuper = statement[1] && superclass.nil?
klass = register ClassObject.new(namespace, classname) do |o|
o.superclass = superclass if superclass
o.superclass.type = :class if o.superclass.is_a?(Proxy)
end
if is_a_struct
parse_struct_superclass(klass, statement[1])
elsif klass
create_attributes(klass, members_from_tags(klass))
end
parse_block(statement[2], :namespace => klass)
if undocsuper
raise YARD::Parser::UndocumentableError, 'superclass (class was added without superclass)'
end
elsif statement.type == :sclass
if statement[0] == s(:var_ref, s(:kw, "self"))
parse_block(statement[1], :namespace => namespace, :scope => :class)
else
proxy = Proxy.new(namespace, classname)
# Allow constants to reference class names
if ConstantObject === proxy
if proxy.value =~ /\A#{NAMESPACEMATCH}\Z/
proxy = Proxy.new(namespace, proxy.value)
else
raise YARD::Parser::UndocumentableError, "constant class reference '#{classname}'"
end
end
if classname[0, 1] =~ /[A-Z]/
register ClassObject.new(namespace, classname) if Proxy === proxy
parse_block(statement[1], :namespace => proxy, :scope => :class)
else
raise YARD::Parser::UndocumentableError, "class '#{classname}'"
end
end
else
sig_end = (statement[1] ? statement[1].source_end : statement[0].source_end) - statement.source_start
raise YARD::Parser::UndocumentableError, "class: #{statement.source[0..sig_end]}"
end
end
private
# Extract the parameters from the Struct.new AST node, returning them as a list
# of strings
#
# @param [MethodCallNode] superclass the AST node for the Struct.new call
# @return [Array<String>] the member names to generate methods for
def extract_parameters(superclass)
members = superclass.parameters.select {|x| x && x.type == :symbol_literal }
members.map! {|x| x.source.strip[1..-1] }
members
end
def create_struct_superclass(superclass, superclass_def)
return if superclass == "Struct"
the_super = register ClassObject.new(P("Struct"), superclass[8..-1]) do |o|
o.superclass = "Struct"
end
parse_struct_superclass(the_super, superclass_def)
the_super
end
def struct_superclass_name(superclass)
if superclass.call?
first = superclass.parameters.first
if first.type == :string_literal && first[0].type == :string_content && first[0].size == 1
return "Struct::#{first[0][0][0]}"
end
end
"Struct"
end
def parse_struct_superclass(klass, superclass)
return unless superclass.call? && superclass.parameters
members = extract_parameters(superclass)
create_attributes(klass, members)
end
def parse_superclass(superclass)
return nil unless superclass
case superclass.type
when :var_ref
return namespace.path if superclass.first == s(:kw, "self")
return superclass.source if superclass.first.type == :const
when :const, :const_ref, :const_path_ref, :top_const_ref
return superclass.source
when :fcall, :command
methname = superclass.method_name.source
return superclass.parameters.first.source if methname == "DelegateClass"
return methname if superclass.method_name.type == :const
when :call, :command_call
cname = superclass.namespace.source
if cname =~ /^O?Struct$/ && superclass.method_name(true) == :new
return cname
end
end
nil
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
# Handles a class variable (@@variable)
class YARD::Handlers::Ruby::ClassVariableHandler < YARD::Handlers::Ruby::Base
handles :assign
namespace_only
process do
if statement[0].type == :var_field && statement[0][0].type == :cvar
name = statement[0][0][0]
value = statement[1].source
register ClassVariableObject.new(namespace, name) do |o|
o.source = statement
o.value = value
end
end
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
# Handles any lone comment statement in a Ruby file
class YARD::Handlers::Ruby::CommentHandler < YARD::Handlers::Ruby::Base
handles :comment, :void_stmt
namespace_only
process do
register_docstring(nil)
end
end

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
# Handles any constant assignment
class YARD::Handlers::Ruby::ConstantHandler < YARD::Handlers::Ruby::Base
include YARD::Handlers::Ruby::StructHandlerMethods
handles :assign
namespace_only
process do
if statement[1].call? && statement[1][0][0] == s(:const, "Struct") &&
statement[1][2] == s(:ident, "new")
process_structclass(statement)
elsif statement[0].type == :var_field && statement[0][0].type == :const
process_constant(statement)
elsif statement[0].type == :const_path_field
process_constant(statement)
end
end
private
def process_constant(statement)
name = statement[0].source
value = statement[1].source
obj = P(namespace, name)
if obj.is_a?(NamespaceObject) && obj.namespace == namespace
raise YARD::Parser::UndocumentableError, "constant for existing #{obj.type} #{obj}"
else
ensure_loaded! obj.parent
register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value.strip }
end
end
def process_structclass(statement)
lhs = statement[0][0]
if lhs.type == :const
klass = create_class(lhs[0], P(:Struct))
create_attributes(klass, extract_parameters(statement[1]))
parse_block(statement[1].block[1], :namespace => klass) unless statement[1].block.nil?
else
raise YARD::Parser::UndocumentableError, "Struct assignment to #{statement[0].source}"
end
end
# Extract the parameters from the Struct.new AST node, returning them as a list
# of strings
#
# @param [MethodCallNode] superclass the AST node for the Struct.new call
# @return [Array<String>] the member names to generate methods for
def extract_parameters(superclass)
return [] unless superclass.parameters
members = superclass.parameters.select {|x| x && x.type == :symbol_literal }
members.map! {|x| x.source.strip[1..-1] }
members
end
end

View File

@ -0,0 +1,123 @@
# frozen_string_literal: true
# Helper methods to assist with processing decorators.
module YARD::Handlers::Ruby::DecoratorHandlerMethods
# @overload process_decorator(*nodes, opts = {}, &block)
# Takes care of parsing method definitions passed to decorators
# as parameters, as well as parsing chained decorators.
#
# Use this in a handler's process block.
#
# @yieldparam method [YARD::CodeObjects::MethodObject] Method being decorated.
# @yieldparam node [YARD::Parser::Ruby::AstNode] AST node of the decorated method.
# @yieldparam name [Symbol] Name of the decorated method.
# @return [Array<Hash>] Array of hashes containing :method, :node, :name.
# See yield params.
#
# @param nodes [YARD::Parser::Ruby::AstNode] AST nodes that refer to decorated
# methods, like indexes of statement.parameter. Defaults to all parameters.
# Pass nil to specify zero parameters.
#
# @option opts [:instance, :class] :scope (:instance) Scope to use for each
# MethodObject.
#
# @option opts [true, false] :transfer_docstring Set false to disable
# transferring the decorator docstring to method definitions passed to the
# decorator as parameters.
#
# @option opts [true, false] :transfer_source Set false to disable
# transferring the decorator source code string to method definitions
# passed to the decorator as parameters.
#
# @example Basic Usage
# # Simply pass the method docs through to the method definition.
# process do
# process_decorator
# end
#
# @example Setting a method's visibility to private.
# process do
# process_decorator :scope => :class do |method|
# method.visibility = :private if method.respond_to? :visibility
# end
# end
def process_decorator(*nodes, &block)
opts = nodes.last.is_a?(Hash) ? nodes.pop : {}
all_nodes = statement.parameters.select do |p|
p.is_a? YARD::Parser::Ruby::AstNode
end
# Parse decorator parameters (decorator chain).
all_nodes.each do |param|
parse_block param if param.call? || param.def?
end
selected_nodes =
if nodes.empty?
all_nodes
elsif nodes.count == 1 && nodes.first.nil?
[]
else
nodes
end
decorated_methods = selected_nodes.map do |param|
process_decorator_parameter param, opts, &block
end.flatten
# Store method nodes in decorator node.
statement.define_singleton_method :decorators do
decorated_methods.map {|h| h[:node] }
end
decorated_methods
end
private
def process_decorator_parameter(node, opts = {}, &block)
scope = opts.fetch :scope, :instance
transfer_docstring = opts.fetch :transfer_docstring, true
transfer_source = opts.fetch :transfer_source, true
name = nil
if node.call?
if node.respond_to? :decorators
return node.decorators.map do |n|
process_decorator_parameter n, opts, &block
end
end
elsif node.def?
name = node.jump(:def).method_name.source
else
name = node.jump(:ident, :string_content, :const).source
end
if name.nil?
raise YARD::Parser::UndocumentableError, 'statement, cannot determine method name'
end
method = YARD::CodeObjects::Proxy.new(
namespace,
(scope == :instance ? '#' : '.') + name.to_s,
:method
)
# Transfer source to methods passed to the helper as parameters.
method.source = statement.source if transfer_source && node.def?
# Transfer decorator docstring to methods passed to the helper as parameters.
if transfer_docstring && node.def? &&
statement.docstring && method.docstring.empty?
tags = method.tags if method.respond_to? :tags
tags ||= []
method.docstring = statement.docstring
tags.each {|t| method.add_tag t }
end
yield method, node, name.to_sym if block_given?
[{:method => method, :node => node, :name => name.to_sym}]
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module YARD
module Handlers
module Ruby
# Handles automatic detection of dsl-style methods
class DSLHandler < Base
include CodeObjects
include DSLHandlerMethods
handles method_call
namespace_only
process { handle_comments }
end
end
end
end

View File

@ -0,0 +1,96 @@
# frozen_string_literal: true
module YARD
module Handlers
module Ruby
module DSLHandlerMethods
include CodeObjects
include Parser
IGNORE_METHODS = Hash[*%w(alias alias_method autoload attr attr_accessor
attr_reader attr_writer extend include module_function public private
protected private_constant private_class_method public_class_method).
map {|n| [n, true] }.flatten]
def handle_comments
return if IGNORE_METHODS[caller_method]
@docstring = statement.comments || ""
@docstring = @docstring.join("\n") if @docstring.is_a?(Array)
attaching = false
if @docstring =~ /^@!?macro\s+\[[^\]]*attach/
register_docstring(nil)
@docstring = ""
attaching = true
end
macro = find_attached_macro
if macro
txt = macro.expand([caller_method, *call_params], statement.source)
@docstring += "\n" + txt
# macro may have a directive
return register_docstring(nil) if !attaching && txt.match(/^\s*@!/)
elsif !statement.comments_hash_flag && !implicit_docstring?
return register_docstring(nil)
end
# ignore DSL definitions if @method/@attribute directive is used
if @docstring =~ /^@!?(method|attribute)\b/
return register_docstring(nil)
end
register MethodObject.new(namespace, method_name, scope) do |o|
o.signature = method_signature
end
end
def register_docstring(object, docstring = @docstring, stmt = statement)
super
end
private
def implicit_docstring?
tags = %w(method attribute overload visibility scope return)
tags.any? {|tag| @docstring =~ /^@!?#{tag}\b/ }
end
def method_name
name = call_params.first || ""
if name =~ /^#{CodeObjects::METHODNAMEMATCH}$/
name
else
raise UndocumentableError, "method, missing name"
end
end
def method_signature
"def #{method_name}"
end
def find_attached_macro
Registry.all(:macro).each do |macro|
next unless macro.method_object
next unless macro_name_matches(macro)
(namespace.inheritance_tree(true) + [P('Object')]).each do |obj|
return macro if obj == macro.method_object.namespace
end
end
nil
end
# @return [Boolean] whether caller method matches a macro or
# its alias names.
def macro_name_matches(macro)
objs = [macro.method_object]
if objs.first.type != :proxy && objs.first.respond_to?(:aliases)
objs.concat(objs.first.aliases)
end
objs.any? {|obj| obj.name.to_s == caller_method.to_s }
end
end
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
# Handles 'raise' calls inside methods
class YARD::Handlers::Ruby::ExceptionHandler < YARD::Handlers::Ruby::Base
handles method_call(:raise)
process do
return unless owner.is_a?(MethodObject) # Only methods yield
return if [:command_call, :call].include? statement.type
return if owner.has_tag?(:raise)
klass = nil
if statement.call?
params = statement.parameters(false)
if params.size == 1
if params.first.ref? && params.first.first.type != :ident
klass = params.first.source
elsif params.first.call? && params.first.method_name(true) == :new
klass = params.first.namespace.source
end
elsif params.size > 1
klass = params.first.source
end
end
owner.add_tag YARD::Tags::Tag.new(:raise, '', klass) if klass
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
# Handles 'extend' call to include modules into the class scope of another
# @see MixinHandler
class YARD::Handlers::Ruby::ExtendHandler < YARD::Handlers::Ruby::MixinHandler
handles method_call(:extend)
namespace_only
def scope; :class end
private
def process_mixin(mixin)
if mixin == s(:var_ref, s(:kw, "self"))
if namespace.is_a?(ClassObject)
raise UndocumentableError, "extend(self) statement on class"
end
namespace.mixins(scope) << namespace
else
super
end
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
# (see Ruby::AliasHandler)
class YARD::Handlers::Ruby::Legacy::AliasHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\Aalias(_method)?(\s|\()/)
namespace_only
process do
if TkALIAS === statement.tokens.first
tokens = statement.tokens[2..-1].to_s.split(/\s+/)
names = [tokens[0], tokens[1]].map {|t| t.gsub(/^:(['"])?(.+?)\1?$|^(:)(.+)/, '\2') }
else
names = tokval_list(statement.tokens[2..-1], :attr)
end
raise YARD::Parser::UndocumentableError, statement.tokens.first.text if names.size != 2
names = names.map {|n| Symbol === n ? n.to_s.delete('"') : n }
new_meth = names[0].to_sym
old_meth = names[1].to_sym
old_obj = namespace.child(:name => old_meth, :scope => scope)
new_obj = register MethodObject.new(namespace, new_meth, scope) do |o|
o.add_file(parser.file, statement.tokens.first.line_no, statement.comments)
end
if old_obj
new_obj.signature = old_obj.signature
new_obj.source = old_obj.source
new_obj.docstring = old_obj.docstring + YARD::Docstring.new(statement.comments)
new_obj.docstring.line_range = statement.comments_range
new_obj.docstring.hash_flag = statement.comments_hash_flag
new_obj.docstring.object = new_obj
else
new_obj.signature = "def #{new_meth}" # this is all we know.
end
namespace.aliases[new_obj] = old_meth
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
# (see Ruby::AttributeHandler)
class YARD::Handlers::Ruby::Legacy::AttributeHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\Aattr(?:_(?:reader|writer|accessor))?(?:\s|\()/)
namespace_only
process do
begin
attr_type = statement.tokens.first.text.to_sym
symbols = tokval_list statement.tokens[2..-1], :attr, TkTRUE, TkFALSE
read = true
write = false
rescue SyntaxError
raise YARD::Parser::UndocumentableError, attr_type
end
# Change read/write based on attr_reader/writer/accessor
case attr_type
when :attr
# In the case of 'attr', the second parameter (if given) isn't a symbol.
write = symbols.pop if symbols.size == 2
when :attr_accessor
write = true
when :attr_reader
# change nothing
when :attr_writer
read = false
write = true
end
# Add all attributes
symbols.each do |name|
namespace.attributes[scope][name] = SymbolHash[:read => nil, :write => nil]
# Show their methods as well
{:read => name, :write => "#{name}="}.each do |type, meth|
if type == :read ? read : write
o = MethodObject.new(namespace, meth, scope)
if type == :write
o.parameters = [['value', nil]]
src = "def #{meth}(value)"
full_src = "#{src}\n @#{name} = value\nend"
doc = "Sets the attribute #{name}\n@param value the value to set the attribute #{name} to."
else
src = "def #{meth}"
full_src = "#{src}\n @#{name}\nend"
doc = "Returns the value of attribute #{name}"
end
o.source ||= full_src
o.signature ||= src
register(o)
o.docstring = doc if o.docstring.blank?(false)
# Regsiter the object explicitly
namespace.attributes[scope][name][type] = o
else
obj = namespace.children.find {|other| other.name == meth.to_sym && other.scope == scope }
# register an existing method as attribute
namespace.attributes[scope][name][type] = obj if obj
end
end
end
end
end

View File

@ -0,0 +1,245 @@
# frozen_string_literal: true
module YARD
module Handlers
module Ruby::Legacy
# This is the base handler for the legacy parser. To implement a legacy
# handler, subclass this class.
#
# @abstract (see Ruby::Base)
class Base < Handlers::Base
# For tokens like TkDEF, TkCLASS, etc.
include YARD::Parser::Ruby::Legacy::RubyToken
# @return [Boolean] whether or not a {Parser::Ruby::Legacy::Statement} object should be handled
# by this handler.
def self.handles?(stmt)
handlers.any? do |a_handler|
case a_handler
when String
stmt.tokens.first.text == a_handler
when Regexp
stmt.tokens.to_s =~ a_handler
else
a_handler == stmt.tokens.first.class
end
end
end
# Parses a statement's block with a set of state values. If the
# statement has no block, nothing happens. A description of state
# values can be found at {Handlers::Base#push_state}
#
# @param [Hash] opts State options
# @option opts (see Handlers::Base#push_state)
# @see Handlers::Base#push_state #push_state
def parse_block(opts = {})
push_state(opts) do
if statement.block
blk = Parser::Ruby::Legacy::StatementList.new(statement.block)
parser.process(blk)
end
end
end
def call_params
if statement.tokens.first.is_a?(TkDEF)
extract_method_details.last.map(&:first)
else
tokens = statement.tokens[1..-1]
tokval_list(tokens, :attr, :identifier, TkId).map(&:to_s)
end
end
def caller_method
if statement.tokens.first.is_a?(TkIDENTIFIER)
statement.tokens.first.text
elsif statement.tokens.first.is_a?(TkDEF)
extract_method_details.first
end
end
private
# Extracts method information for macro expansion only
#
# @todo This is a duplicate implementation of {MethodHandler}. Refactor.
# @return [Array<String,Array<Array<String>>>] the method name followed by method
# arguments (name and optional value)
def extract_method_details
if statement.tokens.to_s =~ /^def\s+(#{METHODMATCH})(?:(?:\s+|\s*\()(.*)(?:\)\s*$)?)?/m
meth = $1
args = $2
meth.gsub!(/\s+/, '')
args = tokval_list(Parser::Ruby::Legacy::TokenList.new(args), :all)
args.map! {|a| k, v = *a.split('=', 2); [k.strip, (v ? v.strip : nil)] } if args
meth = $` if meth =~ /(?:#{NSEPQ}|#{CSEPQ})([^#{NSEP}#{CSEPQ}]+)$/
[meth, args]
end
end
# The string value of a token. For example, the return value for the symbol :sym
# would be :sym. The return value for a string +"foo #{ bar}"+ would be the literal
# +"foo #{ bar}"+ without any interpolation. The return value of the identifier
# 'test' would be the same value: 'test'. Here is a list of common types and
# their return values:
#
# @example
# tokval(TokenList.new('"foo"').first) => "foo"
# tokval(TokenList.new(':foo').first) => :foo
# tokval(TokenList.new('CONSTANT').first, RubyToken::TkId) => "CONSTANT"
# tokval(TokenList.new('identifier').first, RubyToken::TkId) => "identifier"
# tokval(TokenList.new('3.25').first) => 3.25
# tokval(TokenList.new('/xyz/i').first) => /xyz/i
#
# @param [Token] token The token of the class
#
# @param [Array<Class<Token>>, Symbol] accepted_types
# The allowed token types that this token can be. Defaults to [{TkVal}].
# A list of types would be, for example, [+TkSTRING+, +TkSYMBOL+], to return
# the token's value if it is either of those types. If +TkVal+ is accepted,
# +TkNode+ is also accepted.
#
# Certain symbol keys are allowed to specify multiple types in one fell swoop.
# These symbols are:
# :string => +TkSTRING+, +TkDSTRING+, +TkDXSTRING+ and +TkXSTRING+
# :attr => +TkSYMBOL+ and +TkSTRING+
# :identifier => +TkIDENTIFIER, +TkFID+ and +TkGVAR+.
# :number => +TkFLOAT+, +TkINTEGER+
#
# @return [Object] if the token is one of the accepted types, in its real value form.
# It should be noted that identifiers and constants are kept in String form.
# @return [nil] if the token is not any of the specified accepted types
def tokval(token, *accepted_types)
accepted_types = [TkVal] if accepted_types.empty?
accepted_types.push(TkNode) if accepted_types.include? TkVal
if accepted_types.include?(:attr)
accepted_types.push(TkSTRING, TkSYMBOL)
end
if accepted_types.include?(:string)
accepted_types.push(TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING)
end
if accepted_types.include?(:identifier)
accepted_types.push(TkIDENTIFIER, TkFID, TkGVAR)
end
if accepted_types.include?(:number)
accepted_types.push(TkFLOAT, TkINTEGER)
end
return unless accepted_types.any? {|t| t === token }
case token
when TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING
token.text[1..-2]
when TkSYMBOL
token.text[1..-1].to_sym
when TkFLOAT
token.text.to_f
when TkINTEGER
token.text.to_i
when TkREGEXP
token.text =~ %r{\A/(.+)/([^/])\Z}
Regexp.new($1, $2)
when TkTRUE
true
when TkFALSE
false
when TkNIL
nil
else
token.text
end
end
# Returns a list of symbols or string values from a statement.
# The list must be a valid comma delimited list, and values
# will only be returned to the end of the list only.
#
# Example:
# attr_accessor :a, 'b', :c, :d => ['a', 'b', 'c', 'd']
# attr_accessor 'a', UNACCEPTED_TYPE, 'c' => ['a', 'c']
#
# The tokval list of a {Parser::Ruby::Legacy::TokenList} of the above
# code would be the {#tokval} value of :a, 'b',
# :c and :d.
#
# It should also be noted that this function stops immediately at
# any ruby keyword encountered:
# "attr_accessor :a, :b, :c if x == 5" => ['a', 'b', 'c']
#
# @param [TokenList] tokenlist The list of tokens to process.
# @param [Array<Class<Token>>] accepted_types passed to {#tokval}
# @return [Array<String>] the list of tokvalues in the list.
# @return [Array<EMPTY>] if there are no symbols or Strings in the list
# @see #tokval
def tokval_list(tokenlist, *accepted_types)
return [] unless tokenlist
out = [[]]
parencount = 0
beforeparen = 0
needcomma = false
seen_comma = true
tokenlist.each do |token|
tokval = accepted_types == [:all] ? token.text : tokval(token, *accepted_types)
parencond = !out.last.empty? && !tokval.nil?
# puts "#{seen_comma.inspect} #{parencount} #{token.class.class_name} #{out.inspect}"
case token
when TkCOMMA
if parencount == 0
out << [] unless out.last.empty?
needcomma = false
seen_comma = true
elsif parencond
out.last << token.text
end
when TkLPAREN
if seen_comma
beforeparen += 1
else
parencount += 1
out.last << token.text if parencond
end
when TkRPAREN
if beforeparen > 0
beforeparen -= 1
else
out.last << token.text if parencount > 0 && !tokval.nil?
parencount -= 1
end
when TkLBRACE, TkLBRACK, TkDO
parencount += 1
out.last << token.text unless tokval.nil?
when TkRBRACE, TkRBRACK, TkEND
out.last << token.text unless tokval.nil?
parencount -= 1
else
break if TkKW === token && ![TkTRUE, TkFALSE, TkSUPER, TkSELF, TkNIL].include?(token.class)
seen_comma = false unless TkWhitespace === token
if parencount == 0
next if needcomma
next if TkWhitespace === token
if !tokval.nil?
out.last << tokval
else
out.last.clear
needcomma = true
end
elsif parencond
needcomma = true
out.last << token.text
end
end
break if beforeparen == 0 && parencount < 0
end
# Flatten any single element lists
out.map {|e| e.empty? ? nil : (e.size == 1 ? e.pop : e.flatten.join) }.compact
end
end
end
end
end

View File

@ -0,0 +1,83 @@
# frozen_string_literal: true
# (see Ruby::ClassConditionHandler)
# @since 0.5.4
class YARD::Handlers::Ruby::Legacy::ClassConditionHandler < YARD::Handlers::Ruby::Legacy::Base
namespace_only
handles TkIF, TkELSIF, TkUNLESS
process do
condition = parse_condition
if condition.nil?
# Parse both blocks if we're unsure of the condition
parse_then_block
parse_else_block
elsif condition
parse_then_block
else
parse_else_block
end
end
protected
# Parses the condition part of the if/unless statement
#
# @return [true, false, nil] true if the condition can be definitely
# parsed to true, false if not, and nil if the condition cannot be
# parsed with certainty (it's dynamic)
# @since 0.5.5
def parse_condition
condition = nil
# Right now we can handle very simple unary conditions like:
# if true
# if false
# if 0
# if 100 (not 0)
# if defined? SOME_CONSTANT
#
# The last case will do a lookup in the registry and then one
# in the Ruby world (using eval).
case statement.tokens[1..-1].to_s.strip
when /^(\d+)$/
condition = $1 != "0"
when /^defined\?\s*\(?\s*([A-Za-z0-9:_]+?)\s*\)?$/
# defined? keyword used, let's see if we can look up the name
# in the registry, then we'll try using Ruby's powers. eval() is not
# *too* dangerous here since code is not actually executed.
name = $1
obj = YARD::Registry.resolve(namespace, name, true)
begin
condition = true if obj || Object.instance_eval("defined? #{name}")
rescue SyntaxError, NameError
condition = false
end
when "true"
condition = true
when "false"
condition = false
end
if TkUNLESS === statement.tokens.first
condition = !condition unless condition.nil?
end
condition
end
# @since 0.5.5
def parse_then_block
parse_block(:visibility => visibility)
end
# @since 0.5.5
def parse_else_block
return unless statement.block
stmtlist = YARD::Parser::Ruby::Legacy::StatementList
stmtlist.new(statement.block).each do |stmt|
next unless TkELSE === stmt.tokens.first
push_state(:visibility => visibility) do
parser.process(stmtlist.new(stmt.block))
end
end
end
end

View File

@ -0,0 +1,113 @@
# frozen_string_literal: true
# (see Ruby::ClassHandler)
class YARD::Handlers::Ruby::Legacy::ClassHandler < YARD::Handlers::Ruby::Legacy::Base
include YARD::Handlers::Ruby::StructHandlerMethods
handles TkCLASS
namespace_only
process do
if statement.tokens.to_s =~ /^class\s+(#{NAMESPACEMATCH})\s*(?:<\s*(.+)|\Z)/m
classname = $1
superclass_def = $2
superclass = parse_superclass($2)
classname = classname.gsub(/\s/, '')
if superclass == "Struct"
is_a_struct = true
superclass = struct_superclass_name(superclass_def)
create_struct_superclass(superclass, superclass_def)
end
undocsuper = superclass_def && superclass.nil?
klass = register ClassObject.new(namespace, classname) do |o|
o.superclass = superclass if superclass
o.superclass.type = :class if o.superclass.is_a?(Proxy)
end
if is_a_struct
parse_struct_subclass(klass, superclass_def)
elsif klass
create_attributes(klass, members_from_tags(klass))
end
parse_block(:namespace => klass)
if undocsuper
raise YARD::Parser::UndocumentableError, 'superclass (class was added without superclass)'
end
elsif statement.tokens.to_s =~ /^class\s*<<\s*([\w\:\s]+)/
classname = $1.gsub(/\s/, '')
proxy = Proxy.new(namespace, classname)
# Allow constants to reference class names
if ConstantObject === proxy
if proxy.value =~ /\A#{NAMESPACEMATCH}\Z/
proxy = Proxy.new(namespace, proxy.value)
else
raise YARD::Parser::UndocumentableError, "constant class reference '#{classname}'"
end
end
if classname == "self"
parse_block(:namespace => namespace, :scope => :class)
elsif classname[0, 1] =~ /[A-Z]/
register ClassObject.new(namespace, classname) if Proxy === proxy
parse_block(:namespace => proxy, :scope => :class)
else
raise YARD::Parser::UndocumentableError, "class '#{classname}'"
end
else
raise YARD::Parser::UndocumentableError, "class: #{statement.tokens}"
end
end
private
# Extracts the parameter list from the Struct.new declaration and returns it
# formatted as a list of member names. Expects the user will have used symbols
# to define the struct member names
#
# @param [String] superstring the string declaring the superclass
# @return [Array<String>] a list of member names
def extract_parameters(superstring)
paramstring = superstring.match(/\A(O?Struct)\.new\((.*?)\)/)[2]
paramstring.split(",").select {|x| x.strip[0, 1] == ":" }.map {|x| x.strip[1..-1] } # the 1..-1 chops the leading :
end
def create_struct_superclass(superclass, superclass_def)
return if superclass == "Struct"
the_super = register ClassObject.new(P("Struct"), superclass[8..-1]) do |o|
o.superclass = "Struct"
end
parse_struct_subclass(the_super, superclass_def)
the_super
end
def struct_superclass_name(superclass)
match = superclass.match(/\A(Struct)\.new\((.*?)\)/)
if match
paramstring = match[2].split(",")
first = paramstring.first.strip
if first[0, 1] =~ /['"]/ && first[-1, 1] =~ /['"]/ && first !~ /\#\{/
return "Struct::#{first[1..-2]}"
end
end
"Struct"
end
def parse_struct_subclass(klass, superclass_def)
# Bounce if there's no parens
return unless superclass_def =~ /O?Struct\.new\((.*?)\)/
members = extract_parameters(superclass_def)
create_attributes(klass, members)
end
def parse_superclass(superclass)
case superclass
when /\A(#{NAMESPACEMATCH})(?:\s|\Z)/,
/\A(Struct|OStruct)\.new/,
/\ADelegateClass\((.+?)\)\s*\Z/,
/\A(#{NAMESPACEMATCH})\(/
$1
when "self"
namespace.path
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
# (see Ruby::ClassVariableHandler)
class YARD::Handlers::Ruby::Legacy::ClassVariableHandler < YARD::Handlers::Ruby::Legacy::Base
HANDLER_MATCH = /\A@@\w+\s*=\s*/m
handles HANDLER_MATCH
namespace_only
process do
name, value = *statement.tokens.to_s.split(/\s*=\s*/, 2)
register ClassVariableObject.new(namespace, name) do |o|
o.source = statement
o.value = value.strip
end
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
# (see Ruby::CommentHandler)
class YARD::Handlers::Ruby::Legacy::CommentHandler < YARD::Handlers::Ruby::Legacy::Base
handles TkCOMMENT
namespace_only
process do
register_docstring(nil)
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
# (see Ruby::ConstantHandler)
class YARD::Handlers::Ruby::Legacy::ConstantHandler < YARD::Handlers::Ruby::Legacy::Base
include YARD::Handlers::Ruby::StructHandlerMethods
HANDLER_MATCH = /\A[A-Z]\w*\s*=[^=]\s*/m
handles HANDLER_MATCH
namespace_only
process do
name, value = *statement.tokens.to_s.split(/\s*=\s*/, 2)
if value =~ /\A\s*Struct.new(?:\s*\(?|\b)/
process_structclass(name, $')
else
register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value.strip }
end
end
private
def process_structclass(classname, parameters)
klass = create_class(classname, P(:Struct))
create_attributes(klass, extract_parameters(parameters))
end
def extract_parameters(parameters)
members = tokval_list(YARD::Parser::Ruby::Legacy::TokenList.new(parameters), TkSYMBOL)
members.map(&:to_s)
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module YARD
module Handlers
module Ruby
module Legacy
# (see Ruby::DSLHandler)
class DSLHandler < Base
include CodeObjects
include DSLHandlerMethods
handles TkIDENTIFIER
namespace_only
process { handle_comments }
end
end
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
# (see Ruby::ExceptionHandler)
class YARD::Handlers::Ruby::Legacy::ExceptionHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\Araise(\s|\(|\Z)/)
process do
return unless owner.is_a?(MethodObject) # Only methods yield
return if owner.has_tag?(:raise)
klass = statement.tokens.to_s[/^raise[\(\s]*(#{NAMESPACEMATCH})\s*(?:\)|,|\s(?:if|unless|until)|;|(?:(?:\.|\:\:)\s*)?new|$)/, 1]
owner.add_tag YARD::Tags::Tag.new(:raise, '', klass) if klass
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
# (see Ruby::ExtendHandler)
class YARD::Handlers::Ruby::Legacy::ExtendHandler < YARD::Handlers::Ruby::Legacy::MixinHandler
handles(/\Aextend(\s|\()/)
namespace_only
def scope; :class end
private
def process_mixin(mixin)
if mixin == "self"
if namespace.is_a?(ClassObject)
raise UndocumentableError, "extend(self) statement on class"
end
namespace.mixins(scope) << namespace
else
super
end
end
end

View File

@ -0,0 +1,90 @@
# frozen_string_literal: true
# (see Ruby::MethodHandler)
class YARD::Handlers::Ruby::Legacy::MethodHandler < YARD::Handlers::Ruby::Legacy::Base
handles TkDEF
process do
nobj = namespace
mscope = scope
if statement.tokens.to_s =~ /^def\s+(#{METHODMATCH})(?:(?:\s+|\s*\()(.*)(?:\)\s*$)?)?/m
meth = $1
args = $2
meth.gsub!(/\s+/, '')
args = tokval_list(YARD::Parser::Ruby::Legacy::TokenList.new(args), :all)
args.map! do |a|
k, v, r = *a.split(/(:)|=/, 2)
if r
k += v
v = r
end
[k.strip, (v ? v.strip : nil)]
end if args
else
raise YARD::Parser::UndocumentableError, "method: invalid name"
end
# Class method if prefixed by self(::|.) or Module(::|.)
if meth =~ /(?:#{NSEPQ}|#{CSEPQ})([^#{NSEP}#{CSEPQ}]+)$/
mscope = :class
meth = $1
prefix = $`
if prefix =~ /^[a-z]/ && prefix != "self"
raise YARD::Parser::UndocumentableError, 'method defined on object instance'
end
nobj = P(namespace, prefix) unless prefix == "self"
end
nobj = P(namespace, nobj.value) while nobj.type == :constant
obj = register MethodObject.new(nobj, meth, mscope) do |o|
o.explicit = true
o.parameters = args
end
# delete any aliases referencing old method
nobj.aliases.each do |aobj, name|
next unless name == obj.name
nobj.aliases.delete(aobj)
end if nobj.is_a?(NamespaceObject)
if mscope == :instance && meth == "initialize"
unless obj.has_tag?(:return)
obj.add_tag(YARD::Tags::Tag.new(:return,
"a new instance of #{namespace.name}", namespace.name.to_s))
end
elsif mscope == :class && obj.docstring.blank? && %w(inherited included
extended method_added method_removed method_undefined).include?(meth)
obj.add_tag(YARD::Tags::Tag.new(:private, nil))
elsif meth.to_s =~ /\?$/
if obj.tag(:return) && (obj.tag(:return).types || []).empty?
obj.tag(:return).types = ['Boolean']
elsif obj.tag(:return).nil?
unless obj.tags(:overload).any? {|overload| overload.tag(:return) }
obj.add_tag(YARD::Tags::Tag.new(:return, "", "Boolean"))
end
end
end
if obj.has_tag?(:option)
# create the options parameter if its missing
obj.tags(:option).each do |option|
expected_param = option.name
unless obj.tags(:param).find {|x| x.name == expected_param }
new_tag = YARD::Tags::Tag.new(:param, "a customizable set of options", "Hash", expected_param)
obj.add_tag(new_tag)
end
end
end
info = obj.attr_info
if info
if meth.to_s =~ /=$/ # writer
info[:write] = obj if info[:read]
elsif info[:write]
info[:read] = obj
end
end
parse_block(:owner => obj) # mainly for yield/exceptions
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
# (see Ruby::MixinHandler)
class YARD::Handlers::Ruby::Legacy::MixinHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\Ainclude(\s|\()/)
namespace_only
process do
errors = []
statement.tokens[1..-1].to_s.split(/\s*,\s*/).reverse.each do |mixin|
mixin = mixin.strip
begin
process_mixin(mixin)
rescue YARD::Parser::UndocumentableError => err
errors << err.message
end
end
unless errors.empty?
msg = errors.size == 1 ? ": #{errors[0]}" : "s: #{errors.join(", ")}"
raise YARD::Parser::UndocumentableError, "mixin#{msg} for class #{namespace.path}"
end
end
private
def process_mixin(mixin)
mixmatch = mixin[/\A(#{NAMESPACEMATCH})/, 1]
raise YARD::Parser::UndocumentableError unless mixmatch
case obj = Proxy.new(namespace, mixmatch)
when ConstantObject # If a constant is included, use its value as the real object
obj = Proxy.new(namespace, obj.value, :module)
else
obj = Proxy.new(namespace, mixmatch, :module)
end
namespace.mixins(scope).unshift(obj) unless namespace.mixins(scope).include?(obj)
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
# (see Ruby::ModuleFunctionHandler)
class YARD::Handlers::Ruby::Legacy::ModuleFunctionHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\A(module_function)(\s|\(|$)/)
namespace_only
process do
if statement.tokens.size == 1
self.scope = :module
else
tokval_list(statement.tokens[2..-1], :attr).each do |name|
instance_method = MethodObject.new(namespace, name)
class_method = MethodObject.new(namespace, name, :module)
instance_method.copy_to(class_method)
class_method.visibility = :public
end
end
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
# (see Ruby::ModuleHandler)
class YARD::Handlers::Ruby::Legacy::ModuleHandler < YARD::Handlers::Ruby::Legacy::Base
handles TkMODULE
namespace_only
process do
modname = statement.tokens.to_s[/^module\s+(#{NAMESPACEMATCH})/, 1]
mod = register ModuleObject.new(namespace, modname)
parse_block(:namespace => mod)
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
# (see Ruby::PrivateClassMethodHandler)
class YARD::Handlers::Ruby::Legacy::PrivateClassMethodHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\Aprivate_class_method(\s|\(|$)/)
namespace_only
process do
tokval_list(statement.tokens[2..-1], :attr).each do |name|
privatize_class_method name
end
end
private
def privatize_class_method(name)
method = Proxy.new(namespace, name)
ensure_loaded!(method)
method.visibility = :private
rescue YARD::Handlers::NamespaceMissingError
raise UndocumentableError, "private visibility set on unrecognized method: #{name}"
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
# (see Ruby::PrivateConstantHandler)
class YARD::Handlers::Ruby::Legacy::PrivateConstantHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\Aprivate_constant(\s|\(|$)/)
namespace_only
process do
tokval_list(statement.tokens[2..-1], :attr, TkCONSTANT).each do |name|
privatize_constant name
end
end
private
def privatize_constant(name)
const = Proxy.new(namespace, name)
ensure_loaded!(const)
const.visibility = :private
rescue NamespaceMissingError
raise UndocumentableError, "private visibility set on unrecognized constant: #{name}"
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
# (see Ruby::VisibilityHandler)
class YARD::Handlers::Ruby::Legacy::VisibilityHandler < YARD::Handlers::Ruby::Legacy::Base
handles(/\A(protected|private|public)(\s|\(|$)/)
namespace_only
process do
vis = statement.tokens.first.text
if statement.tokens.size == 1
self.visibility = vis
else
tokval_list(statement.tokens[2..-1], :attr).each do |name|
MethodObject.new(namespace, name, scope) {|o| o.visibility = vis }
end
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
# (see Ruby::YieldHandler)
class YARD::Handlers::Ruby::Legacy::YieldHandler < YARD::Handlers::Ruby::Legacy::Base
handles TkYIELD
process do
return unless owner.is_a?(MethodObject) # Only methods yield
return if owner.has_tag? :yield # Don't override yield tags
return if owner.has_tag? :yieldparam # Same thing.
yieldtag = YARD::Tags::Tag.new(:yield, "", [])
tokval_list(statement.tokens[2..-1], Token).each do |item|
item = item.inspect unless item.is_a?(String)
if item == "self"
yieldtag.types << '_self'
owner.add_tag YARD::Tags::Tag.new(:yieldparam,
"the object that the method was called on", owner.namespace.path, '_self')
elsif item == "super"
yieldtag.types << '_super'
owner.add_tag YARD::Tags::Tag.new(:yieldparam,
"the result of the method from the superclass", nil, '_super')
else
yieldtag.types << item
end
end
owner.add_tag(yieldtag) unless yieldtag.types.empty?
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
# Handles a conditional inside a method
class YARD::Handlers::Ruby::MethodConditionHandler < YARD::Handlers::Ruby::Base
handles :if_mod, :unless_mod
process do
parse_block(statement.then_block, :owner => owner)
end
end

Some files were not shown because too many files have changed in this diff Show More