Add useIdentifierNodes mode to parser

In this mode non-namespaced names that are currently represented
using strings will be represented using Identifier nodes instead.
Identifier nodes have a string $name subnode and coerce to string.

This allows preserving attributes and in particular location
information on identifiers.
This commit is contained in:
Nikita Popov 2016-12-22 21:13:42 +01:00
parent 3e8c8d248d
commit 6bcc6c31dd
10 changed files with 2919 additions and 2600 deletions

View File

@ -35,9 +35,13 @@ semi_reserved:
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
;
identifier_ex:
T_STRING { $$ = makeIdent($1); }
| semi_reserved { $$ = makeIdent($1); }
;
identifier:
T_STRING { $$ = $1; }
| semi_reserved { $$ = $1; }
T_STRING { $$ = makeIdent($1); }
;
namespace_name_parts:
@ -103,7 +107,7 @@ inline_use_declarations:
unprefixed_use_declaration:
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS T_STRING
| namespace_name T_AS identifier
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
@ -123,7 +127,7 @@ constant_declaration_list:
;
constant_declaration:
T_STRING '=' static_scalar { $$ = Node\Const_[$1, $3]; }
identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; }
;
class_const_list:
@ -132,7 +136,7 @@ class_const_list:
;
class_const:
identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; }
identifier_ex '=' static_scalar { $$ = Node\Const_[$1, $3]; }
;
inner_statement_list_ex:
@ -186,8 +190,8 @@ non_empty_statement:
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
| T_STRING ':' { $$ = Stmt\Label[$1]; }
| T_GOTO identifier ';' { $$ = Stmt\Goto_[$2]; }
| identifier ':' { $$ = Stmt\Label[$1]; }
| expr error { $$ = $1; }
| error { $$ = array(); /* means: no statement */ }
;
@ -230,18 +234,18 @@ optional_ellipsis:
;
function_declaration_statement:
T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
;
class_declaration_statement:
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
$this->checkClass($$, #2); }
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
$this->checkInterface($$, #2); }
| T_TRAIT T_STRING '{' class_statement_list '}'
| T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
;
@ -293,7 +297,7 @@ declare_list:
;
declare_list_element:
T_STRING '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; }
identifier '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; }
;
switch_case_list:
@ -437,7 +441,7 @@ class_statement:
variable_modifiers property_declaration_list ';'
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; }
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
$this->checkClassMethod($$, #1); }
| T_USE name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
@ -456,22 +460,22 @@ trait_adaptation_list:
trait_adaptation:
trait_method_reference_fully_qualified T_INSTEADOF name_list ';'
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
| trait_method_reference T_AS member_modifier identifier ';'
| trait_method_reference T_AS member_modifier identifier_ex ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
| trait_method_reference T_AS member_modifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
| trait_method_reference T_AS T_STRING ';'
| trait_method_reference T_AS identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
| trait_method_reference T_AS reserved_non_modifiers ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;
trait_method_reference_fully_qualified:
name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = array($1, $3); }
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
;
trait_method_reference:
trait_method_reference_fully_qualified { $$ = $1; }
| identifier { $$ = array(null, $1); }
| identifier_ex { $$ = array(null, $1); }
;
method_body:
@ -674,7 +678,7 @@ lexical_var:
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier argument_list
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
{ $$ = Expr\StaticCall[$1, $4, $6]; }
@ -776,7 +780,7 @@ common_scalar:
static_scalar:
common_scalar { $$ = $1; }
| class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = Expr\ClassConstFetch[$1, $3]; }
| class_name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = Expr\ClassConstFetch[$1, $3]; }
| name { $$ = Expr\ConstFetch[$1]; }
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
@ -821,7 +825,7 @@ static_operation:
constant:
name { $$ = Expr\ConstFetch[$1]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
{ $$ = Expr\ClassConstFetch[$1, $3]; }
;
@ -922,7 +926,7 @@ dim_offset:
;
object_property:
T_STRING { $$ = $1; }
identifier { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| variable_without_objects { $$ = $1; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
@ -978,7 +982,7 @@ encaps_base_var:
encaps_var:
encaps_base_var { $$ = $1; }
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'

View File

@ -35,9 +35,13 @@ semi_reserved:
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
;
identifier_ex:
T_STRING { $$ = makeIdent($1); }
| semi_reserved { $$ = makeIdent($1); }
;
identifier:
T_STRING { $$ = $1; }
| semi_reserved { $$ = $1; }
T_STRING { $$ = makeIdent($1); }
;
namespace_name_parts:
@ -103,7 +107,7 @@ inline_use_declarations:
unprefixed_use_declaration:
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS T_STRING
| namespace_name T_AS identifier
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
@ -123,7 +127,7 @@ constant_declaration_list:
;
constant_declaration:
T_STRING '=' expr { $$ = Node\Const_[$1, $3]; }
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
;
class_const_list:
@ -132,7 +136,7 @@ class_const_list:
;
class_const:
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
;
inner_statement_list_ex:
@ -182,8 +186,8 @@ non_empty_statement:
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
| T_STRING ':' { $$ = Stmt\Label[$1]; }
| T_GOTO identifier ';' { $$ = Stmt\Goto_[$2]; }
| identifier ':' { $$ = Stmt\Label[$1]; }
| expr error { $$ = $1; }
| error { $$ = array(); /* means: no statement */ }
;
@ -231,18 +235,18 @@ optional_ellipsis:
;
function_declaration_statement:
T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
;
class_declaration_statement:
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
$this->checkClass($$, #2); }
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
$this->checkInterface($$, #2); }
| T_TRAIT T_STRING '{' class_statement_list '}'
| T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
;
@ -294,7 +298,7 @@ declare_list:
;
declare_list_element:
T_STRING '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
;
switch_case_list:
@ -442,7 +446,7 @@ class_statement:
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
| method_modifiers T_CONST class_const_list ';'
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
$this->checkClassMethod($$, #1); }
| T_USE name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
@ -461,22 +465,22 @@ trait_adaptation_list:
trait_adaptation:
trait_method_reference_fully_qualified T_INSTEADOF name_list ';'
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
| trait_method_reference T_AS member_modifier identifier ';'
| trait_method_reference T_AS member_modifier identifier_ex ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
| trait_method_reference T_AS member_modifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
| trait_method_reference T_AS T_STRING ';'
| trait_method_reference T_AS identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
| trait_method_reference T_AS reserved_non_modifiers ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;
trait_method_reference_fully_qualified:
name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = array($1, $3); }
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
;
trait_method_reference:
trait_method_reference_fully_qualified { $$ = $1; }
| identifier { $$ = array(null, $1); }
| identifier_ex { $$ = array(null, $1); }
;
method_body:
@ -694,7 +698,7 @@ ctor_arguments:
constant:
name { $$ = Expr\ConstFetch[$1]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
{ $$ = Expr\ClassConstFetch[$1, $3]; }
/* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */
@ -800,13 +804,13 @@ new_variable:
;
member_name:
identifier { $$ = $1; }
identifier_ex { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = Expr\Variable[$1]; }
;
property_name:
T_STRING { $$ = $1; }
identifier { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = Expr\Variable[$1]; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
@ -865,7 +869,7 @@ encaps_base_var:
encaps_var:
encaps_base_var { $$ = $1; }
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$1, $3]; }
| encaps_base_var T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'

View File

@ -210,6 +210,13 @@ function resolveMacros($code) {
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
}
if ('makeIdent' == $name) {
assertArgs(1, $args, $name);
return '($this->useIdentifierNodes ? new Node\Identifier(' . $args[0] . ', '
. '$this->startAttributeStack[#1] + $this->endAttributes) : ' . $args[0] . ')';
}
return $matches[0];
},
$code

View File

@ -0,0 +1,38 @@
<?php
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* Represents a non-namespaced name. Namespaced names are represented using Name nodes.
*/
class Identifier extends NodeAbstract
{
/** @var string Identifier as string */
public $name;
/**
* Constructs an identifier node.
*
* @param string $name Identifier as string
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = array()) {
parent::__construct($attributes);
$this->name = $name;
}
public function getSubNodeNames() {
return array('name');
}
/**
* Get identifier as string.
*
* @return string Identifier as string.
*/
public function __toString() {
return $this->name;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -111,15 +111,22 @@ abstract class ParserAbstract implements Parser
/** @var int Error state, used to avoid error floods */
protected $errorState;
/** @var bool Whether to create Identifier nodes for non-namespaced names */
protected $useIdentifierNodes;
/**
* Creates a parser instance.
*
* Options: If "useIdentifierNodes" is enabled, the parser will create Identifier nodes for
* non-namespaced names. Otherwise plain strings will be used.
*
* @param Lexer $lexer A lexer
* @param array $options Options array. Currently no options are supported.
* @param array $options Options array. Currently only "useIdentifierNodes" is supporter.
*/
public function __construct(Lexer $lexer, array $options = array()) {
$this->lexer = $lexer;
$this->errors = array();
$this->useIdentifierNodes = !empty($options['useIdentifierNodes']);
if (isset($options['throwOnError'])) {
throw new \LogicException(

View File

@ -16,12 +16,13 @@ class CodeParsingTest extends CodeTestAbstract
$modes = [];
}
$parserOptions = ['useIdentifierNodes' => isset($modes['ident'])];
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
)));
$parser5 = new Parser\Php5($lexer);
$parser7 = new Parser\Php7($lexer);
$parser5 = new Parser\Php5($lexer, $parserOptions);
$parser7 = new Parser\Php7($lexer, $parserOptions);
$dumpPositions = isset($modes['positions']);
$output5 = $this->getParseOutput($parser5, $code, $dumpPositions);

View File

@ -56,7 +56,7 @@ class PrettyPrinterTest extends CodeTestAbstract
/**
* @dataProvider provideTestPrettyPrint
* @covers PhpParser\PrettyPrinter\Standard<extended>
* @covers \PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrint($name, $code, $expected, $mode) {
$this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $expected, $mode);
@ -64,7 +64,7 @@ class PrettyPrinterTest extends CodeTestAbstract
/**
* @dataProvider provideTestPrettyPrintFile
* @covers PhpParser\PrettyPrinter\Standard<extended>
* @covers \PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrintFile($name, $code, $expected, $mode) {
$this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $expected, $mode);

View File

@ -0,0 +1,246 @@
Identifier node mode
-----
<?php
use Foo as Bar;
class Foo {
const BAR = 1;
function foo() {}
use A, B {
A::b as c;
d as public e;
}
}
interface Bar {}
trait Baz {}
function foo() {}
const FOO = 1;
declare(foo=1);
foo:
goto foo;
Foo::BAR;
$foo->bar;
$foo->bar();
Foo::bar();
"$foo->bar";
$foo;
-----
!!ident
array(
0: Stmt_Use(
type: TYPE_NORMAL (1)
uses: array(
0: Stmt_UseUse(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: Foo
)
)
alias: Identifier(
name: Bar
)
)
)
)
1: Stmt_Class(
flags: 0
name: Identifier(
name: Foo
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
flags: 0
consts: array(
0: Const(
name: Identifier(
name: BAR
)
value: Scalar_LNumber(
value: 1
)
)
)
)
1: Stmt_ClassMethod(
flags: 0
byRef: false
name: Identifier(
name: foo
)
params: array(
)
returnType: null
stmts: array(
)
)
2: Stmt_TraitUse(
traits: array(
0: Name(
parts: array(
0: A
)
)
1: Name(
parts: array(
0: B
)
)
)
adaptations: array(
0: Stmt_TraitUseAdaptation_Alias(
trait: Name(
parts: array(
0: A
)
)
method: Identifier(
name: b
)
newModifier: null
newName: Identifier(
name: c
)
)
1: Stmt_TraitUseAdaptation_Alias(
trait: null
method: Identifier(
name: d
)
newModifier: MODIFIER_PUBLIC (1)
newName: Identifier(
name: e
)
)
)
)
)
)
2: Stmt_Interface(
name: Identifier(
name: Bar
)
extends: array(
)
stmts: array(
)
)
3: Stmt_Trait(
name: Identifier(
name: Baz
)
stmts: array(
)
)
4: Stmt_Function(
byRef: false
name: Identifier(
name: foo
)
params: array(
)
returnType: null
stmts: array(
)
)
5: Stmt_Const(
consts: array(
0: Const(
name: Identifier(
name: FOO
)
value: Scalar_LNumber(
value: 1
)
)
)
)
6: Stmt_Declare(
declares: array(
0: Stmt_DeclareDeclare(
key: Identifier(
name: foo
)
value: Scalar_LNumber(
value: 1
)
)
)
stmts: null
)
7: Stmt_Label(
name: Identifier(
name: foo
)
)
8: Stmt_Goto(
name: Identifier(
name: foo
)
)
9: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
)
name: Identifier(
name: BAR
)
)
10: Expr_PropertyFetch(
var: Expr_Variable(
name: foo
)
name: Identifier(
name: bar
)
)
11: Expr_MethodCall(
var: Expr_Variable(
name: foo
)
name: Identifier(
name: bar
)
args: array(
)
)
12: Expr_StaticCall(
class: Name(
parts: array(
0: Foo
)
)
name: Identifier(
name: bar
)
args: array(
)
)
13: Scalar_Encapsed(
parts: array(
0: Expr_PropertyFetch(
var: Expr_Variable(
name: foo
)
name: Identifier(
name: bar
)
)
)
)
14: Expr_Variable(
name: foo
)
)