Add Composer autoloading functions and PHPStan for testing

This commit is contained in:
Alex Cabal 2019-02-26 13:03:45 -06:00
parent e198c4db65
commit f5d7d4e02a
1518 changed files with 169063 additions and 30 deletions

25
vendor/phpstan/phpdoc-parser/build-abnfgen.sh vendored Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT_DIR="$DIR"
if [[ ! -d "$ROOT_DIR/tools/abnfgen" ]]; then
rm -rf "$ROOT_DIR/temp/abnfgen"
mkdir -p "$ROOT_DIR/temp/abnfgen"
wget http://www.quut.com/abnfgen/abnfgen-0.20.tar.gz \
--output-document "$ROOT_DIR/temp/abnfgen.tar.gz"
tar xf "$ROOT_DIR/temp/abnfgen.tar.gz" \
--directory "$ROOT_DIR/temp/abnfgen" \
--strip-components 1
cd "$ROOT_DIR/temp/abnfgen"
./configure
make
mkdir -p "$ROOT_DIR/tools/abnfgen"
mv abnfgen "$ROOT_DIR/tools/abnfgen"
rm -rf "$ROOT_DIR/temp/abnfgen" "$ROOT_DIR/temp/abnfgen.tar.gz"
fi

View file

@ -0,0 +1,28 @@
{
"name": "phpstan/phpdoc-parser",
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"license": "MIT",
"require": {
"php": "~7.1"
},
"require-dev": {
"consistence/coding-standard": "^2.0.0",
"jakub-onderka/php-parallel-lint": "^0.9.2",
"phing/phing": "^2.16.0",
"phpstan/phpstan": "^0.10",
"phpunit/phpunit": "^6.3",
"slevomat/coding-standard": "^3.3.0",
"symfony/process": "^3.4 || ^4.0"
},
"autoload": {
"psr-4": {"PHPStan\\PhpDocParser\\": ["src/"]}
},
"autoload-dev": {
"psr-4": {"PHPStan\\PhpDocParser\\": ["tests/PHPStan"]}
},
"extra": {
"branch-alias": {
"dev-master": "0.3-dev"
}
}
}

View file

@ -0,0 +1,41 @@
PhpDocMethod
= AnnotationName IsStatic? MethodReturnType? MethodName MethodParameters? Description?
AnnotationName
= '@method'
IsStatic
= 'static'
MethodReturnType
= Type
MethodName
= [a-zA-Z_\127-\255][a-zA-Z0-9_\127-\255]*
MethodParameters
= '(' MethodParametersInner? ')'
MethodParametersInner
= MethodParameter (',' MethodParameter)*
MethodParameter
= MethodParameterType? IsReference? IsVariaric? MethodParameterName MethodParameterDefaultValue?
MethodParameterType
= Type
IsReference
= '&'
IsVariaric
= '...'
MethodParameterName
= '$' [a-zA-Z_\127-\255][a-zA-Z0-9_\127-\255]*
MethodParameterDefaultValue
= '=' PhpConstantExpr
Description
= .+ # TODO: exclude EOL or another PhpDocTag start

View file

@ -0,0 +1,14 @@
PhpDocParam
= AnnotationName Type IsVariadic? ParameterName Description?
AnnotationName
= '@param'
IsVariaric
= '...'
ParameterName
= '$' [a-zA-Z_\127-\255][a-zA-Z0-9_\127-\255]*
Description
= .+ # TODO: exclude EOL or another PhpDocTag start

View file

