# frozen_string_literal: true

require_relative "../test_helper"

module Prism
  class ParseTest < TestCase
    def test_parse_result
      result = Prism.parse("")
      assert_kind_of ParseResult, result

      result = Prism.parse_file(__FILE__)
      assert_kind_of ParseResult, result
    end

    def test_parse_empty_string
      result = Prism.parse("")
      assert_equal [], result.value.statements.body
    end

    def test_parse_takes_file_path
      filepath = "filepath.rb"
      result = Prism.parse("def foo; __FILE__; end", filepath: filepath)

      assert_equal filepath, find_source_file_node(result.value).filepath
    end

    def test_parse_takes_line
      line = 4
      result = Prism.parse("def foo\n __FILE__\nend", line: line)

      assert_equal line, result.value.location.start_line
      assert_equal line + 1, find_source_file_node(result.value).location.start_line

      result = Prism.parse_lex("def foo\n __FILE__\nend", line: line)
      assert_equal line, result.value.first.location.start_line
    end

    def test_parse_takes_negative_lines
      line = -2
      result = Prism.parse("def foo\n __FILE__\nend", line: line)

      assert_equal line, result.value.location.start_line
      assert_equal line + 1, find_source_file_node(result.value).location.start_line

      result = Prism.parse_lex("def foo\n __FILE__\nend", line: line)
      assert_equal line, result.value.first.location.start_line
    end

    def test_parse_file
      node = Prism.parse_file(__FILE__).value
      assert_kind_of ProgramNode, node

      error = assert_raise Errno::ENOENT do
        Prism.parse_file("idontexist.rb")
      end

      assert_equal "No such file or directory - idontexist.rb", error.message

      assert_raise TypeError do
        Prism.parse_file(nil)
      end
    end

    def test_parse_tempfile
      Tempfile.create(["test_parse_tempfile", ".rb"]) do |t|
        t.puts ["begin\n", " end\n"]
        t.flush
        Prism.parse_file(t.path)
      end
    end

    if RUBY_ENGINE != "truffleruby"
      def test_parse_nonascii
        Dir.mktmpdir do |dir|
          path = File.join(dir, "\u{3042 3044 3046 3048 304a}.rb".encode(Encoding::Windows_31J))
          File.write(path, "ok")
          Prism.parse_file(path)
        end
      end
    end

    def test_parse_directory
      error = nil

      begin
        Prism.parse_file(__dir__)
      rescue SystemCallError => error
      end

      assert_kind_of Errno::EISDIR, error
    end

    def test_partial_script
      assert Prism.parse_failure?("break")
      assert Prism.parse_success?("break", partial_script: true)

      assert Prism.parse_failure?("next")
      assert Prism.parse_success?("next", partial_script: true)

      assert Prism.parse_failure?("redo")
      assert Prism.parse_success?("redo", partial_script: true)

      assert Prism.parse_failure?("yield")
      assert Prism.parse_success?("yield", partial_script: true)
    end

    def test_version
      assert Prism.parse_success?("1 + 1", version: "3.3")
      assert Prism.parse_success?("1 + 1", version: "3.3.0")
      assert Prism.parse_success?("1 + 1", version: "3.3.1")
      assert Prism.parse_success?("1 + 1", version: "3.3.9")
      assert Prism.parse_success?("1 + 1", version: "3.3.10")

      assert Prism.parse_success?("1 + 1", version: "3.4")
      assert Prism.parse_success?("1 + 1", version: "3.4.0")
      assert Prism.parse_success?("1 + 1", version: "3.4.9")
      assert Prism.parse_success?("1 + 1", version: "3.4.10")

      assert Prism.parse_success?("1 + 1", version: "3.5")
      assert Prism.parse_success?("1 + 1", version: "3.5.0")

      assert Prism.parse_success?("1 + 1", version: "4.0")
      assert Prism.parse_success?("1 + 1", version: "4.0.0")

      assert Prism.parse_success?("1 + 1", version: "4.1")
      assert Prism.parse_success?("1 + 1", version: "4.1.0")

      assert Prism.parse_success?("1 + 1", version: "latest")

      # Test edge case
      error = assert_raise(ArgumentError) { Prism.parse("1 + 1", version: "latest2") }
      assert_equal "invalid version: latest2", error.message

      assert_raise ArgumentError do
        Prism.parse("1 + 1", version: "3.3.a")
      end

      # Not supported version (too old)
      assert_raise ArgumentError do
        Prism.parse("1 + 1", version: "3.2.0")
      end

      # Not supported version (too new)
      assert_raise ArgumentError do
        Prism.parse("1 + 1", version: "3.6.0")
      end
    end

    def test_version_current
      if RUBY_VERSION >= "3.3"
        assert Prism.parse_success?("1 + 1", version: "current")
      else
        assert_raise(CurrentVersionError) { Prism.parse_success?("1 + 1", version: "current") }
      end
    end

    def test_nearest
      assert Prism.parse_success?("1 + 1", version: "nearest")
    end

    def test_scopes
      assert_kind_of Prism::CallNode, Prism.parse_statement("foo")
      assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]])
      assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [Prism.scope(locals: [:foo])])

      assert Prism.parse_failure?("foo(*)")
      assert Prism.parse_success?("foo(*)", scopes: [Prism.scope(forwarding: [:*])])

      assert Prism.parse_failure?("foo(**)")
      assert Prism.parse_success?("foo(**)", scopes: [Prism.scope(forwarding: [:**])])

      assert Prism.parse_failure?("foo(&)")
      assert Prism.parse_success?("foo(&)", scopes: [Prism.scope(forwarding: [:&])])

      assert Prism.parse_failure?("foo(...)")
      assert Prism.parse_success?("foo(...)", scopes: [Prism.scope(forwarding: [:"..."])])
    end

    private

    def find_source_file_node(program)
      queue = [program]
      while (node = queue.shift)
        return node if node.is_a?(SourceFileNode)
        queue.concat(node.compact_child_nodes)
      end
    end
  end
end
