1
0
mirror of https://github.com/nikic/PHP-Parser.git synced 2025-07-15 03:16:32 +02:00

Compare commits

..

7 Commits

27 changed files with 1437 additions and 1136 deletions

@ -1,8 +1,17 @@
Version 4.13.3-dev
Version 4.14.1-dev
------------------
Nothing yet.
Version 4.14.0 (2022-05-31)
---------------------------
### Added
* Added support for readonly classes.
* Added `rawValue` attribute to `LNumber`, `DNumber` and `String_` nodes, which stores the unparsed
value of the literal (e.g. `"1_000"` rather than `1000`).
Version 4.13.2 (2021-11-30)
---------------------------

@ -3,10 +3,10 @@ PHP Parser
[![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
This is a PHP 5.2 to PHP 8.0 parser written in PHP. Its purpose is to simplify static code analysis and
This is a PHP 5.2 to PHP 8.1 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.0).
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.1).
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).

@ -689,9 +689,7 @@ array_expr:
scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
@ -793,10 +791,8 @@ ctor_arguments:
common_scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }

@ -382,8 +382,18 @@ enum_case_expr:
class_entry_type:
T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; }
| class_modifiers T_CLASS { $$ = $1; }
;
class_modifiers:
class_modifier { $$ = $1; }
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
;
class_modifier:
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
extends_from:
@ -1014,9 +1024,7 @@ dereferencable_scalar:
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); }
| array_short_syntax { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
@ -1024,7 +1032,7 @@ dereferencable_scalar:
scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; }
| class_constant { $$ = $1; }

@ -128,14 +128,6 @@ function resolveMacros($code) {
. ' else { ' . $args[0] . ' = null; }';
}
if ('strKind' === $name) {
assertArgs(1, $args, $name);
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);

@ -67,7 +67,7 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this;
}
@ -78,7 +78,13 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
public function makeReadonly() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}

@ -310,4 +310,13 @@ final class BuilderHelpers
Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier;
}
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
}

@ -11,7 +11,7 @@ class Const_ extends NodeAbstract
/** @var Expr Value */
public $value;
/** @var Name Namespaced name (if using NameResolver) */
/** @var Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
@ -30,7 +30,7 @@ class Const_ extends NodeAbstract
public function getSubNodeNames() : array {
return ['name', 'value'];
}
public function getType() : string {
return 'Const';
}

@ -24,6 +24,17 @@ class DNumber extends Scalar
return ['value'];
}
/**
* @param mixed[] $attributes
*/
public static function fromString(string $str, array $attributes = []): DNumber
{
$attributes['rawValue'] = $str;
$float = self::parse($str);
return new DNumber($float, $attributes);
}
/**
* @internal
*
@ -63,7 +74,7 @@ class DNumber extends Scalar
// dec
return (float) $str;
}
public function getType() : string {
return 'Scalar_DNumber';
}

@ -41,6 +41,8 @@ class LNumber extends Scalar
* @return LNumber The constructed LNumber, including kind attribute
*/
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$attributes['rawValue'] = $str;
$str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) {
@ -71,7 +73,7 @@ class LNumber extends Scalar
$attributes['kind'] = LNumber::KIND_OCT;
return new LNumber(intval($str, 8), $attributes);
}
public function getType() : string {
return 'Scalar_LNumber';
}

@ -42,6 +42,22 @@ class String_ extends Scalar
return ['value'];
}
/**
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*/
public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self
{
$attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B')))
? Scalar\String_::KIND_SINGLE_QUOTED
: Scalar\String_::KIND_DOUBLE_QUOTED;
$attributes['rawValue'] = $str;
$string = self::parse($str, $parseUnicodeEscape);
return new self($string, $attributes);
}
/**
* @internal
*

@ -13,7 +13,7 @@ abstract class ClassLike extends Node\Stmt
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Name Namespaced name (if using NameResolver) */
/** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**

@ -68,6 +68,10 @@ class Class_ extends ClassLike
return (bool) ($this->flags & self::MODIFIER_FINAL);
}
public function isReadonly() : bool {
return (bool) ($this->flags & self::MODIFIER_READONLY);
}
/**
* Whether the class is anonymous.
*
@ -77,6 +81,27 @@ class Class_ extends ClassLike
return null === $this->name;
}
/**
* @internal
*/
public static function verifyClassModifier($a, $b) {
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class');
}
}
/**
* @internal
*/