@ -0,0 +1,231 @@
; ---------------------------------------------------------------------------- ;
; Type ;
; ---------------------------------------------------------------------------- ;
Type
= Atomic [Union / Intersection]
/ Nullable
Union
= 1*(TokenUnion Atomic)
Intersection
= 1*(TokenIntersection Atomic)
Nullable
= TokenNullable TokenIdentifier [Generic]
Atomic
= TokenIdentifier [Generic / Callable / Array]
/ TokenThisVariable
/ TokenParenthesesOpen Type TokenParenthesesClose [Array]
Generic
= TokenAngleBracketOpen Type *(TokenComma Type) TokenAngleBracketClose
Callable
= TokenParenthesesOpen [CallableParameters] TokenParenthesesClose TokenColon CallableReturnType
CallableParameters
= CallableParameter *(TokenComma CallableParameter)
CallableParameter
= Type [CallableParameterIsReference] [CallableParameterIsVariadic] [CallableParameterName] [CallableParameterIsOptional]
CallableParameterIsReference
= TokenIntersection
CallableParameterIsVariadic
= TokenVariadic
CallableParameterName
= TokenVariable
CallableParameterIsOptional
= TokenEqualSign
CallableReturnType
= TokenIdentifier [Generic]
/ Nullable
/ TokenParenthesesOpen Type TokenParenthesesClose
Array
= 1*(TokenSquareBracketOpen TokenSquareBracketClose)
; ---------------------------------------------------------------------------- ;
; ConstantExpr ;
; ---------------------------------------------------------------------------- ;
ConstantExpr
= ConstantFloat *ByteHorizontalWs
/ ConstantInt *ByteHorizontalWs
/ ConstantTrue *ByteHorizontalWs
/ ConstantFalse *ByteHorizontalWs
/ ConstantNull *ByteHorizontalWs
/ ConstantString *ByteHorizontalWs
/ ConstantArray *ByteHorizontalWs
/ ConstantFetch *ByteHorizontalWs
ConstantFloat
= ["-"] 1*ByteDecDigit "." *ByteDecDigit [ConstantFloatExp]
/ ["-"] 1*ByteDecDigit ConstantFloatExp
/ ["-"] "." 1*ByteDecDigit [ConstantFloatExp]
ConstantFloatExp
= "e" ["-"] 1*ByteDecDigit
ConstantInt
= ["-"] "0b" 1*ByteBinDigit
/ ["-"] "0o" 1*ByteOctDigit
/ ["-"] "0x" 1*ByteHexDigit
/ ["-"] 1*ByteDecDigit
ConstantTrue
= "true"
ConstantFalse
= "false"
ConstantNull
= "null"
ConstantString
= ByteSingleQuote *(ByteBackslash ByteNotEol / ByteNotEolAndNotBackslashAndNotSingleQuote) ByteSingleQuote
/ ByteDoubleQuote *(ByteBackslash ByteNotEol / ByteNotEolAndNotBackslashAndNotDoubleQuote) ByteDoubleQuote
ConstantArray
= TokenSquareBracketOpen [ConstantArrayItems] TokenSquareBracketClose
/ "array" TokenParenthesesOpen [ConstantArrayItems] TokenParenthesesClose
ConstantArrayItems
= ConstantArrayItem *(TokenComma ConstantArrayItem) [TokenComma]
ConstantArrayItem
= ConstantExpr [TokenDoubleArrow ConstantExpr]
ConstantFetch
= TokenIdentifier [TokenDoubleColon ByteIdentifierFirst *ByteIdentifierSecond *ByteHorizontalWs]
; ---------------------------------------------------------------------------- ;
; Tokens ;
; ---------------------------------------------------------------------------- ;
TokenUnion
= "|" *ByteHorizontalWs
TokenIntersection
= "&" *ByteHorizontalWs
TokenNullable
= "?" *ByteHorizontalWs
TokenParenthesesOpen
= "(" *ByteHorizontalWs
TokenParenthesesClose
= ")" *ByteHorizontalWs
TokenAngleBracketOpen
= "<" *ByteHorizontalWs
TokenAngleBracketClose
= ">" *ByteHorizontalWs
TokenSquareBracketOpen
= "[" *ByteHorizontalWs
TokenSquareBracketClose
= "]" *ByteHorizontalWs
TokenComma
= "," *ByteHorizontalWs
TokenColon
= ":" *ByteHorizontalWs
TokenVariadic
= "..." *ByteHorizontalWs
TokenEqualSign
= "=" *ByteHorizontalWs
TokenVariable
= "$" ByteIdentifierFirst *ByteIdentifierSecond *ByteHorizontalWs
TokenDoubleArrow
= "=>" *ByteHorizontalWs
TokenDoubleColon
= "::" *ByteHorizontalWs
TokenThisVariable
= %x24.74.68.69.73 *ByteHorizontalWs
TokenIdentifier
= [ByteBackslash] ByteIdentifierFirst *ByteIdentifierSecond *(ByteBackslash ByteIdentifierFirst *ByteIdentifierSecond) *ByteHorizontalWs
; ---------------------------------------------------------------------------- ;
; Bytes ;
; ---------------------------------------------------------------------------- ;
ByteHorizontalWs
= %x09 ; horizontal tab
/ %x20 ; space
ByteBinDigit
= %x30-31 ; 0-1
ByteOctDigit
= %x30-37 ; 0-7
ByteDecDigit
= %x30-39 ; 0-9
ByteHexDigit
= %x30-39 ; 0-9
/ %x41-46 ; A-F
/ %x61-66 ; a-f
ByteIdentifierFirst
= %x41-5A ; A-Z
/ %x5F ; _
/ %x61-7A ; a-z
/ %x80-FF
ByteIdentifierSecond
= %x30-39 ; 0-9
/ %x41-5A ; A-Z
/ %x5F ; _
/ %x61-7A ; a-z
/ %x80-FF
ByteSingleQuote
= %x27 ; '
ByteDoubleQuote
= %x22 ; "
ByteBackslash
= %x5C ; \
ByteNotEol
= %x00-09 ; skip LF
/ %x0B-0C ; skip CR
/ %x0E-FF
ByteNotEolAndNotBackslashAndNotSingleQuote
= %x00-09 ; skip LF
/ %x0B-0C ; skip CR
/ %x0E-26 ; skip single quote
/ %x28-5B ; skip backslash
/ %x5D-FF
ByteNotEolAndNotBackslashAndNotDoubleQuote
= %x00-09 ; skip LF
/ %x0B-0C ; skip CR
/ %x0E-21 ; skip double quote
/ %x23-5B ; skip backslash
/ %x5D-FF

View file

@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprArrayItemNode implements ConstExprNode
{
/** @var null|ConstExprNode */
public $key;
/** @var ConstExprNode */
public $value;
public function __construct(?ConstExprNode $key, ConstExprNode $value)
{
$this->key = $key;
$this->value = $value;
}
public function __toString(): string
{
if ($this->key !== null) {
return "{$this->key} => {$this->value}";
} else {
return "{$this->value}";
}
}
}

View file

