Merge pull request #278 from TysonAndre/parse-property-declaration

Implement support for parsing php 7.4 typed properties
This commit is contained in:
Rob Lourens 2019-02-17 10:28:53 -08:00 коммит произвёл GitHub
Родитель b662587eb7 9d7f8aef2e
Коммит db476e97f3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 812 добавлений и 10 удалений

Просмотреть файл

@ -14,7 +14,15 @@ class MissingMemberDeclaration extends Node {
/** @var Token[] */
public $modifiers;
/** @var Token|null needed along with typeDeclaration for what looked like typed property declarations but was missing VariableName */
public $questionToken;
/** @var QualifiedName|Token|null */
public $typeDeclaration;
const CHILD_NAMES = [
'modifiers'
'modifiers',
'questionToken',
'typeDeclaration',
];
}

Просмотреть файл

@ -15,6 +15,12 @@ class PropertyDeclaration extends Node {
/** @var Token[] */
public $modifiers;
/** @var Token|null question token for PHP 7.4 type declaration */
public $questionToken;
/** @var QualifiedName|Token|null */
public $typeDeclaration;
/** @var DelimitedList\ExpressionList */
public $propertyElements;
@ -23,6 +29,8 @@ class PropertyDeclaration extends Node {
const CHILD_NAMES = [
'modifiers',
'questionToken',
'typeDeclaration',
'propertyElements',
'semicolon'
];

Просмотреть файл

@ -596,6 +596,12 @@ class Parser {
case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);
case TokenKind::QuestionToken:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
$parentNode,
$modifiers,
$this->eat1(TokenKind::QuestionToken)
);
case TokenKind::VariableName:
return $this->parsePropertyDeclaration($parentNode, $modifiers);
@ -603,10 +609,7 @@ class Parser {
return $this->parseTraitUseClause($parentNode);
default:
$missingClassMemberDeclaration = new MissingMemberDeclaration();
$missingClassMemberDeclaration->parent = $parentNode;
$missingClassMemberDeclaration->modifiers = $modifiers;
return $missingClassMemberDeclaration;
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
}
};
}
@ -2771,11 +2774,39 @@ class Parser {
return $classConstDeclaration;
}
private function parsePropertyDeclaration($parentNode, $modifiers) {
/**
* @param Node $parentNode
* @param Token[] $modifiers
* @param Token|null $questionToken
*/
private function parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers, $questionToken = null)
{
$typeDeclaration = $this->tryParseParameterTypeDeclaration(null);
if ($questionToken !== null && $typeDeclaration === null) {
$typeDeclaration = new MissingToken(TokenKind::PropertyType, $this->getCurrentToken()->fullStart);
}
if ($this->getCurrentToken()->kind !== TokenKind::VariableName) {
return $this->makeMissingMemberDeclaration($parentNode, $modifiers, $questionToken, $typeDeclaration);
}
return $this->parsePropertyDeclaration($parentNode, $modifiers, $questionToken, $typeDeclaration);
}
/**
* @param Node $parentNode
* @param Token[] $modifiers
* @param Token|null $questionToken
* @param QualifiedName|Token|null $typeDeclaration
*/
private function parsePropertyDeclaration($parentNode, $modifiers, $questionToken = null, $typeDeclaration = null) {
$propertyDeclaration = new PropertyDeclaration();
$propertyDeclaration->parent = $parentNode;
$propertyDeclaration->modifiers = $modifiers;
$propertyDeclaration->questionToken = $questionToken; //
$propertyDeclaration->typeDeclaration = $typeDeclaration;
if ($typeDeclaration instanceof Node) {
$typeDeclaration->parent = $propertyDeclaration;
}
$propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration);
$propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
@ -3029,6 +3060,12 @@ class Parser {
case TokenKind::FunctionKeyword:
return $this->parseMethodDeclaration($parentNode, $modifiers);
case TokenKind::QuestionToken:
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration(
$parentNode,
$modifiers,
$this->eat1(TokenKind::QuestionToken)
);
case TokenKind::VariableName:
return $this->parsePropertyDeclaration($parentNode, $modifiers);
@ -3036,14 +3073,29 @@ class Parser {
return $this->parseTraitUseClause($parentNode);
default:
$missingTraitMemberDeclaration = new MissingMemberDeclaration();
$missingTraitMemberDeclaration->parent = $parentNode;
$missingTraitMemberDeclaration->modifiers = $modifiers;
return $missingTraitMemberDeclaration;
return $this->parseRemainingPropertyDeclarationOrMissingMemberDeclaration($parentNode, $modifiers);
}
};
}
/**
* @param Node $parentNode
* @param Token[] $modifiers
* @param Token $questionToken
* @param QualifiedName|Token|null $typeDeclaration
*/
private function makeMissingMemberDeclaration($parentNode, $modifiers, $questionToken = null, $typeDeclaration = null) {
$missingTraitMemberDeclaration = new MissingMemberDeclaration();
$missingTraitMemberDeclaration->parent = $parentNode;
$missingTraitMemberDeclaration->modifiers = $modifiers;
$missingTraitMemberDeclaration->questionToken = $questionToken;
$missingTraitMemberDeclaration->typeDeclaration = $typeDeclaration;
if ($typeDeclaration instanceof Node) {
$typeDeclaration->parent = $missingTraitMemberDeclaration;
}
return $missingTraitMemberDeclaration;
}
private function parseTraitUseClause($parentNode) {
$traitUseClause = new TraitUseClause();
$traitUseClause->parent = $parentNode;

Просмотреть файл

@ -185,6 +185,7 @@ class TokenKind {
const RealReservedWord = 335;
const ReturnType = 336;
const InlineHtml = 337;
const PropertyType = 338;
// const DollarOpenCurly = 339;
const EncapsedAndWhitespace = 400;

Просмотреть файл

@ -96,6 +96,10 @@ class ParserInvariantsTest extends LexerInvariantsTest {
public function testParentOfNodeHasSameChildNode($filename, Node $sourceFileNode) {
foreach ($sourceFileNode->getDescendantNodesAndTokens() as $child) {
if ($child instanceof Node) {
if (!$child->parent) {
$this->fail("Missing parent for " . var_export($child, true));
}
$this->assertContains(
$child, $child->parent->getChildNodesAndTokens(),
"Invariant: Parent of Node contains same child node."

Просмотреть файл

@ -39,6 +39,8 @@
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": null,
"propertyElements": {
"ExpressionList": {
"children": [

Просмотреть файл

@ -39,6 +39,8 @@
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": null,
"propertyElements": {
"ExpressionList": {
"children": [

Просмотреть файл

@ -39,6 +39,8 @@
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": null,
"propertyElements": {
"ExpressionList": {
"children": [

Просмотреть файл

@ -43,6 +43,8 @@
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": null,
"propertyElements": {
"ExpressionList": {
"children": [

Просмотреть файл

@ -43,6 +43,8 @@
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": null,
"propertyElements": {
"ExpressionList": {
"children": [

Просмотреть файл

@ -0,0 +1,7 @@
<?php
class HasTypedProperties {
public static ?int $intProp = null;
private stdClass $s;
protected static \foo\bar $s;
public namespace\stdClass $s;
}

Просмотреть файл

@ -0,0 +1 @@
[]

Просмотреть файл

@ -0,0 +1,259 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ClassDeclaration": {
"abstractOrFinalModifier": null,
"classKeyword": {
"kind": "ClassKeyword",
"textLength": 5
},
"name": {
"kind": "Name",
"textLength": 18
},
"classBaseClause": null,
"classInterfaceClause": null,
"classMembers": {
"ClassMembersNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"classMemberDeclarations": [
{
"PropertyDeclaration": {
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
},
{
"kind": "StaticKeyword",
"textLength": 6
}
],
"questionToken": {
"kind": "QuestionToken",
"textLength": 1
},
"typeDeclaration": {
"kind": "IntReservedWord",
"textLength": 3
},
"propertyElements": {
"ExpressionList": {
"children": [
{
"AssignmentExpression": {
"leftOperand": {
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 8
}
}
},
"operator": {
"kind": "EqualsToken",
"textLength": 1
},
"byRef": null,
"rightOperand": {
"ReservedWord": {
"children": {
"kind": "NullReservedWord",
"textLength": 4
}
}
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"PropertyDeclaration": {
"modifiers": [
{
"kind": "PrivateKeyword",
"textLength": 7
}
],
"questionToken": null,
"typeDeclaration": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 8
}
]
}
},
"propertyElements": {
"ExpressionList": {
"children": [
{
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 2
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"PropertyDeclaration": {
"modifiers": [
{
"kind": "ProtectedKeyword",
"textLength": 9
},
{
"kind": "StaticKeyword",
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": {
"QualifiedName": {
"globalSpecifier": {
"kind": "BackslashToken",
"textLength": 1
},
"relativeSpecifier": null,
"nameParts": [
{
"kind": "Name",
"textLength": 3
},
{
"kind": "BackslashToken",
"textLength": 1
},
{
"kind": "Name",
"textLength": 3
}
]
}
},
"propertyElements": {
"ExpressionList": {
"children": [
{
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 2
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
},
{
"PropertyDeclaration": {
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": {
"QualifiedName": {
"globalSpecifier": null,
"relativeSpecifier": {
"RelativeSpecifier": {
"namespaceKeyword": {
"kind": "NamespaceKeyword",
"textLength": 9
},
"backslash": {
"kind": "BackslashToken",
"textLength": 1
}
}
},
"nameParts": [
{
"kind": "Name",
"textLength": 8
}
]
}
},
"propertyElements": {
"ExpressionList": {
"children": [
{
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 2
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<?php
class Foo {
public int function x() {}
}

Просмотреть файл

@ -0,0 +1 @@
[]

Просмотреть файл

@ -0,0 +1,103 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ClassDeclaration": {
"abstractOrFinalModifier": null,
"classKeyword": {
"kind": "ClassKeyword",
"textLength": 5
},
"name": {
"kind": "Name",
"textLength": 3
},
"classBaseClause": null,
"classInterfaceClause": null,
"classMembers": {
"ClassMembersNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"classMemberDeclarations": [
{
"MissingMemberDeclaration": {
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": {
"kind": "IntReservedWord",
"textLength": 3
}
}
},
{
"MethodDeclaration": {
"modifiers": [],
"functionKeyword": {
"kind": "FunctionKeyword",
"textLength": 8
},
"byRefToken": null,
"name": {
"kind": "Name",
"textLength": 1
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"parameters": null,
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colonToken": null,
"questionToken": null,
"returnType": null,
"compoundStatementOrSemicolon": {
"CompoundStatementNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"statements": [],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<?php
class X {
public static ?function x() {}
}

Просмотреть файл

@ -0,0 +1,8 @@
[
{
"kind": 0,
"message": "'PropertyType' expected.",
"start": 35,
"length": 0
}
]

Просмотреть файл

@ -0,0 +1,111 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ClassDeclaration": {
"abstractOrFinalModifier": null,
"classKeyword": {
"kind": "ClassKeyword",
"textLength": 5
},
"name": {
"kind": "Name",
"textLength": 1
},
"classBaseClause": null,
"classInterfaceClause": null,
"classMembers": {
"ClassMembersNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"classMemberDeclarations": [
{
"MissingMemberDeclaration": {
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
},
{
"kind": "StaticKeyword",
"textLength": 6
}
],
"questionToken": {
"kind": "QuestionToken",
"textLength": 1
},
"typeDeclaration": {
"error": "MissingToken",
"kind": "PropertyType",
"textLength": 0
}
}
},
{
"MethodDeclaration": {
"modifiers": [],
"functionKeyword": {
"kind": "FunctionKeyword",
"textLength": 8
},
"byRefToken": null,
"name": {
"kind": "Name",
"textLength": 1
},
"openParen": {
"kind": "OpenParenToken",
"textLength": 1
},
"parameters": null,
"closeParen": {
"kind": "CloseParenToken",
"textLength": 1
},
"colonToken": null,
"questionToken": null,
"returnType": null,
"compoundStatementOrSemicolon": {
"CompoundStatementNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"statements": [],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<?php
class Foo {
public ?$x=2;
}

Просмотреть файл

@ -0,0 +1,8 @@
[
{
"kind": 0,
"message": "'PropertyType' expected.",
"start": 30,
"length": 0
}
]

Просмотреть файл

@ -0,0 +1,104 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ClassDeclaration": {
"abstractOrFinalModifier": null,
"classKeyword": {
"kind": "ClassKeyword",
"textLength": 5
},
"name": {
"kind": "Name",
"textLength": 3
},
"classBaseClause": null,
"classInterfaceClause": null,
"classMembers": {
"ClassMembersNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"classMemberDeclarations": [
{
"PropertyDeclaration": {
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
}
],
"questionToken": {
"kind": "QuestionToken",
"textLength": 1
},
"typeDeclaration": {
"error": "MissingToken",
"kind": "PropertyType",
"textLength": 0
},
"propertyElements": {
"ExpressionList": {
"children": [
{
"AssignmentExpression": {
"leftOperand": {
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 2
}
}
},
"operator": {
"kind": "EqualsToken",
"textLength": 1
},
"byRef": null,
"rightOperand": {
"NumericLiteral": {
"children": {
"kind": "IntegerLiteralToken",
"textLength": 1
}
}
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<?php
class X {
public int $a = 2;
}

Просмотреть файл

@ -0,0 +1 @@
[]

Просмотреть файл

@ -0,0 +1,100 @@
{
"SourceFileNode": {
"statementList": [
{
"InlineHtml": {
"scriptSectionEndTag": null,
"text": null,
"scriptSectionStartTag": {
"kind": "ScriptSectionStartTag",
"textLength": 6
}
}
},
{
"ClassDeclaration": {
"abstractOrFinalModifier": null,
"classKeyword": {
"kind": "ClassKeyword",
"textLength": 5
},
"name": {
"kind": "Name",
"textLength": 1
},
"classBaseClause": null,
"classInterfaceClause": null,
"classMembers": {
"ClassMembersNode": {
"openBrace": {
"kind": "OpenBraceToken",
"textLength": 1
},
"classMemberDeclarations": [
{
"PropertyDeclaration": {
"modifiers": [
{
"kind": "PublicKeyword",
"textLength": 6
}
],
"questionToken": null,
"typeDeclaration": {
"kind": "IntReservedWord",
"textLength": 3
},
"propertyElements": {
"ExpressionList": {
"children": [
{
"AssignmentExpression": {
"leftOperand": {
"Variable": {
"dollar": null,
"name": {
"kind": "VariableName",
"textLength": 2
}
}
},
"operator": {
"kind": "EqualsToken",
"textLength": 1
},
"byRef": null,
"rightOperand": {
"NumericLiteral": {
"children": {
"kind": "IntegerLiteralToken",
"textLength": 1
}
}
}
}
}
]
}
},
"semicolon": {
"kind": "SemicolonToken",
"textLength": 1
}
}
}
],
"closeBrace": {
"kind": "CloseBraceToken",
"textLength": 1
}
}
}
}
}
],
"endOfFileToken": {
"kind": "EndOfFileToken",
"textLength": 0
}
}
}