from io import BytesIO

from translate.misc.multistring import multistring
from translate.storage import php

from . import test_monolingual


def test_php_escaping_single_quote() -> None:
    """
    Test the helper escaping functions for 'single quotes'.

    The tests are built mostly from examples from the PHP
    `string type definition <http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.single>`_.
    """
    # Decoding - PHP -> Python
    assert (
        php.phpdecode(r"\'") == r"'"
    )  # To specify a literal single quote, escape it with a backslash (\).
    assert php.phpdecode(r'"') == r'"'
    assert (
        php.phpdecode(r"\\'") == r"\'"
    )  # To specify a literal backslash before a single quote, or at the end of the string, double it (\\)
    assert (
        php.phpdecode(r"\x") == r"\x"
    )  # Note that attempting to escape any other character will print the backslash too.
    assert php.phpdecode(r"\t") == r"\t"
    assert php.phpdecode(r"\n") == r"\n"
    assert php.phpdecode(r"this is a simple string") == r"this is a simple string"
    assert (
        php.phpdecode(
            """You can also have embedded newlines in
strings this way as it is
okay to do"""
        )
        == """You can also have embedded newlines in
strings this way as it is
okay to do"""
    )
    assert (
        php.phpdecode(r"This will not expand: \n a newline")
        == r"This will not expand: \n a newline"
    )
    assert (
        php.phpdecode(r'Arnold once said: "I\'ll be back"')
        == r'''Arnold once said: "I'll be back"'''
    )
    assert php.phpdecode(r"You deleted C:\\*.*?") == r"You deleted C:\*.*?"
    assert php.phpdecode(r"You deleted C:\*.*?") == r"You deleted C:\*.*?"
    assert (
        php.phpdecode(r"\117\143\164\141\154") == r"\117\143\164\141\154"
    )  # We don't handle Octal like " does
    assert php.phpdecode(r"\x48\x65\x78") == r"\x48\x65\x78"  # Don't handle Hex either
    # Should implement for false interpretation of double quoted data.
    # Encoding - Python -> PHP
    assert (
        php.phpencode(r"'") == r"\'"
    )  # To specify a literal single quote, escape it with a backslash (\).
    assert (
        php.phpencode(r"\'") == r"\\'"
    )  # To specify a literal backslash before a single quote, or at the end of the string, double it (\\)
    assert php.phpencode(r'"') == r'"'
    assert (
        php.phpencode(r"\x") == r"\x"
    )  # Note that attempting to escape any other character will print the backslash too.
    assert php.phpencode(r"\t") == r"\t"
    assert php.phpencode(r"\n") == r"\n"
    assert (
        php.phpencode(
            r"""String with
newline"""
        )
        == r"""String with
newline"""
    )
    assert (
        php.phpencode(r"This will not expand: \n a newline")
        == r"This will not expand: \n a newline"
    )
    assert (
        php.phpencode(r'''Arnold once said: "I'll be back"''')
        == r'''Arnold once said: "I\'ll be back"'''
    )
    assert php.phpencode(r"You deleted C:\*.*?") == r"You deleted C:\*.*?"


def test_php_escaping_double_quote() -> None:
    """Test the helper escaping functions for 'double quotes'."""
    # Decoding - PHP -> Python
    assert php.phpdecode("'", quotechar='"') == "'"  # we do nothing with single quotes
    assert (
        php.phpdecode(r"\n", quotechar='"') == "\n"
    )  # See table of escaped characters
    assert (
        php.phpdecode(r"\r", quotechar='"') == "\r"
    )  # See table of escaped characters
    assert (
        php.phpdecode(r"\t", quotechar='"') == "\t"
    )  # See table of escaped characters
    assert (
        php.phpdecode(r"\v", quotechar='"') == "\v"
    )  # See table of escaped characters
    assert (
        php.phpdecode(r"\f", quotechar='"') == "\f"
    )  # See table of escaped characters
    assert (
        php.phpdecode(r"\\", quotechar='"') == "\\"
    )  # See table of escaped characters
    # assert php.phpdecode(r"\$", quotechar='"') == "$"      # See table of escaped characters - this may cause confusion with actual variables in roundtripping
    assert (
        php.phpdecode(r"\$", quotechar='"') == "\\$"
    )  # Just to check that we don't unescape this
    assert php.phpdecode(r"\"", quotechar='"') == '"'  # See table of escaped characters
    assert (
        php.phpdecode(r"\117\143\164\141\154", quotechar='"') == "Octal"
    )  # Octal: \[0-7]{1,3}
    assert (
        php.phpdecode(r"\x48\x65\x78", quotechar='"') == "Hex"
    )  # Hex: \x[0-9A-Fa-f]{1,2}
    assert php.phpdecode(r"\117\\c\164\141\154", quotechar='"') == r"O\ctal"  # Mixed
    # Decoding - special examples
    assert (
        php.phpdecode(r"Don't escape me here\'s", quotechar='"')
        == r"Don't escape me here\'s"
    )  # See bug #589
    assert (
        php.phpdecode("Line1\nLine2") == "Line1\nLine2"
    )  # Preserve newlines in multiline messages
    assert php.phpdecode("Line1\r\nLine2") == "Line1\r\nLine2"  # DOS PHP files
    # Encoding - Python -> PHP
    assert php.phpencode("'", quotechar='"') == "'"
    assert (
        php.phpencode("\n", quotechar='"') == "\n"
    )  # See table of escaped characters - we leave newlines unescaped so that we can try best to preserve pretty printing. See bug 588
    assert (
        php.phpencode("\r", quotechar='"') == r"\r"
    )  # See table of escaped characters
    assert (
        php.phpencode("\t", quotechar='"') == r"\t"
    )  # See table of escaped characters
    assert (
        php.phpencode("\v", quotechar='"') == r"\v"
    )  # See table of escaped characters
    assert (
        php.phpencode("\f", quotechar='"') == r"\f"
    )  # See table of escaped characters
    assert (
        php.phpencode(r"\\", quotechar='"') == r"\\"
    )  # See table of escaped characters
    # assert php.phpencode("\$", quotechar='"') == "$"      # See table of escaped characters - this may cause confusion with actual variables in roundtripping
    assert (
        php.phpencode(r"\$", quotechar='"') == r"\$"
    )  # Just to check that we don't unescape this
    assert php.phpencode('"', quotechar='"') == r"\""
    assert (
        php.phpencode(r"Don't escape me here\'s", quotechar='"')
        == r"Don't escape me here\'s"
    )  # See bug #589