@ -0,0 +1,25 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprArrayNode implements ConstExprNode
{
/** @var ConstExprArrayItemNode[] */
public $items;
/**
* @param ConstExprArrayItemNode[] $items
*/
public function __construct(array $items)
{
$this->items = $items;
}
public function __toString(): string
{
return '[' . implode(', ', $this->items) . ']';
}
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprFalseNode implements ConstExprNode
{
public function __toString(): string
{
return 'false';
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprFloatNode implements ConstExprNode
{
/** @var string */
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString(): string
{
return $this->value;
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprIntegerNode implements ConstExprNode
{
/** @var string */
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString(): string
{
return $this->value;
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
use PHPStan\PhpDocParser\Ast\Node;
interface ConstExprNode extends Node
{
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprNullNode implements ConstExprNode
{
public function __toString(): string
{
return 'null';
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprStringNode implements ConstExprNode
{
/** @var string */
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString(): string
{
return $this->value;
}
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstExprTrueNode implements ConstExprNode
{
public function __toString(): string
{
return 'true';
}
}

View file

@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\ConstExpr;
class ConstFetchNode implements ConstExprNode
{
/** @var string class name for class constants or empty string for non-class constants */
public $className;
/** @var string */
public $name;
public function __construct(string $className, string $name)
{
$this->className = $className;
$this->name = $name;
}
public function __toString(): string
{
if ($this->className === '') {
return $this->name;
} else {
return "{$this->className}::{$this->name}";
}
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast;
interface Node
{
public function __toString(): string;
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
class GenericTagValueNode implements PhpDocTagValueNode
{
/** @var string (may be empty) */
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString(): string
{
return $this->value;
}
}

View file

@ -0,0 +1,26 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
class InvalidTagValueNode implements PhpDocTagValueNode
{
/** @var string (may be empty) */
public $value;
/** @var \PHPStan\PhpDocParser\Parser\ParserException */
public $exception;
public function __construct(string $value, \PHPStan\PhpDocParser\Parser\ParserException $exception)
{
$this->value = $value;
$this->exception = $exception;
}
public function __toString(): string
{
return $this->value;
}
}

View file

@ -0,0 +1,44 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class MethodTagValueNode implements PhpDocTagValueNode
{
/** @var bool */
public $isStatic;
/** @var null|TypeNode */
public $returnType;
/** @var string */
public $methodName;
/** @var MethodTagValueParameterNode[] */
public $parameters;
/** @var string (may be empty) */
public $description;
public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description)
{
$this->isStatic = $isStatic;
$this->returnType = $returnType;
$this->methodName = $methodName;
$this->parameters = $parameters;
$this->description = $description;
}
public function __toString(): string
{
$static = $this->isStatic ? 'static ' : '';
$returnType = $this->returnType ? "{$this->returnType} " : '';
$parameters = implode(', ', $this->parameters);
$description = $this->description !== '' ? " {$this->description}" : '';
return "{$static}{$returnType}{$this->methodName}({$parameters}){$description}";
}
}

View file

@ -0,0 +1,46 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class MethodTagValueParameterNode implements Node
{
/** @var null|TypeNode */
public $type;
/** @var bool */
public $isReference;
/** @var bool */
public $isVariadic;
/** @var string */
public $parameterName;
/** @var null|ConstExprNode */
public $defaultValue;
public function __construct(?TypeNode $type, bool $isReference, bool $isVariadic, string $parameterName, ?ConstExprNode $defaultValue)
{
$this->type = $type;
$this->isReference = $isReference;
$this->isVariadic = $isVariadic;
$this->parameterName = $parameterName;
$this->defaultValue = $defaultValue;
}
public function __toString(): string
{
$type = $this->type ? "{$this->type} " : '';
$isReference = $this->isReference ? '&' : '';
$isVariadic = $this->isVariadic ? '...' : '';
$default = $this->defaultValue ? " = {$this->defaultValue}" : '';
return "{$type}{$isReference}{$isVariadic}{$this->parameterName}{$default}";
}
}

View file

@ -0,0 +1,37 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class ParamTagValueNode implements PhpDocTagValueNode
{
/** @var TypeNode */
public $type;
/** @var bool */
public $isVariadic;
/** @var string (may be empty) */
public $parameterName;
/** @var string (may be empty) */
public $description;
public function __construct(TypeNode $type, bool $isVariadic, string $parameterName, string $description)
{
$this->type = $type;
$this->isVariadic = $isVariadic;
$this->parameterName = $parameterName;
$this->description = $description;
}
public function __toString(): string
{
$variadic = $this->isVariadic ? '...' : '';
return trim("{$this->type} {$variadic}{$this->parameterName} {$this->description}");
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Node;
interface PhpDocChildNode extends Node
{
}

View file

@ -0,0 +1,162 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Node;
class PhpDocNode implements Node
{
/** @var PhpDocChildNode[] */
public $children;
/**
* @param PhpDocChildNode[] $children
*/
public function __construct(array $children)
{
$this->children = $children;
}
/**
* @return PhpDocTagNode[]
*/
public function getTags(): array
{
return array_filter($this->children, function (PhpDocChildNode $child): bool {
return $child instanceof PhpDocTagNode;
});
}
/**
* @param string $tagName
* @return PhpDocTagNode[]
*/
public function getTagsByName(string $tagName): array
{
return array_filter($this->getTags(), function (PhpDocTagNode $tag) use ($tagName): bool {
return $tag->name === $tagName;
});
}
/**
* @return VarTagValueNode[]
*/
public function getVarTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@var'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof VarTagValueNode;
}),
'value'
);
}
/**
* @return ParamTagValueNode[]
*/
public function getParamTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@param'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof ParamTagValueNode;
}),
'value'
);
}
/**
* @return ReturnTagValueNode[]
*/
public function getReturnTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@return'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof ReturnTagValueNode;
}),
'value'
);
}
/**
* @return ThrowsTagValueNode[]
*/
public function getThrowsTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@throws'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof ThrowsTagValueNode;
}),
'value'
);
}
/**
* @return PropertyTagValueNode[]
*/
public function getPropertyTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@property'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof PropertyTagValueNode;
}),
'value'
);
}
/**
* @return PropertyTagValueNode[]
*/
public function getPropertyReadTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@property-read'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof PropertyTagValueNode;
}),
'value'
);
}
/**
* @return PropertyTagValueNode[]
*/
public function getPropertyWriteTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@property-write'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof PropertyTagValueNode;
}),
'value'
);
}
/**
* @return MethodTagValueNode[]
*/
public function getMethodTagValues(): array
{
return array_column(
array_filter($this->getTagsByName('@method'), function (PhpDocTagNode $tag): bool {
return $tag->value instanceof MethodTagValueNode;
}),
'value'
);
}
public function __toString(): string
{
return "/**\n * " . implode("\n * ", $this->children) . '*/';
}
}

