require File.dirname(__FILE__) + '/spec_helper'

describe "YARD::Handlers::Ruby::#{LEGACY_PARSER ? "Legacy::" : ""}MethodHandler" do
  before(:all) do
    log.enter_level(Logger::ERROR) do
      parse_file :method_handler_001, __FILE__
    end
  end

  it "should add methods to parent's #meths list" do
    P(:Foo).meths.should include(P("Foo#method1"))
  end

  it "should parse/add class methods (self.method2)" do
    P(:Foo).meths.should include(P("Foo.method2"))
  end

  it "should parse/add class methods from other namespaces (String.hello)" do
    P("String.hello").should be_instance_of(CodeObjects::MethodObject)
  end

  [:[], :[]=, :allowed?, :/, :=~, :==, :`, :|, :*, :&, :%, :'^', :-@, :+@, :'~@'].each do |name|
    it "should allow valid method #{name}" do
      Registry.at("Foo##{name}").should_not be_nil
    end
  end

  it "should allow self.methname" do
    Registry.at("Foo.new").should_not be_nil
  end

  it "should mark dynamic methods as such" do
    P('Foo#dynamic').dynamic?.should == true
  end

  it "should show that a method is explicitly defined (if it was originally defined implicitly by attribute)" do
    P('Foo#method1').is_explicit?.should == true
  end

  it "should handle parameters" do
    P('Foo#[]').parameters.should == [['key', "'default'"]]
    P('Foo#/').parameters.should == [['x', "File.new('x', 'w')"], ['y', '2']]
  end

  it "should handle opts = {} as parameter" do
    P('Foo#optsmeth').parameters.should == [['x', nil], ['opts', '{}']]
  end

  it "should handle &block as parameter" do
    P('Foo#blockmeth').parameters.should == [['x', nil], ['&block', nil]]
  end

  it "should handle overloads" do
    meth = P('Foo#foo')

    o1 = meth.tags(:overload).first
    o1.name.should == :bar
    o1.parameters.should == [['a', nil], ['b', "1"]]
    o1.tag(:return).type.should == "String"

    o2 = meth.tags(:overload)[1]
    o2.name.should == :baz
    o2.parameters.should == [['b', nil], ['c', nil]]
    o2.tag(:return).type.should == "Fixnum"

    o3 = meth.tags(:overload)[2]
    o3.name.should == :bang
    o3.parameters.should == [['d', nil], ['e', nil]]
    o3.docstring.should be_empty
    o3.docstring.should be_blank
  end

  it "should set a return tag if not set on #initialize" do
    meth = P('Foo#initialize')

    meth.should have_tag(:return)
    meth.tag(:return).types.should == ["Foo"]
    meth.tag(:return).text.should == "a new instance of Foo"
  end

  %w(inherited included method_added method_removed method_undefined).each do |meth|
    it "should set @private tag on #{meth} callback method if no docstring is set" do
      P('Foo.' + meth).should have_tag(:private)
    end
  end

  it "should not set @private tag on extended callback method since docstring is set" do
    P('Foo.extended').should_not have_tag(:private)
  end

  it "should add @return [Boolean] tag to methods ending in ? without return types" do
    meth = P('Foo#boolean?')
    meth.should have_tag(:return)
    meth.tag(:return).types.should == ['Boolean']
  end

  it "should add Boolean type to return tag without types" do
    meth = P('Foo#boolean2?')
    meth.should have_tag(:return)
    meth.tag(:return).types.should == ['Boolean']
  end

  it "should not change return type for method ending in ? with return types set" do
    meth = P('Foo#boolean3?')
    meth.should have_tag(:return)
    meth.tag(:return).types.should == ['NotBoolean', 'nil']
  end

  it "should not change return type for method ending in ? with return types set by @overload" do
    meth = P('Foo#rainy?')
    meth.should have_tag(:overload)
    meth.tag(:overload).should have_tag(:return)
    meth.should_not have_tag(:return)
  end

  it "should add method writer to existing attribute" do
    Registry.at('Foo#attr_name').should be_reader
    Registry.at('Foo#attr_name=').should be_writer
  end

  it "should add method reader to existing attribute" do
    Registry.at('Foo#attr_name2').should be_reader
    Registry.at('Foo#attr_name2=').should be_writer
  end

  it "should generate an options parameter if @option refers to an undocumented parameter" do
    meth = P('Foo#auto_opts')
    meth.should have_tag(:param)
    meth.tag(:param).name.should == "opts"
    meth.tag(:param).types.should == ["Hash"]
  end

  it "should raise an undocumentable error when a method is defined on an object instance" do
    undoc_error "error = Foo; def error.at(foo) end"
    Registry.at('error').should be_nil
  end

  it "should allow class method to be defined on constant reference object" do
    Registry.at('Foo.meth_on_const').should_not be_nil
    Registry.at('Foo.meth2_on_const').should_not be_nil
  end

  it "should copy alias information on method (re-)definition to new method" do
    Registry.at('D').aliases.should be_empty
    Registry.at('D#b').is_alias?.should == false
    Registry.at('D#a').is_alias?.should == false
  end

  it "should add macros for class methods" do
    macro = CodeObjects::MacroObject.find('prop')
    macro.should_not be_nil
    macro.macro_data.should == "@!method $1(value)\n$3\n@return [$2]"
    macro.method_object.should == Registry.at('E.property')
    macro.should be_attached
    obj = Registry.at('E#foo')
    obj.should_not be_nil
    obj.docstring.should == 'create a foo'
    obj.signature.should == 'def foo(value)'
    obj.tag(:return).types.should == ['String']
  end

  it "should handle macros on any object" do
    macro = CodeObjects::MacroObject.find('xyz')
    macro.should_not be_nil
    macro.macro_data.should == '@!method $1'
  end

  it "should skip macros on instance methods" do
    Registry.at('E#a').should be_nil
  end

  it "should warn if the macro name is invalid" do
    log.should_receive(:warn).with(/Invalid directive.*@!macro/)
    YARD.parse_string "class Foo\n# @!macro\ndef self.foo; end\nend"
  end

  it "should handle 'def end' methods" do
    obj = Registry.at('F::A#foo')
    obj.should_not be_nil
    obj = Registry.at('F::A#bar')
    obj.should_not be_nil
    obj.docstring.should == 'PASS'
  end
end
