getLexer(); $code = 'assertEquals([ new Token(\T_OPEN_TAG, 'tokenize($code)); } /** * @dataProvider provideTestReplaceKeywords */ public function testReplaceKeywordsUppercase(string $keyword, int $expectedToken): void { $lexer = $this->getLexer(); $code = 'assertEquals([ new Token(\T_OPEN_TAG, 'tokenize($code)); } /** * @dataProvider provideTestReplaceKeywords */ public function testNoReplaceKeywordsAfterObjectOperator(string $keyword): void { $lexer = $this->getLexer(); $code = '' . $keyword; $this->assertEquals([ new Token(\T_OPEN_TAG, '', 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 = ' ' . $keyword; $this->assertEquals([ new Token(\T_OPEN_TAG, '', 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 = '' . $keyword; $this->assertEquals([ new Token(\T_OPEN_TAG, '', 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.4 ['__PROPERTY__', \T_PROPERTY_C], // 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('getLexer(); $fullCode = 'assertEquals([ new Token(\T_OPEN_TAG, 'tokenize($fullCode)); } /** * @dataProvider provideTestLexNewFeatures */ public function testErrorAfterEmulation($code): void { $errorHandler = new ErrorHandler\Collecting(); $lexer = $this->getLexer(); $lexer->tokenize('getErrors(); $this->assertCount(1, $errors); $error = $errors[0]; $this->assertSame('Unexpected null byte', $error->getRawMessage()); $attrs = $error->getAttributes(); $expPos = strlen('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 ["<<', [ [\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 ["<<public(set)', [ [\T_OBJECT_OPERATOR, '->'], [\T_STRING, 'public'], [\ord('('), '('], [\T_STRING, 'set'], [\ord(')'), ')'], ]], ['?-> public(set)', [ [\T_NULLSAFE_OBJECT_OPERATOR, '?->'], [\T_STRING, 'public'], [\ord('('), '('], [\T_STRING, 'set'], [\ord(')'), ')'], ]], ]; } /** * @dataProvider provideTestTargetVersion */ public function testTargetVersion(string $phpVersion, string $code, array $expectedTokens): void { $lexer = new Emulative(PhpVersion::fromString($phpVersion)); $this->assertSameTokens($expectedTokens, $lexer->tokenize('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('"'), '"'], ]], ['8.4', '__PROPERTY__', [[\T_PROPERTY_C, '__PROPERTY__']]], ['8.3', '__PROPERTY__', [[\T_STRING, '__PROPERTY__']]], ['8.4', '__property__', [[\T_PROPERTY_C, '__property__']]], ['8.3', '__property__', [[\T_STRING, '__property__']]], ['8.4', 'public(set)', [ [\T_PUBLIC_SET, 'public(set)'], ]], ['8.3', 'public(set)', [ [\T_PUBLIC, 'public'], [\ord('('), '('], [\T_STRING, 'set'], [\ord(')'), ')'] ]], ]; } }