lib/yard/handlers/c/handler_methods.rb in yard-0.9.18 vs lib/yard/handlers/c/handler_methods.rb in yard-0.9.19

- old
+ new

@@ -1,211 +1,212 @@ -# frozen_string_literal: true -module YARD - module Handlers - module C - module HandlerMethods - include Parser::C - include CodeObjects - - 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 - obj.add_tag(Tags::Tag.new(:return, '', 'Boolean')) 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 +# 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