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 "parses dangling block expressions" do
    s = stmt <<-eof
      if
          foo
        puts 'hi'
      end
eof

    expect(s.tokens.to_s(true)).to eq "if\n          foo\n        ...\n      end"
    expect(s.tokens.to_s).to eq "if\n          foo"
    expect(s.block.to_s).to eq "puts 'hi'"

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

    expect(s.tokens.to_s(true)).to eq "if foo ||\n          bar\n        ...\n      end"
    expect(s.tokens.to_s).to eq "if foo ||\n          bar"
    expect(s.block.to_s).to eq "puts 'hi'"
  end

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

    expect(s.tokens.to_s(true)).to eq "(foo; bar)"
    expect(s.block).to be nil
  end

  it "allows for non-block statements" do
    s = stmt "hello_world(1, 2, 3)"
    expect(s.tokens.to_s).to eq "hello_world(1, 2, 3)"
    expect(s.block).to be nil
  end

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

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

  it "allows continued processing after a block" do
    s = stmt "if foo; end.stuff"
    expect(s.tokens.to_s(true)).to eq "if foo; end.stuff"
    expect(s.block.to_s).to eq ""

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

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

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

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

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

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

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

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

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

    expect(s.tokens.to_s(true)).to eq "foo do\n        ...\n      end"
    expect(s.block.to_s).to eq "puts 'hi'"
  end

  it "parses blocks with {}" do
    s = stmt "x { y }"
    expect(s.tokens.to_s(true)).to eq "x { ... }"
    expect(s.block.to_s).to eq "y"

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

  it "parses blocks with begin/end" do
    s = stmt "begin xyz end"
    expect(s.tokens.to_s(true)).to eq "begin ... end"
    expect(s.block.to_s).to eq "xyz"
  end

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

    expect(s.tokens.to_s(true)).to eq "foo(:x) { ... }"
    expect(s.block.to_s).to eq "baz(:y) { skippy }"
  end

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

    s = stmt "x = {}"
    expect(s.tokens.to_s(true)).to eq "x = {}"
    expect(s.block.to_s).to eq ""

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

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

    expect(s.tokens.to_s(true)).to eq "x {...}"
    expect(s.block.to_s).to eq "x = {}"
  end

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

    expect(s.tokens.to_s(true)).to eq "[:foo, x {}]"
    expect(s.block.to_s).to eq ""
  end

  it "handles multiple methods" do
    s = stmt <<-eof
      def %; end
      def b; end
    eof
    expect(s.to_s).to eq "def %; end"
  end

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

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

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

    s = stmt <<-eof

      # comment
      # comment
      def method; end
    eof
    expect(s.comments).to eq ["comment", "comment"]
    expect(s.comments_range).to eq (2..3)

    s = stmt <<-eof
      # comment
      # comment

      def method; end
    eof
    expect(s.comments).to eq ["comment", "comment"]
    expect(s.comments_range).to eq (1..2)

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

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

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

      # comments

      def method; end
    eof
    expect(s.comments).to eq ["comments"]

    s = stmt <<-eof
      # comments


      def method; end
    eof
    expect(s.comments).to eq nil

    ss = stmts <<-eof
      # comments


      def method; end

      # hello
      def method2; end
    eof
    expect(ss[0].comments).to eq nil
    expect(ss[1].comments).to eq ['hello']
  end

  it "handles 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")
    expect(s[1].comments).to eq ['Test Test', '', 'Example:', '  example code']
  end

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

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

  it "allows aliasing keywords" do
    ['do x', 'x do', 'end begin', 'begin end'].each do |a|
      s = stmt("alias #{a}\ndef foo; end")
      expect(s.tokens.to_s).to eq "alias #{a}"
      expect(s.block).to be nil
    end

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

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

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