View file

@ -0,0 +1,26 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
class PhpDocTagNode implements PhpDocChildNode
{
/** @var string */
public $name;
/** @var PhpDocTagValueNode */
public $value;
public function __construct(string $name, PhpDocTagValueNode $value)
{
$this->name = $name;
$this->value = $value;
}
public function __toString(): string
{
return trim("{$this->name} {$this->value}");
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Node;
interface PhpDocTagValueNode extends Node
{
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
class PhpDocTextNode implements PhpDocChildNode
{
/** @var string */
public $text;
public function __construct(string $text)
{
$this->text = $text;
}
public function __toString(): string
{
return $this->text;
}
}

View file

@ -0,0 +1,32 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class PropertyTagValueNode implements PhpDocTagValueNode
{
/** @var TypeNode */
public $type;
/** @var string */
public $propertyName;
/** @var string (may be empty) */
public $description;
public function __construct(TypeNode $type, string $propertyName, string $description)
{
$this->type = $type;
$this->propertyName = $propertyName;
$this->description = $description;
}
public function __toString(): string
{
return trim("{$this->type} {$this->propertyName} {$this->description}");
}
}

View file

@ -0,0 +1,28 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class ReturnTagValueNode implements PhpDocTagValueNode
{
/** @var TypeNode */
public $type;
/** @var string (may be empty) */
public $description;
public function __construct(TypeNode $type, string $description)
{
$this->type = $type;
$this->description = $description;
}
public function __toString(): string
{
return trim("{$this->type} {$this->description}");
}
}

View file

@ -0,0 +1,28 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class ThrowsTagValueNode implements PhpDocTagValueNode
{
/** @var TypeNode */
public $type;
/** @var string (may be empty) */
public $description;
public function __construct(TypeNode $type, string $description)
{
$this->type = $type;
$this->description = $description;
}
public function __toString(): string
{
return trim("{$this->type} {$this->description}");
}
}

View file

@ -0,0 +1,32 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\PhpDoc;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
class VarTagValueNode implements PhpDocTagValueNode
{
/** @var TypeNode */
public $type;
/** @var string (may be empty) */
public $variableName;
/** @var string (may be empty) */
public $description;
public function __construct(TypeNode $type, string $variableName, string $description)
{
$this->type = $type;
$this->variableName = $variableName;
$this->description = $description;
}
public function __toString(): string
{
return trim("$this->type " . trim("{$this->variableName} {$this->description}"));
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class ArrayTypeNode implements TypeNode
{
/** @var TypeNode */
public $type;
public function __construct(TypeNode $type)
{
$this->type = $type;
}
public function __toString(): string
{
return $this->type . '[]';
}
}

View file

@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class CallableTypeNode implements TypeNode
{
/** @var IdentifierTypeNode */
public $identifier;
/** @var CallableTypeParameterNode[] */
public $parameters;
/** @var TypeNode */
public $returnType;
public function __construct(IdentifierTypeNode $identifier, array $parameters, TypeNode $returnType)
{
$this->identifier = $identifier;
$this->parameters = $parameters;
$this->returnType = $returnType;
}
public function __toString(): string
{
$parameters = implode(', ', $this->parameters);
return "{$this->identifier}({$parameters}): {$this->returnType}";
}
}

View file

@ -0,0 +1,44 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
use PHPStan\PhpDocParser\Ast\Node;
class CallableTypeParameterNode implements Node
{
/** @var TypeNode */
public $type;
/** @var bool */
public $isReference;
/** @var bool */
public $isVariadic;
/** @var string (may be empty) */
public $parameterName;
/** @var bool */
public $isOptional;
public function __construct(TypeNode $type, bool $isReference, bool $isVariadic, string $parameterName, bool $isOptional)
{
$this->type = $type;
$this->isReference = $isReference;
$this->isVariadic = $isVariadic;
$this->parameterName = $parameterName;
$this->isOptional = $isOptional;
}
public function __toString(): string
{
$type = "{$this->type} ";
$isReference = $this->isReference ? '&' : '';
$isVariadic = $this->isVariadic ? '...' : '';
$default = $this->isOptional ? ' = default' : '';
return "{$type}{$isReference}{$isVariadic}{$this->parameterName}{$default}";
}
}

View file

@ -0,0 +1,26 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class GenericTypeNode implements TypeNode
{
/** @var IdentifierTypeNode */
public $type;
/** @var TypeNode[] */
public $genericTypes;
public function __construct(IdentifierTypeNode $type, array $genericTypes)
{
$this->type = $type;
$this->genericTypes = $genericTypes;
}
public function __toString(): string
{
return $this->type . '<' . implode(', ', $this->genericTypes) . '>';
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class IdentifierTypeNode implements TypeNode
{
/** @var string */
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __toString(): string
{
return $this->name;
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class IntersectionTypeNode implements TypeNode
{
/** @var TypeNode[] */
public $types;
public function __construct(array $types)
{
$this->types = $types;
}
public function __toString(): string
{
return '(' . implode(' & ', $this->types) . ')';
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class NullableTypeNode implements TypeNode
{
/** @var TypeNode */
public $type;
public function __construct(TypeNode $type)
{
$this->type = $type;
}
public function __toString(): string
{
return '?' . $this->type;
}
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class ThisTypeNode implements TypeNode
{
public function __toString(): string
{
return '$this';
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
use PHPStan\PhpDocParser\Ast\Node;
interface TypeNode extends Node
{
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
class UnionTypeNode implements TypeNode
{
/** @var TypeNode[] */
public $types;
public function __construct(array $types)
{
$this->types = $types;
}
public function __toString(): string
{
return '(' . implode(' | ', $this->types) . ')';
}
}

View file

@ -0,0 +1,158 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Lexer;
/**
* Implementation based on Nette Tokenizer (New BSD License; https://github.com/nette/tokenizer)
*/
class Lexer
{
public const TOKEN_REFERENCE = 0;
public const TOKEN_UNION = 1;
public const TOKEN_INTERSECTION = 2;
public const TOKEN_NULLABLE = 3;
public const TOKEN_OPEN_PARENTHESES = 4;
public const TOKEN_CLOSE_PARENTHESES = 5;
public const TOKEN_OPEN_ANGLE_BRACKET = 6;
public const TOKEN_CLOSE_ANGLE_BRACKET = 7;
public const TOKEN_OPEN_SQUARE_BRACKET = 8;
public const TOKEN_CLOSE_SQUARE_BRACKET = 9;
public const TOKEN_COMMA = 10;
public const TOKEN_COLON = 29;
public const TOKEN_VARIADIC = 11;
public const TOKEN_DOUBLE_COLON = 12;
public const TOKEN_DOUBLE_ARROW = 13;
public const TOKEN_EQUAL = 14;
public const TOKEN_OPEN_PHPDOC = 15;
public const TOKEN_CLOSE_PHPDOC = 16;
public const TOKEN_PHPDOC_TAG = 17;
public const TOKEN_PHPDOC_EOL = 26;
public const TOKEN_FLOAT = 18;
public const TOKEN_INTEGER = 19;
public const TOKEN_SINGLE_QUOTED_STRING = 20;
public const TOKEN_DOUBLE_QUOTED_STRING = 21;
public const TOKEN_IDENTIFIER = 22;
public const TOKEN_THIS_VARIABLE = 23;
public const TOKEN_VARIABLE = 24;
public const TOKEN_HORIZONTAL_WS = 25;
public const TOKEN_OTHER = 27;
public const TOKEN_END = 28;
public const TOKEN_LABELS = [
self::TOKEN_REFERENCE => '\'&\'',
self::TOKEN_UNION => '\'|\'',
self::TOKEN_INTERSECTION => '\'&\'',
self::TOKEN_NULLABLE => '\'?\'',
self::TOKEN_OPEN_PARENTHESES => '\'(\'',
self::TOKEN_CLOSE_PARENTHESES => '\')\'',
self::TOKEN_OPEN_ANGLE_BRACKET => '\'<\'',
self::TOKEN_CLOSE_ANGLE_BRACKET => '\'>\'',
self::TOKEN_OPEN_SQUARE_BRACKET => '\'[\'',
self::TOKEN_CLOSE_SQUARE_BRACKET => '\']\'',
self::TOKEN_COMMA => '\',\'',
self::TOKEN_COLON => '\':\'',
self::TOKEN_VARIADIC => '\'...\'',
self::TOKEN_DOUBLE_COLON => '\'::\'',
self::TOKEN_DOUBLE_ARROW => '\'=>\'',
self::TOKEN_EQUAL => '\'=\'',
self::TOKEN_OPEN_PHPDOC => '\'/**\'',
self::TOKEN_CLOSE_PHPDOC => '\'*/\'',
self::TOKEN_PHPDOC_TAG => 'TOKEN_PHPDOC_TAG',
self::TOKEN_PHPDOC_EOL => 'TOKEN_PHPDOC_EOL',
self::TOKEN_FLOAT => 'TOKEN_FLOAT',
self::TOKEN_INTEGER => 'TOKEN_INTEGER',
self::TOKEN_SINGLE_QUOTED_STRING => 'TOKEN_SINGLE_QUOTED_STRING',
self::TOKEN_DOUBLE_QUOTED_STRING => 'TOKEN_DOUBLE_QUOTED_STRING',
self::TOKEN_IDENTIFIER => 'TOKEN_IDENTIFIER',
self::TOKEN_THIS_VARIABLE => '\'$this\'',
self::TOKEN_VARIABLE => 'TOKEN_VARIABLE',
self::TOKEN_HORIZONTAL_WS => 'TOKEN_HORIZONTAL_WS',
self::TOKEN_OTHER => 'TOKEN_OTHER',
self::TOKEN_END => 'TOKEN_END',
];
public const VALUE_OFFSET = 0;
public const TYPE_OFFSET = 1;
/** @var null|string */
private $regexp;
/** @var null|int[] */
private $types;
public function tokenize(string $s): array
{
if ($this->regexp === null || $this->types === null) {
$this->initialize();
}
assert($this->regexp !== null);
assert($this->types !== null);
preg_match_all($this->regexp, $s, $tokens, PREG_SET_ORDER);
$count = count($this->types);
foreach ($tokens as &$match) {
for ($i = 1; $i <= $count; $i++) {
if ($match[$i] !== null && $match[$i] !== '') {
$match = [$match[0], $this->types[$i - 1]];
break;
}
}
}
$tokens[] = ['', self::TOKEN_END];
return $tokens;
}
private function initialize()
{
$patterns = [
// '&' followed by TOKEN_VARIADIC, TOKEN_VARIABLE, TOKEN_EQUAL, TOKEN_EQUAL or TOKEN_CLOSE_PARENTHESES
self::TOKEN_REFERENCE => '&(?=\\s*+(?:[.,=)]|(?:\\$(?!this(?![0-9a-z_\\x80-\\xFF])))))',
self::TOKEN_UNION => '\\|',
self::TOKEN_INTERSECTION => '&',
self::TOKEN_NULLABLE => '\\?',
self::TOKEN_OPEN_PARENTHESES => '\\(',
self::TOKEN_CLOSE_PARENTHESES => '\\)',
self::TOKEN_OPEN_ANGLE_BRACKET => '<',
self::TOKEN_CLOSE_ANGLE_BRACKET => '>',
self::TOKEN_OPEN_SQUARE_BRACKET => '\\[',
self::TOKEN_CLOSE_SQUARE_BRACKET => '\\]',
self::TOKEN_COMMA => ',',
self::TOKEN_VARIADIC => '\\.\\.\\.',
self::TOKEN_DOUBLE_COLON => '::',
self::TOKEN_DOUBLE_ARROW => '=>',
self::TOKEN_EQUAL => '=',
self::TOKEN_COLON => ':',
self::TOKEN_OPEN_PHPDOC => '/\\*\\*(?=\\s)',
self::TOKEN_CLOSE_PHPDOC => '\\*/',
self::TOKEN_PHPDOC_TAG => '@[a-z-]++',
self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/))?',
self::TOKEN_FLOAT => '(?:-?[0-9]++\\.[0-9]*+(?:e-?[0-9]++)?)|(?:-?[0-9]*+\\.[0-9]++(?:e-?[0-9]++)?)|(?:-?[0-9]++e-?[0-9]++)',
self::TOKEN_INTEGER => '-?(?:(?:0b[0-1]++)|(?:0o[0-7]++)|(?:0x[0-9a-f]++)|(?:[0-9]++))',
self::TOKEN_SINGLE_QUOTED_STRING => '\'(?:\\\\[^\\r\\n]|[^\'\\r\\n\\\\])*+\'',
self::TOKEN_DOUBLE_QUOTED_STRING => '"(?:\\\\[^\\r\\n]|[^"\\r\\n\\\\])*+"',
self::TOKEN_IDENTIFIER => '(?:[\\\\]?+[a-z_\\x80-\\xFF][0-9a-z_\\x80-\\xFF]*+)++',
self::TOKEN_THIS_VARIABLE => '\\$this(?![0-9a-z_\\x80-\\xFF])',
self::TOKEN_VARIABLE => '\\$[a-z_\\x80-\\xFF][0-9a-z_\\x80-\\xFF]*+',
self::TOKEN_HORIZONTAL_WS => '[\\x09\\x20]++',
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++',
];
$this->regexp = '~(' . implode(')|(', $patterns) . ')~Asi';
$this->types = array_keys($patterns);
}
}

View file

@ -0,0 +1,97 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Ast;
use PHPStan\PhpDocParser\Lexer\Lexer;
class ConstExprParser
{
public function parse(TokenIterator $tokens): Ast\ConstExpr\ConstExprNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) {
$value = $tokens->currentTokenValue();
$tokens->next();
return new Ast\ConstExpr\ConstExprFloatNode($value);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
$value = $tokens->currentTokenValue();
$tokens->next();
return new Ast\ConstExpr\ConstExprIntegerNode($value);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
$value = $tokens->currentTokenValue();
$tokens->next();
return new Ast\ConstExpr\ConstExprStringNode($value);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
$value = $tokens->currentTokenValue();
$tokens->next();
return new Ast\ConstExpr\ConstExprStringNode($value);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
$identifier = $tokens->currentTokenValue();
$tokens->next();
switch (strtolower($identifier)) {
case 'true':
return new Ast\ConstExpr\ConstExprTrueNode();
case 'false':
return new Ast\ConstExpr\ConstExprFalseNode();
case 'null':
return new Ast\ConstExpr\ConstExprNullNode();
case 'array':
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES);
}
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
$classConstantName = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
return new Ast\ConstExpr\ConstFetchNode($identifier, $classConstantName);
} else {
return new Ast\ConstExpr\ConstFetchNode('', $identifier);
}
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
}
throw new \LogicException($tokens->currentTokenValue());
}
private function parseArray(TokenIterator $tokens, int $endToken): Ast\ConstExpr\ConstExprArrayNode
{
$items = [];
if (!$tokens->tryConsumeTokenType($endToken)) {
do {
$items[] = $this->parseArrayItem($tokens);
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType($endToken));
$tokens->consumeTokenType($endToken);
}
return new Ast\ConstExpr\ConstExprArrayNode($items);
}
private function parseArrayItem(TokenIterator $tokens): Ast\ConstExpr\ConstExprArrayItemNode
{
$expr = $this->parse($tokens);
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_ARROW)) {
$key = $expr;
$value = $this->parse($tokens);
} else {
$key = null;
$value = $expr;
}
return new Ast\ConstExpr\ConstExprArrayItemNode($key, $value);
}
}

View file

@ -0,0 +1,69 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Lexer\Lexer;
class ParserException extends \Exception
{
/** @var string */
private $currentTokenValue;
/** @var int */
private $currentTokenType;
/** @var int */
private $currentOffset;
/** @var int */
private $expectedTokenType;
public function __construct(
string $currentTokenValue,
int $currentTokenType,
int $currentOffset,
int $expectedTokenType
)
{
$this->currentTokenValue = $currentTokenValue;
$this->currentTokenType = $currentTokenType;
$this->currentOffset = $currentOffset;
$this->expectedTokenType = $expectedTokenType;
$json = json_encode($currentTokenValue, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
assert($json !== false);
parent::__construct(sprintf(
'Unexpected token %s, expected %s at offset %d',
$json,
Lexer::TOKEN_LABELS[$expectedTokenType],
$currentOffset
));
}
public function getCurrentTokenValue(): string
{
return $this->currentTokenValue;
}
public function getCurrentTokenType(): int
{
return $this->currentTokenType;
}
public function getCurrentOffset(): int
{
return $this->currentOffset;
}
public function getExpectedTokenType(): int
{
return $this->expectedTokenType;
}
}

View file

@ -0,0 +1,273 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Ast;
use PHPStan\PhpDocParser\Lexer\Lexer;
class PhpDocParser
{
private const DISALLOWED_DESCRIPTION_START_TOKENS = [
Lexer::TOKEN_UNION,
Lexer::TOKEN_INTERSECTION,
Lexer::TOKEN_OPEN_ANGLE_BRACKET,
];
/** @var TypeParser */
private $typeParser;
/** @var ConstExprParser */
private $constantExprParser;
public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser)
{
$this->typeParser = $typeParser;
$this->constantExprParser = $constantExprParser;
}
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)) {
$children[] = $this->parseChild($tokens);
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) {
$children[] = $this->parseChild($tokens);
}
}
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PHPDOC);
return new Ast\PhpDoc\PhpDocNode(array_values($children));
}
private function parseChild(TokenIterator $tokens): Ast\PhpDoc\PhpDocChildNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_TAG)) {
return $this->parseTag($tokens);
} else {
return $this->parseText($tokens);
}
}
private function parseText(TokenIterator $tokens): Ast\PhpDoc\PhpDocTextNode
{
$text = $tokens->joinUntil(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END);
$text = rtrim($text, " \t"); // the trimmed characters MUST match Lexer::TOKEN_HORIZONTAL_WS
return new Ast\PhpDoc\PhpDocTextNode($text);
}
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
{
try {
$tokens->pushSavePoint();
switch ($tag) {
case '@param':
$tagValue = $this->parseParamTagValue($tokens);
break;
case '@var':
$tagValue = $this->parseVarTagValue($tokens);
break;
case '@return':
$tagValue = $this->parseReturnTagValue($tokens);
break;
case '@throws':
$tagValue = $this->parseThrowsTagValue($tokens);
break;
case '@property':
case '@property-read':
case '@property-write':
$tagValue = $this->parsePropertyTagValue($tokens);
break;
case '@method':
$tagValue = $this->parseMethodTagValue($tokens);
break;
default:
$tagValue = new Ast\PhpDoc\GenericTagValueNode($this->parseOptionalDescription($tokens));
break;
}
$tokens->dropSavePoint();
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
$tokens->rollback();
$tagValue = new Ast\PhpDoc\InvalidTagValueNode($this->parseOptionalDescription($tokens), $e);
}
return $tagValue;
}
private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamTagValueNode
{
$type = $this->typeParser->parse($tokens);
$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
$parameterName = $this->parseRequiredVariableName($tokens);
$description = $this->parseOptionalDescription($tokens);
return new Ast\PhpDoc\ParamTagValueNode($type, $isVariadic, $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 parsePropertyTagValue(TokenIterator $tokens): Ast\PhpDoc\PropertyTagValueNode
{
$type = $this->typeParser->parse($tokens);
$parameterName = $this->parseRequiredVariableName($tokens);
$description = $this->parseOptionalDescription($tokens);
return new Ast\PhpDoc\PropertyTagValueNode($type, $parameterName, $description);
}
private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueNode
{
$isStatic = $tokens->tryConsumeTokenValue('static');
$returnTypeOrMethodName = $this->typeParser->parse($tokens);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
$returnType = $returnTypeOrMethodName;
$methodName = $tokens->currentTokenValue();
$tokens->next();
} elseif ($returnTypeOrMethodName instanceof Ast\Type\IdentifierTypeNode) {
$returnType = $isStatic ? new Ast\Type\IdentifierTypeNode('static') : null;
$methodName = $returnTypeOrMethodName->name;
$isStatic = false;
} else {
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); // will throw exception
exit;
}
$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);
return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description);
}
private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode
{
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 new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue);
}
private function parseOptionalVariableName(TokenIterator $tokens): string
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
$parameterName = $tokens->currentTokenValue();
$tokens->next();
} else {
$parameterName = '';
}
return $parameterName;
}
private function parseRequiredVariableName(TokenIterator $tokens): string
{
$parameterName = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
return $parameterName;
}
private function parseOptionalDescription(TokenIterator $tokens, bool $limitStartToken = false): string
{
if ($limitStartToken) {
foreach (self::DISALLOWED_DESCRIPTION_START_TOKENS as $disallowedStartToken) {
if ($tokens->isCurrentTokenType($disallowedStartToken)) {
$tokens->consumeTokenType(Lexer::TOKEN_OTHER); // will throw exception
}
}
}
return $this->parseText($tokens)->text;
}
}