class TestPhpUnit(test_monolingual.TestMonolingualUnit):
    UnitClass = php.phpunit

    def test_difficult_escapes(self) -> None:
        pass


class TestPhpFile(test_monolingual.TestMonolingualStore):
    StoreClass = php.phpfile

    @staticmethod
    def phpparse(phpsource):
        """Helper that parses php source without requiring files."""
        dummyfile = BytesIO(phpsource.encode())
        return php.phpfile(dummyfile)

    def phpregen(self, phpsource):
        """Helper that converts php source to phpfile object and back."""
        return bytes(self.phpparse(phpsource)).decode("utf-8")

    def test_simpledefinition(self) -> None:
        """Checks that a simple php definition is parsed correctly."""
        phpsource = """$lang['mediaselect'] = 'Bestand selective';"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang['mediaselect']"
        assert phpunit.source == "Bestand selective"

    def test_simpledefinition_source(self) -> None:
        """Checks that a simple php definition can be regenerated as source."""
        phpsource = """<?php
$lang['mediaselect'] = 'Bestand selective';"""
        phpregen = self.phpregen(phpsource)
        assert f"{phpsource}\n" == phpregen

    def test_spaces_in_name(self) -> None:
        """Check that spaces in the array name doesn't throw us off."""
        phpsource = """$lang[ 'mediaselect' ] = 'Bestand selective';"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang[ 'mediaselect' ]"
        assert phpunit.source == "Bestand selective"

    def test_comment_definition(self) -> None:
        """Check that comments are fully preserved."""
        phpsource = """/*
 * Comment line 1
 * Comment line 2
 */
$foo = "bar";
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$foo"
        assert phpunit.source == "bar"
        assert phpunit._comments == [
            """/*
 * Comment line 1
 * Comment line 2
 */"""
        ]

    def test_comment_blocks(self) -> None:
        """Check that we don't process name value pairs in comment blocks."""
        phpsource = """/*
 * $lang[0] = "Blah";
 * $lang[1] = "Bluh";
 */
$lang[2] = "Yeah";
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang[2]"
        assert phpunit.source == "Yeah"

    def test_comment_output(self) -> None:
        """Check that linebreaks and spacing is preserved when comments are output."""
        phpsource = """/*
 * Comment line 1
 * Comment line 2
 */
$foo = 'bar';
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert str(phpunit) == phpsource

    def test_comment_add(self) -> None:
        """Check that comments are actually added."""
        phpsource = """/* NOTE 1 */
$foo = 'bar';
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$foo"
        assert phpunit.source == "bar"
        assert phpunit._comments == ["/* NOTE 1 */"]
        # Replace existing notes
        phpunit.addnote("/* NOTE 2 */", None, "replace")
        # Set unknown note
        phpunit.addnote("/* NOTE 2 */", "unknown")
        assert phpunit._comments == ["/* NOTE 2 */"]
        assert str(phpunit) == phpsource.replace("NOTE 1", "NOTE 2")

    def test_multiline(self) -> None:
        """Check that we preserve newlines in a multiline message."""
        phpsource = """$lang['multiline'] = "Line1%sLine2";"""
        # Try DOS and Unix and make sure the output has the same
        for lineending in ("\n", "\r\n"):
            phpfile = self.phpparse(phpsource % lineending)
            assert len(phpfile.units) == 1
            phpunit = phpfile.units[0]
            assert phpunit.name == "$lang['multiline']"
            assert phpunit.source == f"Line1{lineending}Line2"

    def test_parsing_arrays(self) -> None:
        """Parse the array syntax."""
        phpsource = """$lang = %s(
         'item1' => 'value1',
         'item2' => 'value2',
      );"""
        for arrayfn in ["array", "Array", "ARRAY"]:
            phpfile = self.phpparse(phpsource % arrayfn)
            assert len(phpfile.units) == 2
            phpunit = phpfile.units[0]
            assert phpunit.name == "$lang->'item1'"
            assert phpunit.source == "value1"

    def test_parsing_array_no_array_syntax(self) -> None:
        """Parse the array syntax."""
        phpsource = """global $_LANGPDF;
        $_LANGPDF = array();
        $_LANGPDF['PDF065ab3a28ca4f16f55f103adc7d0226f'] = 'Delivery';
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$_LANGPDF['PDF065ab3a28ca4f16f55f103adc7d0226f']"
        assert phpunit.source == "Delivery"

    def test_parsing_arrays_keys_with_spaces(self) -> None:
        """Ensure that our identifiers can have spaces. Bug #1683."""
        phpsource = """$lang = array(
         'item 1' => 'value1',
         'item 2' => 'value2',
      );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item 1'"
        assert phpunit.source == "value1"

    def test_parsing_arrays_keys_with_quotes(self) -> None:
        """Ensure that our identifiers get escaped. Bug #5348."""
        phpsource = r"""<?php
return [
    'item\'s 1' => 'value1',
    'item "2"' => 'value2',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "return[]->'item's 1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "return[]->'item \"2\"'"
        assert phpunit.source == "value2"

        assert bytes(phpfile).decode() == phpsource

    def test_parsing_arrays_keys_with_number_as_value(self) -> None:
        """Ensure that our identifiers can have numbers as value. Bug #5355."""
        phpsource = r"""<?php
return [
    1 => 'value1',
    2 => 'value2',
    '3' => 'value3',
    '4\'' => 'value4',
    '5"' => 'value5',
    '6\"' => 'value6',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 6
        phpunit = phpfile.units[0]
        assert phpunit.name == "return[]->1"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "return[]->2"
        assert phpunit.source == "value2"
        phpunit = phpfile.units[2]
        assert phpunit.name == "return[]->'3'"
        assert phpunit.source == "value3"
        phpunit = phpfile.units[3]
        assert phpunit.name == "return[]->'4''"
        assert phpunit.source == "value4"
        phpunit = phpfile.units[4]
        assert phpunit.name == "return[]->'5\"'"
        assert phpunit.source == "value5"
        phpunit = phpfile.units[5]
        assert phpunit.name == "return[]->'6\\\"'"
        assert phpunit.source == "value6"

        assert bytes(phpfile).decode() == phpsource

    def test_parsing_arrays_non_textual(self) -> None:
        """Don't break on non-textual data. Bug #1684."""
        phpsource = """$lang = array(
         'item 1' => 'value1',
         'item 2' => false,
         'item 3' => 'value3',
      );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'item 3'"
        assert phpunit.source == "value3"

    def test_parsing_simple_define(self) -> None:
        """Parse simple define syntax."""
        phpsource = """define("_FINISH", "Rematar");
