mirror of
https://github.com/phabelio/PHP-Parser.git
synced 2024-12-11 08:39:44 +01:00
c758510a37
With the introduction of intersection types, PHP now lexes the token '&' either as T_AMPERSAND_(NOT_)FOLLOWED_BY_VAR_OR_VARARG. This completely breaks parsing of any code containing '&'. Fix this by canonicalizing to the new token format (unconditionally, independent of emulation) and adjusting the parser to use the two new tokens. This doesn't add actual support for intersection types yet.
123 lines
4.1 KiB
PHP
123 lines
4.1 KiB
PHP
<?php declare(strict_types=1);
|
|
|
|
namespace PhpParser;
|
|
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Stmt;
|
|
|
|
class CodeParsingTest extends CodeTestAbstract
|
|
{
|
|
/**
|
|
* @dataProvider provideTestParse
|
|
*/
|
|
public function testParse($name, $code, $expected, $modeLine) {
|
|
if (null !== $modeLine) {
|
|
$modes = array_fill_keys(explode(',', $modeLine), true);
|
|
} else {
|
|
$modes = [];
|
|
}
|
|
|
|
list($parser5, $parser7) = $this->createParsers($modes);
|
|
list($stmts5, $output5) = $this->getParseOutput($parser5, $code, $modes);
|
|
list($stmts7, $output7) = $this->getParseOutput($parser7, $code, $modes);
|
|
|
|
if (isset($modes['php5'])) {
|
|
$this->assertSame($expected, $output5, $name);
|
|
$this->assertNotSame($expected, $output7, $name);
|
|
} elseif (isset($modes['php7'])) {
|
|
$this->assertNotSame($expected, $output5, $name);
|
|
$this->assertSame($expected, $output7, $name);
|
|
} else {
|
|
$this->assertSame($expected, $output5, $name);
|
|
$this->assertSame($expected, $output7, $name);
|
|
}
|
|
|
|
$this->checkAttributes($stmts5);
|
|
$this->checkAttributes($stmts7);
|
|
}
|
|
|
|
public function createParsers(array $modes) {
|
|
$lexer = new Lexer\Emulative(['usedAttributes' => [
|
|
'startLine', 'endLine',
|
|
'startFilePos', 'endFilePos',
|
|
'startTokenPos', 'endTokenPos',
|
|
'comments'
|
|
]]);
|
|
|
|
return [
|
|
new Parser\Php5($lexer),
|
|
new Parser\Php7($lexer),
|
|
];
|
|
}
|
|
|
|
// Must be public for updateTests.php
|
|
public function getParseOutput(Parser $parser, $code, array $modes) {
|
|
$dumpPositions = isset($modes['positions']);
|
|
|
|
$errors = new ErrorHandler\Collecting;
|
|
$stmts = $parser->parse($code, $errors);
|
|
|
|
$output = '';
|
|
foreach ($errors->getErrors() as $error) {
|
|
$output .= $this->formatErrorMessage($error, $code) . "\n";
|
|
}
|
|
|
|
if (null !== $stmts) {
|
|
$dumper = new NodeDumper(['dumpComments' => true, 'dumpPositions' => $dumpPositions]);
|
|
$output .= $dumper->dump($stmts, $code);
|
|
}
|
|
|
|
return [$stmts, canonicalize($output)];
|
|
}
|
|
|
|
public function provideTestParse() {
|
|
return $this->getTests(__DIR__ . '/../code/parser', 'test');
|
|
}
|
|
|
|
private function formatErrorMessage(Error $e, $code) {
|
|
if ($e->hasColumnInfo()) {
|
|
return $e->getMessageWithColumnInfo($code);
|
|
}
|
|
|
|
return $e->getMessage();
|
|
}
|
|
|
|
private function checkAttributes($stmts) {
|
|
if ($stmts === null) {
|
|
return;
|
|
}
|
|
|
|
$traverser = new NodeTraverser();
|
|
$traverser->addVisitor(new class extends NodeVisitorAbstract {
|
|
public function enterNode(Node $node) {
|
|
$startLine = $node->getStartLine();
|
|
$endLine = $node->getEndLine();
|
|
$startFilePos = $node->getStartFilePos();
|
|
$endFilePos = $node->getEndFilePos();
|
|
$startTokenPos = $node->getStartTokenPos();
|
|
$endTokenPos = $node->getEndTokenPos();
|
|
if ($startLine < 0 || $endLine < 0 ||
|
|
$startFilePos < 0 || $endFilePos < 0 ||
|
|
$startTokenPos < 0 || $endTokenPos < 0
|
|
) {
|
|
throw new \Exception('Missing location information on ' . $node->getType());
|
|
}
|
|
|
|
if ($endLine < $startLine ||
|
|
$endFilePos < $startFilePos ||
|
|
$endTokenPos < $startTokenPos
|
|
) {
|
|
// Nop and Error can have inverted order, if they are empty.
|
|
// This can also happen for a Param containing an Error.
|
|
if (!$node instanceof Stmt\Nop && !$node instanceof Expr\Error &&
|
|
!$node instanceof Node\Param
|
|
) {
|
|
throw new \Exception('End < start on ' . $node->getType());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
$traverser->traverse($stmts);
|
|
}
|
|
}
|