View file

@ -0,0 +1,179 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Lexer\Lexer;
class TokenIterator
{
/** @var mixed[][] */
private $tokens;
/** @var int */
private $index;
/** @var int[] */
private $savePoints = [];
public function __construct(array $tokens, int $index = 0)
{
$this->tokens = $tokens;
$this->index = $index;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
}
public function currentTokenValue(): string
{
return $this->tokens[$this->index][Lexer::VALUE_OFFSET];
}
public function currentTokenType(): int
{
return $this->tokens[$this->index][Lexer::TYPE_OFFSET];
}
public function currentTokenOffset(): int
{
$offset = 0;
for ($i = 0; $i < $this->index; $i++) {
$offset += strlen($this->tokens[$i][Lexer::VALUE_OFFSET]);
}
return $offset;
}
public function isCurrentTokenValue(string $tokenValue): bool
{
return $this->tokens[$this->index][Lexer::VALUE_OFFSET] === $tokenValue;
}
public function isCurrentTokenType(int $tokenType): bool
{
return $this->tokens[$this->index][Lexer::TYPE_OFFSET] === $tokenType;
}
/**
* @param int $tokenType
* @throws \PHPStan\PhpDocParser\Parser\ParserException
*/
public function consumeTokenType(int $tokenType)
{
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) {
$this->throwError($tokenType);
}
$this->index++;
if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
}
public function tryConsumeTokenValue(string $tokenValue): bool
{
if ($this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) {
return false;
}
$this->index++;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
return true;
}
public function tryConsumeTokenType(int $tokenType): bool
{
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== $tokenType) {
return false;
}
$this->index++;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
return true;
}
public function getSkippedHorizontalWhiteSpaceIfAny(): string
{
if ($this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
return $this->tokens[$this->index - 1][Lexer::VALUE_OFFSET];
}
return '';
}
public function joinUntil(int ...$tokenType): string
{
$s = '';
while (!in_array($this->tokens[$this->index][Lexer::TYPE_OFFSET], $tokenType, true)) {
$s .= $this->tokens[$this->index++][Lexer::VALUE_OFFSET];
}
return $s;
}
public function next()
{
$this->index++;
if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
$this->index++;
}
}
public function pushSavePoint()
{
$this->savePoints[] = $this->index;
}
public function dropSavePoint()
{
array_pop($this->savePoints);
}
public function rollback()
{
$index = array_pop($this->savePoints);
assert($index !== null);
$this->index = $index;
}
/**
* @param int $expectedTokenType
* @throws \PHPStan\PhpDocParser\Parser\ParserException
*/
private function throwError(int $expectedTokenType)
{
throw new \PHPStan\PhpDocParser\Parser\ParserException(
$this->currentTokenValue(),
$this->currentTokenType(),
$this->currentTokenOffset(),
$expectedTokenType
);
}
}