define('_POSTEDON', 'Enviado o');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_FINISH"'
        assert phpunit.source == "Rematar"
        phpunit = phpfile.units[1]
        assert phpunit.name == "define('_POSTEDON'"
        assert phpunit.source == "Enviado o"

    def test_parsing_simple_define_with_spaces_before_key(self) -> None:
        """Parse simple define syntax with spaces before key."""
        phpsource = """define( "_FINISH", "Rematar");
define( '_CM_POSTED', 'Enviado');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define( "_FINISH"'
        assert phpunit.source == "Rematar"
        phpunit = phpfile.units[1]
        assert phpunit.name == "define( '_CM_POSTED'"
        assert phpunit.source == "Enviado"

    def test_parsing_define_spaces_after_equal_delimiter(self) -> None:
        """Parse define syntax with spaces after the equal delimiter."""
        phpsource = """define("_RELOAD",       "Recargar");
define('_CM_POSTED',    'Enviado');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_RELOAD"'
        assert phpunit.source == "Recargar"
        phpunit = phpfile.units[1]
        assert phpunit.name == "define('_CM_POSTED'"
        assert phpunit.source == "Enviado"

    def test_parsing_define_spaces_after_equal_delimiter_and_before_key(self) -> None:
        """
        Parse define syntax with spaces after the equal delimiter as well
        before the key.
        """
        phpsource = """define( "_FINISH",       "Rematar");
define(  '_UPGRADE_CHARSET',    'Upgrade charset');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define( "_FINISH"'
        assert phpunit.source == "Rematar"
        phpunit = phpfile.units[1]
        assert phpunit.name == "define(  '_UPGRADE_CHARSET'"
        assert phpunit.source == "Upgrade charset"

    def test_parsing_define_no_spaces_after_equal_delimiter(self) -> None:
        """Parse define syntax without spaces after the equal delimiter."""
        phpsource = """define("_POSTEDON","Enviado o");
define('_UPGRADE_CHARSET','Upgrade charset');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_POSTEDON"'
        assert phpunit.source == "Enviado o"
        phpunit = phpfile.units[1]
        assert phpunit.name == "define('_UPGRADE_CHARSET'"
        assert phpunit.source == "Upgrade charset"

    def test_parsing_define_no_spaces_after_equaldel_but_before_key(self) -> None:
        """
        Parse define syntax without spaces after the equal delimiter but
        with spaces before the key.
        """
        phpsource = """define( "_FINISH","Rematar");
define( '_CM_POSTED','Enviado');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define( "_FINISH"'
        assert phpunit.source == "Rematar"
        phpunit = phpfile.units[1]
        assert phpunit.name == "define( '_CM_POSTED'"
        assert phpunit.source == "Enviado"

    def test_parsing_define_entries_with_quotes(self) -> None:
        """Parse define syntax for entries with quotes."""
        phpsource = r"""define('_SETTINGS_COOKIEPREFIX', 'Prefixo da "cookie"');
