getLexer(); $lexer->startLexing('assertSame($expectedToken, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken()); } /** * @dataProvider provideTestReplaceKeywords */ public function testReplaceKeywordsUppercase($keyword, $expectedToken) { $lexer = $this->getLexer(); $lexer->startLexing('assertSame($expectedToken, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken()); } /** * @dataProvider provideTestReplaceKeywords */ public function testNoReplaceKeywordsAfterObjectOperator(string $keyword) { $lexer = $this->getLexer(); $lexer->startLexing('' . $keyword); $this->assertSame(Tokens::T_OBJECT_OPERATOR, $lexer->getNextToken()); $this->assertSame(Tokens::T_STRING, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken()); } /** * @dataProvider provideTestReplaceKeywords */ public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword) { $lexer = $this->getLexer(); $lexer->startLexing(' ' . $keyword); $this->assertSame(Tokens::T_OBJECT_OPERATOR, $lexer->getNextToken()); $this->assertSame(Tokens::T_STRING, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken()); } /** * @dataProvider provideTestReplaceKeywords */ public function testNoReplaceKeywordsAfterNullsafeObjectOperator(string $keyword) { $lexer = $this->getLexer(); $lexer->startLexing('' . $keyword); $this->assertSame(Tokens::T_NULLSAFE_OBJECT_OPERATOR, $lexer->getNextToken()); $this->assertSame(Tokens::T_STRING, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken()); } public function provideTestReplaceKeywords() { return [ // PHP 8.0 ['match', Tokens::T_MATCH], // PHP 7.4 ['fn', Tokens::T_FN], // PHP 5.5 ['finally', Tokens::T_FINALLY], ['yield', Tokens::T_YIELD], // PHP 5.4 ['callable', Tokens::T_CALLABLE], ['insteadof', Tokens::T_INSTEADOF], ['trait', Tokens::T_TRAIT], ['__TRAIT__', Tokens::T_TRAIT_C], // PHP 5.3 ['__DIR__', Tokens::T_DIR], ['goto', Tokens::T_GOTO], ['namespace', Tokens::T_NAMESPACE], ['__NAMESPACE__', Tokens::T_NS_C], ]; } private function assertSameTokens(array $expectedTokens, Lexer $lexer) { $tokens = []; while (0 !== $token = $lexer->getNextToken($text)) { $tokens[] = [$token, $text]; } $this->assertSame($expectedTokens, $tokens); } /** * @dataProvider provideTestLexNewFeatures */ public function testLexNewFeatures($code, array $expectedTokens) { $lexer = $this->getLexer(); $lexer->startLexing('assertSameTokens($expectedTokens, $lexer); } /** * @dataProvider provideTestLexNewFeatures */ public function testLeaveStuffAloneInStrings($code) { $stringifiedToken = '"' . addcslashes($code, '"\\') . '"'; $lexer = $this->getLexer(); $lexer->startLexing('assertSame(Tokens::T_CONSTANT_ENCAPSED_STRING, $lexer->getNextToken($text)); $this->assertSame($stringifiedToken, $text); $this->assertSame(0, $lexer->getNextToken()); } /** * @dataProvider provideTestLexNewFeatures */ public function testErrorAfterEmulation($code) { $errorHandler = new ErrorHandler\Collecting; $lexer = $this->getLexer(); $lexer->startLexing('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 function provideTestLexNewFeatures() { return [ ['yield from', [ [Tokens::T_YIELD_FROM, 'yield from'], ]], ["yield\r\nfrom", [ [Tokens::T_YIELD_FROM, "yield\r\nfrom"], ]], ['...', [ [Tokens::T_ELLIPSIS, '...'], ]], ['**', [ [Tokens::T_POW, '**'], ]], ['**=', [ [Tokens::T_POW_EQUAL, '**='], ]], ['??', [ [Tokens::T_COALESCE, '??'], ]], ['<=>', [ [Tokens::T_SPACESHIP, '<=>'], ]], ['0b1010110', [ [Tokens::T_LNUMBER, '0b1010110'], ]], ['0b1011010101001010110101010010101011010101010101101011001110111100', [ [Tokens::T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'], ]], ['\\', [ [Tokens::T_NS_SEPARATOR, '\\'], ]], ["<<<'NOWDOC'\nNOWDOC;\n", [ [Tokens::T_START_HEREDOC, "<<<'NOWDOC'\n"], [Tokens::T_END_HEREDOC, 'NOWDOC'], [ord(';'), ';'], ]], ["<<<'NOWDOC'\nFoobar\nNOWDOC;\n", [ [Tokens::T_START_HEREDOC, "<<<'NOWDOC'\n"], [Tokens::T_ENCAPSED_AND_WHITESPACE, "Foobar\n"], [Tokens::T_END_HEREDOC, 'NOWDOC'], [ord(';'), ';'], ]], // PHP 7.3: Flexible heredoc/nowdoc ["<<', [ [Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'], ]], ['#[Attr]', [ [Tokens::T_ATTRIBUTE, '#['], [Tokens::T_STRING, 'Attr'], [ord(']'), ']'], ]], ["#[\nAttr\n]", [ [Tokens::T_ATTRIBUTE, '#['], [Tokens::T_STRING, 'Attr'], [ord(']'), ']'], ]], // Test interaction of two patch-based emulators ["<<getLexer(['phpVersion' => $phpVersion]); $lexer->startLexing('assertSameTokens($expectedTokens, $lexer); } public function provideTestTargetVersion() { return [ ['8.0', 'match', [[Tokens::T_MATCH, 'match']]], ['7.4', 'match', [[Tokens::T_STRING, 'match']]], // Keywords are not case-sensitive. ['7.4', 'fn', [[Tokens::T_FN, 'fn']]], ['7.4', 'FN', [[Tokens::T_FN, 'FN']]], ['7.3', 'fn', [[Tokens::T_STRING, 'fn']]], ['7.3', 'FN', [[Tokens::T_STRING, 'FN']]], // Tested here to skip testLeaveStuffAloneInStrings. ['8.0', '"$foo?->bar"', [ [ord('"'), '"'], [Tokens::T_VARIABLE, '$foo'], [Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'], [Tokens::T_STRING, 'bar'], [ord('"'), '"'], ]], ['8.0', '"$foo?->bar baz"', [ [ord('"'), '"'], [Tokens::T_VARIABLE, '$foo'], [Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'], [Tokens::T_STRING, 'bar'], [Tokens::T_ENCAPSED_AND_WHITESPACE, ' baz'], [ord('"'), '"'], ]], ]; } }