@ -20,7 +20,7 @@ class Function_ extends Node\Stmt implements FunctionLike
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Name Namespaced name (if using NameResolver) */
/** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**

@ -2147,8 +2147,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
392 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(4-1)][0] === "'" || ($this->semStack[$stackPos-(4-1)][1] === "'" && ($this->semStack[$stackPos-(4-1)][0] === 'b' || $this->semStack[$stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
$this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(4-1)]), $attrs), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
$this->semValue = new Expr\ArrayDimFetch(Scalar\String_::fromString($this->semStack[$stackPos-(4-1)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
393 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
@ -2275,11 +2274,10 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true);
},
434 => function ($stackPos) {
$this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
$this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
435 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
$this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)], false), $attrs);
$this->semValue = Scalar\String_::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, false);
},
436 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);

File diff suppressed because it is too large Load Diff

@ -875,6 +875,15 @@ abstract class ParserAbstract implements Parser
return $attributes;
}
protected function checkClassModifier($a, $b, $modifierPos) {
try {
Class_::verifyClassModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {

@ -68,6 +68,20 @@ class ClassTest extends \PHPUnit\Framework\TestCase
);
}
public function testReadonly() {
$node = $this->createClassBuilder('Test')
->makeReadonly()
->getNode()
;
$this->assertEquals(
new Stmt\Class_('Test', [
'flags' => Stmt\Class_::MODIFIER_READONLY
]),
$node
);
}
public function testStatementOrder() {
$method = new Stmt\ClassMethod('testMethod');
$property = new Stmt\Property(

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class DNumberTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo 1_234.56;');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(DNumber::class, $lLumber);
/** @var DNumber $dnumber */
$this->assertSame(1234.56, $lLumber->value);
$this->assertSame('1_234.56', $lLumber->getAttribute('rawValue'));
}
}

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class NumberTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo 1_234;');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$lLumber = $echo->exprs[0];
$this->assertInstanceOf(LNumber::class, $lLumber);
/** @var LNumber $lnumber */
$this->assertSame(1234, $lLumber->value);
$this->assertSame('1_234', $lLumber->getAttribute('rawValue'));
}
}

@ -2,8 +2,28 @@
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Echo_;
use PhpParser\ParserFactory;
class StringTest extends \PHPUnit\Framework\TestCase
{
public function testRawValue()
{
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodes = $parser->parse('<?php echo "sequence \x41";');
$echo = $nodes[0];
$this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */
$string = $echo->exprs[0];
$this->assertInstanceOf(String_::class, $string);
/** @var String_ $string */
$this->assertSame('sequence A', $string->value);
$this->assertSame('"sequence \\x41"', $string->getAttribute('rawValue'));
}
/**
* @dataProvider provideTestParseEscapeSequences
*/

@ -245,6 +245,7 @@ PHP;
"attributes": {
"startLine": 4,
"endLine": 4,
"rawValue": "0",
"kind": 10
}
},
@ -273,7 +274,8 @@ PHP;
"value": 1,
"attributes": {
"startLine": 4,
"endLine": 4
"endLine": 4,
"rawValue": "1.0"
}
},
"flags": 0,
@ -295,7 +297,8 @@ PHP;
"attributes": {
"startLine": 5,
"endLine": 5,
"kind": 1
"kind": 1,
"rawValue": "'Foo'"
}
}
],
@ -398,6 +401,7 @@ JSON;
"attributes": {
"startLine": 4,
"endLine": 4,
"rawValue": "0",
"kind": 10
},
"value": 0
@ -426,7 +430,8 @@ JSON;
"nodeType": "Scalar_DNumber",
"attributes": {
"startLine": 4,
"endLine": 4
"endLine": 4,
"rawValue": "1.0"
},
"value": 1
},
@ -448,7 +453,8 @@ JSON;
"attributes": {
"startLine": 5,
"endLine": 5,
"kind": 1
"kind": 1,
"rawValue": "'Foo'"
},
"value": "Foo"
}

@ -1521,4 +1521,4 @@ array(
)
)
)
)
)

@ -150,4 +150,4 @@ array(
)
)
)
)
)

@ -66,7 +66,7 @@ array(
)
)
-----
<?php class A { readonly readonly $a; }
<?php class C { readonly readonly $a; }
-----
!!php7
Multiple readonly modifiers are not allowed from 1:26 to 1:33
@ -76,7 +76,7 @@ array(
)
flags: 0
name: Identifier(
name: A
name: C
)
extends: null
implements: array(
@ -231,8 +231,29 @@ array(
)
-----
<?php abstract final class A { }
-----
!!php7
Cannot use the final modifier on an abstract class from 1:16 to 1:20
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
!!php5
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array(
0: Stmt_Class(
@ -258,6 +279,7 @@ array(
<?php readonly class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
!!php5
Syntax error, unexpected T_READONLY from 1:7 to 1:14
array(
0: Stmt_Class(
@ -280,7 +302,7 @@ array(
)
)
-----
<?php class A { abstract $a; }
<?php class B { abstract $b; }
-----
Properties cannot be declared abstract from 1:17 to 1:24
array(
@ -289,7 +311,7 @@ array(
)
flags: 0
name: Identifier(
name: A
name: B
)
extends: null
implements: array(
@ -303,7 +325,7 @@ array(
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: a
name: b
)
default: null
)

@ -0,0 +1,68 @@
Readonly class
-----
<?php
readonly class A {
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_READONLY (64)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
readonly class A {
}
-----
!!php5
Syntax error, unexpected T_READONLY from 3:1 to 3:8
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
final readonly class A {
}
-----
!!php7
array(
0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL | MODIFIER_READONLY (96)
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
)
)
)

@ -0,0 +1,12 @@
Readonly class
-----
<?php
readonly class Foo
{
}
-----
!!php7
readonly class Foo
{
}