define('_YOUR_USERNAME', 'O seu nome de usuario: "cookie"');
define("_REGISTER", "Register <a href=\"register.php\">here</a>");"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == "define('_SETTINGS_COOKIEPREFIX'"
        assert phpunit.source == 'Prefixo da "cookie"'
        phpunit = phpfile.units[1]
        assert phpunit.name == "define('_YOUR_USERNAME'"
        assert phpunit.source == 'O seu nome de usuario: "cookie"'
        phpunit = phpfile.units[2]
        assert phpunit.name == 'define("_REGISTER"'
        assert phpunit.source == 'Register <a href="register.php">here</a>'

    def test_parsing_define_comments_at_entry_line_end(self) -> None:
        """Parse define syntax with comments at the end of the entry line."""
        phpsource = """define("_POSTEDON", "Enviado o");// Keep this short
define('_CM_POSTED', 'Enviado'); // Posted date"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_POSTEDON"'
        assert phpunit.source == "Enviado o"
        assert phpunit._comments == ["// Keep this short"]
        phpunit = phpfile.units[1]
        assert phpunit.name == "define('_CM_POSTED'"
        assert phpunit.source == "Enviado"
        assert phpunit._comments == ["// Posted date"]

    def test_parsing_define_double_slash_comments_before_entries(self) -> None:
        """Parse define syntax with double slash comments before the entries."""
        phpsource = """// Keep this short
define("_FINISH", "Rematar");

// This means it was published
// It appears besides posts
define('_CM_POSTED', 'Enviado');"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_FINISH"'
        assert phpunit.source == "Rematar"
        assert phpunit._comments == ["// Keep this short"]
        phpunit = phpfile.units[1]
        assert phpunit.name == "define('_CM_POSTED'"
        assert phpunit.source == "Enviado"
        assert phpunit._comments == [
            "// This means it was published",
            "// It appears besides posts",
        ]

    def test_parsing_define_spaces_before_end_delimiter(self) -> None:
        """Parse define syntax with spaces before the end delimiter."""
        phpsource = """define("_POSTEDON", "Enviado o");
define("_FINISH", "Rematar"     );
define("_RELOAD", "Recargar");"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_POSTEDON"'
        assert phpunit.source == "Enviado o"
        phpunit = phpfile.units[1]
        assert phpunit.name == 'define("_FINISH"'
        assert phpunit.source == "Rematar"
        phpunit = phpfile.units[2]
        assert phpunit.name == 'define("_RELOAD"'
        assert phpunit.source == "Recargar"

    def test_parsing_simpledefinition_spaces_before_end_delimiter(self) -> None:
        """Parse simple definition syntax with spaces before the end delimiter."""
        phpsource = """$month_jan = 'Jan';
$month_feb = 'Feb'  ;
$month_mar = 'Mar';"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == "$month_jan"
        assert phpunit.source == "Jan"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$month_feb"
        assert phpunit.source == "Feb"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$month_mar"
        assert phpunit.source == "Mar"

    def test_parsing_arrays_no_trailing_comma(self) -> None:
        """
        Parse the array syntax where we don't have a trailing comma.

        Bug #1685
        """
        phpsource = """$lang = array(
         'item1' => 'value1',
         'item2' => 'value2'
      );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"

    def test_parsing_arrays_space_before_comma(self) -> None:
        """
        Parse the array syntax with spaces before the comma.

        Bug #1898
        """
        phpsource = """$lang = array(
         'item1' => 'value1',
         'item2' => 'value2' ,
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"

    def test_parsing_arrays_with_space_before_array_declaration(self) -> None:
        """
        Parse the array syntax with spaces before the array declaration.

        Bug #2646
        """
        phpsource = """$lang = array   (
         'item1' => 'value1',
         'item2' => 'value2',
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"

    def test_parsing_arrays_declared_in_a_single_line(self) -> None:
        """
        Parse an array declared in a single line.

        Bug #3629
        """
        phpsource = """$lang = array( 'item1' => 'value1', 'item2' => 'value2', 'item3' => 'value3' );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$lang->'item3'"
        assert phpunit.source == "value3"

    def test_parsing_arrays_with_no_keys(self) -> None:
        """Parse an array with no keys."""
        phpsource = """<?php
$days = array(
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    "Saturday",
);
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 7
        phpunit = phpfile.units[0]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Sunday"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Monday"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Tuesday"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Wednesday"
        phpunit = phpfile.units[4]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Thursday"
        phpunit = phpfile.units[5]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Friday"
        phpunit = phpfile.units[6]
        assert phpunit.name == "$days->[]"
        assert phpunit.source == "Saturday"
        assert bytes(phpfile).decode() == phpsource

    def test_parsing_arrays_with_no_keys_assigned_to_array(self) -> None:
        """Parse an array with no keys assigned to another array."""
        phpsource = """<?php
$messages['days_short'] = array(
    'Sun',
    'Mon',
    'Tue',
    'Wed',
    'Thu',
    "Fri",
    'Sat',
);
"""
        phpfile = self.phpparse(phpsource)
        assert bytes(phpfile).decode() == phpsource
        assert len(phpfile.units) == 7
        phpunit = phpfile.units[0]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Sun"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Mon"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Tue"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Wed"
        phpunit = phpfile.units[4]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Thu"
        phpunit = phpfile.units[5]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Fri"
        phpunit = phpfile.units[6]
        assert phpunit.name == "$messages['days_short']->[]"
        assert phpunit.source == "Sat"

    def test_parsing_nested_arrays_with_no_keys(self) -> None:
        """Parse a nested array with no keys."""
        phpsource = """$lang = array(array("key" => "value"));"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->[]->'key'"
        assert phpunit.source == "value"

    def test_assignment_in_line_where_multiline_comment_ends(self) -> None:
        """Check parsing assignment in same line a multiline comment ends."""
        phpsource = """<?php
/*
   Multi-line
   comment
*/ $messages['help'] = 'Help';
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$messages['help']"
        assert phpunit.source == "Help"

    def test_parsing_arrays_using_short_array_syntax(self) -> None:
        """Parse short array syntax.  Bug #3626."""
        phpsource = """<?php
$lang = [
    'item1' => 'value1',
    'item2' => 'value2',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang[]->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang[]->'item2'"
        assert phpunit.source == "value2"
        assert bytes(phpfile).decode() == phpsource

    def test_parsing_nested_arrays(self) -> None:
        """Parse the nested array syntax. Bug #2240."""
        phpsource = """$app_list_strings = array(
            'Mailbox' => 'Mailbox',
            'moduleList' => array(
                'Home' => 'Home',
                'Contacts' => 'Contacts',
                'Accounts' => 'Accounts',
            ),
            'FAQ' => 'FAQ',
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 5
        phpunit = phpfile.units[0]
        assert phpunit.name == "$app_list_strings->'Mailbox'"
        assert phpunit.source == "Mailbox"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$app_list_strings->'moduleList'->'Home'"
        assert phpunit.source == "Home"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$app_list_strings->'moduleList'->'Contacts'"
        assert phpunit.source == "Contacts"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$app_list_strings->'moduleList'->'Accounts'"
        assert phpunit.source == "Accounts"
        phpunit = phpfile.units[4]
        assert phpunit.name == "$app_list_strings->'FAQ'"
        assert phpunit.source == "FAQ"

    def test_parsing_nested_arrays_with_space_before_array_declaration(self) -> None:
        """Parse the nested array syntax with whitespace before the array declaration."""
        phpsource = """$app_list_strings = array  (
            'Mailbox' => 'Mailbox',
            'moduleList' => array  (
                'Home' => 'Home',
                'Contacts' => 'Contacts',
                'Accounts' => 'Accounts',
            ),
            'tools' => array  (
                'Pen' => 'Pen',
                'Brush' => 'Brush',
                'Pencil' => 'Pencil',
            ),
            'FAQ' => 'FAQ',
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 8
        phpunit = phpfile.units[0]
        assert phpunit.name == "$app_list_strings->'Mailbox'"
        assert phpunit.source == "Mailbox"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$app_list_strings->'moduleList'->'Home'"
        assert phpunit.source == "Home"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$app_list_strings->'moduleList'->'Contacts'"
        assert phpunit.source == "Contacts"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$app_list_strings->'moduleList'->'Accounts'"
        assert phpunit.source == "Accounts"
        phpunit = phpfile.units[4]
        assert phpunit.name == "$app_list_strings->'tools'->'Pen'"
        assert phpunit.source == "Pen"
        phpunit = phpfile.units[5]
        assert phpunit.name == "$app_list_strings->'tools'->'Brush'"
        assert phpunit.source == "Brush"
        phpunit = phpfile.units[6]
        assert phpunit.name == "$app_list_strings->'tools'->'Pencil'"
        assert phpunit.source == "Pencil"
        phpunit = phpfile.units[7]
        assert phpunit.name == "$app_list_strings->'FAQ'"
        assert phpunit.source == "FAQ"

    def test_parsing_unnamed_nested_arrays(self) -> None:
        """Parse the unnamed nested array."""
        phpsource = """return array  (
            'name1' => 'target1',
            'list1' => array(
                'l1' => 'target_l1_1',
                'l2' => 'target_l1_2',
                'l3' => 'target_l1_3',
            ),
            'list2' => array(
                'l1' => 'target_l2_1',
                'l2' => 'target_l2_2',
                'l3' => 'target_l2_3',
            ),
            'name2' => 'target2',
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 8
        phpunit = phpfile.units[0]
        assert phpunit.name == "return->'name1'"
        assert phpunit.source == "target1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "return->'list1'->'l1'"
        assert phpunit.source == "target_l1_1"
        phpunit = phpfile.units[2]
        assert phpunit.name == "return->'list1'->'l2'"
        assert phpunit.source == "target_l1_2"
        phpunit = phpfile.units[3]
        assert phpunit.name == "return->'list1'->'l3'"
        assert phpunit.source == "target_l1_3"
        phpunit = phpfile.units[4]
        assert phpunit.name == "return->'list2'->'l1'"
        assert phpunit.source == "target_l2_1"

    def test_parsing_nested_arrays_with_array_declaration_in_next_line(self) -> None:
        """
        Parse the nested array syntax with array declaration in the next line.

        Bug #2647
        """
        phpsource = """$lang = array(
            'item1' => 'value1',
            'newsletter_frequency_dom' =>
                array(
                    'Weekly' => 'Weekly',
                ),
            'item2' => 'value2',
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'newsletter_frequency_dom'->'Weekly'"
        assert phpunit.source == "Weekly"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"

    def test_parsing_array_with_newline_after_delimiter(self) -> None:
        """
        Parse array with newline between key and value.

        Bug #3628.
        """
        phpsource = """$lang = array(
            'item1' => 'value1',
            'frequency' =>
                'Weekly',
            'item2' => 'value2',
        );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'frequency'"
        assert phpunit.source == "Weekly"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"

    def test_parsing_nested_arrays_with_blank_entries(self) -> None:
        """Parse the nested array syntax with blank entries. Bug #2648."""
        phpsource = """<?php
$lang = array(
    'item1' => 'value1',
    'newsletter_frequency_dom' => array(
        '' => '',
        'Weekly' => 'Weekly',
    ),
    'item2' => 'value2',
);
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 4
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang->'item1'"
        assert phpunit.source == "value1"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'newsletter_frequency_dom'->''"
        assert phpunit.source == ""
        phpunit = phpfile.units[2]
        assert phpunit.name == "$lang->'newsletter_frequency_dom'->'Weekly'"
        assert phpunit.source == "Weekly"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$lang->'item2'"
        assert phpunit.source == "value2"
        assert bytes(phpfile).decode() == phpsource

    def test_slashstar_in_string(self) -> None:
        """Ignore the /* comment delimiter when it is part of a string.  Bug #3627."""
        phpsource = """$definition['key'] = 'Value /* value continued';
      $lang = array(
         'somekey' => 'Some value',
         'otherkey' => 'Other value /* continued',
         'thirdkey' => 'Third value',
      );"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 4
        phpunit = phpfile.units[0]
        assert phpunit.name == "$definition['key']"
        assert phpunit.source == "Value /* value continued"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang->'somekey'"
        assert phpunit.source == "Some value"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$lang->'otherkey'"
        assert phpunit.source == "Other value /* continued"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$lang->'thirdkey'"
        assert phpunit.source == "Third value"

    def test_parsing_simple_heredoc_syntax(self) -> None:
        """Parse the heredoc syntax. Bug #2611."""
        phpsource = """$month_jan = 'Jan';
$lang_register_approve_email = <<<EOT
A new user with the username "{USER_NAME}" has registered in your gallery.

In order to activate the account, you need to click on the link below.

<a href="{ACT_LINK}">{ACT_LINK}</a>
EOT;

$foobar = <<<FOOBAR
Simple example
FOOBAR;

$month_mar = 'Mar';
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 4
        phpunit = phpfile.units[0]
        assert phpunit.name == "$month_jan"
        assert phpunit.source == "Jan"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang_register_approve_email"
        assert (
            phpunit.source
            == 'A new user with the username "{USER_NAME}" has registered in your gallery.\n\nIn order to activate the account, you need to click on the link below.\n\n<a href="{ACT_LINK}">{ACT_LINK}</a>'
        )
        phpunit = phpfile.units[2]
        assert phpunit.name == "$foobar"
        assert phpunit.source == "Simple example"
        phpunit = phpfile.units[3]
        assert phpunit.name == "$month_mar"
        assert phpunit.source == "Mar"

    def test_simpledefinition_after_define(self) -> None:
        """Check that a simple definition after define is parsed correctly."""
        phpsource = """define("_FINISH", "Rematar");
$lang['mediaselect'] = 'Bestand selective';"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == 'define("_FINISH"'
        assert phpunit.source == "Rematar"
        phpunit = phpfile.units[1]
        assert phpunit.name == "$lang['mediaselect']"
        assert phpunit.source == "Bestand selective"

    def test_quotes(self) -> None:
        phpsource = """<?php
$txt[\'DISPLAYEDINFOS\'] = "<a href=\\"__PARAM2__\\">Modificar...</a>";
"""
        phpfile = self.phpparse(phpsource)
        assert bytes(phpfile).decode() == phpsource

    def test_concatenation(self) -> None:
        """Check that concatenating strings and variables is parsed correctly."""
        phpsource = """
$lang['mediaselect'] = "Really \\\"something\\\"" . $variable . "\\\"something else\\\"";
$messages['welcome'] = 'Welcome ' . $name . '!';
$messages['greeting'] = 'Hi ' . $name;
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        phpunit = phpfile.units[0]
        assert phpunit.name == "$lang['mediaselect']"
        assert phpunit.source == 'Really "something"$variable"something else"'
        phpunit = phpfile.units[1]
        assert phpunit.name == "$messages['welcome']"
        assert phpunit.source == "Welcome $name!"
        phpunit = phpfile.units[2]
        assert phpunit.name == "$messages['greeting']"
        assert phpunit.source == "Hi $name"

    def test_serialize(self) -> None:
        phpsource = """<?php
# Comment 1
define("_FINISH", 'Rematar');
// Comment 2
define("_START", "Zacni");
/* Comment 3 */
$lang['mediaselect'] = 'Bestand selective';
$texts = array(
    /*
     * Multiline comment 4
     */
    'other' => 'Andere',
    'nested' => array(
        'and' => 'Und',
        'second' => 'Zweite',
    ),
);
"""
        phpfile = self.phpparse(phpsource)
        assert bytes(phpfile).decode() == phpsource

    def test_space_before_comma(self) -> None:
        """
        Check that spacing before comma or semicolon doesn't break parser.

        See #1898
        """
        phpsource = """
        $english = array(
            'item:site'  =>  "Sítios",
            'login'  =>  "Entrar" ,
            'loginok'  =>  "Entrou com sucesso." ,
        );
        $month_jan = 'Jan';
        $month_feb = 'Feb'  ;
        $month_mar = 'Mar';
        define("_SEARCH","Search");
        define("_LOGIN","Login"  );
        define("_POLLS","Polls");
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 9

    def test_equals_in_id(self) -> None:
        """
        Check that equals in id doesn't break parser.

        See #1929
        """
        phpsource = """
        $strings['key = value'] = 'Message';
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        assert phpfile.units[0].source == "Message"
        assert phpfile.units[0].name == "$strings['key = value']"

    def test_comma_in_string(self) -> None:
        """
        Check that comma in string doesn't break parser.

        See #3608
        """
        phpsource = """
        $t = array(
            'key' => ' text (comment **),**',
        );
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        assert phpfile.units[0].source == " text (comment **),**"
        assert phpfile.units[0].name == "$t->'key'"

    def test_nowdoc(self) -> None:
        """Check parsing nowdoc strings."""
        phpsource = """$str = <<<'EOD'
Example of string
spanning multiple lines
using nowdoc syntax.
EOD;
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        assert (
            phpfile.units[0].source
            == """Example of string
spanning multiple lines
using nowdoc syntax."""
        )
        assert phpfile.units[0].name == "$str"

    def test_plain_concatenation(self) -> None:
        """Check parsing concatenated strings."""
        phpsource = """$str = 'Concatenated' . ' ' . 'string';
        $arr['x'] = 'Concatenated' . ' ' . 'string';
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        assert phpfile.units[0].source == "Concatenated string"
        assert phpfile.units[0].name == "$str"
        assert phpfile.units[1].source == "Concatenated string"
        assert phpfile.units[1].name == "$arr['x']"

    def test_array_keys(self) -> None:
        """Check parsing different array keys."""
        phpsource = """
        $arr = [
            '1234' => 'First',
            1234 => 'Second',
            '12' . '45' => 'Third',
        ];
        """
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 3
        assert phpfile.units[0].source == "First"
        assert phpfile.units[0].name == "$arr[]->'1234'"
        assert phpfile.units[1].source == "Second"
        assert phpfile.units[1].name == "$arr[]->1234"
        assert phpfile.units[2].source == "Third"
        assert phpfile.units[2].name == "$arr[]->'1245'"

    def test_double_var(self) -> None:
        """Checks that a double $ is handled correctly."""
        phpsource = """$$lang['mediaselect'] = 'Bestand selective';"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "$$lang['mediaselect']"
        assert phpunit.source == "Bestand selective"

    def test_return_array(self) -> None:
        phpsource = """<?php
return array(
    'peach' => 'pesca',
);
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "return->'peach'"
        assert phpunit.source == "pesca"
        assert bytes(phpfile).decode() == phpsource

    def test_return_array_with_spaces(self) -> None:
        """Test that return array with spaces before 'array' is handled correctly."""
        phpsource = """<?php
return  array(
    'peach' => 'pesca',
);
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        # The unit name should be "return->'peach'", NOT "return  array->'peach'"
        assert phpunit.name == "return->'peach'"
        assert phpunit.source == "pesca"
        # The output should normalize spacing
        assert bytes(phpfile) == b"<?php\nreturn array(\n    'peach' => 'pesca',\n);\n"

    def test_return_array_with_comments(self) -> None:
        """Test that return array with comments preserves comments correctly."""
        phpsource = """<?php

return array(

    /*
    |--------------------------------------------------------------------------
    | String1
    |--------------------------------------------------------------------------
    |
    */

  'string1'    => 'This is string1',
);
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "return->'string1'"
        assert phpunit.source == "This is string1"
        # Check that comments are preserved
        assert "String1" in phpunit.getnotes("developer")

    def test_return_array_short(self) -> None:
        phpsource = """<?php
return [
    'peach' => 'pesca',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.name == "return[]->'peach'"
        assert phpunit.source == "pesca"
        assert bytes(phpfile).decode() == phpsource
        phpunit.source = "ryba"
        assert bytes(phpfile).decode() != phpsource
        phpunit.source = "pesca"
        assert bytes(phpfile).decode() == phpsource

    def test_return_array_short_quotes(self) -> None:
        phpsource = r"""<?php
return [
    'peach' => "foo \"pesca\"",
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 1
        phpunit = phpfile.units[0]
        assert phpunit.escape_type == '"'
        assert phpunit.name == "return[]->'peach'"
        assert phpunit.source == 'foo "pesca"'
        assert bytes(phpfile).decode() == phpsource
        phpunit.source = "ryba"
        assert bytes(phpfile) != phpsource
        phpunit.source = 'foo "pesca"'
        assert bytes(phpfile).decode() == phpsource

    def test_addunit(self) -> None:
        expected = """<?php
$key = 'first';
$sec = 'second';
"""
        store = self.StoreClass()

        unit = self.StoreClass.UnitClass("first")
        unit.setid("key")
        store.addunit(unit)
        unit = self.StoreClass.UnitClass("second")
        unit.setid("$sec")
        store.addunit(unit)
        assert bytes(store).decode() == expected


class TestLaravelPhpUnit(test_monolingual.TestMonolingualUnit):
    UnitClass = php.LaravelPHPUnit


class TestLaravelPhpFile(test_monolingual.TestMonolingualStore):
    StoreClass = php.LaravelPHPFile

    def phpparse(self, phpsource):
        """Helper that parses php source without requiring files."""
        dummyfile = BytesIO(phpsource.encode())
        return self.StoreClass(dummyfile)

    def test_plurals(self) -> None:
        phpsource = r"""<?php
return [
    'welcome' => 'Welcome to our application',
    'apples' => 'There is one apple|There are many apples',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        phpunit = phpfile.units[0]
        assert phpunit.name == "return[]->'welcome'"
        assert phpunit.source == "Welcome to our application"
        phpunit = phpfile.units[1]
        assert phpunit.name == "return[]->'apples'"
        assert phpunit.source == multistring(
            ["There is one apple", "There are many apples"]
        )
        assert bytes(phpfile).decode() == phpsource
        phpunit.source = multistring(["There is an apple", "There are many apples"])
        assert bytes(phpfile).decode() == phpsource.replace("one apple", "an apple")

    def test_key_stripping(self) -> None:
        """Test that Laravel PHP files strip the return prefix from keys."""
        phpsource = r"""<?php
return [
    'welcome' => 'Welcome to our application',
    'apples' => 'There is one apple|There are many apples',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        # Check that getid() returns keys without the return[]-> prefix
        assert phpfile.units[0].getid() == "welcome"
        assert phpfile.units[1].getid() == "apples"
        # Check that getlocations() also returns stripped keys
        assert phpfile.units[0].getlocations() == ["welcome"]
        assert phpfile.units[1].getlocations() == ["apples"]

    def test_key_stripping_array_syntax(self) -> None:
        """Test key stripping works with array() syntax."""
        phpsource = r"""<?php
return array(
    'welcome' => 'Welcome',
    'goodbye' => 'Goodbye',
);
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        assert phpfile.units[0].getid() == "welcome"
        assert phpfile.units[1].getid() == "goodbye"

    def test_key_stripping_numeric_keys(self) -> None:
        """Test key stripping works with numeric keys."""
        phpsource = r"""<?php
return [
    1 => 'One',
    2 => 'Two',
];
"""
        phpfile = self.phpparse(phpsource)
        assert len(phpfile.units) == 2
        assert phpfile.units[0].getid() == "1"
        assert phpfile.units[1].getid() == "2"

    def test_roundtrip_short_array(self) -> None:
        """Test round trip serialization with short array syntax."""
        phpsource = r"""<?php
return [
    'welcome' => 'Welcome to our application',
    'apples' => 'There is one apple|There are many apples',
];
"""
        phpfile = self.phpparse(phpsource)
        assert bytes(phpfile).decode() == phpsource

    def test_roundtrip_array_syntax(self) -> None:
        """Test round trip serialization with array() syntax."""
        phpsource = r"""<?php
return array(
    'welcome' => 'Welcome',
    'goodbye' => 'Goodbye',
);
"""
        phpfile = self.phpparse(phpsource)
        assert bytes(phpfile).decode() == phpsource

    def test_setid_preserves_structure(self) -> None:
        """Test that setid() preserves the return array structure."""
        phpsource = r"""<?php
return [
    'welcome' => 'Welcome',
];
"""
        phpfile = self.phpparse(phpsource)
        unit = phpfile.units[0]

        # Change the key
        unit.setid("greeting")

        # Check that getid() returns the clean key
        assert unit.getid() == "greeting"

        # Check that serialization produces valid PHP with return structure
        output = bytes(phpfile).decode()
        assert "return [" in output
        assert "'greeting' => 'Welcome'" in output
        assert "return[]->" not in output  # Should not leak internal structure

    def test_setid_with_array_syntax(self) -> None:
        """Test that setid() preserves array() syntax."""
        phpsource = r"""<?php
return array(
    'welcome' => 'Welcome',
);
"""
        phpfile = self.phpparse(phpsource)
        unit = phpfile.units[0]

        # Change the key
        unit.setid("greeting")

        # Check that getid() returns the clean key
        assert unit.getid() == "greeting"

        # Check that serialization produces valid PHP with return array() structure
        output = bytes(phpfile).decode()
        assert "return array(" in output
        assert "'greeting' => 'Welcome'" in output

    def test_addunit_with_setid(self) -> None:
        """Test creating new units programmatically with setid()."""
        phpfile = self.StoreClass()

        unit1 = phpfile.addsourceunit("Welcome")
        unit1.setid("welcome")

        unit2 = phpfile.addsourceunit("Goodbye")
        unit2.setid("goodbye")

        # Check IDs
        assert unit1.getid() == "welcome"
        assert unit2.getid() == "goodbye"

        # Check serialization produces valid Laravel PHP structure
        output = bytes(phpfile).decode()
        expected = """<?php
return array(
    'welcome' => 'Welcome',
    'goodbye' => 'Goodbye',
);
"""
        assert output == expected

    def test_add_unit_to_short_array_file(self) -> None:
        """Test adding a new unit to a file with short array syntax []."""
        phpsource = r"""<?php
return [
    'existing' => 'Existing message',
];
"""
        phpfile = self.phpparse(phpsource)

        # Add a new unit
        new_unit = phpfile.addsourceunit("New message")
        new_unit.setid("new_key")

        # Check the new unit has correct ID
        assert new_unit.getid() == "new_key"

        # Check serialization produces valid PHP with consistent [] syntax
        output = bytes(phpfile).decode()
        expected = """<?php
return [
    'existing' => 'Existing message',
    'new_key' => 'New message',
];
"""
        assert output == expected

    def test_add_unit_to_array_function_file(self) -> None:
        """Test adding a new unit to a file with array() syntax."""
        phpsource = r"""<?php
return array(
    'existing' => 'Existing message',
);
"""
        phpfile = self.phpparse(phpsource)

        # Add a new unit
        new_unit = phpfile.addsourceunit("New message")
        new_unit.setid("new_key")

        # Check the new unit has correct ID
        assert new_unit.getid() == "new_key"

        # Check serialization produces valid PHP with consistent array() syntax
        output = bytes(phpfile).decode()
        expected = """<?php
return array(
    'existing' => 'Existing message',
    'new_key' => 'New message',
);
"""
        assert output == expected

    def test_add_multiple_units_to_short_array(self) -> None:
        """Test adding multiple units to a file with short array syntax."""
        phpsource = r"""<?php
return [
    'first' => 'First',
];
"""
        phpfile = self.phpparse(phpsource)

        # Add multiple new units
        unit2 = phpfile.addsourceunit("Second")
        unit2.setid("second")

        unit3 = phpfile.addsourceunit("Third")
        unit3.setid("third")

        # Check serialization produces valid PHP
        output = bytes(phpfile).decode()
        expected = """<?php
return [
    'first' => 'First',
    'second' => 'Second',
    'third' => 'Third',
];
"""
        assert output == expected

    def test_add_multiple_units_to_array_function(self) -> None:
        """Test adding multiple units to a file with array() syntax."""
        phpsource = r"""<?php
return array(
    'first' => 'First',
);
"""
        phpfile = self.phpparse(phpsource)

        # Add multiple new units
        unit2 = phpfile.addsourceunit("Second")
        unit2.setid("second")

        unit3 = phpfile.addsourceunit("Third")
        unit3.setid("third")

        # Check serialization produces valid PHP
        output = bytes(phpfile).decode()
        expected = """<?php
return array(
    'first' => 'First',
    'second' => 'Second',
    'third' => 'Third',
);
"""
        assert output == expected

    def test_numeric_keys_no_quotes(self) -> None:
        """Test that numeric keys (including negative) are not quoted."""
        phpfile = self.StoreClass()

        # Test positive integer
        unit1 = phpfile.addsourceunit("Message 1")
        unit1.setid("1")

        # Test negative integer
        unit2 = phpfile.addsourceunit("Message 2")
        unit2.setid("-1")

        # Test zero
        unit3 = phpfile.addsourceunit("Message 3")
        unit3.setid("0")

        output = bytes(phpfile).decode()
        expected = """<?php
return array(
    1 => 'Message 1',
    -1 => 'Message 2',
    0 => 'Message 3',
);
"""
        assert output == expected

    def test_empty_string_key(self) -> None:
        """Test that empty string keys are properly quoted."""
        phpfile = self.StoreClass()

        unit = phpfile.addsourceunit("Empty key message")
        unit.setid("")

        output = bytes(phpfile).decode()
        expected = """<?php
return array(
    '' => 'Empty key message',
);
"""
        assert output == expected

    def test_setid_with_non_string_value(self) -> None:
        """Test that setid handles non-string values gracefully."""
        phpfile = self.StoreClass()

        # Test with integer
        unit1 = phpfile.addsourceunit("Message")
        unit1.setid(123)

        output = bytes(phpfile).decode()
        expected = """<?php
return array(
    123 => 'Message',
);
"""
        assert output == expected
