require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper')

describe YARD::Parser::Ruby::Legacy::StatementList do
  def stmts(code) YARD::Parser::Ruby::Legacy::StatementList.new(code) end
  def stmt(code) stmts(code).first end

  it "should parse dangling block expressions" do
    s = stmt <<-eof
      if
          foo
        puts 'hi'
      end
eof

    s.tokens.to_s(true).should == "if\n          foo\n        ...\n      end"
    s.tokens.to_s.should == "if\n          foo"
    s.block.to_s.should == "puts 'hi'"

    s = stmt <<-eof
      if foo ||
          bar
        puts 'hi'
      end
eof

    s.tokens.to_s(true).should == "if foo ||\n          bar\n        ...\n      end"
    s.tokens.to_s.should == "if foo ||\n          bar"
    s.block.to_s.should == "puts 'hi'"
  end

  it "should allow semicolons within parentheses" do
    s = stmt "(foo; bar)"

    s.tokens.to_s(true).should == "(foo; bar)"
    s.block.should be_nil
  end

  it "should allow for non-block statements" do
    s = stmt "hello_world(1, 2, 3)"
    s.tokens.to_s.should == "hello_world(1, 2, 3)"
    s.block.should be_nil
  end

  it "should allow block statements to be used as part of other block statements" do
    s = stmt "while (foo; bar); foo = 12; end; while"

    s.tokens.to_s(true).should == "while (foo; bar); ... end"
    s.tokens.to_s.should == "while (foo; bar)"
    s.block.to_s.should == "foo = 12"
  end

  it "should allow continued processing after a block" do
    s = stmt "if foo; end.stuff"
    s.tokens.to_s(true).should == "if foo; end.stuff"
    s.block.to_s.should == ""

    s = stmt "if foo; end[stuff]"
    s.tokens.to_s(true).should == "if foo; end[stuff]"
    s.block.to_s.should == ""

    s = stmt "if foo; hi end.map do; 123; end"
    s.tokens.to_s(true).should == "if foo; ... end.map do; 123; end"
    s.block.to_s.should == "hi"
  end

  it "should parse default arguments" do
    s = stmt "def foo(bar, baz = 1, bang = 2); bar; end"
    s.tokens.to_s(true).should == "def foo(bar, baz = 1, bang = 2) ... end"
    s.block.to_s.should == "bar"

    s = stmt "def foo bar, baz = 1, bang = 2; bar; end"
    s.tokens.to_s(true).should == "def foo bar, baz = 1, bang = 2; ... end"
    s.block.to_s.should == "bar"

    s = stmt "def foo bar , baz = 1 , bang = 2; bar; end"
    s.tokens.to_s(true).should == "def foo bar , baz = 1 , bang = 2; ... end"
    s.block.to_s.should == "bar"
  end

  it "should parse complex default arguments" do
    s = stmt "def foo(bar, baz = File.new(1, 2), bang = 3); bar; end"
    s.tokens.to_s(true).should == "def foo(bar, baz = File.new(1, 2), bang = 3) ... end"
    s.block.to_s.should == "bar"

    s = stmt "def foo bar, baz = File.new(1, 2), bang = 3; bar; end"
    s.tokens.to_s(true).should == "def foo bar, baz = File.new(1, 2), bang = 3; ... end"
    s.block.to_s.should == "bar"

    s = stmt "def foo bar , baz = File.new(1, 2) , bang = 3; bar; end"
    s.tokens.to_s(true).should == "def foo bar , baz = File.new(1, 2) , bang = 3; ... end"
    s.block.to_s.should == "bar"
  end

  it "should parse blocks with do/end" do
    s = stmt <<-eof
      foo do
        puts 'hi'
      end
    eof

    s.tokens.to_s(true).should == "foo do\n        ...\n      end"
    s.block.to_s.should == "puts 'hi'"
  end

  it "should parse blocks with {}" do
    s = stmt "x { y }"
    s.tokens.to_s(true).should == "x { ... }"
    s.block.to_s.should == "y"

    s = stmt "x() { y }"
    s.tokens.to_s(true).should == "x() { ... }"
    s.block.to_s.should == "y"
  end

  it "should parse blocks with begin/end" do
    s = stmt "begin xyz end"
    s.tokens.to_s(true).should == "begin ... end"
    s.block.to_s.should == "xyz"
  end

  it "should parse nested blocks" do
    s = stmt "foo(:x) { baz(:y) { skippy } }"

    s.tokens.to_s(true).should == "foo(:x) { ... }"
    s.block.to_s.should == "baz(:y) { skippy }"
  end

  it "should not parse hashes as blocks" do
    s = stmt "x({})"
    s.tokens.to_s(true).should == "x({})"
    s.block.to_s.should == ""

    s = stmt "x = {}"
    s.tokens.to_s(true).should == "x = {}"
    s.block.to_s.should == ""

    s = stmt "x(y, {})"
    s.tokens.to_s(true).should == "x(y, {})"
    s.block.to_s.should == ""
  end

  it "should parse hashes in blocks with {}" do
    s = stmt "x {x = {}}"

    s.tokens.to_s(true).should == "x {...}"
    s.block.to_s.should == "x = {}"
  end

  it "should parse blocks with {} in hashes" do
    s = stmt "[:foo, x {}]"

    s.tokens.to_s(true).should == "[:foo, x {}]"
    s.block.to_s.should == ""
  end

  it "should handle multiple methods" do
    s = stmt <<-eof
      def %; end
      def b; end
    eof
    s.to_s.should == "def %; end"
  end

  it "should handle nested methods" do
    s = stmt <<-eof
      def *(o) def +@; end
        def ~@
        end end
    eof
    s.tokens.to_s(true).should == "def *(o) ... end"
    s.block.to_s.should == "def +@; end\n        def ~@\n        end"

    s = stmts(<<-eof)
      def /(other) 'hi' end
      def method1
        def dynamic; end
      end
    eof
    s[1].to_s.should == "def method1\n        def dynamic; end\n      end"
  end

  it "should get comment line numbers" do
    s = stmt <<-eof
      # comment
      # comment
      # comment
      def method; end
    eof
    s.comments.should == ["comment", "comment", "comment"]
    s.comments_range.should == (1..3)

    s = stmt <<-eof

      # comment
      # comment
      def method; end
    eof
    s.comments.should == ["comment", "comment"]
    s.comments_range.should == (2..3)

    s = stmt <<-eof
      # comment
      # comment

      def method; end
    eof
    s.comments.should == ["comment", "comment"]
    s.comments_range.should == (1..2)

    s = stmt <<-eof
      # comment
      def method; end
    eof
    s.comments.should == ["comment"]
    s.comments_range.should == (1..1)

    s = stmt <<-eof
      def method; end # comment
    eof
    s.comments.should == ["comment"]
    s.comments_range.should == (1..1)
  end

  it "should only look up to two lines back for comments" do
    s = stmt <<-eof
      # comments

      # comments

      def method; end
    eof
    s.comments.should == ["comments"]

    s = stmt <<-eof
      # comments


      def method; end
    eof
    s.comments.should == nil

    ss = stmts <<-eof
      # comments


      def method; end

      # hello
      def method2; end
    eof
    ss[0].comments.should == nil
    ss[1].comments.should == ['hello']
  end

  it "should handle CRLF (Windows) newlines" do
    s = stmts("require 'foo'\r\n\r\n# Test Test\r\n# \r\n# Example:\r\n#   example code\r\ndef test\r\nend\r\n")
    s[1].comments.should == ['Test Test', '', 'Example:', '  example code']
  end

  it "should handle elsif blocks" do
    s = stmts(stmt("if 0\n  foo\nelsif 2\n  bar\nend\nbaz").block)
    s.size.should == 2
    s[1].tokens.to_s.should == "elsif 2"
    s[1].block.to_s.should == "bar"
  end

  it "should handle else blocks" do
    s = stmts(stmt("if 0\n  foo\nelse\n  bar\nend\nbaz").block)
    s.size.should == 2
    s[1].tokens.to_s.should == "else"
    s[1].block.to_s.should == "bar"
  end

  it "should allow aliasing keywords" do
    ['do x', 'x do', 'end begin', 'begin end'].each do |a|
      s = stmt("alias #{a}\ndef foo; end")
      s.tokens.to_s.should == "alias #{a}"
      s.block.should be_nil
    end

    s = stmt("alias do x if 2 ==\n 2")
    s.tokens.to_s.should == "alias do x if 2 ==\n 2"
  end

  it "should not open a block on an aliased keyword block opener" do
    s = stmts(<<-eof)
      class A; alias x do end
      class B; end
    eof
    s[0].block.to_s.should == 'alias x do'
    s.size.should > 1
  end

  it "should convert heredoc to string" do
    src = "<<-XML\n  foo\n\nXML"
    s = stmt(src)
    s.source.should == '"foo\n\n"'
  end
end
