From 2810eb5c488b4741d3911103cc0870a862db6128 Mon Sep 17 00:00:00 2001 From: Loren Segal Date: Sat, 25 Dec 2010 05:30:40 -0500 Subject: [PATCH] Add support for `private_constant` class method calls to recognize private class, module and constant definitions (proposed for Ruby 1.9.3). Closes gh-219 --- lib/yard/autoload.rb | 70 ++++++++++--------- lib/yard/code_objects/base.rb | 11 ++- lib/yard/code_objects/method_object.rb | 9 --- .../ruby/legacy/private_constant_handler.rb | 21 ++++++ .../handlers/ruby/private_constant_handler.rb | 36 ++++++++++ .../private_constant_handler_001.rb.txt | 8 +++ .../handlers/private_constant_handler_spec.rb | 24 +++++++ 7 files changed, 130 insertions(+), 49 deletions(-) create mode 100644 lib/yard/handlers/ruby/legacy/private_constant_handler.rb create mode 100644 lib/yard/handlers/ruby/private_constant_handler.rb create mode 100644 spec/handlers/examples/private_constant_handler_001.rb.txt create mode 100644 spec/handlers/private_constant_handler_spec.rb diff --git a/lib/yard/autoload.rb b/lib/yard/autoload.rb index 67df02a54..0a87e549d 100644 --- a/lib/yard/autoload.rb +++ b/lib/yard/autoload.rb @@ -56,46 +56,48 @@ module CodeObjects module Handlers module Ruby # All Ruby handlers module Legacy # Handlers for old Ruby 1.8 parser - autoload :Base, __p('handlers/ruby/legacy/base') + 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 :ConstantHandler, __p('handlers/ruby/legacy/constant_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 :ProcessHandler, __p('handlers/ruby/legacy/process_handler') - autoload :VisibilityHandler, __p('handlers/ruby/legacy/visibility_handler') - autoload :YieldHandler, __p('handlers/ruby/legacy/yield_handler') + 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 :ConstantHandler, __p('handlers/ruby/legacy/constant_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 :PrivateConstantHandler, __p('handlers/ruby/legacy/private_constant_handler') + autoload :ProcessHandler, __p('handlers/ruby/legacy/process_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 :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 :ConstantHandler, __p('handlers/ruby/constant_handler') - 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 :ProcessHandler, __p('handlers/ruby/process_handler') - autoload :StructHandlerMethods, __p('handlers/ruby/struct_handler_methods') - autoload :VisibilityHandler, __p('handlers/ruby/visibility_handler') - autoload :YieldHandler, __p('handlers/ruby/yield_handler') + 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 :ConstantHandler, __p('handlers/ruby/constant_handler') + 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 :PrivateConstantHandler, __p('handlers/ruby/private_constant_handler') + autoload :ProcessHandler, __p('handlers/ruby/process_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 :Processor, __p('handlers/processor') + autoload :Base, __p('handlers/base') + autoload :Processor, __p('handlers/processor') end # The parser namespace holds all parsing engines used by YARD. diff --git a/lib/yard/code_objects/base.rb b/lib/yard/code_objects/base.rb index 351e907ce..08d4e74d7 100644 --- a/lib/yard/code_objects/base.rb +++ b/lib/yard/code_objects/base.rb @@ -152,12 +152,10 @@ class Base # @see #dynamic def dynamic?; @dynamic end - # This attribute exists in order to maintain a consistent interface - # with the {MethodObject} class, so that a {Verifier} expression need - # not check the object type before accessing visibility. - # - # @return [Symbol] always returns public for a base object. - def visibility; :public 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 @@ -212,6 +210,7 @@ def initialize(namespace, name, *args, &block) @current_file_has_comments = false @name = name.to_sym @source_type = :ruby + @visibility = :public @tags = [] @docstring = Docstring.new('', self) @namespace = nil diff --git a/lib/yard/code_objects/method_object.rb b/lib/yard/code_objects/method_object.rb index 2059e9365..5c4bcfe65 100644 --- a/lib/yard/code_objects/method_object.rb +++ b/lib/yard/code_objects/method_object.rb @@ -1,11 +1,6 @@ module YARD::CodeObjects # Represents a Ruby method in source class MethodObject < Base - # The visibility of the method (+:public:+, +:protected+, +:private+) - # - # @return [Symbol] the method visibility - attr_reader :visibility - # The scope of the method (+:class+ or +:instance+) # # @return [Symbol] the scope @@ -49,10 +44,6 @@ def scope=(v) @scope = v.to_sym YARD::Registry.register(self) if reregister end - - # Sets the visibility - # @param [Symbol] v the new visibility (:public, :private, or :protected) - def visibility=(v) @visibility = v.to_sym end # @return whether or not the method is the #initialize constructor method def constructor? diff --git a/lib/yard/handlers/ruby/legacy/private_constant_handler.rb b/lib/yard/handlers/ruby/legacy/private_constant_handler.rb new file mode 100644 index 000000000..a4c0909c5 --- /dev/null +++ b/lib/yard/handlers/ruby/legacy/private_constant_handler.rb @@ -0,0 +1,21 @@ +# (see Ruby::PrivateConstantHandler) +class YARD::Handlers::Ruby::Legacy::PrivateConstantHandler < YARD::Handlers::Ruby::Legacy::Base + namespace_only + handles /\Aprivate_constant(\s|\(|$)/ + + 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 diff --git a/lib/yard/handlers/ruby/private_constant_handler.rb b/lib/yard/handlers/ruby/private_constant_handler.rb new file mode 100644 index 000000000..b57aa2571 --- /dev/null +++ b/lib/yard/handlers/ruby/private_constant_handler.rb @@ -0,0 +1,36 @@ +# Sets visibility of a constant (class, module, const) +class YARD::Handlers::Ruby::PrivateConstantHandler < YARD::Handlers::Ruby::Base + namespace_only + handles method_call(:private_constant) + + process do + errors = [] + statement.parameters.each do |param| + next unless param.respond_to?(:type) + begin + privatize_constant(param) + rescue UndocumentableError => err + errors << err.message + end + end + if errors.size > 0 + msg = errors.size == 1 ? ": #{errors[0]}" : "s: #{errors.join(", ")}" + raise UndocumentableError, "private constant#{msg} for #{namespace.path}" + end + end + + private + + def privatize_constant(node) + if node.literal? || (node.type == :var_ref && node[0].type == :const) + node = node.jump(:tstring_content, :const) + const = Proxy.new(namespace, node[0]) + ensure_loaded!(const) + const.visibility = :private + else + raise UndocumentableError, "invalid argument to private_constant: #{node.source}" + end + rescue NamespaceMissingError + raise UndocumentableError, "private visibility set on unrecognized constant: #{node[0]}" + end +end \ No newline at end of file diff --git a/spec/handlers/examples/private_constant_handler_001.rb.txt b/spec/handlers/examples/private_constant_handler_001.rb.txt new file mode 100644 index 000000000..de4bfee07 --- /dev/null +++ b/spec/handlers/examples/private_constant_handler_001.rb.txt @@ -0,0 +1,8 @@ +module A + Foo = 1 + class B; end + module C; end + module D; end + + private_constant :Foo, 'B', C +end diff --git a/spec/handlers/private_constant_handler_spec.rb b/spec/handlers/private_constant_handler_spec.rb new file mode 100644 index 000000000..0e9a02cd6 --- /dev/null +++ b/spec/handlers/private_constant_handler_spec.rb @@ -0,0 +1,24 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "YARD::Handlers::Ruby::#{RUBY18 ? "Legacy::" : ""}PrivateConstantHandler" do + before(:all) { parse_file :private_constant_handler_001, __FILE__ } + + it "should handle private_constant statement" do + Registry.at('A::Foo').visibility.should == :private + Registry.at('A::B').visibility.should == :private + Registry.at('A::C').visibility.should == :private + end + + it "should make all other constants public" do + Registry.at('A::D').visibility.should == :public + end + + it "should fail if parameter is not String, Symbol or Constant" do + undoc_error 'class Foo; private_constant x; end' + undoc_error 'class Foo; X = 1; private_constant X.new("hi"); end' + end if RUBY19 + + it "should fail if constant can't be recognized" do + undoc_error 'class Foo2; private_constant :X end' + end +end