| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- <?php declare(strict_types=1);
- namespace PhpParser\Lexer;
- use PhpParser\ErrorHandler;
- use PhpParser\Lexer;
- use PhpParser\LexerTest;
- use PhpParser\Parser\Php7;
- use PhpParser\PhpVersion;
- use PhpParser\Token;
- require __DIR__ . '/../../../lib/PhpParser/compatibility_tokens.php';
- class EmulativeTest extends LexerTest {
- protected function getLexer() {
- return new Emulative();
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testReplaceKeywords(string $keyword, int $expectedToken): void {
- $lexer = $this->getLexer();
- $code = '<?php ' . $keyword;
- $this->assertEquals([
- new Token(\T_OPEN_TAG, '<?php ', 1, 0),
- new Token($expectedToken, $keyword, 1, 6),
- new Token(0, "\0", 1, \strlen($code)),
- ], $lexer->tokenize($code));
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testReplaceKeywordsUppercase(string $keyword, int $expectedToken): void {
- $lexer = $this->getLexer();
- $code = '<?php ' . strtoupper($keyword);
- $this->assertEquals([
- new Token(\T_OPEN_TAG, '<?php ', 1, 0),
- new Token($expectedToken, \strtoupper($keyword), 1, 6),
- new Token(0, "\0", 1, \strlen($code)),
- ], $lexer->tokenize($code));
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testNoReplaceKeywordsAfterObjectOperator(string $keyword): void {
- $lexer = $this->getLexer();
- $code = '<?php ->' . $keyword;
- $this->assertEquals([
- new Token(\T_OPEN_TAG, '<?php ', 1, 0),
- new Token(\T_OBJECT_OPERATOR, '->', 1, 6),
- new Token(\T_STRING, $keyword, 1, 8),
- new Token(0, "\0", 1, \strlen($code)),
- ], $lexer->tokenize($code));
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword): void {
- $lexer = $this->getLexer();
- $code = '<?php -> ' . $keyword;
- $this->assertEquals([
- new Token(\T_OPEN_TAG, '<?php ', 1, 0),
- new Token(\T_OBJECT_OPERATOR, '->', 1, 6),
- new Token(\T_WHITESPACE, ' ', 1, 8),
- new Token(\T_STRING, $keyword, 1, 12),
- new Token(0, "\0", 1, \strlen($code)),
- ], $lexer->tokenize($code));
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testNoReplaceKeywordsAfterNullsafeObjectOperator(string $keyword): void {
- $lexer = $this->getLexer();
- $code = '<?php ?->' . $keyword;
- $this->assertEquals([
- new Token(\T_OPEN_TAG, '<?php ', 1, 0),
- new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', 1, 6),
- new Token(\T_STRING, $keyword, 1, 9),
- new Token(0, "\0", 1, \strlen($code)),
- ], $lexer->tokenize($code));
- }
- public static function provideTestReplaceKeywords() {
- return [
- // PHP 8.0
- ['match', \T_MATCH],
- // PHP 7.4
- ['fn', \T_FN],
- // PHP 5.5
- ['finally', \T_FINALLY],
- ['yield', \T_YIELD],
- // PHP 5.4
- ['callable', \T_CALLABLE],
- ['insteadof', \T_INSTEADOF],
- ['trait', \T_TRAIT],
- ['__TRAIT__', \T_TRAIT_C],
- // PHP 5.3
- ['__DIR__', \T_DIR],
- ['goto', \T_GOTO],
- ['namespace', \T_NAMESPACE],
- ['__NAMESPACE__', \T_NS_C],
- ];
- }
- private function assertSameTokens(array $expectedTokens, array $tokens): void {
- $reducedTokens = [];
- foreach ($tokens as $token) {
- if ($token->id === 0 || $token->isIgnorable()) {
- continue;
- }
- $reducedTokens[] = [$token->id, $token->text];
- }
- $this->assertSame($expectedTokens, $reducedTokens);
- }
- /**
- * @dataProvider provideTestLexNewFeatures
- */
- public function testLexNewFeatures(string $code, array $expectedTokens): void {
- $lexer = $this->getLexer();
- $this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code));
- }
- /**
- * @dataProvider provideTestLexNewFeatures
- */
- public function testLeaveStuffAloneInStrings(string $code): void {
- $stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
- $lexer = $this->getLexer();
- $fullCode = '<?php ' . $stringifiedToken;
- $this->assertEquals([
- new Token(\T_OPEN_TAG, '<?php ', 1, 0),
- new Token(\T_CONSTANT_ENCAPSED_STRING, $stringifiedToken, 1, 6),
- new Token(0, "\0", \substr_count($fullCode, "\n") + 1, \strlen($fullCode)),
- ], $lexer->tokenize($fullCode));
- }
- /**
- * @dataProvider provideTestLexNewFeatures
- */
- public function testErrorAfterEmulation($code): void {
- $errorHandler = new ErrorHandler\Collecting();
- $lexer = $this->getLexer();
- $lexer->tokenize('<?php ' . $code . "\0", $errorHandler);
- $errors = $errorHandler->getErrors();
- $this->assertCount(1, $errors);
- $error = $errors[0];
- $this->assertSame('Unexpected null byte', $error->getRawMessage());
- $attrs = $error->getAttributes();
- $expPos = strlen('<?php ' . $code);
- $expLine = 1 + substr_count('<?php ' . $code, "\n");
- $this->assertSame($expPos, $attrs['startFilePos']);
- $this->assertSame($expPos, $attrs['endFilePos']);
- $this->assertSame($expLine, $attrs['startLine']);
- $this->assertSame($expLine, $attrs['endLine']);
- }
- public static function provideTestLexNewFeatures() {
- return [
- ['yield from', [
- [\T_YIELD_FROM, 'yield from'],
- ]],
- ["yield\r\nfrom", [
- [\T_YIELD_FROM, "yield\r\nfrom"],
- ]],
- ['...', [
- [\T_ELLIPSIS, '...'],
- ]],
- ['**', [
- [\T_POW, '**'],
- ]],
- ['**=', [
- [\T_POW_EQUAL, '**='],
- ]],
- ['??', [
- [\T_COALESCE, '??'],
- ]],
- ['<=>', [
- [\T_SPACESHIP, '<=>'],
- ]],
- ['0b1010110', [
- [\T_LNUMBER, '0b1010110'],
- ]],
- ['0b1011010101001010110101010010101011010101010101101011001110111100', [
- [\T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'],
- ]],
- ['\\', [
- [\T_NS_SEPARATOR, '\\'],
- ]],
- ["<<<'NOWDOC'\nNOWDOC;\n", [
- [\T_START_HEREDOC, "<<<'NOWDOC'\n"],
- [\T_END_HEREDOC, 'NOWDOC'],
- [ord(';'), ';'],
- ]],
- ["<<<'NOWDOC'\nFoobar\nNOWDOC;\n", [
- [\T_START_HEREDOC, "<<<'NOWDOC'\n"],
- [\T_ENCAPSED_AND_WHITESPACE, "Foobar\n"],
- [\T_END_HEREDOC, 'NOWDOC'],
- [ord(';'), ';'],
- ]],
- // PHP 7.3: Flexible heredoc/nowdoc
- ["<<<LABEL\nLABEL,", [
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_END_HEREDOC, "LABEL"],
- [ord(','), ','],
- ]],
- ["<<<LABEL\n LABEL,", [
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_END_HEREDOC, " LABEL"],
- [ord(','), ','],
- ]],
- ["<<<LABEL\n Foo\n LABEL;", [
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_ENCAPSED_AND_WHITESPACE, " Foo\n"],
- [\T_END_HEREDOC, " LABEL"],
- [ord(';'), ';'],
- ]],
- ["<<<A\n A,<<<A\n A,", [
- [\T_START_HEREDOC, "<<<A\n"],
- [\T_END_HEREDOC, " A"],
- [ord(','), ','],
- [\T_START_HEREDOC, "<<<A\n"],
- [\T_END_HEREDOC, " A"],
- [ord(','), ','],
- ]],
- ["<<<LABEL\nLABELNOPE\nLABEL\n", [
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_ENCAPSED_AND_WHITESPACE, "LABELNOPE\n"],
- [\T_END_HEREDOC, "LABEL"],
- ]],
- // Interpretation changed
- ["<<<LABEL\n LABEL\nLABEL\n", [
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_END_HEREDOC, " LABEL"],
- [\T_STRING, "LABEL"],
- ]],
- // PHP 7.4: Null coalesce equal
- ['??=', [
- [\T_COALESCE_EQUAL, '??='],
- ]],
- // PHP 7.4: Number literal separator
- ['1_000', [
- [\T_LNUMBER, '1_000'],
- ]],
- ['0x7AFE_F00D', [
- [\T_LNUMBER, '0x7AFE_F00D'],
- ]],
- ['0b0101_1111', [
- [\T_LNUMBER, '0b0101_1111'],
- ]],
- ['0137_041', [
- [\T_LNUMBER, '0137_041'],
- ]],
- ['1_000.0', [
- [\T_DNUMBER, '1_000.0'],
- ]],
- ['1_0.0', [
- [\T_DNUMBER, '1_0.0']
- ]],
- ['1_000_000_000.0', [
- [\T_DNUMBER, '1_000_000_000.0']
- ]],
- ['0e1_0', [
- [\T_DNUMBER, '0e1_0']
- ]],
- ['1_0e+10', [
- [\T_DNUMBER, '1_0e+10']
- ]],
- ['1_0e-10', [
- [\T_DNUMBER, '1_0e-10']
- ]],
- ['0b1011010101001010_110101010010_10101101010101_0101101011001_110111100', [
- [\T_DNUMBER, '0b1011010101001010_110101010010_10101101010101_0101101011001_110111100'],
- ]],
- ['0xFFFF_FFFF_FFFF_FFFF', [
- [\T_DNUMBER, '0xFFFF_FFFF_FFFF_FFFF'],
- ]],
- ['1_000+1', [
- [\T_LNUMBER, '1_000'],
- [ord('+'), '+'],
- [\T_LNUMBER, '1'],
- ]],
- ['1_0abc', [
- [\T_LNUMBER, '1_0'],
- [\T_STRING, 'abc'],
- ]],
- ['?->', [
- [\T_NULLSAFE_OBJECT_OPERATOR, '?->'],
- ]],
- ['#[Attr]', [
- [\T_ATTRIBUTE, '#['],
- [\T_STRING, 'Attr'],
- [ord(']'), ']'],
- ]],
- ["#[\nAttr\n]", [
- [\T_ATTRIBUTE, '#['],
- [\T_STRING, 'Attr'],
- [ord(']'), ']'],
- ]],
- // Test interaction of two patch-based emulators
- ["<<<LABEL\n LABEL, #[Attr]", [
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_END_HEREDOC, " LABEL"],
- [ord(','), ','],
- [\T_ATTRIBUTE, '#['],
- [\T_STRING, 'Attr'],
- [ord(']'), ']'],
- ]],
- ["#[Attr] <<<LABEL\n LABEL,", [
- [\T_ATTRIBUTE, '#['],
- [\T_STRING, 'Attr'],
- [ord(']'), ']'],
- [\T_START_HEREDOC, "<<<LABEL\n"],
- [\T_END_HEREDOC, " LABEL"],
- [ord(','), ','],
- ]],
- // Enums use a contextual keyword
- ['enum Foo {}', [
- [\T_ENUM, 'enum'],
- [\T_STRING, 'Foo'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['class Enum {}', [
- [\T_CLASS, 'class'],
- [\T_STRING, 'Enum'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['class Enum extends X {}', [
- [\T_CLASS, 'class'],
- [\T_STRING, 'Enum'],
- [\T_EXTENDS, 'extends'],
- [\T_STRING, 'X'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['class Enum implements X {}', [
- [\T_CLASS, 'class'],
- [\T_STRING, 'Enum'],
- [\T_IMPLEMENTS, 'implements'],
- [\T_STRING, 'X'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['0o123', [
- [\T_LNUMBER, '0o123'],
- ]],
- ['0O123', [
- [\T_LNUMBER, '0O123'],
- ]],
- ['0o1_2_3', [
- [\T_LNUMBER, '0o1_2_3'],
- ]],
- ['0o1000000000000000000000', [
- [\T_DNUMBER, '0o1000000000000000000000'],
- ]],
- ['readonly class', [
- [\T_READONLY, 'readonly'],
- [\T_CLASS, 'class'],
- ]],
- ['function readonly(', [
- [\T_FUNCTION, 'function'],
- [\T_READONLY, 'readonly'],
- [ord('('), '('],
- ]],
- ['function readonly (', [
- [\T_FUNCTION, 'function'],
- [\T_READONLY, 'readonly'],
- [ord('('), '('],
- ]],
- ];
- }
- /**
- * @dataProvider provideTestTargetVersion
- */
- public function testTargetVersion(string $phpVersion, string $code, array $expectedTokens): void {
- $lexer = new Emulative(PhpVersion::fromString($phpVersion));
- $this->assertSameTokens($expectedTokens, $lexer->tokenize('<?php ' . $code));
- }
- public static function provideTestTargetVersion() {
- return [
- ['8.0', 'match', [[\T_MATCH, 'match']]],
- ['7.4', 'match', [[\T_STRING, 'match']]],
- // Keywords are not case-sensitive.
- ['8.0', 'MATCH', [[\T_MATCH, 'MATCH']]],
- ['7.4', 'MATCH', [[\T_STRING, 'MATCH']]],
- // Tested here to skip testLeaveStuffAloneInStrings.
- ['8.0', '"$foo?->bar"', [
- [ord('"'), '"'],
- [\T_VARIABLE, '$foo'],
- [\T_NULLSAFE_OBJECT_OPERATOR, '?->'],
- [\T_STRING, 'bar'],
- [ord('"'), '"'],
- ]],
- ['8.0', '"$foo?->bar baz"', [
- [ord('"'), '"'],
- [\T_VARIABLE, '$foo'],
- [\T_NULLSAFE_OBJECT_OPERATOR, '?->'],
- [\T_STRING, 'bar'],
- [\T_ENCAPSED_AND_WHITESPACE, ' baz'],
- [ord('"'), '"'],
- ]],
- ];
- }
- }
|