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

describe YARD::DocstringParser do
  def parse(content, object = nil, handler = nil)
    @library ||= Tags::Library.instance
    @parser = DocstringParser.new(@library)
    @parser.parse(content, object, handler)
    @parser
  end

  def docstring(content, object = nil, handler = nil)
    parse(content, object, handler).to_docstring
  end

  describe '#parse' do
    it "should parse comments into tags" do
      doc = docstring(<<-eof)
@param name Hello world
  how are you?
@param name2
  this is a new line
@param name3 and this
  is a new paragraph:

  right here.
      eof
      tags = doc.tags(:param)
      tags[0].name.should == "name"
      tags[0].text.should == "Hello world\nhow are you?"
      tags[1].name.should == "name2"
      tags[1].text.should == "this is a new line"
      tags[2].name.should == "name3"
      tags[2].text.should == "and this\nis a new paragraph:\n\nright here."
    end

    it "should end parsing a tag on de-dent" do
      doc = docstring(<<-eof)
@note test
  one two three
rest of docstring
      eof
      doc.tag(:note).text.should == "test\none two three"
      doc.should == "rest of docstring"
    end

    it "should parse examples embedded in doc" do
      doc = docstring(<<-eof)
test string here
@example code

  def foo(x, y, z)
  end

  class A; end

more stuff
eof
      doc.should == "test string here\nmore stuff"
      doc.tag(:example).text.should == "\ndef foo(x, y, z)\nend\n\nclass A; end"
    end

    it "should remove only original indentation from beginning of line in tags" do
      doc = docstring(<<-eof)
@param name
  some value
  foo bar
   baz
eof
      doc.tag(:param).text.should == "some value\nfoo bar\n baz"
    end

    it "should allow numbers in tags" do
      Tags::Library.define_tag(nil, :foo1)
      Tags::Library.define_tag(nil, :foo2)
      Tags::Library.define_tag(nil, :foo3)
      doc = docstring(<<-eof)
@foo1 bar1
@foo2 bar2
@foo3 bar3
eof
      doc.tag(:foo1).text.should == "bar1"
      doc.tag(:foo2).text.should == "bar2"
    end

    it "should end tag on newline if next line is not indented" do
      doc = docstring(<<-eof)
@author bar1
@api bar2
Hello world
eof
      doc.tag(:author).text.should == "bar1"
      doc.tag(:api).text.should == "bar2"
    end

    it "should warn about unknown tag" do
      log.should_receive(:warn).with(/Unknown tag @hello$/)
      docstring("@hello world")
    end

    it "should not add trailing whitespace to freeform tags" do
      doc = docstring("@api private   \t   ")
      doc.tag(:api).text.should == "private"
    end
  end

  describe '#parse with custom tag library' do
    class TestLibrary < Tags::Library; end

    before { @library = TestLibrary.new }

    it "should accept valid tags" do
      valid = %w( testing valid is_a is_A __ )
      valid.each do |tag|
        TestLibrary.define_tag("Tag", tag)
        doc = docstring('@' + tag + ' foo bar')
        doc.tag(tag).text.should == 'foo bar'
      end
    end

    it "should not parse invalid tag names" do
      invalid = %w( @ @return@ @param, @x-y @.x.y.z )
      invalid.each do |tag|
        docstring(tag + ' foo bar').should == tag + ' foo bar'
      end
    end

    it "should allow namespaced tags in the form @x.y.z" do
      TestLibrary.define_tag("Tag", 'x.y.z')
      doc = docstring("@x.y.z foo bar")
      doc.tag('x.y.z').text.should == 'foo bar'
    end

    it "should ignore new directives without @! prefix syntax" do
      TestLibrary.define_directive('dir1', Tags::ScopeDirective)
      log.should_receive(:warn).with(/@dir1/)
      docstring("@dir1")
    end

    %w(attribute endgroup group macro method scope visibility).each do |tag|
      it "should handle non prefixed @#{tag} syntax as directive, not tag" do
        TestLibrary.define_directive(tag, Tags::ScopeDirective)
        parse("@#{tag}")
        @parser.directives.first.should be_a(Tags::ScopeDirective)
      end
    end

    it "should handle directives with @! prefix syntax" do
      TestLibrary.define_directive('dir1', Tags::ScopeDirective)
      docstring("@!dir1 class")
      @parser.state.scope.should == :class
    end
  end

  describe '#text' do
    it "should only return text data" do
      parse("Foo\n@param foo x y z\nBar")
      @parser.text.should == "Foo\nBar"
    end
  end

  describe '#raw_text' do
    it "should return the entire original data" do
      data = "Foo\n@param foo x y z\nBar"
      parse(data)
      @parser.raw_text.should == data
    end
  end

  describe '#tags' do
    it "should return the parsed tags" do
      data = "Foo\n@param foo x y z\nBar"
      parse(data)
      @parser.tags.size.should == 1
      @parser.tags.first.tag_name.should == 'param'
    end
  end

  describe '#directives' do
    it "should group all processed directives" do
      data = "Foo\n@!scope class\n@!visibility private\nBar"
      parse(data)
      dirs = @parser.directives
      dirs.size == 2
      dirs[0].should be_a(Tags::ScopeDirective)
      dirs[0].tag.text.should == 'class'
      dirs[1].should be_a(Tags::VisibilityDirective)
      dirs[1].tag.text.should == 'private'
    end
  end

  describe '#state' do
    it "should handle modified state" do
      parse("@!scope class")
      @parser.state.scope.should == :class
    end
  end

  describe 'after_parse' do
    it "should allow specifying of callbacks" do
      parser = DocstringParser.new
      the_yielded_obj = nil
      DocstringParser.after_parse {|obj| the_yielded_obj = obj }
      parser.parse("Some text")
      the_yielded_obj.should == parser
    end

    it "should warn about invalid named parameters" do
      log.should_receive(:warn).with(/@param tag has unknown parameter name: notaparam/)
      YARD.parse_string <<-eof
        # @param notaparam foo
        def foo(a) end
      eof
    end

    it "should warn about duplicate named parameters" do
      log.should_receive(:warn).with(/@param tag has duplicate parameter name: a/)
      YARD.parse_string <<-eof
        # @param a foo
        # @param a foo
        def foo(a) end
      eof
    end
  end
end
