# frozen_string_literal: true

return if RUBY_VERSION < "3.3.0"

require_relative "test_helper"

module Prism
  class ErrorsTest < TestCase
    base = File.expand_path("errors", __dir__)
    filepaths = Dir[ENV.fetch("FOCUS", "**/*.txt"), base: base]

    PARSE_Y_EXCLUDES = [
      # https://bugs.ruby-lang.org/issues/20409
      "#{base}/4.1/end_block_exit.txt"
    ]

    filepaths.each do |filepath|
      ruby_versions_for(filepath).each do |version|
        define_method(:"test_#{version}_#{File.basename(filepath, ".txt")}") do
          assert_errors(File.join(base, filepath), version)
        end
      end
    end

    def test_newline_preceding_eof
      err = Prism.parse("foo(").errors.first
      assert_equal 1, err.location.start_line

      err = Prism.parse("foo(\n").errors.first
      assert_equal 1, err.location.start_line

      err = Prism.parse("foo(\n\n\n\n\n").errors.first
      assert_equal 5, err.location.start_line
    end

    def test_embdoc_ending
      source = <<~RUBY
        =begin\n=end
        =begin\n=end\0
        =begin\n=end\C-d
        =begin\n=end\C-z
      RUBY

      source.each_line do |line|
        assert_valid_syntax(source)
        assert_predicate Prism.parse(source), :success?
      end
    end

    def test_unterminated_string_closing
      statement = Prism.parse_statement("'hello")
      assert_equal statement.unescaped, "hello"
      assert_nil statement.closing
    end

    def test_unterminated_interpolated_string_closing
      statement = Prism.parse_statement('"hello')
      assert_equal statement.unescaped, "hello"
      assert_nil statement.closing
    end

    def test_unterminated_empty_string_closing
      statement = Prism.parse_statement('"')
      assert_empty statement.unescaped
      assert_nil statement.closing
    end

    def test_regexp_encoding_option_mismatch_error
      # UTF-8 char with ASCII-8BIT modifier
      result = Prism.parse('/Ȃ/n')
      assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch

      # UTF-8 char with EUC-JP modifier
      result = Prism.parse('/Ȃ/e')
      assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch

      # UTF-8 char with Windows-31J modifier
      result = Prism.parse('/Ȃ/s')
      assert_includes result.errors.map(&:type), :regexp_encoding_option_mismatch

      # UTF-8 char with UTF-8 modifier
      result = Prism.parse('/Ȃ/u')
      assert_empty result.errors
    end

    def test_incomplete_def_closing_loc
      statement = Prism.parse_statement("def f; 123")
      assert_nil(statement.end_keyword)
    end

    def test_unclosed_interpolation
      statement = Prism.parse_statement("\"\#{")
      assert_equal('"', statement.opening)
      assert_nil(statement.closing)

      assert_equal(1, statement.parts.count)
      assert_equal('#{', statement.parts[0].opening)
      assert_equal("", statement.parts[0].closing)
      assert_nil(statement.parts[0].statements)
    end

    def test_unclosed_heredoc_and_interpolation
      statement = Prism.parse_statement("<<D\n\#{")
      assert_equal("<<D", statement.opening)
      assert_nil(statement.closing)

      assert_equal(1, statement.parts.count)
      assert_equal('#{', statement.parts[0].opening)
      assert_equal("", statement.parts[0].closing)
      assert_nil(statement.parts[0].statements)
    end

    private

    def assert_errors(filepath, version)
      expected = File.read(filepath, binmode: true, external_encoding: Encoding::UTF_8)

      source = expected.lines.grep_v(/^\s*\^/).join.gsub(/\n*\z/, "")
      if CURRENT_MAJOR_MINOR == version && !PARSE_Y_EXCLUDES.include?(filepath)
        refute_valid_syntax(source)
      end

      result = Prism.parse(source, version: version)
      errors = result.errors
      refute_empty errors, "Expected errors in #{filepath}"

      actual = result.errors_format
      if expected != actual && ENV["UPDATE_SNAPSHOTS"]
        File.write(filepath, actual)
      end

      assert_equal expected, actual, "Expected errors to match for #{filepath}"
    end
  end
end
