123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- <?php declare(strict_types=1);
- namespace PhpParser\Lexer;
- use PhpParser\ErrorHandler;
- use PhpParser\Lexer;
- use PhpParser\LexerTest;
- use PhpParser\Parser\Tokens;
- class EmulativeTest extends LexerTest
- {
- protected function getLexer(array $options = []) {
- return new Emulative($options);
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testReplaceKeywords($keyword, $expectedToken) {
- $lexer = $this->getLexer();
- $lexer->startLexing('<?php ' . $keyword);
- $this->assertSame($expectedToken, $lexer->getNextToken());
- $this->assertSame(0, $lexer->getNextToken());
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testReplaceKeywordsUppercase($keyword, $expectedToken) {
- $lexer = $this->getLexer();
- $lexer->startLexing('<?php ' . strtoupper($keyword));
- $this->assertSame($expectedToken, $lexer->getNextToken());
- $this->assertSame(0, $lexer->getNextToken());
- }
- /**
- * @dataProvider provideTestReplaceKeywords
- */
- public function testNoReplaceKeywordsAfterObjectOperator(string $keyword) {
- $lexer = $this->getLexer();
- $lexer->startLexing('<?php ->' . $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('<?php -> ' . $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('<?php ?->' . $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('<?php ' . $code);
- $this->assertSameTokens($expectedTokens, $lexer);
- }
- /**
- * @dataProvider provideTestLexNewFeatures
- */
- public function testLeaveStuffAloneInStrings($code) {
- $stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
- $lexer = $this->getLexer();
- $lexer->startLexing('<?php ' . $stringifiedToken);
- $this->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('<?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 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
- ["<<<LABEL\nLABEL,", [
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_END_HEREDOC, "LABEL"],
- [ord(','), ','],
- ]],
- ["<<<LABEL\n LABEL,", [
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_END_HEREDOC, " LABEL"],
- [ord(','), ','],
- ]],
- ["<<<LABEL\n Foo\n LABEL;", [
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_ENCAPSED_AND_WHITESPACE, " Foo\n"],
- [Tokens::T_END_HEREDOC, " LABEL"],
- [ord(';'), ';'],
- ]],
- ["<<<A\n A,<<<A\n A,", [
- [Tokens::T_START_HEREDOC, "<<<A\n"],
- [Tokens::T_END_HEREDOC, " A"],
- [ord(','), ','],
- [Tokens::T_START_HEREDOC, "<<<A\n"],
- [Tokens::T_END_HEREDOC, " A"],
- [ord(','), ','],
- ]],
- ["<<<LABEL\nLABELNOPE\nLABEL\n", [
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_ENCAPSED_AND_WHITESPACE, "LABELNOPE\n"],
- [Tokens::T_END_HEREDOC, "LABEL"],
- ]],
- // Interpretation changed
- ["<<<LABEL\n LABEL\nLABEL\n", [
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_END_HEREDOC, " LABEL"],
- [Tokens::T_STRING, "LABEL"],
- ]],
- // PHP 7.4: Null coalesce equal
- ['??=', [
- [Tokens::T_COALESCE_EQUAL, '??='],
- ]],
- // PHP 7.4: Number literal separator
- ['1_000', [
- [Tokens::T_LNUMBER, '1_000'],
- ]],
- ['0x7AFE_F00D', [
- [Tokens::T_LNUMBER, '0x7AFE_F00D'],
- ]],
- ['0b0101_1111', [
- [Tokens::T_LNUMBER, '0b0101_1111'],
- ]],
- ['0137_041', [
- [Tokens::T_LNUMBER, '0137_041'],
- ]],
- ['1_000.0', [
- [Tokens::T_DNUMBER, '1_000.0'],
- ]],
- ['1_0.0', [
- [Tokens::T_DNUMBER, '1_0.0']
- ]],
- ['1_000_000_000.0', [
- [Tokens::T_DNUMBER, '1_000_000_000.0']
- ]],
- ['0e1_0', [
- [Tokens::T_DNUMBER, '0e1_0']
- ]],
- ['1_0e+10', [
- [Tokens::T_DNUMBER, '1_0e+10']
- ]],
- ['1_0e-10', [
- [Tokens::T_DNUMBER, '1_0e-10']
- ]],
- ['0b1011010101001010_110101010010_10101101010101_0101101011001_110111100', [
- [Tokens::T_DNUMBER, '0b1011010101001010_110101010010_10101101010101_0101101011001_110111100'],
- ]],
- ['0xFFFF_FFFF_FFFF_FFFF', [
- [Tokens::T_DNUMBER, '0xFFFF_FFFF_FFFF_FFFF'],
- ]],
- ['1_000+1', [
- [Tokens::T_LNUMBER, '1_000'],
- [ord('+'), '+'],
- [Tokens::T_LNUMBER, '1'],
- ]],
- ['1_0abc', [
- [Tokens::T_LNUMBER, '1_0'],
- [Tokens::T_STRING, 'abc'],
- ]],
- ['?->', [
- [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
- ["<<<LABEL\n LABEL, #[Attr]", [
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_END_HEREDOC, " LABEL"],
- [ord(','), ','],
- [Tokens::T_ATTRIBUTE, '#['],
- [Tokens::T_STRING, 'Attr'],
- [ord(']'), ']'],
- ]],
- ["#[Attr] <<<LABEL\n LABEL,", [
- [Tokens::T_ATTRIBUTE, '#['],
- [Tokens::T_STRING, 'Attr'],
- [ord(']'), ']'],
- [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
- [Tokens::T_END_HEREDOC, " LABEL"],
- [ord(','), ','],
- ]],
- // Enums use a contextual keyword
- ['enum Foo {}', [
- [Tokens::T_ENUM, 'enum'],
- [Tokens::T_STRING, 'Foo'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['class Enum {}', [
- [Tokens::T_CLASS, 'class'],
- [Tokens::T_STRING, 'Enum'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['class Enum extends X {}', [
- [Tokens::T_CLASS, 'class'],
- [Tokens::T_STRING, 'Enum'],
- [Tokens::T_EXTENDS, 'extends'],
- [Tokens::T_STRING, 'X'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['class Enum implements X {}', [
- [Tokens::T_CLASS, 'class'],
- [Tokens::T_STRING, 'Enum'],
- [Tokens::T_IMPLEMENTS, 'implements'],
- [Tokens::T_STRING, 'X'],
- [ord('{'), '{'],
- [ord('}'), '}'],
- ]],
- ['0o123', [
- [Tokens::T_LNUMBER, '0o123'],
- ]],
- ['0O123', [
- [Tokens::T_LNUMBER, '0O123'],
- ]],
- ['0o1_2_3', [
- [Tokens::T_LNUMBER, '0o1_2_3'],
- ]],
- ['0o1000000000000000000000', [
- [Tokens::T_DNUMBER, '0o1000000000000000000000'],
- ]],
- ['readonly class', [
- [Tokens::T_READONLY, 'readonly'],
- [Tokens::T_CLASS, 'class'],
- ]],
- ['function readonly(', [
- [Tokens::T_FUNCTION, 'function'],
- [Tokens::T_READONLY, 'readonly'],
- [ord('('), '('],
- ]],
- ['function readonly (', [
- [Tokens::T_FUNCTION, 'function'],
- [Tokens::T_READONLY, 'readonly'],
- [ord('('), '('],
- ]],
- ];
- }
- /**
- * @dataProvider provideTestTargetVersion
- */
- public function testTargetVersion(string $phpVersion, string $code, array $expectedTokens) {
- $lexer = $this->getLexer(['phpVersion' => $phpVersion]);
- $lexer->startLexing('<?php ' . $code);
- $this->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('"'), '"'],
- ]],
- ];
- }
- }
|