View file

@ -0,0 +1,211 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Ast;
use PHPStan\PhpDocParser\Lexer\Lexer;
class TypeParser
{
public function parse(TokenIterator $tokens): Ast\Type\TypeNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
$type = $this->parseNullable($tokens);
} else {
$type = $this->parseAtomic($tokens);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
$type = $this->parseUnion($tokens, $type);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
$type = $this->parseIntersection($tokens, $type);
}
}
return $type;
}
private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
{
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$type = $this->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
}
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
return new Ast\Type\ThisTypeNode();
} else {
$type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric($tokens, $type);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$type = $this->tryParseCallable($tokens, $type);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
}
}
return $type;
}
private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
{
$types = [$type];
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
$types[] = $this->parseAtomic($tokens);
}
return new Ast\Type\UnionTypeNode($types);
}
private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
{
$types = [$type];
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
$types[] = $this->parseAtomic($tokens);
}
return new Ast\Type\IntersectionTypeNode($types);
}
private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode
{
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
$type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric($tokens, $type);
}
return new Ast\Type\NullableTypeNode($type);
}
private function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\TypeNode
{
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
$genericTypes[] = $this->parse($tokens);
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
$genericTypes[] = $this->parse($tokens);
}
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
return new Ast\Type\GenericTypeNode($baseType, $genericTypes);
}
private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode
{
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
$parameters = [];
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
$parameters[] = $this->parseCallableParameter($tokens);
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
$parameters[] = $this->parseCallableParameter($tokens);
}
}
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
$returnType = $this->parseCallableReturnType($tokens);
return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType);
}
private function parseCallableParameter(TokenIterator $tokens): Ast\Type\CallableTypeParameterNode
{
$type = $this->parse($tokens);
$isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE);
$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
$parameterName = $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
} else {
$parameterName = '';
}
$isOptional = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
return new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional);
}
private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
$type = $this->parseNullable($tokens);
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$type = $this->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
} else {
$type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric($tokens, $type);
}
}
return $type;
}
private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode
{
try {
$tokens->pushSavePoint();
$type = $this->parseCallable($tokens, $identifier);
$tokens->dropSavePoint();
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
$tokens->rollback();
$type = $identifier;
}
return $type;
}
private function tryParseArray(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
{
try {
while ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$tokens->pushSavePoint();
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET);
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
$tokens->dropSavePoint();
$type = new Ast\Type\ArrayTypeNode($type);
}
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
$tokens->rollback();
}
return $type;
}
}