diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 23ed339bcdccd214219f643b532a225cce0824d9..bab4133cffc0b84a9f704b625c24824c34f5e793 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,6 +41,7 @@ jobs: - "8.0" - "8.1" - "8.2" + - "8.3" steps: - name: "Checkout" uses: "actions/checkout@v3" diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c05b245965ffcb41a4dc831d260fa1e85e16c5..5aa1986696c5b44e11e168f5dfb95acc6617485a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +Version 4.18.0 (2023-12-10) +--------------------------- + +### Added + +* Added methods `ParserFactory::createForNewestSupportedVersion()` and + `ParserFactory::createForHostVersion()` for forward-compatibility with PHP-Parser 5.0. + +### Fixed + +* Fixed missing name resolution of class constant types. +* Fixed class members being dropped if an error is encountered while parsing a later class member + (when error recovery is enabeld). + +### Changed + +* The `grammar/` directory has been excluded from exported git archives. + Version 4.17.1 (2023-08-13) --------------------------- diff --git a/grammar/php5.y b/grammar/php5.y index 77e4fb7ede2a4255198bdfbfce397d9fd02e3764..d7d62887829a79f23beff6b91ac601b5c144d379 100644 --- a/grammar/php5.y +++ b/grammar/php5.y @@ -469,7 +469,7 @@ static_var: ; class_statement_list_ex: - class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } } + class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } } | /* empty */ { init(); } ; diff --git a/grammar/php7.y b/grammar/php7.y index 1ef60bfe03e02b8f768056f4036e2ba46473f293..53d619477e5f8206519b4f92eaa0e4be23f44325 100644 --- a/grammar/php7.y +++ b/grammar/php7.y @@ -708,7 +708,7 @@ static_var: ; class_statement_list_ex: - class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } } + class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } } | /* empty */ { init(); } ; diff --git a/lib/PhpParser/NodeVisitor/NameResolver.php b/lib/PhpParser/NodeVisitor/NameResolver.php index d0e7de02f5814e459f03213877e825977ba941bc..83f3ea831c92df1f8bf9e8aea3279a4905962735 100644 --- a/lib/PhpParser/NodeVisitor/NameResolver.php +++ b/lib/PhpParser/NodeVisitor/NameResolver.php @@ -118,6 +118,9 @@ class NameResolver extends NodeVisitorAbstract $this->addNamespacedName($const); } } else if ($node instanceof Stmt\ClassConst) { + if (null !== $node->type) { + $node->type = $this->resolveType($node->type); + } $this->resolveAttrGroups($node); } else if ($node instanceof Stmt\EnumCase) { $this->resolveAttrGroups($node); diff --git a/lib/PhpParser/Parser/Php5.php b/lib/PhpParser/Parser/Php5.php index a43067108b20dae24830ef02a35092f06a79f49a..59bd1e8cf535609cbb7def17406215d65d0bcd6b 100644 --- a/lib/PhpParser/Parser/Php5.php +++ b/lib/PhpParser/Parser/Php5.php @@ -1738,7 +1738,7 @@ class Php5 extends \PhpParser\ParserAbstract $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 259 => function ($stackPos) { - if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } else { $this->semValue = $this->semStack[$stackPos-(2-1)]; } }, 260 => function ($stackPos) { $this->semValue = array(); diff --git a/lib/PhpParser/Parser/Php7.php b/lib/PhpParser/Parser/Php7.php index fc895cb047ca18accbb8ac8297ea1b36b3562b55..6d2b4b0f9ca224f2026c86e50592ec3a65179506 100644 --- a/lib/PhpParser/Parser/Php7.php +++ b/lib/PhpParser/Parser/Php7.php @@ -2056,7 +2056,7 @@ class Php7 extends \PhpParser\ParserAbstract $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 340 => function ($stackPos) { - if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } else { $this->semValue = $this->semStack[$stackPos-(2-1)]; } }, 341 => function ($stackPos) { $this->semValue = array(); diff --git a/lib/PhpParser/ParserFactory.php b/lib/PhpParser/ParserFactory.php index f041e7ffe3adcc4181c6900b55061ed596f2786b..baba23bdb49f9f270c77b089612f4421b885021b 100644 --- a/lib/PhpParser/ParserFactory.php +++ b/lib/PhpParser/ParserFactory.php @@ -2,6 +2,9 @@ namespace PhpParser; +use PhpParser\Lexer\Emulative; +use PhpParser\Parser\Php7; + class ParserFactory { const PREFER_PHP7 = 1; @@ -41,4 +44,33 @@ class ParserFactory ); } } + + /** + * Create a parser targeting the newest version supported by this library. Code for older + * versions will be accepted if there have been no relevant backwards-compatibility breaks in + * PHP. + * + * All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos, + * startFilePos, endFilePos) will be enabled. + */ + public function createForNewestSupportedVersion(): Parser { + return new Php7(new Emulative($this->getLexerOptions())); + } + + /** + * Create a parser targeting the host PHP version, that is the PHP version we're currently + * running on. This parser will not use any token emulation. + * + * All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos, + * startFilePos, endFilePos) will be enabled. + */ + public function createForHostVersion(): Parser { + return new Php7(new Lexer($this->getLexerOptions())); + } + + private function getLexerOptions(): array { + return ['usedAttributes' => [ + 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos', + ]]; + } } diff --git a/test/PhpParser/NodeVisitor/NameResolverTest.php b/test/PhpParser/NodeVisitor/NameResolverTest.php index b5035ce3fe16ddf723c83c411577feaf6e1f2ff4..3b1e920765d8e25784ab386621f143d37ffcde0e 100644 --- a/test/PhpParser/NodeVisitor/NameResolverTest.php +++ b/test/PhpParser/NodeVisitor/NameResolverTest.php @@ -189,7 +189,7 @@ class A extends B implements C, D { E::h as i; E::j insteadof F, G; } - + #[X] public float $php = 7.4; public ?Foo $person; @@ -198,6 +198,10 @@ class A extends B implements C, D { #[X] const C = 1; + + public const X A = X::Bar; + public const X\Foo B = X\Foo::Bar; + public const \X\Foo C = \X\Foo::Bar; } #[X] @@ -263,6 +267,9 @@ class A extends \NS\B implements \NS\C, \NS\D public \NS\A|\NS\B|int $prop; #[\NS\X] const C = 1; + public const \NS\X A = \NS\X::Bar; + public const \NS\X\Foo B = \NS\X\Foo::Bar; + public const \X\Foo C = \X\Foo::Bar; } #[\NS\X] interface A extends \NS\C, \NS\D diff --git a/test/PhpParser/ParserFactoryTest.php b/test/PhpParser/ParserFactoryTest.php index d50981f2a1297d425ebf704600d35158bcae678c..fbdc83ae933008432fff2af31df6a9dc9e5e8a10 100644 --- a/test/PhpParser/ParserFactoryTest.php +++ b/test/PhpParser/ParserFactoryTest.php @@ -5,6 +5,8 @@ namespace PhpParser; /* This test is very weak, because PHPUnit's assertEquals assertion is way too slow dealing with the * large objects involved here. So we just do some basic instanceof tests instead. */ +use PhpParser\Node\Stmt\Echo_; + class ParserFactoryTest extends \PHPUnit\Framework\TestCase { /** @dataProvider provideTestCreate */ @@ -33,4 +35,26 @@ class ParserFactoryTest extends \PHPUnit\Framework\TestCase ] ]; } + + /** @dataProvider provideTestLexerAttributes */ + public function testLexerAttributes(Parser $parser) { + $stmts = $parser->parse("<?php /* Bar */ echo 'Foo';"); + $stmt = $stmts[0]; + $this->assertInstanceOf(Echo_::class, $stmt); + $this->assertCount(1, $stmt->getComments()); + $this->assertSame(1, $stmt->getStartLine()); + $this->assertSame(1, $stmt->getEndLine()); + $this->assertSame(3, $stmt->getStartTokenPos()); + $this->assertSame(6, $stmt->getEndTokenPos()); + $this->assertSame(16, $stmt->getStartFilePos()); + $this->assertSame(26, $stmt->getEndFilePos()); + } + + public function provideTestLexerAttributes() { + $factory = new ParserFactory(); + return [ + [$factory->createForHostVersion()], + [$factory->createForNewestSupportedVersion()], + ]; + } } diff --git a/test/code/parser/errorHandling/recovery.test b/test/code/parser/errorHandling/recovery.test index c86d269f62f7b4bc9071f7d28725565aefb65212..f0ef5b4bd04d1785993bb7e9382e24c0cb265632 100644 --- a/test/code/parser/errorHandling/recovery.test +++ b/test/code/parser/errorHandling/recovery.test @@ -900,12 +900,13 @@ array( <?php class Foo { + public $bar1; publi $foo; public $bar; } ----- !!php7 -Syntax error, unexpected T_STRING from 4:5 to 4:9 +Syntax error, unexpected T_STRING from 5:5 to 5:9 array( 0: Stmt_Class( attrGroups: array( @@ -919,6 +920,20 @@ array( ) stmts: array( 0: Stmt_Property( + attrGroups: array( + ) + flags: MODIFIER_PUBLIC (1) + type: null + props: array( + 0: Stmt_PropertyProperty( + name: VarLikeIdentifier( + name: bar1 + ) + default: null + ) + ) + ) + 1: Stmt_Property( attrGroups: array( ) flags: MODIFIER_PUBLIC (1) @@ -1523,4 +1538,4 @@ array( ) ) ) -) \ No newline at end of file +)