Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1431598
PhpDocParser.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
37 KB
Referenced Files
None
Subscribers
None
PhpDocParser.php
View Options
<?php
declare
(
strict_types
=
1
);
namespace
PHPStan\PhpDocParser\Parser
;
use
LogicException
;
use
PHPStan\PhpDocParser\Ast
;
use
PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode
;
use
PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode
;
use
PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode
;
use
PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine
;
use
PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode
;
use
PHPStan\PhpDocParser\Lexer\Lexer
;
use
PHPStan\PhpDocParser\ParserConfig
;
use
PHPStan\ShouldNotHappenException
;
use
function
array_key_exists
;
use
function
count
;
use
function
rtrim
;
use
function
str_replace
;
use
function
trim
;
/**
* @phpstan-import-type ValueType from Doctrine\DoctrineArgument as DoctrineValueType
*/
class
PhpDocParser
{
private
const
DISALLOWED_DESCRIPTION_START_TOKENS
=
[
Lexer
::
TOKEN_UNION
,
Lexer
::
TOKEN_INTERSECTION
,
];
private
ParserConfig
$config
;
private
TypeParser
$typeParser
;
private
ConstExprParser
$constantExprParser
;
private
ConstExprParser
$doctrineConstantExprParser
;
public
function
__construct
(
ParserConfig
$config
,
TypeParser
$typeParser
,
ConstExprParser
$constantExprParser
)
{
$this
->
config
=
$config
;
$this
->
typeParser
=
$typeParser
;
$this
->
constantExprParser
=
$constantExprParser
;
$this
->
doctrineConstantExprParser
=
$constantExprParser
->
toDoctrine
();
}
public
function
parse
(
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocNode
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_OPEN_PHPDOC
);
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
);
$children
=
[];
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
$lastChild
=
$this
->
parseChild
(
$tokens
);
$children
[]
=
$lastChild
;
while
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
if
(
$lastChild
instanceof
Ast\PhpDoc\PhpDocTagNode
&&
(
$lastChild
->
value
instanceof
Doctrine\DoctrineTagValueNode
||
$lastChild
->
value
instanceof
Ast\PhpDoc\GenericTagValueNode
)
)
{
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
);
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
break
;
}
$lastChild
=
$this
->
parseChild
(
$tokens
);
$children
[]
=
$lastChild
;
continue
;
}
if
(!
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
))
{
break
;
}
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
break
;
}
$lastChild
=
$this
->
parseChild
(
$tokens
);
$children
[]
=
$lastChild
;
}
}
try
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_CLOSE_PHPDOC
);
}
catch
(
ParserException
$e
)
{
$name
=
''
;
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
if
(
count
(
$children
)
>
0
)
{
$lastChild
=
$children
[
count
(
$children
)
-
1
];
if
(
$lastChild
instanceof
Ast\PhpDoc\PhpDocTagNode
)
{
$name
=
$lastChild
->
name
;
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
}
}
$tag
=
new
Ast\PhpDoc\PhpDocTagNode
(
$name
,
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\PhpDoc\InvalidTagValueNode
(
$e
->
getMessage
(),
$e
),
$startLine
,
$startIndex
,
),
);
$tokens
->
forwardToTheEnd
();
$comments
=
$tokens
->
flushComments
();
if
(
$comments
!==
[])
{
throw
new
LogicException
(
'Comments should already be flushed'
);
}
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\PhpDoc\PhpDocNode
([
$this
->
enrichWithAttributes
(
$tokens
,
$tag
,
$startLine
,
$startIndex
)]),
1
,
0
);
}
$comments
=
$tokens
->
flushComments
();
if
(
$comments
!==
[])
{
throw
new
LogicException
(
'Comments should already be flushed'
);
}
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\PhpDoc\PhpDocNode
(
$children
),
1
,
0
);
}
/** @phpstan-impure */
private
function
parseChild
(
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocChildNode
{
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_TAG
))
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
return
$this
->
enrichWithAttributes
(
$tokens
,
$this
->
parseTag
(
$tokens
),
$startLine
,
$startIndex
);
}
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_DOCTRINE_TAG
))
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
$tag
=
$tokens
->
currentTokenValue
();
$tokens
->
next
();
$tagStartLine
=
$tokens
->
currentTokenLine
();
$tagStartIndex
=
$tokens
->
currentTokenIndex
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\PhpDoc\PhpDocTagNode
(
$tag
,
$this
->
enrichWithAttributes
(
$tokens
,
$this
->
parseDoctrineTagValue
(
$tokens
,
$tag
),
$tagStartLine
,
$tagStartIndex
,
),
),
$startLine
,
$startIndex
);
}
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
$text
=
$this
->
parseText
(
$tokens
);
return
$this
->
enrichWithAttributes
(
$tokens
,
$text
,
$startLine
,
$startIndex
);
}
/**
* @template T of Ast\Node
* @param T $tag
* @return T
*/
private
function
enrichWithAttributes
(
TokenIterator
$tokens
,
Ast\Node
$tag
,
int
$startLine
,
int
$startIndex
):
Ast\Node
{
if
(
$this
->
config
->
useLinesAttributes
)
{
$tag
->
setAttribute
(
Ast\Attribute
::
START_LINE
,
$startLine
);
$tag
->
setAttribute
(
Ast\Attribute
::
END_LINE
,
$tokens
->
currentTokenLine
());
}
if
(
$this
->
config
->
useIndexAttributes
)
{
$tag
->
setAttribute
(
Ast\Attribute
::
START_INDEX
,
$startIndex
);
$tag
->
setAttribute
(
Ast\Attribute
::
END_INDEX
,
$tokens
->
endIndexOfLastRelevantToken
());
}
return
$tag
;
}
private
function
parseText
(
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocTextNode
{
$text
=
''
;
$endTokens
=
[
Lexer
::
TOKEN_CLOSE_PHPDOC
,
Lexer
::
TOKEN_END
];
$savepoint
=
false
;
// if the next token is EOL, everything below is skipped and empty string is returned
while
(
true
)
{
$tmpText
=
$tokens
->
getSkippedHorizontalWhiteSpaceIfAny
()
.
$tokens
->
joinUntil
(
Lexer
::
TOKEN_PHPDOC_EOL
,
...
$endTokens
);
$text
.=
$tmpText
;
// stop if we're not at EOL - meaning it's the end of PHPDoc
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
,
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
break
;
}
if
(!
$savepoint
)
{
$tokens
->
pushSavePoint
();
$savepoint
=
true
;
}
elseif
(
$tmpText
!==
''
)
{
$tokens
->
dropSavePoint
();
$tokens
->
pushSavePoint
();
}
$tokens
->
pushSavePoint
();
$tokens
->
next
();
// if we're at EOL, check what's next
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_TAG
,
Lexer
::
TOKEN_DOCTRINE_TAG
,
...
$endTokens
))
{
$tokens
->
rollback
();
break
;
}
// otherwise if the next is text, continue building the description string
$tokens
->
dropSavePoint
();
$text
.=
$tokens
->
getDetectedNewline
()
??
"
\n
"
;
}
if
(
$savepoint
)
{
$tokens
->
rollback
();
$text
=
rtrim
(
$text
,
$tokens
->
getDetectedNewline
()
??
"
\n
"
);
}
return
new
Ast\PhpDoc\PhpDocTextNode
(
trim
(
$text
,
"
\t
"
));
}
private
function
parseOptionalDescriptionAfterDoctrineTag
(
TokenIterator
$tokens
):
string
{
$text
=
''
;
$endTokens
=
[
Lexer
::
TOKEN_CLOSE_PHPDOC
,
Lexer
::
TOKEN_END
];
$savepoint
=
false
;
// if the next token is EOL, everything below is skipped and empty string is returned
while
(
true
)
{
$tmpText
=
$tokens
->
getSkippedHorizontalWhiteSpaceIfAny
()
.
$tokens
->
joinUntil
(
Lexer
::
TOKEN_PHPDOC_TAG
,
Lexer
::
TOKEN_DOCTRINE_TAG
,
Lexer
::
TOKEN_PHPDOC_EOL
,
...
$endTokens
);
$text
.=
$tmpText
;
// stop if we're not at EOL - meaning it's the end of PHPDoc
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
,
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
if
(!
$tokens
->
isPrecededByHorizontalWhitespace
())
{
return
trim
(
$text
.
$this
->
parseText
(
$tokens
)->
text
,
"
\t
"
);
}
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_TAG
))
{
$tokens
->
pushSavePoint
();
$child
=
$this
->
parseChild
(
$tokens
);
if
(
$child
instanceof
Ast\PhpDoc\PhpDocTagNode
)
{
if
(
$child
->
value
instanceof
Ast\PhpDoc\GenericTagValueNode
||
$child
->
value
instanceof
Doctrine\DoctrineTagValueNode
)
{
$tokens
->
rollback
();
break
;
}
if
(
$child
->
value
instanceof
Ast\PhpDoc\InvalidTagValueNode
)
{
$tokens
->
rollback
();
$tokens
->
pushSavePoint
();
$tokens
->
next
();
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_OPEN_PARENTHESES
))
{
$tokens
->
rollback
();
break
;
}
$tokens
->
rollback
();
return
trim
(
$text
.
$this
->
parseText
(
$tokens
)->
text
,
"
\t
"
);
}
}
$tokens
->
rollback
();
return
trim
(
$text
.
$this
->
parseText
(
$tokens
)->
text
,
"
\t
"
);
}
break
;
}
if
(!
$savepoint
)
{
$tokens
->
pushSavePoint
();
$savepoint
=
true
;
}
elseif
(
$tmpText
!==
''
)
{
$tokens
->
dropSavePoint
();
$tokens
->
pushSavePoint
();
}
$tokens
->
pushSavePoint
();
$tokens
->
next
();
// if we're at EOL, check what's next
// if next is a PHPDoc tag, EOL, or end of PHPDoc, stop
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_TAG
,
Lexer
::
TOKEN_DOCTRINE_TAG
,
...
$endTokens
))
{
$tokens
->
rollback
();
break
;
}
// otherwise if the next is text, continue building the description string
$tokens
->
dropSavePoint
();
$text
.=
$tokens
->
getDetectedNewline
()
??
"
\n
"
;
}
if
(
$savepoint
)
{
$tokens
->
rollback
();
$text
=
rtrim
(
$text
,
$tokens
->
getDetectedNewline
()
??
"
\n
"
);
}
return
trim
(
$text
,
"
\t
"
);
}
public
function
parseTag
(
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocTagNode
{
$tag
=
$tokens
->
currentTokenValue
();
$tokens
->
next
();
$value
=
$this
->
parseTagValue
(
$tokens
,
$tag
);
return
new
Ast\PhpDoc\PhpDocTagNode
(
$tag
,
$value
);
}
public
function
parseTagValue
(
TokenIterator
$tokens
,
string
$tag
):
Ast\PhpDoc\PhpDocTagValueNode
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
try
{
$tokens
->
pushSavePoint
();
switch
(
$tag
)
{
case
'@param'
:
case
'@phpstan-param'
:
case
'@psalm-param'
:
case
'@phan-param'
:
$tagValue
=
$this
->
parseParamTagValue
(
$tokens
);
break
;
case
'@param-immediately-invoked-callable'
:
case
'@phpstan-param-immediately-invoked-callable'
:
$tagValue
=
$this
->
parseParamImmediatelyInvokedCallableTagValue
(
$tokens
);
break
;
case
'@param-later-invoked-callable'
:
case
'@phpstan-param-later-invoked-callable'
:
$tagValue
=
$this
->
parseParamLaterInvokedCallableTagValue
(
$tokens
);
break
;
case
'@param-closure-this'
:
case
'@phpstan-param-closure-this'
:
$tagValue
=
$this
->
parseParamClosureThisTagValue
(
$tokens
);
break
;
case
'@pure-unless-callable-is-impure'
:
case
'@phpstan-pure-unless-callable-is-impure'
:
$tagValue
=
$this
->
parsePureUnlessCallableIsImpureTagValue
(
$tokens
);
break
;
case
'@var'
:
case
'@phpstan-var'
:
case
'@psalm-var'
:
case
'@phan-var'
:
$tagValue
=
$this
->
parseVarTagValue
(
$tokens
);
break
;
case
'@return'
:
case
'@phpstan-return'
:
case
'@psalm-return'
:
case
'@phan-return'
:
case
'@phan-real-return'
:
$tagValue
=
$this
->
parseReturnTagValue
(
$tokens
);
break
;
case
'@throws'
:
case
'@phpstan-throws'
:
$tagValue
=
$this
->
parseThrowsTagValue
(
$tokens
);
break
;
case
'@mixin'
:
case
'@phan-mixin'
:
$tagValue
=
$this
->
parseMixinTagValue
(
$tokens
);
break
;
case
'@psalm-require-extends'
:
case
'@phpstan-require-extends'
:
$tagValue
=
$this
->
parseRequireExtendsTagValue
(
$tokens
);
break
;
case
'@psalm-require-implements'
:
case
'@phpstan-require-implements'
:
$tagValue
=
$this
->
parseRequireImplementsTagValue
(
$tokens
);
break
;
case
'@deprecated'
:
$tagValue
=
$this
->
parseDeprecatedTagValue
(
$tokens
);
break
;
case
'@property'
:
case
'@property-read'
:
case
'@property-write'
:
case
'@phpstan-property'
:
case
'@phpstan-property-read'
:
case
'@phpstan-property-write'
:
case
'@psalm-property'
:
case
'@psalm-property-read'
:
case
'@psalm-property-write'
:
case
'@phan-property'
:
case
'@phan-property-read'
:
case
'@phan-property-write'
:
$tagValue
=
$this
->
parsePropertyTagValue
(
$tokens
);
break
;
case
'@method'
:
case
'@phpstan-method'
:
case
'@psalm-method'
:
case
'@phan-method'
:
$tagValue
=
$this
->
parseMethodTagValue
(
$tokens
);
break
;
case
'@template'
:
case
'@phpstan-template'
:
case
'@psalm-template'
:
case
'@phan-template'
:
case
'@template-covariant'
:
case
'@phpstan-template-covariant'
:
case
'@psalm-template-covariant'
:
case
'@template-contravariant'
:
case
'@phpstan-template-contravariant'
:
case
'@psalm-template-contravariant'
:
$tagValue
=
$this
->
typeParser
->
parseTemplateTagValue
(
$tokens
,
fn
(
$tokens
)
=>
$this
->
parseOptionalDescription
(
$tokens
,
true
),
);
break
;
case
'@extends'
:
case
'@phpstan-extends'
:
case
'@phan-extends'
:
case
'@phan-inherits'
:
case
'@template-extends'
:
$tagValue
=
$this
->
parseExtendsTagValue
(
'@extends'
,
$tokens
);
break
;
case
'@implements'
:
case
'@phpstan-implements'
:
case
'@template-implements'
:
$tagValue
=
$this
->
parseExtendsTagValue
(
'@implements'
,
$tokens
);
break
;
case
'@use'
:
case
'@phpstan-use'
:
case
'@template-use'
:
$tagValue
=
$this
->
parseExtendsTagValue
(
'@use'
,
$tokens
);
break
;
case
'@phpstan-type'
:
case
'@psalm-type'
:
case
'@phan-type'
:
$tagValue
=
$this
->
parseTypeAliasTagValue
(
$tokens
);
break
;
case
'@phpstan-import-type'
:
case
'@psalm-import-type'
:
$tagValue
=
$this
->
parseTypeAliasImportTagValue
(
$tokens
);
break
;
case
'@phpstan-assert'
:
case
'@phpstan-assert-if-true'
:
case
'@phpstan-assert-if-false'
:
case
'@psalm-assert'
:
case
'@psalm-assert-if-true'
:
case
'@psalm-assert-if-false'
:
case
'@phan-assert'
:
case
'@phan-assert-if-true'
:
case
'@phan-assert-if-false'
:
$tagValue
=
$this
->
parseAssertTagValue
(
$tokens
);
break
;
case
'@phpstan-this-out'
:
case
'@phpstan-self-out'
:
case
'@psalm-this-out'
:
case
'@psalm-self-out'
:
$tagValue
=
$this
->
parseSelfOutTagValue
(
$tokens
);
break
;
case
'@param-out'
:
case
'@phpstan-param-out'
:
case
'@psalm-param-out'
:
$tagValue
=
$this
->
parseParamOutTagValue
(
$tokens
);
break
;
default
:
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_OPEN_PARENTHESES
))
{
$tagValue
=
$this
->
parseDoctrineTagValue
(
$tokens
,
$tag
);
}
else
{
$tagValue
=
new
Ast\PhpDoc\GenericTagValueNode
(
$this
->
parseOptionalDescriptionAfterDoctrineTag
(
$tokens
));
}
break
;
}
$tokens
->
dropSavePoint
();
}
catch
(
ParserException
$e
)
{
$tokens
->
rollback
();
$tagValue
=
new
Ast\PhpDoc\InvalidTagValueNode
(
$this
->
parseOptionalDescription
(
$tokens
,
false
),
$e
);
}
return
$this
->
enrichWithAttributes
(
$tokens
,
$tagValue
,
$startLine
,
$startIndex
);
}
private
function
parseDoctrineTagValue
(
TokenIterator
$tokens
,
string
$tag
):
Ast\PhpDoc\PhpDocTagValueNode
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
return
new
Doctrine\DoctrineTagValueNode
(
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineAnnotation
(
$tag
,
$this
->
parseDoctrineArguments
(
$tokens
,
false
)),
$startLine
,
$startIndex
,
),
$this
->
parseOptionalDescriptionAfterDoctrineTag
(
$tokens
),
);
}
/**
* @return list<Doctrine\DoctrineArgument>
*/
private
function
parseDoctrineArguments
(
TokenIterator
$tokens
,
bool
$deep
):
array
{
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_OPEN_PARENTHESES
))
{
return
[];
}
if
(!
$deep
)
{
$tokens
->
addEndOfLineToSkippedTokens
();
}
$arguments
=
[];
try
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_OPEN_PARENTHESES
);
do
{
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PARENTHESES
))
{
break
;
}
$arguments
[]
=
$this
->
parseDoctrineArgument
(
$tokens
);
}
while
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_COMMA
));
}
finally
{
if
(!
$deep
)
{
$tokens
->
removeEndOfLineFromSkippedTokens
();
}
}
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_CLOSE_PARENTHESES
);
return
$arguments
;
}
private
function
parseDoctrineArgument
(
TokenIterator
$tokens
):
Doctrine\DoctrineArgument
{
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_IDENTIFIER
))
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineArgument
(
null
,
$this
->
parseDoctrineArgumentValue
(
$tokens
)),
$startLine
,
$startIndex
,
);
}
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
try
{
$tokens
->
pushSavePoint
();
$currentValue
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
$key
=
$this
->
enrichWithAttributes
(
$tokens
,
new
IdentifierTypeNode
(
$currentValue
),
$startLine
,
$startIndex
,
);
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_EQUAL
);
$value
=
$this
->
parseDoctrineArgumentValue
(
$tokens
);
$tokens
->
dropSavePoint
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineArgument
(
$key
,
$value
),
$startLine
,
$startIndex
,
);
}
catch
(
ParserException
$e
)
{
$tokens
->
rollback
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineArgument
(
null
,
$this
->
parseDoctrineArgumentValue
(
$tokens
)),
$startLine
,
$startIndex
,
);
}
}
/**
* @return DoctrineValueType
*/
private
function
parseDoctrineArgumentValue
(
TokenIterator
$tokens
)
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_TAG
,
Lexer
::
TOKEN_DOCTRINE_TAG
))
{
$name
=
$tokens
->
currentTokenValue
();
$tokens
->
next
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineAnnotation
(
$name
,
$this
->
parseDoctrineArguments
(
$tokens
,
true
)),
$startLine
,
$startIndex
,
);
}
if
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_OPEN_CURLY_BRACKET
))
{
$items
=
[];
do
{
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_CURLY_BRACKET
))
{
break
;
}
$items
[]
=
$this
->
parseDoctrineArrayItem
(
$tokens
);
}
while
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_COMMA
));
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_CLOSE_CURLY_BRACKET
);
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineArray
(
$items
),
$startLine
,
$startIndex
,
);
}
$currentTokenValue
=
$tokens
->
currentTokenValue
();
$tokens
->
pushSavePoint
();
// because of ConstFetchNode
if
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
))
{
$identifier
=
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\Type\IdentifierTypeNode
(
$currentTokenValue
),
$startLine
,
$startIndex
,
);
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_DOUBLE_COLON
))
{
$tokens
->
dropSavePoint
();
return
$identifier
;
}
$tokens
->
rollback
();
// because of ConstFetchNode
}
else
{
$tokens
->
dropSavePoint
();
// because of ConstFetchNode
}
$currentTokenValue
=
$tokens
->
currentTokenValue
();
$currentTokenType
=
$tokens
->
currentTokenType
();
$currentTokenOffset
=
$tokens
->
currentTokenOffset
();
$currentTokenLine
=
$tokens
->
currentTokenLine
();
try
{
$constExpr
=
$this
->
doctrineConstantExprParser
->
parse
(
$tokens
);
if
(
$constExpr
instanceof
Ast\ConstExpr\ConstExprArrayNode
)
{
throw
new
ParserException
(
$currentTokenValue
,
$currentTokenType
,
$currentTokenOffset
,
Lexer
::
TOKEN_IDENTIFIER
,
null
,
$currentTokenLine
,
);
}
return
$constExpr
;
}
catch
(
LogicException
$e
)
{
throw
new
ParserException
(
$currentTokenValue
,
$currentTokenType
,
$currentTokenOffset
,
Lexer
::
TOKEN_IDENTIFIER
,
null
,
$currentTokenLine
,
);
}
}
private
function
parseDoctrineArrayItem
(
TokenIterator
$tokens
):
Doctrine\DoctrineArrayItem
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
try
{
$tokens
->
pushSavePoint
();
$key
=
$this
->
parseDoctrineArrayKey
(
$tokens
);
if
(!
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_EQUAL
))
{
if
(!
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_COLON
))
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_EQUAL
);
// will throw exception
}
}
$value
=
$this
->
parseDoctrineArgumentValue
(
$tokens
);
$tokens
->
dropSavePoint
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineArrayItem
(
$key
,
$value
),
$startLine
,
$startIndex
,
);
}
catch
(
ParserException
$e
)
{
$tokens
->
rollback
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Doctrine\DoctrineArrayItem
(
null
,
$this
->
parseDoctrineArgumentValue
(
$tokens
)),
$startLine
,
$startIndex
,
);
}
}
/**
* @return ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|ConstFetchNode
*/
private
function
parseDoctrineArrayKey
(
TokenIterator
$tokens
)
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_INTEGER
))
{
$key
=
new
Ast\ConstExpr\ConstExprIntegerNode
(
str_replace
(
'_'
,
''
,
$tokens
->
currentTokenValue
()));
$tokens
->
next
();
}
elseif
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_DOCTRINE_ANNOTATION_STRING
))
{
$key
=
$this
->
doctrineConstantExprParser
->
parseDoctrineString
(
$tokens
->
currentTokenValue
(),
$tokens
);
$tokens
->
next
();
}
elseif
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_SINGLE_QUOTED_STRING
))
{
$key
=
new
Ast\ConstExpr\ConstExprStringNode
(
StringUnescaper
::
unescapeString
(
$tokens
->
currentTokenValue
()),
Ast\ConstExpr\ConstExprStringNode
::
SINGLE_QUOTED
);
$tokens
->
next
();
}
elseif
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_DOUBLE_QUOTED_STRING
))
{
$value
=
$tokens
->
currentTokenValue
();
$tokens
->
next
();
$key
=
$this
->
doctrineConstantExprParser
->
parseDoctrineString
(
$value
,
$tokens
);
}
else
{
$currentTokenValue
=
$tokens
->
currentTokenValue
();
$tokens
->
pushSavePoint
();
// because of ConstFetchNode
if
(!
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
))
{
$tokens
->
dropSavePoint
();
throw
new
ParserException
(
$tokens
->
currentTokenValue
(),
$tokens
->
currentTokenType
(),
$tokens
->
currentTokenOffset
(),
Lexer
::
TOKEN_IDENTIFIER
,
null
,
$tokens
->
currentTokenLine
(),
);
}
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_DOUBLE_COLON
))
{
$tokens
->
dropSavePoint
();
return
$this
->
enrichWithAttributes
(
$tokens
,
new
IdentifierTypeNode
(
$currentTokenValue
),
$startLine
,
$startIndex
,
);
}
$tokens
->
rollback
();
$constExpr
=
$this
->
doctrineConstantExprParser
->
parse
(
$tokens
);
if
(!
$constExpr
instanceof
Ast\ConstExpr\ConstFetchNode
)
{
throw
new
ParserException
(
$tokens
->
currentTokenValue
(),
$tokens
->
currentTokenType
(),
$tokens
->
currentTokenOffset
(),
Lexer
::
TOKEN_IDENTIFIER
,
null
,
$tokens
->
currentTokenLine
(),
);
}
return
$constExpr
;
}
return
$this
->
enrichWithAttributes
(
$tokens
,
$key
,
$startLine
,
$startIndex
);
}
/**
* @return Ast\PhpDoc\ParamTagValueNode|Ast\PhpDoc\TypelessParamTagValueNode
*/
private
function
parseParamTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocTagValueNode
{
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_REFERENCE
,
Lexer
::
TOKEN_VARIADIC
,
Lexer
::
TOKEN_VARIABLE
)
)
{
$type
=
null
;
}
else
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
}
$isReference
=
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_REFERENCE
);
$isVariadic
=
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_VARIADIC
);
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
if
(
$type
!==
null
)
{
return
new
Ast\PhpDoc\ParamTagValueNode
(
$type
,
$isVariadic
,
$parameterName
,
$description
,
$isReference
);
}
return
new
Ast\PhpDoc\TypelessParamTagValueNode
(
$isVariadic
,
$parameterName
,
$description
,
$isReference
);
}
private
function
parseParamImmediatelyInvokedCallableTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode
{
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode
(
$parameterName
,
$description
);
}
private
function
parseParamLaterInvokedCallableTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode
{
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode
(
$parameterName
,
$description
);
}
private
function
parseParamClosureThisTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\ParamClosureThisTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\ParamClosureThisTagValueNode
(
$type
,
$parameterName
,
$description
);
}
private
function
parsePureUnlessCallableIsImpureTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode
{
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode
(
$parameterName
,
$description
);
}
private
function
parseVarTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\VarTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$variableName
=
$this
->
parseOptionalVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
$variableName
===
''
);
return
new
Ast\PhpDoc\VarTagValueNode
(
$type
,
$variableName
,
$description
);
}
private
function
parseReturnTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\ReturnTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
return
new
Ast\PhpDoc\ReturnTagValueNode
(
$type
,
$description
);
}
private
function
parseThrowsTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\ThrowsTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
return
new
Ast\PhpDoc\ThrowsTagValueNode
(
$type
,
$description
);
}
private
function
parseMixinTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\MixinTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
return
new
Ast\PhpDoc\MixinTagValueNode
(
$type
,
$description
);
}
private
function
parseRequireExtendsTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\RequireExtendsTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
return
new
Ast\PhpDoc\RequireExtendsTagValueNode
(
$type
,
$description
);
}
private
function
parseRequireImplementsTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\RequireImplementsTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
return
new
Ast\PhpDoc\RequireImplementsTagValueNode
(
$type
,
$description
);
}
private
function
parseDeprecatedTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\DeprecatedTagValueNode
{
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\DeprecatedTagValueNode
(
$description
);
}
private
function
parsePropertyTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\PropertyTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\PropertyTagValueNode
(
$type
,
$parameterName
,
$description
);
}
private
function
parseMethodTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\MethodTagValueNode
{
$staticKeywordOrReturnTypeOrMethodName
=
$this
->
typeParser
->
parse
(
$tokens
);
if
(
$staticKeywordOrReturnTypeOrMethodName
instanceof
Ast\Type\IdentifierTypeNode
&&
$staticKeywordOrReturnTypeOrMethodName
->
name
===
'static'
)
{
$isStatic
=
true
;
$returnTypeOrMethodName
=
$this
->
typeParser
->
parse
(
$tokens
);
}
else
{
$isStatic
=
false
;
$returnTypeOrMethodName
=
$staticKeywordOrReturnTypeOrMethodName
;
}
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_IDENTIFIER
))
{
$returnType
=
$returnTypeOrMethodName
;
$methodName
=
$tokens
->
currentTokenValue
();
$tokens
->
next
();
}
elseif
(
$returnTypeOrMethodName
instanceof
Ast\Type\IdentifierTypeNode
)
{
$returnType
=
$isStatic
?
$staticKeywordOrReturnTypeOrMethodName
:
null
;
$methodName
=
$returnTypeOrMethodName
->
name
;
$isStatic
=
false
;
}
else
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
// will throw exception
exit
;
}
$templateTypes
=
[];
if
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_OPEN_ANGLE_BRACKET
))
{
do
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
$templateTypes
[]
=
$this
->
enrichWithAttributes
(
$tokens
,
$this
->
typeParser
->
parseTemplateTagValue
(
$tokens
),
$startLine
,
$startIndex
,
);
}
while
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_COMMA
));
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_CLOSE_ANGLE_BRACKET
);
}
$parameters
=
[];
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_OPEN_PARENTHESES
);
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PARENTHESES
))
{
$parameters
[]
=
$this
->
parseMethodTagValueParameter
(
$tokens
);
while
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_COMMA
))
{
$parameters
[]
=
$this
->
parseMethodTagValueParameter
(
$tokens
);
}
}
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_CLOSE_PARENTHESES
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\MethodTagValueNode
(
$isStatic
,
$returnType
,
$methodName
,
$parameters
,
$description
,
$templateTypes
);
}
private
function
parseMethodTagValueParameter
(
TokenIterator
$tokens
):
Ast\PhpDoc\MethodTagValueParameterNode
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
switch
(
$tokens
->
currentTokenType
())
{
case
Lexer
::
TOKEN_IDENTIFIER
:
case
Lexer
::
TOKEN_OPEN_PARENTHESES
:
case
Lexer
::
TOKEN_NULLABLE
:
$parameterType
=
$this
->
typeParser
->
parse
(
$tokens
);
break
;
default
:
$parameterType
=
null
;
}
$isReference
=
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_REFERENCE
);
$isVariadic
=
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_VARIADIC
);
$parameterName
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_VARIABLE
);
if
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_EQUAL
))
{
$defaultValue
=
$this
->
constantExprParser
->
parse
(
$tokens
);
}
else
{
$defaultValue
=
null
;
}
return
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\PhpDoc\MethodTagValueParameterNode
(
$parameterType
,
$isReference
,
$isVariadic
,
$parameterName
,
$defaultValue
),
$startLine
,
$startIndex
,
);
}
private
function
parseExtendsTagValue
(
string
$tagName
,
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocTagValueNode
{
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
$baseType
=
new
IdentifierTypeNode
(
$tokens
->
currentTokenValue
());
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
$type
=
$this
->
typeParser
->
parseGeneric
(
$tokens
,
$this
->
typeParser
->
enrichWithAttributes
(
$tokens
,
$baseType
,
$startLine
,
$startIndex
),
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
switch
(
$tagName
)
{
case
'@extends'
:
return
new
Ast\PhpDoc\ExtendsTagValueNode
(
$type
,
$description
);
case
'@implements'
:
return
new
Ast\PhpDoc\ImplementsTagValueNode
(
$type
,
$description
);
case
'@use'
:
return
new
Ast\PhpDoc\UsesTagValueNode
(
$type
,
$description
);
}
throw
new
ShouldNotHappenException
();
}
private
function
parseTypeAliasTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\TypeAliasTagValueNode
{
$alias
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
// support phan-type/psalm-type syntax
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_EQUAL
);
$startLine
=
$tokens
->
currentTokenLine
();
$startIndex
=
$tokens
->
currentTokenIndex
();
try
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_CLOSE_PHPDOC
))
{
if
(!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
))
{
throw
new
ParserException
(
$tokens
->
currentTokenValue
(),
$tokens
->
currentTokenType
(),
$tokens
->
currentTokenOffset
(),
Lexer
::
TOKEN_PHPDOC_EOL
,
null
,
$tokens
->
currentTokenLine
(),
);
}
}
return
new
Ast\PhpDoc\TypeAliasTagValueNode
(
$alias
,
$type
);
}
catch
(
ParserException
$e
)
{
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\TypeAliasTagValueNode
(
$alias
,
$this
->
enrichWithAttributes
(
$tokens
,
new
Ast\Type\InvalidTypeNode
(
$e
),
$startLine
,
$startIndex
),
);
}
}
private
function
parseTypeAliasImportTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\TypeAliasImportTagValueNode
{
$importedAlias
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
$tokens
->
consumeTokenValue
(
Lexer
::
TOKEN_IDENTIFIER
,
'from'
);
$identifierStartLine
=
$tokens
->
currentTokenLine
();
$identifierStartIndex
=
$tokens
->
currentTokenIndex
();
$importedFrom
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
$importedFromType
=
$this
->
enrichWithAttributes
(
$tokens
,
new
IdentifierTypeNode
(
$importedFrom
),
$identifierStartLine
,
$identifierStartIndex
,
);
$importedAs
=
null
;
if
(
$tokens
->
tryConsumeTokenValue
(
'as'
))
{
$importedAs
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
}
return
new
Ast\PhpDoc\TypeAliasImportTagValueNode
(
$importedAlias
,
$importedFromType
,
$importedAs
);
}
/**
* @return Ast\PhpDoc\AssertTagValueNode|Ast\PhpDoc\AssertTagPropertyValueNode|Ast\PhpDoc\AssertTagMethodValueNode
*/
private
function
parseAssertTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\PhpDocTagValueNode
{
$isNegated
=
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_NEGATED
);
$isEquality
=
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_EQUAL
);
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$parameter
=
$this
->
parseAssertParameter
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
if
(
array_key_exists
(
'method'
,
$parameter
))
{
return
new
Ast\PhpDoc\AssertTagMethodValueNode
(
$type
,
$parameter
[
'parameter'
],
$parameter
[
'method'
],
$isNegated
,
$description
,
$isEquality
);
}
elseif
(
array_key_exists
(
'property'
,
$parameter
))
{
return
new
Ast\PhpDoc\AssertTagPropertyValueNode
(
$type
,
$parameter
[
'parameter'
],
$parameter
[
'property'
],
$isNegated
,
$description
,
$isEquality
);
}
return
new
Ast\PhpDoc\AssertTagValueNode
(
$type
,
$parameter
[
'parameter'
],
$isNegated
,
$description
,
$isEquality
);
}
/**
* @return array{parameter: string}|array{parameter: string, property: string}|array{parameter: string, method: string}
*/
private
function
parseAssertParameter
(
TokenIterator
$tokens
):
array
{
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_THIS_VARIABLE
))
{
$parameter
=
'$this'
;
$tokens
->
next
();
}
else
{
$parameter
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_VARIABLE
);
}
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_ARROW
))
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_ARROW
);
$propertyOrMethod
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_IDENTIFIER
);
if
(
$tokens
->
tryConsumeTokenType
(
Lexer
::
TOKEN_OPEN_PARENTHESES
))
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_CLOSE_PARENTHESES
);
return
[
'parameter'
=>
$parameter
,
'method'
=>
$propertyOrMethod
];
}
return
[
'parameter'
=>
$parameter
,
'property'
=>
$propertyOrMethod
];
}
return
[
'parameter'
=>
$parameter
];
}
private
function
parseSelfOutTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\SelfOutTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
true
);
return
new
Ast\PhpDoc\SelfOutTagValueNode
(
$type
,
$description
);
}
private
function
parseParamOutTagValue
(
TokenIterator
$tokens
):
Ast\PhpDoc\ParamOutTagValueNode
{
$type
=
$this
->
typeParser
->
parse
(
$tokens
);
$parameterName
=
$this
->
parseRequiredVariableName
(
$tokens
);
$description
=
$this
->
parseOptionalDescription
(
$tokens
,
false
);
return
new
Ast\PhpDoc\ParamOutTagValueNode
(
$type
,
$parameterName
,
$description
);
}
private
function
parseOptionalVariableName
(
TokenIterator
$tokens
):
string
{
if
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_VARIABLE
))
{
$parameterName
=
$tokens
->
currentTokenValue
();
$tokens
->
next
();
}
elseif
(
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_THIS_VARIABLE
))
{
$parameterName
=
'$this'
;
$tokens
->
next
();
}
else
{
$parameterName
=
''
;
}
return
$parameterName
;
}
private
function
parseRequiredVariableName
(
TokenIterator
$tokens
):
string
{
$parameterName
=
$tokens
->
currentTokenValue
();
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_VARIABLE
);
return
$parameterName
;
}
/**
* @param bool $limitStartToken true should be used when the description immediately follows a parsed type
*/
private
function
parseOptionalDescription
(
TokenIterator
$tokens
,
bool
$limitStartToken
):
string
{
if
(
$limitStartToken
)
{
foreach
(
self
::
DISALLOWED_DESCRIPTION_START_TOKENS
as
$disallowedStartToken
)
{
if
(!
$tokens
->
isCurrentTokenType
(
$disallowedStartToken
))
{
continue
;
}
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_OTHER
);
// will throw exception
}
if
(
!
$tokens
->
isCurrentTokenType
(
Lexer
::
TOKEN_PHPDOC_EOL
,
Lexer
::
TOKEN_CLOSE_PHPDOC
,
Lexer
::
TOKEN_END
)
&&
!
$tokens
->
isPrecededByHorizontalWhitespace
()
)
{
$tokens
->
consumeTokenType
(
Lexer
::
TOKEN_HORIZONTAL_WS
);
// will throw exception
}
}
return
$this
->
parseText
(
$tokens
)->
text
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 20:50 (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ee/a7/04a31712d069662a1f18f401d6c4
Default Alt Text
PhpDocParser.php (37 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment