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;
}
}

27
vendor/phpstan/phpstan/.editorconfig vendored Normal file
View file

@ -0,0 +1,27 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[*.{php,phpt}]
indent_style = tab
indent_size = 4
[*.xml]
indent_style = tab
indent_size = 4
[*.neon]
indent_style = tab
indent_size = 4
[*.{yaml,yml}]
indent_style = space
indent_size = 2
[composer.json]
indent_style = tab
indent_size = 4

2
vendor/phpstan/phpstan/.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
/build export-ignore
/tests export-ignore

View file

@ -0,0 +1,15 @@
<!-- Before reporting an issue please check that you are using the latest PHPStan version! -->
### Summary of a problem or a feature request
<!-- Please describe your problem/feature request here. -->
### Code snippet that reproduces the problem
<!-- Try to reproduce the issue you are facing using https://phpstan.org/ (click Preview, check result, and then "Analyze & Persist" to get a unique URL) -->
### Expected output
<!-- Was the issue reported incorrectly? Or should PHPStan detect an issue with the code but doesn't? -->

61
vendor/phpstan/phpstan/.travis.yml vendored Normal file
View file

@ -0,0 +1,61 @@
dist: xenial
language: php
stages:
- test
- name: phar-test
if: branch = master && type = push
- name: phar-push
if: branch = master && type = push
php:
- 7.1
- 7.2
- 7.3
- master
env:
matrix:
- dependencies=lowest
- dependencies=highest
global:
secure: Wxs9IuFkyacKk/Cu4qxFkkSldob6AhJtx3MDS6Nehz/VF4M88awmI/GcVzfcXjFIA2+EJlkA+X0ymxP9xY5sp+vvmF4mkBIOvSvQdIOLsy73Ixpji3nqc8+epuvtBUGA4Y2xlLmBxSPYZlpVc/DRR2mIlzqDfOQc+gUmJ1aOZPUvs533cl4k0V23hU7L3LxgAnNY7j5vfNgYUhLqBHnqZ0yHt+m0x56wTOBHIBXH+LVNdgsl07fZnuC9HBb3lZKhtCtJiMyC0SsFQLljESaedTHRzptcOuvO+3dmt9R+AP/WxbdmleBaozCrwpdCK3Lqwrt9DXkxtn9ERjtVg0uT2KV5Mm5y4W0w9EXzX/8iUexlJaYOP7OYBtaV0R65w+oiC9dupKiFFvQtJkWcMg+4FCqEjNVM/smXin7+4dLgXiioLtqbjyQQqVwy/U+UmkQ5KIcWjJZGWkL79j1z1e29esNudieXqrX/yvGUW7Ng+4U3G2IHdWH3nMmjwx5/oBuOlc+6vu5aXdDRZVE12CJZ2X/uyJW2Ls5YLJsThAJ9roxsZ29MNZRihUOGk2lLDy/Q5TS6VcsaUmRaZHngM7Xt7v7pJLLbPIomgqGshPKxEXFO3vowZ7nXT1gJX0/FaBBqOahW0scGnxfZlJRZPWbvTg8gxhIGRxhNNYaX5Bb2hw8=
matrix:
allow_failures:
- php: master
before_script:
- if php --ri xdebug >/dev/null; then phpenv config-rm xdebug.ini; fi
install:
- if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --no-interaction; fi
- if [ "$dependencies" = "highest" ]; then composer update --no-interaction; fi
script:
- vendor/bin/phing
jobs:
include:
- stage: phar-test
script:
- git clone https://github.com/phpstan/phpstan-compiler && cd phpstan-compiler && ./run-e2e-tests.sh $TRAVIS_COMMIT
- stage: phar-push
script:
- |
git clone https://github.com/phpstan/phpstan-compiler && \
composer install --working-dir=phpstan-compiler && \
php phpstan-compiler/bin/compile $TRAVIS_COMMIT && \
git clone https://${GITHUB_TOKEN}@github.com/phpstan/phpstan-shim.git > /dev/null 2>&1 && \
cp phpstan-compiler/tmp/phpstan.phar phpstan-shim/phpstan.phar && \
cp phpstan-compiler/tmp/phpstan.phar phpstan-shim/phpstan && \
cd phpstan-shim && \
git config user.email "travis@travis-ci.org" && \
git config user.name "Travis CI" && \
git add phpstan phpstan.phar && \
git commit -m "Updated PHPStan to commit ${TRAVIS_COMMIT}" && \
git push --quiet origin master
cache:
directories:
- $HOME/.composer/cache
- tmp

65
vendor/phpstan/phpstan/BACKERS.md vendored Normal file
View file

@ -0,0 +1,65 @@
# Backers
Development of PHPStan is made possible thanks to these awesome backers!
You can become one of them by [pledging on Patreon](https://www.patreon.com/phpstan).
Check out all the tiers - higher ones include additional goodies like placing
the logo of your company in PHPStan's README.
# $50+
* MessageBird
# $10+
* Adam Lundrigan
* Scott Arciszewski
# $5+
* Adam Žurek
* Bart Reunes
* Carlos C Soto
* Craig Mayhew
* David Šolc
* Dennis Haarbrink
* Haralan Dobrev
* Ilija Tovilo
* Jake B
* Jakub Chábek
* Jakub Červený
* Jan Endel
* Jan Kuchař
* Lars Roettig
* Lukas Unger
* Masaru Yamagishi
* Michael Moll
* Pavel Vondrášek
* René Kliment
* Rudolph Gottesheim
* seagoj
* Stefan Zielke
* Thomas Daugaard
* Tomasz
* Tommy Muehle
* Vašek Brychta
* Woda Digital
# $1+
* Andrew Barlow
* Broken Bialek
* Christian Sjöström
* Craig Duncan
* Honza Cerny
* Ian Den Hartog
* Ivan Kvasnica
* korchasa
* Lucas Dos Santos Abreu
* Martin Lukeš
* Matej Drame
* Michal Mleczko
* Michał Włodarczyk
* Oliver Klee
* Ondrej Vodacek
* Wouter Admiraal

View file

@ -0,0 +1,74 @@
# Contributor Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project maintainer at <ondrej@mirtes.cz>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

21
vendor/phpstan/phpstan/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Ondřej Mirtes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

714
vendor/phpstan/phpstan/README.md vendored Normal file
View file

@ -0,0 +1,714 @@
<h1 align="center">PHPStan - PHP Static Analysis Tool</h1>
<p align="center">
<img src="https://i.imgur.com/MOt7taM.png" alt="PHPStan" width="300" height="300">
</p>
<p align="center">
<a href="https://travis-ci.org/phpstan/phpstan"><img src="https://travis-ci.org/phpstan/phpstan.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/phpstan/phpstan"><img src="https://poser.pugx.org/phpstan/phpstan/v/stable" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/phpstan/phpstan/stats"><img src="https://poser.pugx.org/phpstan/phpstan/downloads" alt="Total Downloads"></a>
<a href="https://choosealicense.com/licenses/mit/"><img src="https://poser.pugx.org/phpstan/phpstan/license" alt="License"></a>
<a href="https://github.com/phpstan/phpstan"><img src="https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat" alt="PHPStan Enabled"></a>
</p>
------
PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs
even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code
can be checked before you run the actual line.
**[Read more about PHPStan on Medium.com »](https://medium.com/@ondrejmirtes/phpstan-2939cd0ad0e3)**
**[Try out PHPStan on the on-line playground! »](https://phpstan.org/)**
## Sponsors
<a href="https://mike-pretzlaw.de/"><img src="https://i.imgur.com/TW2US6H.png" alt="Mike Pretzlaw" width="247" height="64"></a>
&nbsp;&nbsp;&nbsp;
<a href="https://coders.thecodingmachine.com/phpstan"><img src="https://i.imgur.com/kQhNOTP.png" alt="TheCodingMachine" width="247" height="64"></a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.wispay.io/t/JdL" target="_blank"><img src="https://assets.wispay.io/wgt2_d_o.png" width="247" height="78"></a>
Check out [PHPStan's Patreon](https://www.patreon.com/phpstan) for sponsoring options. One-time donations [through PayPal](https://paypal.me/phpstan) are also accepted. To request an invoice, [contact me](mailto:ondrej@mirtes.cz) through e-mail.
## Prerequisites
PHPStan requires PHP >= 7.1. You have to run it in environment with PHP 7.x but the actual code does not have to use
PHP 7.x features. (Code written for PHP 5.6 and earlier can run on 7.x mostly unmodified.)
PHPStan works best with modern object-oriented code. The more strongly-typed your code is, the more information
you give PHPStan to work with.
Properly annotated and typehinted code (class properties, function and method arguments, return types) helps
not only static analysis tools but also other people that work with the code to understand it.
## Installation
To start performing analysis on your code, require PHPStan in [Composer](https://getcomposer.org/):
```
composer require --dev phpstan/phpstan
```
Composer will install PHPStan's executable in its `bin-dir` which defaults to `vendor/bin`.
If you have conflicting dependencies or you want to install PHPStan globally, the best way is via a PHAR archive. You will always find the latest stable PHAR archive below the [release notes](https://github.com/phpstan/phpstan/releases). You can also use the [phpstan/phpstan-shim](https://packagist.org/packages/phpstan/phpstan-shim) package to install PHPStan via Composer without the risk of conflicting dependencies.
You can also use [PHPStan via Docker](https://github.com/phpstan/docker-image).
## First run
To let PHPStan analyse your codebase, you have to use the `analyse` command and point it to the right directories.
So, for example if you have your classes in directories `src` and `tests`, you can run PHPStan like this:
```bash
vendor/bin/phpstan analyse src tests
```
PHPStan will probably find some errors, but don't worry, your code might be just fine. Errors found
on the first run tend to be:
* Extra arguments passed to functions (e. g. function requires two arguments, the code passes three)
* Extra arguments passed to print/sprintf functions (e. g. format string contains one placeholder, the code passes two values to replace)
* Obvious errors in dead code
* Magic behaviour that needs to be defined. See [Extensibility](#extensibility).
After fixing the obvious mistakes in the code, look to the following section
for all the configuration options that will bring the number of reported errors to zero
making PHPStan suitable to run as part of your continuous integration script.
## Rule levels
If you want to use PHPStan but your codebase isn't up to speed with strong typing
and PHPStan's strict checks, you can choose from currently 8 levels
(0 is the loosest and 7 is the strictest) by passing `--level` to `analyse` command. Default level is `0`.
This feature enables incremental adoption of PHPStan checks. You can start using PHPStan
with a lower rule level and increase it when you feel like it.
You can also use `--level max` as an alias for the highest level. This will ensure that you will always use the highest level when upgrading to new versions of PHPStan. Please note that this can create a significant obstacle when upgrading to a newer version because you might have to fix a lot of code to bring the number of errors down to zero.
## Extensibility
Unique feature of PHPStan is the ability to define and statically check "magic" behaviour of classes -
accessing properties that are not defined in the class but are created in `__get` and `__set`
and invoking methods using `__call`.
See [Class reflection extensions](#class-reflection-extensions), [Dynamic return type extensions](#dynamic-return-type-extensions) and [Type-specifying extensions](#type-specifying-extensions).
You can also install official framework-specific extensions:
* [Doctrine](https://github.com/phpstan/phpstan-doctrine)
* [PHPUnit](https://github.com/phpstan/phpstan-phpunit)
* [Nette Framework](https://github.com/phpstan/phpstan-nette)
* [Dibi - Database Abstraction Library](https://github.com/phpstan/phpstan-dibi)
* [PHP-Parser](https://github.com/phpstan/phpstan-php-parser)
* [beberlei/assert](https://github.com/phpstan/phpstan-beberlei-assert)
* [webmozart/assert](https://github.com/phpstan/phpstan-webmozart-assert)
* [Symfony Framework](https://github.com/phpstan/phpstan-symfony)
* [Mockery](https://github.com/phpstan/phpstan-mockery)
Unofficial extensions for other frameworks and libraries are also available:
* [Phony](https://github.com/eloquent/phpstan-phony)
* [Prophecy](https://github.com/Jan0707/phpstan-prophecy)
* [Laravel](https://github.com/nunomaduro/larastan)
* [myclabs/php-enum](https://github.com/timeweb/phpstan-enum)
* [Yii2](https://github.com/proget-hq/phpstan-yii2)
* [PhpSpec](https://github.com/proget-hq/phpstan-phpspec)
* [TYPO3](https://github.com/sascha-egerer/phpstan-typo3)
New extensions are becoming available on a regular basis!
## Configuration
Config file is passed to the `phpstan` executable with `-c` option:
```bash
vendor/bin/phpstan analyse -l 4 -c phpstan.neon src tests
```
When using a custom project config file, you have to pass the `--level` (`-l`)
option to `analyse` command (default value does not apply here).
If you do not provide config file explicitly, PHPStan will look for
files named `phpstan.neon` or `phpstan.neon.dist` in current directory.
The resolution priority is as such:
1. If config file is provided on command line, it is used.
2. If config file `phpstan.neon` exists in current directory, it will be used.
3. If config file `phpstan.neon.dist` exists in current directory, it will be used.
4. If none of the above is true, no config will be used.
[NEON file format](https://ne-on.org/) is very similar to YAML.
All the following options are part of the `parameters` section.
#### Configuration variables
- `%rootDir%` - root directory where PHPStan resides (i.e. `vendor/phpstan/phpstan` in Composer installation)
- `%currentWorkingDirectory%` - current working directory where PHPStan was executed
#### Configuration options
- `tmpDir` - specifies the temporary directory used by PHPStan cache (defaults to `sys_get_temp_dir() . '/phpstan'`)
- `level` - specifies analysis level - if specified, `-l` option is not required
- `paths` - specifies analysed paths - if specified, paths are not required to be passed as arguments
### Autoloading
PHPStan uses Composer autoloader so the easiest way how to autoload classes
is through the `autoload`/`autoload-dev` sections in composer.json.
#### Specify paths to scan
If PHPStan complains about some non-existent classes and you're sure the classes
exist in the codebase AND you don't want to use Composer autoloader for some reason,
you can specify directories to scan and concrete files to include using
`autoload_directories` and `autoload_files` array parameters:
```
parameters:
autoload_directories:
- %rootDir%/../../../build
autoload_files:
- %rootDir%/../../../generated/routes/GeneratedRouteList.php
```
`%rootDir%` is expanded to the root directory where PHPStan resides.
#### Autoloading for global installation
PHPStan supports global installation using [`composer global`](https://getcomposer.org/doc/03-cli.md#global) or via a [PHAR archive](#installation).
In this case, it's not part of the project autoloader, but it supports autodiscovery of the Composer autoloader
from current working directory residing in `vendor/`:
```bash
cd /path/to/project
phpstan analyse src tests # looks for autoloader at /path/to/project/vendor/autoload.php
```
If you have your dependencies installed at a different path
or you're running PHPStan from a different directory,
you can specify the path to the autoloader with the `--autoload-file|-a` option:
```bash
phpstan analyse --autoload-file=/path/to/autoload.php src tests
```
### Exclude files from analysis
If your codebase contains some files that are broken on purpose
(e. g. to test behaviour of your application on files with invalid PHP code),
you can exclude them using the `excludes_analyse` array parameter. String at each line
is used as a pattern for the [`fnmatch`](https://secure.php.net/manual/en/function.fnmatch.php) function.
```
parameters:
excludes_analyse:
- %rootDir%/../../../tests/*/data/*
```
### Include custom extensions
If your codebase contains php files with extensions other than the standard .php extension then you can add them
to the `fileExtensions` array parameter:
```
parameters:
fileExtensions:
- php
- module
- inc
```
### Universal object crates
Classes without predefined structure are common in PHP applications.
They are used as universal holders of data - any property can be set and read on them. Notable examples
include `stdClass`, `SimpleXMLElement` (these are enabled by default), objects with results of database queries etc.
Use `universalObjectCratesClasses` array parameter to let PHPStan know which classes
with these characteristics are used in your codebase:
```
parameters:
universalObjectCratesClasses:
- Dibi\Row
- Ratchet\ConnectionInterface
```
### Add non-obviously assigned variables to scope
If you use some variables from a try block in your catch blocks, set `polluteCatchScopeWithTryAssignments` boolean parameter to `true`.
```php
try {
$author = $this->getLoggedInUser();
$post = $this->postRepository->getById($id);
} catch (PostNotFoundException $e) {
// $author is probably defined here
throw new ArticleByAuthorCannotBePublished($author);
}
```
If you are enumerating over all possible situations in if-elseif branches
and PHPStan complains about undefined variables after the conditions, you can write
an else branch with throwing an exception:
```php
if (somethingIsTrue()) {
$foo = true;
} elseif (orSomethingElseIsTrue()) {
$foo = false;
} else {
throw new ShouldNotHappenException();
}
doFoo($foo);
```
I recommend leaving `polluteCatchScopeWithTryAssignments` set to `false` because it leads to a clearer and more maintainable code.
### Custom early terminating method calls
Previous example showed that if a condition branches end with throwing an exception, that branch does not have
to define a variable used after the condition branches end.
But exceptions are not the only way how to terminate execution of a method early. Some specific method calls
can be perceived by project developers also as early terminating - like a `redirect()` that stops execution
by throwing an internal exception.
```php
if (somethingIsTrue()) {
$foo = true;
} elseif (orSomethingElseIsTrue()) {
$foo = false;
} else {
$this->redirect('homepage');
}
doFoo($foo);
```
These methods can be configured by specifying a class on whose instance they are called like this:
```
parameters:
earlyTerminatingMethodCalls:
Nette\Application\UI\Presenter:
- redirect
- redirectUrl
- sendJson
- sendResponse
```
### Ignore error messages with regular expressions
If some issue in your code base is not easy to fix or just simply want to deal with it later,
you can exclude error messages from the analysis result with regular expressions:
```
parameters:
ignoreErrors:
- '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#'
- '#Call to an undefined method [a-zA-Z0-9\\_]+::expects\(\)#'
- '#Access to an undefined property PHPUnit_Framework_MockObject_MockObject::\$[a-zA-Z0-9_]+#'
- '#Call to an undefined method PHPUnit_Framework_MockObject_MockObject::[a-zA-Z0-9_]+\(\)#'
```
To exclude an error in a specific directory or file, specify a `path` along with the `message`:
```
parameters:
ignoreErrors:
-
message: '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#'
path: %currentWorkingDirectory%/some/dir/SomeFile.php
-
message: '#Call to an undefined method [a-zA-Z0-9\\_]+::method\(\)#'
path: %currentWorkingDirectory%/other/dir/*
- '#Other error to catch anywhere#'
```
If some of the patterns do not occur in the result anymore, PHPStan will let you know
and you will have to remove the pattern from the configuration. You can turn off
this behaviour by setting `reportUnmatchedIgnoredErrors` to `false` in PHPStan configuration.
### Bootstrap file
If you need to initialize something in PHP runtime before PHPStan runs (like your own autoloader),
you can provide your own bootstrap file:
```
parameters:
bootstrap: %rootDir%/../../../phpstan-bootstrap.php
```
### Custom rules
PHPStan allows writing custom rules to check for specific situations in your own codebase. Your rule class
needs to implement the `PHPStan\Rules\Rule` interface and registered as a service in the configuration file:
```
services:
-
class: MyApp\PHPStan\Rules\DefaultValueTypesAssignedToPropertiesRule
tags:
- phpstan.rules.rule
```
For inspiration on how to implement a rule turn to [src/Rules](https://github.com/phpstan/phpstan/tree/master/src/Rules)
to see a lot of built-in rules.
Check out also [phpstan-strict-rules](https://github.com/phpstan/phpstan-strict-rules) repository for extra strict and opinionated rules for PHPStan!
### Custom error formatters
PHPStan outputs errors via formatters. You can customize the output by implementing the `ErrorFormatter` interface in a new class and add it to the configuration. For existing formatters, see next chapter.
```php
interface ErrorFormatter
{
/**
* Formats the errors and outputs them to the console.
*
* @param \PHPStan\Command\AnalysisResult $analysisResult
* @param \Symfony\Component\Console\Style\OutputStyle $style
* @return int Error code.
*/
public function formatErrors(
AnalysisResult $analysisResult,
\Symfony\Component\Console\Style\OutputStyle $style
): int;
}
```
Register the formatter in your `phpstan.neon`:
```
services:
errorFormatter.awesome:
class: App\PHPStan\AwesomeErrorFormatter
```
Use the name part after `errorFormatter.` as the CLI option value:
```bash
vendor/bin/phpstan analyse -c phpstan.neon -l 4 --error-format awesome src tests
```
### Existing error formatters to be used
You can pass the following keywords to the `--error-format=X` parameter in order to affect the output:
- `table`: Default. Grouped errors by file, colorized. For human consumption.
- `raw`: Contains one error per line, with path to file, line number, and error description
- `checkstyle`: Creates a checkstyle.xml compatible output. Note that you'd have to redirect output into a file in order to capture the results for later processing.
## Class reflection extensions
Classes in PHP can expose "magical" properties and methods decided in run-time using
class methods like `__get`, `__set` and `__call`. Because PHPStan is all about static analysis
(testing code for errors without running it), it has to know about those properties and methods beforehand.
When PHPStan stumbles upon a property or a method that is unknown to built-in class reflection, it iterates
over all registered class reflection extensions until it finds one that defines the property or method.
Class reflection extension cannot have `PHPStan\Broker\Broker` (service for obtaining class reflections) injected in the constructor due to circular reference issue, but the extensions can implement `PHPStan\Reflection\BrokerAwareExtension` interface to obtain Broker via a setter.
### Properties class reflection extensions
This extension type must implement the following interface:
```php
namespace PHPStan\Reflection;
interface PropertiesClassReflectionExtension
{
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool;
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection;
}
```
Most likely you will also have to implement a new `PropertyReflection` class:
```php
namespace PHPStan\Reflection;
interface PropertyReflection
{
public function getType(): Type;
public function getDeclaringClass(): ClassReflection;
public function isStatic(): bool;
public function isPrivate(): bool;
public function isPublic(): bool;
}
```
This is how you register the extension in project's PHPStan config file:
```
services:
-
class: App\PHPStan\PropertiesFromAnnotationsClassReflectionExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
```
### Methods class reflection extensions
This extension type must implement the following interface:
```php
namespace PHPStan\Reflection;
interface MethodsClassReflectionExtension
{
public function hasMethod(ClassReflection $classReflection, string $methodName): bool;
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection;
}
```
Most likely you will also have to implement a new `MethodReflection` class:
```php
namespace PHPStan\Reflection;
interface MethodReflection
{
public function getDeclaringClass(): ClassReflection;
public function getPrototype(): self;
public function isStatic(): bool;
public function isPrivate(): bool;
public function isPublic(): bool;
public function getName(): string;
/**
* @return \PHPStan\Reflection\ParameterReflection[]
*/
public function getParameters(): array;
public function isVariadic(): bool;
public function getReturnType(): Type;
}
```
This is how you register the extension in project's PHPStan config file:
```
services:
-
class: App\PHPStan\EnumMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension
```
## Dynamic return type extensions
If the return type of a method is not always the same, but depends on an argument passed to the method,
you can specify the return type by writing and registering an extension.
Because you have to write the code with the type-resolving logic, it can be as complex as you want.
After writing the sample extension, the variable `$mergedArticle` will have the correct type:
```php
$mergedArticle = $this->entityManager->merge($article);
// $mergedArticle will have the same type as $article
```
This is the interface for dynamic return type extension:
```php
namespace PHPStan\Type;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
interface DynamicMethodReturnTypeExtension
{
public function getClass(): string;
public function isMethodSupported(MethodReflection $methodReflection): bool;
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type;
}
```
And this is how you'd write the extension that correctly resolves the EntityManager::merge() return type:
```php
public function getClass(): string
{
return \Doctrine\ORM\EntityManager::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'merge';
}
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
if (count($methodCall->args) === 0) {
return \PHPStan\Reflection\ParametersAcceptorSelector::selectFromArgs(
$scope,
$methodCall->args,
$methodReflection->getVariants()
)->getReturnType();
}
$arg = $methodCall->args[0]->value;
return $scope->getType($arg);
}
```
And finally, register the extension to PHPStan in the project's config file:
```
services:
-
class: App\PHPStan\EntityManagerDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
```
There's also an analogous functionality for:
* **static methods** using `DynamicStaticMethodReturnTypeExtension` interface
and `phpstan.broker.dynamicStaticMethodReturnTypeExtension` service tag.
* **functions** using `DynamicFunctionReturnTypeExtension` interface and `phpstan.broker.dynamicFunctionReturnTypeExtension` service tag.
## Type-specifying extensions
These extensions allow you to specify types of expressions based on certain pre-existing conditions. This is best illustrated with couple examples:
```php
if (is_int($variable)) {
// here we can be sure that $variable is integer
}
```
```php
// using PHPUnit's asserts
self::assertNotNull($variable);
// here we can be sure that $variable is not null
```
Type-specifying extension cannot have `PHPStan\Analyser\TypeSpecifier` injected in the constructor due to circular reference issue, but the extensions can implement `PHPStan\Analyser\TypeSpecifierAwareExtension` interface to obtain TypeSpecifier via a setter.
This is the interface for type-specifying extension:
```php
namespace PHPStan\Type;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\MethodReflection;
interface StaticMethodTypeSpecifyingExtension
{
public function getClass(): string;
public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool;
public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes;
}
```
And this is how you'd write the extension for the second example above:
```php
public function getClass(): string
{
return \PHPUnit\Framework\Assert::class;
}
public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context): bool;
{
// The $context argument tells us if we're in an if condition or not (as in this case).
return $staticMethodReflection->getName() === 'assertNotNull' && $context->null();
}
public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
{
// Assuming extension implements \PHPStan\Analyser\TypeSpecifierAwareExtension.
return $this->typeSpecifier->create($node->var, \PHPStan\Type\TypeCombinator::removeNull($scope->getType($node->var)), $context);
}
```
And finally, register the extension to PHPStan in the project's config file:
```
services:
-
class: App\PHPStan\AssertNotNullTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
```
There's also an analogous functionality for:
* **dynamic methods** using `MethodTypeSpecifyingExtension` interface
and `phpstan.typeSpecifier.methodTypeSpecifyingExtension` service tag.
* **functions** using `FunctionTypeSpecifyingExtension` interface and `phpstan.typeSpecifier.functionTypeSpecifyingExtension` service tag.
## Known issues
* If `include` or `require` are used in the analysed code (instead of `include_once` or `require_once`),
PHPStan will throw `Cannot redeclare class` error. Use the `_once` variants to avoid this error.
* If PHPStan crashes without outputting any error, it's quite possible that it's
because of a low memory limit set on your system. **Run PHPStan again** to read a couple of hints
what you can do to prevent the crashes.
## Code of Conduct
This project adheres to a [Contributor Code of Conduct](https://github.com/phpstan/phpstan/blob/master/CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code.
## Contributing
Any contributions are welcome.
### Building
You can either run the whole build including linting and coding standards using
```bash
vendor/bin/phing
```
or run only tests using
```bash
vendor/bin/phing tests
```

70
vendor/phpstan/phpstan/appveyor.yml vendored Normal file
View file

@ -0,0 +1,70 @@
build: false
clone_folder: c:\projects\phpstan
clone_depth: 1
platform:
- x64
environment:
matrix:
- dependencies: lowest
php_version: 7.1
- dependencies: highest
php_version: 7.1
- dependencies: lowest
php_version: 7.2
- dependencies: highest
php_version: 7.2
- dependencies: lowest
php_version: 7.3
- dependencies: highest
php_version: 7.3
project_directory: c:\projects\phpstan
composer_directory: c:\tools\composer
composer_executable: c:\tools\composer\composer.phar
composer_installer: c:\tools\composer\installer.php
php_root_directory: c:\tools\php
cache:
- c:\ProgramData\chocolatey\bin -> appveyor.yml
- c:\ProgramData\chocolatey\lib -> appveyor.yml
- c:\tools\composer -> appveyor.yml
- '%LOCALAPPDATA%\Composer -> appveyor.yml'
- c:\tools\php -> appveyor.yml
init:
- ps: $Env:php_directory = $Env:php_root_directory + '\' + $Env:php_version
- ps: $Env:exact_php_version = (((choco search php --exact --all-versions --limit-output | Select-String -pattern $Env:php_version) -replace '[php|]', '') | %{ New-Object System.Version $_ } | Sort-Object | Select-Object -Last 1).ToString()
- ps: $Env:PATH = $Env:php_directory + ';' + $Env:composer_directory + ';' + $Env:PATH
- ps: $Env:COMPOSER_NO_INTERACTION = 1
- ps: $Env:ANSICON = '121x90 (121x90)'
install:
# Install PHP
- ps: If ((Test-Path $Env:php_directory) -eq $False) { New-Item -Path $Env:php_directory -ItemType 'directory' }
- ps: $php_install_parameters = '"/DontAddToPath /InstallDir:' + $Env:php_directory + '"'
- ps: appveyor-retry choco upgrade php --yes --version=$Env:exact_php_version --params=$php_install_parameters
# Prepare PHP
- ps: cd $Env:php_directory
- ps: Copy-Item php.ini-production -Destination php.ini
- ps: Add-Content -Path php.ini -Value 'memory_limit=1G'
- ps: Add-Content -Path php.ini -Value 'date.timezone="UTC"'
- ps: Add-Content -Path php.ini -Value 'extension_dir=ext'
- ps: Add-Content -Path php.ini -Value 'extension=php_curl.dll'
- ps: Add-Content -Path php.ini -Value 'extension=php_mbstring.dll'
- ps: Add-Content -Path php.ini -Value 'extension=php_openssl.dll'
- ps: Add-Content -Path php.ini -Value 'extension=php_soap.dll'
- ps: Add-Content -Path php.ini -Value 'extension=php_mysqli.dll'
- ps: Add-Content -Path php.ini -Value 'extension=php_intl.dll'
- ps: php --version
# Prepare composer
- ps: If ((Test-Path $Env:composer_directory) -eq $False) { New-Item -Path $Env:composer_directory -ItemType 'directory' }
- ps: If ((Test-Path $Env:composer_installer) -eq $False) { appveyor-retry appveyor DownloadFile https://getcomposer.org/installer -FileName $Env:composer_installer }
- ps: If ((Test-Path $Env:composer_executable) -eq $False) { php $Env:composer_installer --install-dir=$Env:composer_directory }
- ps: Set-Content -Path ($Env:composer_directory + '\composer.bat') -Value ('@php ' + $Env:composer_executable + ' %*')
# Install dependencies
- ps: cd $Env:project_directory
- IF %dependencies%==lowest composer update --prefer-dist --prefer-lowest --prefer-stable --no-progress
- IF %dependencies%==highest composer update --prefer-dist --no-progress
test_script:
- ps: cd $Env:project_directory
- vendor\bin\phing

41
vendor/phpstan/phpstan/bin/phpstan vendored Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
use Composer\XdebugHandler\XdebugHandler;
use PHPStan\Command\AnalyseCommand;
use PHPStan\Command\DumpDependenciesCommand;
gc_disable(); // performance boost
define('__PHPSTAN_RUNNING__', true);
$autoloaderInWorkingDirectory = getcwd() . '/vendor/autoload.php';
if (is_file($autoloaderInWorkingDirectory)) {
require_once $autoloaderInWorkingDirectory;
}
$composerAutoloadFile = __DIR__ . '/../vendor/autoload.php';
if (!is_file($composerAutoloadFile)) {
$composerAutoloadFile = __DIR__ . '/../../../autoload.php';
}
require_once $composerAutoloadFile;
$xdebug = new XdebugHandler('phpstan', '--ansi');
$xdebug->check();
unset($xdebug);
$version = 'Version unknown';
try {
$version = \Jean85\PrettyVersions::getVersion('phpstan/phpstan')->getPrettyVersion();
} catch (\OutOfBoundsException $e) {
}
$application = new \Symfony\Component\Console\Application(
'PHPStan - PHP Static Analysis Tool',
$version
);
$application->add(new AnalyseCommand());
$application->add(new DumpDependenciesCommand());
$application->run();

244
vendor/phpstan/phpstan/build.xml vendored Normal file
View file

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<project name="PHPStan" default="check">
<property name="path.composer-require-checker"
value="tmp/composer-require-checker-1.1.0.phar"/>
<property name="url.composer-require-checker"
value="https://github.com/maglnet/ComposerRequireChecker/releases/download/1.1.0/composer-require-checker.phar"/>
<target name="check" depends="
composer-validate,
composer-install,
lint,
cs,
composer-normalize-check,
composer-require-checker,
test-configuration-validate,
tests,
phpstan
"/>
<target name="composer-validate">
<exec
executable="composer"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="validate"/>
<arg value="--ansi"/>
</exec>
</target>
<target name="composer-install">
<exec
executable="composer"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="install"/>
<arg value="--ansi"/>
</exec>
</target>
<target name="composer-normalize-check">
<exec
executable="composer"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="normalize"/>
<arg value="--ansi"/>
<arg value="--dry-run"/>
</exec>
</target>
<target name="composer-normalize-fix">
<exec
executable="composer"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="normalize"/>
<arg value="--ansi"/>
</exec>
</target>
<target name="composer-require-checker">
<if>
<and>
<not>
<available file="${path.composer-require-checker}"/>
</not>
<not><os family="windows"/></not>
</and>
<then>
<exec
executable="wget"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="-O"/>
<arg path="${path.composer-require-checker}"/>
<arg value="${url.composer-require-checker}"/>
</exec>
</then>
</if>
<if>
<available file="${path.composer-require-checker}"/>
<then>
<exec
executable="php"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg path="${path.composer-require-checker}"/>
<arg value="check"/>
<arg value="--config-file"/>
<arg path="${project.basedir}/build/composer-require-checker.json"/>
<arg path="composer.json"/>
</exec>
</then>
</if>
</target>
<target name="lint">
<exec
executable="vendor/bin/parallel-lint"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="--colors"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Analyser/data"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Rules/Methods/data"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Rules/Functions/data"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php"/>
<arg value="--exclude"/>
<arg path="tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php"/>
<arg path="src" />
<arg path="tests" />
</exec>
</target>
<target name="cs">
<exec
executable="vendor/bin/phpcs"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="--colors"/>
<arg value="--extensions=php"/>
<arg value="--encoding=utf-8"/>
<arg value="--tab-width=4"/>
<arg value="--cache=tmp/cache/phpcs"/>
<arg value="--ignore=tests/*/data,tests/*/traits,tests/notAutoloaded,src/Reflection/SignatureMap/functionMap.php"/>
<arg value="-sp"/>
<arg path="src"/>
<arg path="tests"/>
</exec>
</target>
<target name="cs-fix">
<exec
executable="vendor/bin/phpcbf"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="--colors"/>
<arg value="--extensions=php"/>
<arg value="--encoding=utf-8"/>
<arg value="--tab-width=4"/>
<arg value="-sp"/>
<arg path="src"/>
<arg path="tests"/>
</exec>
</target>
<target name="test-configuration-validate" depends="composer-install">
<xmllint schema="vendor/phpunit/phpunit/phpunit.xsd" file="tests/phpunit.xml"/>
</target>
<target name="tests">
<if>
<os family="windows"/>
<then>
<property name="phpunit.executable" value="vendor/bin/phpunit"/>
</then>
<else>
<property name="phpunit.executable" value="vendor/bin/paratest"/>
</else>
</if>
<exec
executable="${phpunit.executable}"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="-c"/>
<arg value="tests/phpunit.xml"/>
<arg path="tests"/>
</exec>
</target>
<target name="phpstan">
<property name="phpstan.config" value="build/phpstan-generated.neon"/>
<php expression="PHP_VERSION_ID &gt;= 70300 ?'true':'false'" returnProperty="isPHP73" level="verbose" />
<touch file="${phpstan.config}"/>
<append
destFile="${phpstan.config}"
text="includes: [ phpstan.neon"
append="false"
></append>
<if>
<os family="windows" />
<then>
<append
destFile="${phpstan.config}"
text=", ignore-windows-errors.neon"
></append>
</then>
</if>
<if>
<equals arg1="${isPHP73}" arg2="false" />
<then>
<append
destFile="${phpstan.config}"
text=", ignore-lt-php7.3-errors.neon"
></append>
</then>
</if>
<append
destFile="${phpstan.config}"
text=" ]"
></append>
<exec
executable="php"
logoutput="true"
passthru="true"
checkreturn="true"
>
<arg value="-d"/>
<arg value="memory_limit=512M"/>
<arg path="bin/phpstan"/>
<arg value="analyse"/>
<arg value="-c"/>
<arg path="${phpstan.config}"/>
<arg value="-l"/>
<arg value="7"/>
<arg path="build/PHPStan"/>
<arg path="src"/>
<arg path="tests"/>
</exec>
</target>
</project>

68
vendor/phpstan/phpstan/composer.json vendored Normal file
View file

@ -0,0 +1,68 @@
{
"name": "phpstan/phpstan",
"description": "PHPStan - PHP Static Analysis Tool",
"license": [
"MIT"
],
"require": {
"php": "~7.1",
"composer/xdebug-handler": "^1.3.0",
"jean85/pretty-package-versions": "^1.0.3",
"nette/bootstrap": "^2.4 || ^3.0",
"nette/di": "^2.4.7 || ^3.0",
"nette/robot-loader": "^3.0.1",
"nette/utils": "^2.4.5 || ^3.0",
"nikic/php-parser": "^4.0.2",
"phpstan/phpdoc-parser": "^0.3",
"symfony/console": "~3.2 || ~4.0",
"symfony/finder": "~3.2 || ~4.0"
},
"conflict": {
"symfony/console": "3.4.16 || 4.1.5"
},
"require-dev": {
"ext-intl": "*",
"ext-mysqli": "*",
"ext-soap": "*",
"ext-zip": "*",
"brianium/paratest": "^2.0",
"consistence/coding-standard": "^3.5",
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.4",
"jakub-onderka/php-parallel-lint": "^1.0",
"localheinz/composer-normalize": "^1.1.0",
"phing/phing": "^2.16.0",
"phpstan/phpstan-deprecation-rules": "^0.11",
"phpstan/phpstan-php-parser": "^0.11",
"phpstan/phpstan-phpunit": "^0.11",
"phpstan/phpstan-strict-rules": "^0.11",
"phpunit/phpunit": "^7.0",
"slevomat/coding-standard": "^4.7.2",
"squizlabs/php_codesniffer": "^3.3.2"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "0.11-dev"
}
},
"autoload": {
"psr-4": {
"PHPStan\\": [
"src/",
"build/PHPStan"
]
}
},
"autoload-dev": {
"classmap": [
"tests/PHPStan"
]
},
"minimum-stability": "dev",
"prefer-stable": true,
"bin": [
"bin/phpstan"
]
}

View file

@ -0,0 +1,132 @@
parameters:
customRulesetUsed: false
rules:
- PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule
- PHPStan\Rules\Arrays\OffsetAccessWithoutDimForReadingRule
- PHPStan\Rules\Classes\ClassConstantDeclarationRule
- PHPStan\Rules\Classes\ClassConstantRule
- PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule
- PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule
- PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
- PHPStan\Rules\Classes\ExistingClassInTraitUseRule
- PHPStan\Rules\Classes\InstantiationRule
- PHPStan\Rules\Classes\RequireParentConstructCallRule
- PHPStan\Rules\Classes\UnusedConstructorParametersRule
- PHPStan\Rules\Functions\CallToFunctionParametersRule
- PHPStan\Rules\Functions\ExistingClassesInClosureTypehintsRule
- PHPStan\Rules\Functions\ExistingClassesInTypehintsRule
- PHPStan\Rules\Functions\InnerFunctionRule
- PHPStan\Rules\Functions\NonExistentDefinedFunctionRule
- PHPStan\Rules\Functions\PrintfParametersRule
- PHPStan\Rules\Functions\UnusedClosureUsesRule
- PHPStan\Rules\Methods\ExistingClassesInTypehintsRule
- PHPStan\Rules\Properties\AccessStaticPropertiesRule
- PHPStan\Rules\Variables\ThisVariableRule
services:
-
class: PHPStan\Rules\Classes\ExistingClassInInstanceOfRule
tags:
- phpstan.rules.rule
arguments:
checkClassCaseSensitivity: %checkClassCaseSensitivity%
-
class: PHPStan\Rules\Exceptions\CaughtExceptionExistenceRule
tags:
- phpstan.rules.rule
arguments:
checkClassCaseSensitivity: %checkClassCaseSensitivity%
-
class: PHPStan\Rules\Functions\CallToNonExistentFunctionRule
tags:
- phpstan.rules.rule
arguments:
checkFunctionNameCase: %checkFunctionNameCase%
-
class: PHPStan\Rules\Methods\CallMethodsRule
tags:
- phpstan.rules.rule
arguments:
checkFunctionNameCase: %checkFunctionNameCase%
reportMagicMethods: %reportMagicMethods%
-
class: PHPStan\Rules\Methods\CallStaticMethodsRule
tags:
- phpstan.rules.rule
arguments:
checkFunctionNameCase: %checkFunctionNameCase%
reportMagicMethods: %reportMagicMethods%
-
class: PHPStan\Rules\Namespaces\ExistingNamesInGroupUseRule
tags:
- phpstan.rules.rule
arguments:
checkFunctionNameCase: %checkFunctionNameCase%
-
class: PHPStan\Rules\Namespaces\ExistingNamesInUseRule
tags:
- phpstan.rules.rule
arguments:
checkFunctionNameCase: %checkFunctionNameCase%
-
class: PHPStan\Rules\Operators\InvalidIncDecOperationRule
tags:
- phpstan.rules.rule
arguments:
checkThisOnly: %checkThisOnly%
-
class: PHPStan\Rules\Properties\AccessPropertiesRule
tags:
- phpstan.rules.rule
arguments:
reportMagicProperties: %reportMagicProperties%
-
class: PHPStan\Rules\Properties\ExistingClassesInPropertiesRule
tags:
- phpstan.rules.rule
arguments:
checkClassCaseSensitivity: %checkClassCaseSensitivity%
-
class: PHPStan\Rules\Properties\WritingToReadOnlyPropertiesRule
arguments:
checkThisOnly: %checkThisOnly%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Properties\ReadingWriteOnlyPropertiesRule
arguments:
checkThisOnly: %checkThisOnly%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Variables\DefinedVariableRule
arguments:
cliArgumentsVariablesRegistered: %cliArgumentsVariablesRegistered%
checkMaybeUndefinedVariables: %checkMaybeUndefinedVariables%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Variables\DefinedVariableInAnonymousFunctionUseRule
arguments:
checkMaybeUndefinedVariables: %checkMaybeUndefinedVariables%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Regexp\RegularExpressionPatternRule
tags:
- phpstan.rules.rule

View file

@ -0,0 +1,11 @@
includes:
- config.level0.neon
parameters:
checkMaybeUndefinedVariables: true
reportMagicMethods: true
reportMagicProperties: true
rules:
- PHPStan\Rules\Constants\ConstantRule
- PHPStan\Rules\Variables\VariableCertaintyInIssetRule

View file

@ -0,0 +1,29 @@
includes:
- config.level1.neon
parameters:
checkClassCaseSensitivity: true
checkThisOnly: false
rules:
- PHPStan\Rules\Cast\EchoRule
- PHPStan\Rules\Cast\InvalidCastRule
- PHPStan\Rules\Cast\InvalidPartOfEncapsedStringRule
- PHPStan\Rules\Cast\PrintRule
- PHPStan\Rules\Functions\IncompatibleDefaultParameterTypeRule
- PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule
- PHPStan\Rules\Operators\InvalidBinaryOperationRule
- PHPStan\Rules\Operators\InvalidUnaryOperationRule
- PHPStan\Rules\Operators\InvalidComparisonOperationRule
- PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule
- PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule
- PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule
- PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule
services:
-
class: PHPStan\Rules\Functions\CallCallablesRule
arguments:
reportMaybes: %reportMaybes%
tags:
- phpstan.rules.rule

View file

@ -0,0 +1,52 @@
includes:
- config.level2.neon
rules:
- PHPStan\Rules\Arrays\AppendedArrayItemTypeRule
- PHPStan\Rules\Arrays\IterableInForeachRule
- PHPStan\Rules\Arrays\OffsetAccessAssignmentRule
- PHPStan\Rules\Arrays\OffsetAccessAssignOpRule
- PHPStan\Rules\Functions\ClosureReturnTypeRule
- PHPStan\Rules\Functions\ReturnTypeRule
- PHPStan\Rules\Methods\ReturnTypeRule
- PHPStan\Rules\Properties\DefaultValueTypesAssignedToPropertiesRule
- PHPStan\Rules\Properties\TypesAssignedToPropertiesRule
- PHPStan\Rules\Variables\ThrowTypeRule
- PHPStan\Rules\Variables\VariableCloningRule
services:
-
class: PHPStan\Rules\Arrays\AppendedArrayKeyTypeRule
arguments:
checkUnionTypes: %checkUnionTypes%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Arrays\InvalidKeyInArrayDimFetchRule
arguments:
reportMaybes: %reportMaybes%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Arrays\InvalidKeyInArrayItemRule
arguments:
reportMaybes: %reportMaybes%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Arrays\NonexistentOffsetInArrayDimFetchRule
arguments:
reportMaybes: %reportMaybes%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Methods\MethodSignatureRule
arguments:
reportMaybes: %reportMaybesInMethodSignatures%
reportStatic: %reportStaticMethodSignatures%
tags:
- phpstan.rules.rule

View file

@ -0,0 +1,49 @@
includes:
- config.level3.neon
rules:
- PHPStan\Rules\Arrays\DeadForeachRule
- PHPStan\Rules\Comparison\BooleanAndConstantConditionRule
- PHPStan\Rules\Comparison\BooleanNotConstantConditionRule
- PHPStan\Rules\Comparison\BooleanOrConstantConditionRule
- PHPStan\Rules\Comparison\ElseIfConstantConditionRule
- PHPStan\Rules\Comparison\IfConstantConditionRule
- PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule
- PHPStan\Rules\Comparison\UnreachableIfBranchesRule
- PHPStan\Rules\Comparison\UnreachableTernaryElseBranchRule
services:
-
class: PHPStan\Rules\Classes\ImpossibleInstanceOfRule
arguments:
checkAlwaysTrueInstanceof: %checkAlwaysTrueInstanceof%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeFunctionCallRule
arguments:
checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule
arguments:
checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeStaticMethodCallRule
arguments:
checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\StrictComparisonOfDifferentTypesRule
arguments:
checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison%
tags:
- phpstan.rules.rule

View file

@ -0,0 +1,6 @@
includes:
- config.level4.neon
parameters:
checkFunctionArgumentTypes: true
checkArgumentsPassedByReference: true

View file

@ -0,0 +1,6 @@
includes:
- config.level5.neon
parameters:
checkUnionTypes: true
reportMaybes: true

View file

@ -0,0 +1,5 @@
includes:
- config.level6.neon
parameters:
checkNullables: true

View file

@ -0,0 +1,2 @@
includes:
- config.level7.neon

630
vendor/phpstan/phpstan/conf/config.neon vendored Normal file
View file

@ -0,0 +1,630 @@
parameters:
bootstrap: null
excludes_analyse: []
autoload_directories: []
autoload_files: []
fileExtensions:
- php
checkAlwaysTrueCheckTypeFunctionCall: false
checkAlwaysTrueInstanceof: false
checkAlwaysTrueStrictComparison: false
checkClassCaseSensitivity: false
checkFunctionArgumentTypes: false
checkFunctionNameCase: false
checkArgumentsPassedByReference: false
checkMaybeUndefinedVariables: false
checkNullables: false
checkThisOnly: true
checkUnionTypes: false
reportMaybes: false
reportMaybesInMethodSignatures: false
reportStaticMethodSignatures: false
polluteScopeWithLoopInitialAssignments: true
polluteScopeWithAlwaysIterableForeach: true
polluteCatchScopeWithTryAssignments: false
reportMagicMethods: false
reportMagicProperties: false
ignoreErrors: []
internalErrorsCountLimit: 50
cache:
nodesByFileCountMax: 512
nodesByStringCountMax: 512
reportUnmatchedIgnoredErrors: true
scopeClass: PHPStan\Analyser\Scope
universalObjectCratesClasses:
- stdClass
- SimpleXMLElement
earlyTerminatingMethodCalls: []
memoryLimitFile: %tmpDir%/.memory_limit
benchmarkFile: null
dynamicConstantNames:
- ICONV_IMPL
- PHP_VERSION
- PHP_MAJOR_VERSION
- PHP_MINOR_VERSION
- PHP_RELEASE_VERSION
- PHP_VERSION_ID
- PHP_EXTRA_VERSION
- PHP_ZTS
- PHP_DEBUG
- PHP_MAXPATHLEN
- PHP_OS
- PHP_OS_FAMILY
- PHP_SAPI
- PHP_EOL
- PHP_INT_MAX
- PHP_INT_MIN
- PHP_INT_SIZE
- PHP_FLOAT_DIG
- PHP_FLOAT_EPSILON
- PHP_FLOAT_MIN
- PHP_FLOAT_MAX
- DEFAULT_INCLUDE_PATH
- PEAR_INSTALL_DIR
- PEAR_EXTENSION_DIR
- PHP_EXTENSION_DIR
- PHP_PREFIX
- PHP_BINDIR
- PHP_BINARY
- PHP_MANDIR
- PHP_LIBDIR
- PHP_DATADIR
- PHP_SYSCONFDIR
- PHP_LOCALSTATEDIR
- PHP_CONFIG_FILE_PATH
- PHP_CONFIG_FILE_SCAN_DIR
- PHP_SHLIB_SUFFIX
- PHP_FD_SETSIZE
extensions:
rules: PHPStan\DependencyInjection\RulesExtension
conditionalTags: PHPStan\DependencyInjection\ConditionalTagsExtension
services:
-
class: PhpParser\BuilderFactory
-
class: PhpParser\Lexer
-
class: PhpParser\NodeTraverser
setup:
- addVisitor(@PhpParser\NodeVisitor\NameResolver)
-
class: PhpParser\NodeVisitor\NameResolver
-
class: PhpParser\Parser\Php7
-
class: PhpParser\PrettyPrinter\Standard
-
class: PHPStan\Broker\AnonymousClassNameHelper
-
class: PHPStan\PhpDocParser\Lexer\Lexer
-
class: PHPStan\PhpDocParser\Parser\TypeParser
-
class: PHPStan\PhpDocParser\Parser\ConstExprParser
-
class: PHPStan\PhpDocParser\Parser\PhpDocParser
-
class: PHPStan\PhpDoc\PhpDocNodeResolver
-
class: PHPStan\PhpDoc\PhpDocStringResolver
-
class: PHPStan\PhpDoc\TypeNodeResolver
factory: @typeNodeResolverFactory::create
-
class: PHPStan\PhpDoc\TypeStringResolver
-
class: PHPStan\Analyser\Analyser
arguments:
ignoreErrors: %ignoreErrors%
reportUnmatchedIgnoredErrors: %reportUnmatchedIgnoredErrors%
internalErrorsCountLimit: %internalErrorsCountLimit%
benchmarkFile: %benchmarkFile%
-
class: PHPStan\Analyser\ScopeFactory
arguments:
scopeClass: %scopeClass%
-
class: PHPStan\Analyser\NodeScopeResolver
arguments:
polluteScopeWithLoopInitialAssignments: %polluteScopeWithLoopInitialAssignments%
polluteCatchScopeWithTryAssignments: %polluteCatchScopeWithTryAssignments%
polluteScopeWithAlwaysIterableForeach: %polluteScopeWithAlwaysIterableForeach%
earlyTerminatingMethodCalls: %earlyTerminatingMethodCalls%
-
class: PHPStan\Cache\Cache
arguments:
storage: @cacheStorage
-
class: PHPStan\Command\AnalyseApplication
arguments:
memoryLimitFile: %memoryLimitFile%
currentWorkingDirectory: %currentWorkingDirectory%
-
class: PHPStan\Dependency\DependencyDumper
-
class: PHPStan\Dependency\DependencyResolver
-
class: PHPStan\File\FileHelper
arguments:
workingDirectory: %currentWorkingDirectory%
-
class: PHPStan\File\FileExcluder
arguments:
analyseExcludes: %excludes_analyse%
-
class: PHPStan\File\FileFinder
arguments:
fileExtensions: %fileExtensions%
-
class: PHPStan\Parser\CachedParser
arguments:
originalParser: @directParser
cachedNodesByFileCountMax: %cache.nodesByFileCountMax%
cachedNodesByStringCountMax: %cache.nodesByStringCountMax%
-
class: PHPStan\Parser\FunctionCallStatementFinder
-
implement: PHPStan\Reflection\FunctionReflectionFactory
-
class: PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension
-
class: PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension
-
class: PHPStan\Reflection\Php\PhpClassReflectionExtension
-
class: PHPStan\Reflection\PhpDefect\PhpDefectClassReflectionExtension
-
implement: PHPStan\Reflection\Php\PhpMethodReflectionFactory
-
class: PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension
tags:
- phpstan.broker.propertiesClassReflectionExtension
arguments:
classes: %universalObjectCratesClasses%
-
class: PHPStan\Reflection\SignatureMap\SignatureMapParser
-
class: PHPStan\Reflection\SignatureMap\SignatureMapProvider
-
class: PHPStan\Rules\ClassCaseSensitivityCheck
-
class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper
-
class: PHPStan\Rules\FunctionCallParametersCheck
arguments:
checkArgumentTypes: %checkFunctionArgumentTypes%
checkArgumentsPassedByReference: %checkArgumentsPassedByReference%
-
class: PHPStan\Rules\FunctionDefinitionCheck
arguments:
checkClassCaseSensitivity: %checkClassCaseSensitivity%
checkThisOnly: %checkThisOnly%
-
class: PHPStan\Rules\FunctionReturnTypeCheck
-
class: PHPStan\Rules\Properties\PropertyDescriptor
-
class: PHPStan\Rules\Properties\PropertyReflectionFinder
-
class: PHPStan\Rules\RegistryFactory
-
class: PHPStan\Rules\RuleLevelHelper
arguments:
checkNullables: %checkNullables%
checkThisOnly: %checkThisOnly%
checkUnionTypes: %checkUnionTypes%
-
class: PHPStan\Rules\UnusedFunctionParametersCheck
-
class: PHPStan\Type\FileTypeMapper
-
class: PHPStan\Type\Php\ArgumentBasedFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayFillFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayFillKeysFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayFilterFunctionReturnTypeReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayKeyDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayKeyExistsFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\ArrayKeyFirstDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayKeyLastDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayKeysFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayMapFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayMergeFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayPopFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayReduceFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayShiftFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArraySliceFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArraySearchFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayValuesFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\CountFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\CountFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\CurlInitReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\DioStatDynamicFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ExplodeFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\GetParentClassDynamicFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\GettimeofdayDynamicFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\StatDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Type\Php\MethodExistsTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\PropertyExistsTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\MinMaxFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\PathinfoFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ReplaceFunctionsDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ArrayPointerFunctionsDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\VarExportFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\MbFunctionsReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\MicrotimeFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\HrtimeFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\StrtotimeFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\RangeFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\AssertFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\DefineConstantTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\DefinedConstantTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\InArrayFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsIntFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsFloatFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsNullFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsArrayFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsBoolFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsCallableFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsCountableFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsResourceFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsIterableFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsStringFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsSubclassOfFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsObjectFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsNumericFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsScalarFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\IsAFunctionTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension
-
class: PHPStan\Type\Php\JsonThrowOnErrorDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\TypeSpecifyingFunctionsDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\StrSplitFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
typeSpecifier:
class: PHPStan\Analyser\TypeSpecifier
factory: @typeSpecifierFactory::create
typeSpecifierFactory:
class: PHPStan\Analyser\TypeSpecifierFactory
relativePathHelper:
class: PHPStan\File\RelativePathHelper
dynamic: true
broker:
class: PHPStan\Broker\Broker
factory: @brokerFactory::create
brokerFactory:
class: PHPStan\Broker\BrokerFactory
cacheStorage:
class: PHPStan\Cache\FileCacheStorage
arguments:
directory: %tmpDir%/cache/PHPStan
autowired: no
directParser:
class: PHPStan\Parser\DirectParser
autowired: no
registry:
class: PHPStan\Rules\Registry
factory: @PHPStan\Rules\RegistryFactory::create
typeNodeResolverFactory:
class: PHPStan\PhpDoc\TypeNodeResolverFactory
errorFormatter.raw:
class: PHPStan\Command\ErrorFormatter\RawErrorFormatter
errorFormatter.table:
class: PHPStan\Command\ErrorFormatter\TableErrorFormatter
errorFormatter.checkstyle:
class: PHPStan\Command\ErrorFormatter\CheckstyleErrorFormatter
errorFormatter.json:
class: PHPStan\Command\ErrorFormatter\JsonErrorFormatter
arguments:
pretty: false
errorFormatter.prettyJson:
class: PHPStan\Command\ErrorFormatter\JsonErrorFormatter
arguments:
pretty: true

62
vendor/phpstan/phpstan/phpcs.xml vendored Normal file
View file

@ -0,0 +1,62 @@
<?xml version="1.0"?>
<ruleset name="PHPStan">
<rule ref="vendor/consistence/coding-standard/Consistence/ruleset.xml">
<exclude name="SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.InvalidFormat"/>
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameAfterKeyword"/>
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation"/>
<exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
<exclude name="Consistence.Exceptions.ExceptionDeclaration"/>
<exclude name="Squiz.Commenting.FunctionComment.MissingParamTag"/>
<exclude name="Squiz.Commenting.FunctionComment.ParamNameNoMatch"/>
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
<properties>
<property name="caseSensitive" value="false"/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
<properties>
<property name="newlinesCountBetweenOpenTagAndDeclare" value="0"/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration">
<properties>
<property name="usefulAnnotations" type="array" value="
@dataProvider,
@requires
"/>
<property name="enableObjectTypeHint" value="false"/>
</properties>
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversableParameterTypeHintSpecification"/>
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingTraversableReturnTypeHintSpecification"/>
</rule>
<rule ref="SlevomatCodingStandard.ControlStructures.AssignmentInCondition"/>
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowEqualOperators"/>
<rule ref="SlevomatCodingStandard.ControlStructures.RequireNullCoalesceOperator"/>
<rule ref="SlevomatCodingStandard.ControlStructures.EarlyExit"/>
<rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming"/>
<rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming"/>
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowShortTernaryOperator"/>
<rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName">
<properties>
<property name="rootNamespaces" type="array" value="src=>PHPStan,tests/PHPStan=>PHPStan"/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Classes.ModernClassNameReference"/>
<rule ref="SlevomatCodingStandard.Functions.StaticClosure"/>
<rule ref="SlevomatCodingStandard.Operators.RequireCombinedAssignmentOperator"/>
<rule ref="SlevomatCodingStandard.TypeHints.NullTypeHintOnLastPosition"/>
<rule ref="SlevomatCodingStandard.Classes.TraitUseDeclaration"/>
<rule ref="SlevomatCodingStandard.Classes.TraitUseSpacing"/>
<rule ref="SlevomatCodingStandard.Variables.UnusedVariable"/>
<rule ref="SlevomatCodingStandard.Variables.UselessVariable"/>
<!--<rule ref="SlevomatCodingStandard.Functions.UnusedParameter"/>-->
<rule ref="SlevomatCodingStandard.Functions.UnusedInheritedVariablePassedToClosure"/>
<rule ref="SlevomatCodingStandard.Namespaces.UselessAlias"/>
<rule ref="SlevomatCodingStandard.PHP.UselessSemicolon"/>
<rule ref="SlevomatCodingStandard.PHP.UselessParentheses"/>
<exclude-pattern>tests/*/data</exclude-pattern>
<exclude-pattern>tests/*/traits</exclude-pattern>
<exclude-pattern>tests/notAutoloaded</exclude-pattern>
<exclude-pattern>src/Reflection/SignatureMap/functionMap.php</exclude-pattern>
</ruleset>

View file

@ -0,0 +1,8 @@
<?php declare(strict_types = 1);
namespace PHPStan;
abstract class AnalysedCodeException extends \Exception
{
}

View file

@ -0,0 +1,302 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use Nette\Utils\Json;
use PHPStan\File\FileHelper;
use PHPStan\Parser\Parser;
use PHPStan\Rules\FileRuleError;
use PHPStan\Rules\LineRuleError;
use PHPStan\Rules\Registry;
class Analyser
{
/** @var \PHPStan\Analyser\ScopeFactory */
private $scopeFactory;
/** @var \PHPStan\Parser\Parser */
private $parser;
/** @var \PHPStan\Rules\Registry */
private $registry;
/** @var \PHPStan\Analyser\NodeScopeResolver */
private $nodeScopeResolver;
/** @var \PHPStan\File\FileHelper */
private $fileHelper;
/** @var (string|array<string, string>)[] */
private $ignoreErrors;
/** @var bool */
private $reportUnmatchedIgnoredErrors;
/** @var int */
private $internalErrorsCountLimit;
/** @var string|null */
private $benchmarkFile;
/** @var float[] */
private $benchmarkData = [];
/**
* @param \PHPStan\Analyser\ScopeFactory $scopeFactory
* @param \PHPStan\Parser\Parser $parser
* @param \PHPStan\Rules\Registry $registry
* @param \PHPStan\Analyser\NodeScopeResolver $nodeScopeResolver
* @param \PHPStan\File\FileHelper $fileHelper
* @param (string|array<string, string>)[] $ignoreErrors
* @param bool $reportUnmatchedIgnoredErrors
* @param int $internalErrorsCountLimit
* @param string|null $benchmarkFile
*/
public function __construct(
ScopeFactory $scopeFactory,
Parser $parser,
Registry $registry,
NodeScopeResolver $nodeScopeResolver,
FileHelper $fileHelper,
array $ignoreErrors,
bool $reportUnmatchedIgnoredErrors,
int $internalErrorsCountLimit,
?string $benchmarkFile = null
)
{
$this->scopeFactory = $scopeFactory;
$this->parser = $parser;
$this->registry = $registry;
$this->nodeScopeResolver = $nodeScopeResolver;
$this->fileHelper = $fileHelper;
$this->ignoreErrors = $ignoreErrors;
$this->reportUnmatchedIgnoredErrors = $reportUnmatchedIgnoredErrors;
$this->internalErrorsCountLimit = $internalErrorsCountLimit;
$this->benchmarkFile = $benchmarkFile;
}
/**
* @param string[] $files
* @param bool $onlyFiles
* @param \Closure(string $file): void|null $preFileCallback
* @param \Closure(string $file): void|null $postFileCallback
* @param bool $debug
* @return string[]|\PHPStan\Analyser\Error[] errors
*/
public function analyse(
array $files,
bool $onlyFiles,
?\Closure $preFileCallback = null,
?\Closure $postFileCallback = null,
bool $debug = false
): array
{
$errors = [];
foreach ($this->ignoreErrors as $ignoreError) {
try {
if (is_array($ignoreError)) {
if (!isset($ignoreError['message'])) {
$errors[] = sprintf(
'Ignored error %s is missing a message.',
Json::encode($ignoreError)
);
continue;
}
if (!isset($ignoreError['path'])) {
$errors[] = sprintf(
'Ignored error %s is missing a path.',
Json::encode($ignoreError)
);
}
$ignoreMessage = $ignoreError['message'];
} else {
$ignoreMessage = $ignoreError;
}
\Nette\Utils\Strings::match('', $ignoreMessage);
} catch (\Nette\Utils\RegexpException $e) {
$errors[] = $e->getMessage();
}
}
if (count($errors) > 0) {
return $errors;
}
$this->nodeScopeResolver->setAnalysedFiles($files);
$internalErrorsCount = 0;
$reachedInternalErrorsCountLimit = false;
foreach ($files as $file) {
try {
$fileErrors = [];
if ($preFileCallback !== null) {
$preFileCallback($file);
}
if (is_file($file)) {
$parserBenchmarkTime = $this->benchmarkStart();
$parserNodes = $this->parser->parseFile($file);
$this->benchmarkEnd($parserBenchmarkTime, 'parser');
$scopeBenchmarkTime = $this->benchmarkStart();
$this->nodeScopeResolver->processNodes(
$parserNodes,
$this->scopeFactory->create(ScopeContext::create($file)),
function (\PhpParser\Node $node, Scope $scope) use (&$fileErrors, $file, &$scopeBenchmarkTime): void {
$this->benchmarkEnd($scopeBenchmarkTime, 'scope');
$uniquedAnalysedCodeExceptionMessages = [];
foreach ($this->registry->getRules(get_class($node)) as $rule) {
try {
$ruleBenchmarkTime = $this->benchmarkStart();
$ruleErrors = $rule->processNode($node, $scope);
$this->benchmarkEnd($ruleBenchmarkTime, sprintf('rule-%s', get_class($rule)));
} catch (\PHPStan\AnalysedCodeException $e) {
if (isset($uniquedAnalysedCodeExceptionMessages[$e->getMessage()])) {
continue;
}
$uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true;
$fileErrors[] = new Error($e->getMessage(), $file, $node->getLine(), false);
continue;
}
foreach ($ruleErrors as $ruleError) {
$line = $node->getLine();
$fileName = $scope->getFileDescription();
if (is_string($ruleError)) {
$message = $ruleError;
} else {
$message = $ruleError->getMessage();
if (
$ruleError instanceof LineRuleError
&& $ruleError->getLine() !== -1
) {
$line = $ruleError->getLine();
}
if (
$ruleError instanceof FileRuleError
&& $ruleError->getFile() !== ''
) {
$fileName = $ruleError->getFile();
}
}
$fileErrors[] = new Error($message, $fileName, $line);
}
}
$scopeBenchmarkTime = $this->benchmarkStart();
}
);
} elseif (is_dir($file)) {
$fileErrors[] = new Error(sprintf('File %s is a directory.', $file), $file, null, false);
} else {
$fileErrors[] = new Error(sprintf('File %s does not exist.', $file), $file, null, false);
}
if ($postFileCallback !== null) {
$postFileCallback($file);
}
$errors = array_merge($errors, $fileErrors);
} catch (\PhpParser\Error $e) {
$errors[] = new Error($e->getMessage(), $file, $e->getStartLine() !== -1 ? $e->getStartLine() : null, false);
} catch (\PHPStan\Parser\ParserErrorsException $e) {
foreach ($e->getErrors() as $error) {
$errors[] = new Error($error->getMessage(), $file, $error->getStartLine() !== -1 ? $error->getStartLine() : null, false);
}
} catch (\PHPStan\AnalysedCodeException $e) {
$errors[] = new Error($e->getMessage(), $file, null, false);
} catch (\Throwable $t) {
if ($debug) {
throw $t;
}
$internalErrorsCount++;
$internalErrorMessage = sprintf('Internal error: %s', $t->getMessage());
$internalErrorMessage .= sprintf(
'%sRun PHPStan with --debug option and post the stack trace to:%s%s',
"\n",
"\n",
'https://github.com/phpstan/phpstan/issues/new'
);
$errors[] = new Error($internalErrorMessage, $file);
if ($internalErrorsCount >= $this->internalErrorsCountLimit) {
$reachedInternalErrorsCountLimit = true;
break;
}
}
}
if ($this->benchmarkFile !== null) {
uasort($this->benchmarkData, static function (float $a, float $b): int {
return $b <=> $a;
});
file_put_contents($this->benchmarkFile, Json::encode($this->benchmarkData, Json::PRETTY));
}
$unmatchedIgnoredErrors = $this->ignoreErrors;
$addErrors = [];
$errors = array_values(array_filter($errors, function (Error $error) use (&$unmatchedIgnoredErrors, &$addErrors): bool {
foreach ($this->ignoreErrors as $i => $ignore) {
if (IgnoredError::shouldIgnore($this->fileHelper, $error, $ignore)) {
unset($unmatchedIgnoredErrors[$i]);
if (!$error->canBeIgnored()) {
$addErrors[] = sprintf(
'Error message "%s" cannot be ignored, use excludes_analyse instead.',
$error->getMessage()
);
return true;
}
return false;
}
}
return true;
}));
$errors = array_merge($errors, $addErrors);
if (!$onlyFiles && $this->reportUnmatchedIgnoredErrors && !$reachedInternalErrorsCountLimit) {
foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) {
$errors[] = sprintf(
'Ignored error pattern %s was not matched in reported errors.',
IgnoredError::stringifyPattern($unmatchedIgnoredError)
);
}
}
if ($reachedInternalErrorsCountLimit) {
$errors[] = sprintf('Reached internal errors count limit of %d, exiting...', $this->internalErrorsCountLimit);
}
return $errors;
}
private function benchmarkStart(): ?float
{
if ($this->benchmarkFile === null) {
return null;
}
return microtime(true);
}
private function benchmarkEnd(?float $startTime, string $description): void
{
if ($this->benchmarkFile === null) {
return;
}
if ($startTime === null) {
return;
}
$elapsedTime = microtime(true) - $startTime;
if (!isset($this->benchmarkData[$description])) {
$this->benchmarkData[$description] = $elapsedTime;
return;
}
$this->benchmarkData[$description] += $elapsedTime;
}
}

View file

@ -0,0 +1,48 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
class Error
{
/** @var string */
private $message;
/** @var string */
private $file;
/** @var int|NULL */
private $line;
/** @var bool */
private $canBeIgnored;
public function __construct(string $message, string $file, ?int $line = null, bool $canBeIgnored = true)
{
$this->message = $message;
$this->file = $file;
$this->line = $line;
$this->canBeIgnored = $canBeIgnored;
}
public function getMessage(): string
{
return $this->message;
}
public function getFile(): string
{
return $this->file;
}
public function getLine(): ?int
{
return $this->line;
}
public function canBeIgnored(): bool
{
return $this->canBeIgnored;
}
}

View file

@ -0,0 +1,58 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PHPStan\File\FileExcluder;
use PHPStan\File\FileHelper;
class IgnoredError
{
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
* @param array<string, string>|string $ignoredError
* @return string Representation of the ignored error
*/
public static function stringifyPattern($ignoredError): string
{
if (!is_array($ignoredError)) {
return $ignoredError;
}
// ignore by path
if (isset($ignoredError['path'])) {
return sprintf('%s in path %s', $ignoredError['message'], $ignoredError['path']);
}
return $ignoredError['message'];
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
* @param FileHelper $fileHelper
* @param Error $error
* @param array<string, string>|string $ignoredError
* @return bool To ignore or not to ignore?
*/
public static function shouldIgnore(
FileHelper $fileHelper,
Error $error,
$ignoredError
): bool
{
if (is_array($ignoredError)) {
// ignore by path
if (isset($ignoredError['path'])) {
$fileExcluder = new FileExcluder($fileHelper, [$ignoredError['path']]);
return \Nette\Utils\Strings::match($error->getMessage(), $ignoredError['message']) !== null
&& $fileExcluder->isExcludedFromAnalysing($error->getFile());
}
throw new \PHPStan\ShouldNotHappenException();
}
return \Nette\Utils\Strings::match($error->getMessage(), $ignoredError) !== null;
}
}

View file

@ -0,0 +1,119 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PhpParser\Node\Stmt\Break_;
use PhpParser\Node\Stmt\Continue_;
class LookForAssignsSettings
{
private const EARLY_TERMINATION_CONTINUE = 1;
private const EARLY_TERMINATION_BREAK = 2;
private const EARLY_TERMINATION_STOP = 4;
private const EARLY_TERMINATION_ALL = self::EARLY_TERMINATION_CONTINUE
+ self::EARLY_TERMINATION_BREAK
+ self::EARLY_TERMINATION_STOP;
private const EARLY_TERMINATION_CLOSURE = 8;
private const REPEAT_ANALYSIS = 16;
/** @var int */
private $respectEarlyTermination;
/** @var self[] */
private static $registry = [];
private function __construct(
int $respectEarlyTermination
)
{
$this->respectEarlyTermination = $respectEarlyTermination;
}
public static function default(): self
{
return self::create(self::EARLY_TERMINATION_ALL);
}
public static function insideLoop(): self
{
return self::create(self::EARLY_TERMINATION_STOP + self::EARLY_TERMINATION_BREAK + self::REPEAT_ANALYSIS);
}
public static function afterLoop(): self
{
return self::create(self::EARLY_TERMINATION_STOP + self::REPEAT_ANALYSIS);
}
public static function afterSwitch(): self
{
return self::create(self::EARLY_TERMINATION_STOP);
}
public static function insideFinally(): self
{
return self::create(0);
}
public static function insideClosure(): self
{
return self::create(self::EARLY_TERMINATION_CLOSURE);
}
private static function create(int $value): self
{
self::$registry[$value] = self::$registry[$value] ?? new self($value);
return self::$registry[$value];
}
public function shouldRepeatAnalysis(): bool
{
return ($this->respectEarlyTermination & self::REPEAT_ANALYSIS) === self::REPEAT_ANALYSIS;
}
public function shouldSkipBranch(\PhpParser\Node $earlyTerminationStatement): bool
{
return $this->isRespected($earlyTerminationStatement);
}
private function isRespected(\PhpParser\Node $earlyTerminationStatement): bool
{
if (
$earlyTerminationStatement instanceof Break_
) {
return ($this->respectEarlyTermination & self::EARLY_TERMINATION_BREAK) === self::EARLY_TERMINATION_BREAK;
}
if (
$earlyTerminationStatement instanceof Continue_
) {
return ($this->respectEarlyTermination & self::EARLY_TERMINATION_CONTINUE) === self::EARLY_TERMINATION_CONTINUE;
}
return ($this->respectEarlyTermination & self::EARLY_TERMINATION_STOP) === self::EARLY_TERMINATION_STOP;
}
public function shouldIntersectVariables(?\PhpParser\Node $earlyTerminationStatement): bool
{
if ($earlyTerminationStatement === null) {
return true;
}
if ($this->shouldSkipBranch($earlyTerminationStatement)) {
throw new \PHPStan\ShouldNotHappenException();
}
return $earlyTerminationStatement instanceof Break_
|| $earlyTerminationStatement instanceof Continue_
|| ($this->respectEarlyTermination & self::EARLY_TERMINATION_STOP) === 0;
}
public function shouldGeneralizeConstantTypesOfNonIdempotentOperations(): bool
{
return (
($this->respectEarlyTermination & self::EARLY_TERMINATION_STOP) === self::EARLY_TERMINATION_STOP
&& $this->respectEarlyTermination !== self::EARLY_TERMINATION_ALL
) || $this->respectEarlyTermination === self::EARLY_TERMINATION_CLOSURE;
}
}

View file

@ -0,0 +1,57 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
class NameScope
{
/** @var string|null */
private $namespace;
/** @var string[] alias(string) => fullName(string) */
private $uses;
/** @var string|null */
private $className;
/**
* @param string|null $namespace
* @param string[] $uses alias(string) => fullName(string)
* @param string|null $className
*/
public function __construct(?string $namespace, array $uses, ?string $className = null)
{
$this->namespace = $namespace;
$this->uses = $uses;
$this->className = $className;
}
public function getClassName(): ?string
{
return $this->className;
}
public function resolveStringName(string $name): string
{
if (strpos($name, '\\') === 0) {
return ltrim($name, '\\');
}
$nameParts = explode('\\', $name);
$firstNamePart = strtolower($nameParts[0]);
if (isset($this->uses[$firstNamePart])) {
if (count($nameParts) === 1) {
return $this->uses[$firstNamePart];
}
array_shift($nameParts);
return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts));
}
if ($this->namespace !== null) {
return sprintf('%s\\%s', $this->namespace, $name);
}
return $name;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PHPStan\Reflection\ClassMemberAccessAnswerer;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ConstantReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\PropertyReflection;
class OutOfClassScope implements ClassMemberAccessAnswerer
{
public function isInClass(): bool
{
return false;
}
public function getClassReflection(): ?ClassReflection
{
return null;
}
public function canAccessProperty(PropertyReflection $propertyReflection): bool
{
return $propertyReflection->isPublic();
}
public function canCallMethod(MethodReflection $methodReflection): bool
{
return $methodReflection->isPublic();
}
public function canAccessConstant(ConstantReflection $constantReflection): bool
{
return $constantReflection->isPublic();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,78 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PHPStan\Reflection\ClassReflection;
class ScopeContext
{
/** @var string */
private $file;
/** @var ClassReflection|null */
private $classReflection;
/** @var ClassReflection|null */
private $traitReflection;
private function __construct(
string $file,
?ClassReflection $classReflection,
?ClassReflection $traitReflection
)
{
$this->file = $file;
$this->classReflection = $classReflection;
$this->traitReflection = $traitReflection;
}
public static function create(string $file): self
{
return new self($file, null, null);
}
public function beginFile(): self
{
return new self($this->file, null, null);
}
public function enterClass(ClassReflection $classReflection): self
{
if ($this->classReflection !== null && !$classReflection->isAnonymous()) {
throw new \PHPStan\ShouldNotHappenException();
}
if ($classReflection->isTrait()) {
throw new \PHPStan\ShouldNotHappenException();
}
return new self($this->file, $classReflection, null);
}
public function enterTrait(ClassReflection $traitReflection): self
{
if ($this->classReflection === null) {
throw new \PHPStan\ShouldNotHappenException();
}
if (!$traitReflection->isTrait()) {
throw new \PHPStan\ShouldNotHappenException();
}
return new self($this->file, $this->classReflection, $traitReflection);
}
public function getFile(): string
{
return $this->file;
}
public function getClassReflection(): ?ClassReflection
{
return $this->classReflection;
}
public function getTraitReflection(): ?ClassReflection
{
return $this->traitReflection;
}
}

View file

@ -0,0 +1,99 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PhpParser\Node\Expr;
use PHPStan\Broker\Broker;
use PHPStan\Type\Type;
class ScopeFactory
{
/** @var string */
private $scopeClass;
/** @var \PHPStan\Broker\Broker */
private $broker;
/** @var \PhpParser\PrettyPrinter\Standard */
private $printer;
/** @var \PHPStan\Analyser\TypeSpecifier */
private $typeSpecifier;
/** @var string[] */
private $dynamicConstantNames;
public function __construct(
string $scopeClass,
Broker $broker,
\PhpParser\PrettyPrinter\Standard $printer,
TypeSpecifier $typeSpecifier,
\Nette\DI\Container $container
)
{
$this->scopeClass = $scopeClass;
$this->broker = $broker;
$this->printer = $printer;
$this->typeSpecifier = $typeSpecifier;
$this->dynamicConstantNames = $container->parameters['dynamicConstantNames'];
}
/**
* @param \PHPStan\Analyser\ScopeContext $context
* @param bool $declareStrictTypes
* @param \PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection|null $function
* @param string|null $namespace
* @param \PHPStan\Analyser\VariableTypeHolder[] $variablesTypes
* @param \PHPStan\Analyser\VariableTypeHolder[] $moreSpecificTypes
* @param string|null $inClosureBindScopeClass
* @param \PHPStan\Type\Type|null $inAnonymousFunctionReturnType
* @param \PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|null $inFunctionCall
* @param bool $negated
* @param bool $inFirstLevelStatement
* @param string[] $currentlyAssignedExpressions
*
* @return Scope
*/
public function create(
ScopeContext $context,
bool $declareStrictTypes = false,
$function = null,
?string $namespace = null,
array $variablesTypes = [],
array $moreSpecificTypes = [],
?string $inClosureBindScopeClass = null,
?Type $inAnonymousFunctionReturnType = null,
?Expr $inFunctionCall = null,
bool $negated = false,
bool $inFirstLevelStatement = true,
array $currentlyAssignedExpressions = []
): Scope
{
$scopeClass = $this->scopeClass;
if (!is_a($scopeClass, Scope::class, true)) {
throw new \PHPStan\ShouldNotHappenException();
}
return new $scopeClass(
$this,
$this->broker,
$this->printer,
$this->typeSpecifier,
$context,
$declareStrictTypes,
$function,
$namespace,
$variablesTypes,
$moreSpecificTypes,
$inClosureBindScopeClass,
$inAnonymousFunctionReturnType,
$inFunctionCall,
$negated,
$inFirstLevelStatement,
$currentlyAssignedExpressions,
$this->dynamicConstantNames
);
}
}

View file

@ -0,0 +1,103 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PHPStan\Type\TypeCombinator;
class SpecifiedTypes
{
/** @var mixed[] */
private $sureTypes;
/** @var mixed[] */
private $sureNotTypes;
/**
* @param mixed[] $sureTypes
* @param mixed[] $sureNotTypes
*/
public function __construct(array $sureTypes = [], array $sureNotTypes = [])
{
$this->sureTypes = $sureTypes;
$this->sureNotTypes = $sureNotTypes;
}
/**
* @return mixed[]
*/
public function getSureTypes(): array
{
return $this->sureTypes;
}
/**
* @return mixed[]
*/
public function getSureNotTypes(): array
{
return $this->sureNotTypes;
}
public function intersectWith(SpecifiedTypes $other): self
{
$sureTypeUnion = [];
$sureNotTypeUnion = [];
foreach ($this->sureTypes as $exprString => [$exprNode, $type]) {
if (!isset($other->sureTypes[$exprString])) {
continue;
}
$sureTypeUnion[$exprString] = [
$exprNode,
TypeCombinator::union($type, $other->sureTypes[$exprString][1]),
];
}
foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) {
if (!isset($other->sureNotTypes[$exprString])) {
continue;
}
$sureNotTypeUnion[$exprString] = [
$exprNode,
TypeCombinator::intersect($type, $other->sureNotTypes[$exprString][1]),
];
}
return new self($sureTypeUnion, $sureNotTypeUnion);
}
public function unionWith(SpecifiedTypes $other): self
{
$sureTypeUnion = $this->sureTypes + $other->sureTypes;
$sureNotTypeUnion = $this->sureNotTypes + $other->sureNotTypes;
foreach ($this->sureTypes as $exprString => [$exprNode, $type]) {
if (!isset($other->sureTypes[$exprString])) {
continue;
}
$sureTypeUnion[$exprString] = [
$exprNode,
TypeCombinator::intersect($type, $other->sureTypes[$exprString][1]),
];
}
foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) {
if (!isset($other->sureNotTypes[$exprString])) {
continue;
}
$sureNotTypeUnion[$exprString] = [
$exprNode,
TypeCombinator::union($type, $other->sureNotTypes[$exprString][1]),
];
}
return new self($sureTypeUnion, $sureNotTypeUnion);
}
}

View file

@ -0,0 +1,73 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
class StatementList
{
/** @var \PHPStan\Analyser\Scope */
private $scope;
/** @var \PhpParser\Node[] */
private $statements;
/** @var bool */
private $filterByTruthyValue;
/** @var callable(Scope $scope): Scope|null */
private $processScope;
/**
* @param Scope $scope
* @param \PhpParser\Node[] $statements
* @param bool $filterByTruthyValue
* @param callable(Scope $scope): Scope|null $processScope
*/
public function __construct(
Scope $scope,
array $statements,
bool $filterByTruthyValue = false,
?callable $processScope = null
)
{
$this->scope = $scope;
$this->statements = $statements;
$this->filterByTruthyValue = $filterByTruthyValue;
$this->processScope = $processScope;
}
public static function fromList(Scope $scope, self $list): self
{
return new self(
$scope,
$list->statements,
$list->filterByTruthyValue,
$list->processScope
);
}
public function getScope(): Scope
{
$scope = $this->scope;
if ($this->processScope !== null) {
$callback = $this->processScope;
$scope = $callback($scope);
}
return $scope;
}
/**
* @return \PhpParser\Node[]
*/
public function getStatements(): array
{
return $this->statements;
}
public function shouldFilterByTruthyValue(): bool
{
return $this->filterByTruthyValue;
}
}

View file

@ -0,0 +1,562 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\BinaryOp\LogicalAnd;
use PhpParser\Node\Expr\BinaryOp\LogicalOr;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Name;
use PHPStan\Broker\Broker;
use PHPStan\Type\Accessory\HasOffsetType;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NonexistentParentClassType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
class TypeSpecifier
{
/** @var \PhpParser\PrettyPrinter\Standard */
private $printer;
/** @var \PHPStan\Broker\Broker */
private $broker;
/** @var \PHPStan\Type\FunctionTypeSpecifyingExtension[] */
private $functionTypeSpecifyingExtensions = [];
/** @var \PHPStan\Type\MethodTypeSpecifyingExtension[] */
private $methodTypeSpecifyingExtensions = [];
/** @var \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] */
private $staticMethodTypeSpecifyingExtensions = [];
/** @var \PHPStan\Type\MethodTypeSpecifyingExtension[][]|null */
private $methodTypeSpecifyingExtensionsByClass;
/** @var \PHPStan\Type\StaticMethodTypeSpecifyingExtension[][]|null */
private $staticMethodTypeSpecifyingExtensionsByClass;
/**
* @param \PhpParser\PrettyPrinter\Standard $printer
* @param \PHPStan\Broker\Broker $broker
* @param \PHPStan\Type\FunctionTypeSpecifyingExtension[] $functionTypeSpecifyingExtensions
* @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions
* @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions
*/
public function __construct(
\PhpParser\PrettyPrinter\Standard $printer,
Broker $broker,
array $functionTypeSpecifyingExtensions,
array $methodTypeSpecifyingExtensions,
array $staticMethodTypeSpecifyingExtensions
)
{
$this->printer = $printer;
$this->broker = $broker;
foreach (array_merge($functionTypeSpecifyingExtensions, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions) as $extension) {
if (!($extension instanceof TypeSpecifierAwareExtension)) {
continue;
}
$extension->setTypeSpecifier($this);
}
$this->functionTypeSpecifyingExtensions = $functionTypeSpecifyingExtensions;
$this->methodTypeSpecifyingExtensions = $methodTypeSpecifyingExtensions;
$this->staticMethodTypeSpecifyingExtensions = $staticMethodTypeSpecifyingExtensions;
}
public function specifyTypesInCondition(
Scope $scope,
Expr $expr,
TypeSpecifierContext $context,
bool $defaultHandleFunctions = false
): SpecifiedTypes
{
if ($expr instanceof Instanceof_) {
if ($expr->class instanceof Name) {
$className = (string) $expr->class;
$lowercasedClassName = strtolower($className);
if ($lowercasedClassName === 'self' && $scope->isInClass()) {
$type = new ObjectType($scope->getClassReflection()->getName());
} elseif ($lowercasedClassName === 'static' && $scope->isInClass()) {
$type = new StaticType($scope->getClassReflection()->getName());
} elseif ($lowercasedClassName === 'parent') {
if (
$scope->isInClass()
&& $scope->getClassReflection()->getParentClass() !== false
) {
$type = new ObjectType($scope->getClassReflection()->getParentClass()->getName());
} else {
$type = new NonexistentParentClassType();
}
} else {
$type = new ObjectType($className);
}
return $this->create($expr->expr, $type, $context);
}
if ($context->true()) {
return $this->create($expr->expr, new ObjectWithoutClassType(), $context);
}
} elseif ($expr instanceof Node\Expr\BinaryOp\Identical) {
$expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr);
if ($expressions !== null) {
/** @var Expr $exprNode */
$exprNode = $expressions[0];
/** @var \PHPStan\Type\ConstantScalarType $constantType */
$constantType = $expressions[1];
if ($constantType->getValue() === false) {
$types = $this->create($exprNode, $constantType, $context);
return $types->unionWith($this->specifyTypesInCondition(
$scope,
$exprNode,
$context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate()
));
}
if ($constantType->getValue() === true) {
$types = $this->create($exprNode, $constantType, $context);
return $types->unionWith($this->specifyTypesInCondition(
$scope,
$exprNode,
$context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate()
));
}
if ($constantType->getValue() === null) {
return $this->create($exprNode, $constantType, $context);
}
if (
!$context->null()
&& $exprNode instanceof FuncCall
&& count($exprNode->args) === 1
&& $exprNode->name instanceof Name
&& strtolower((string) $exprNode->name) === 'count'
&& $constantType instanceof ConstantIntegerType
) {
if ($context->truthy() || $constantType->getValue() === 0) {
$newContext = $context;
if ($constantType->getValue() === 0) {
$newContext = $newContext->negate();
}
$argType = $scope->getType($exprNode->args[0]->value);
if ((new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($argType)->yes()) {
return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext);
}
}
}
}
if ($context->true()) {
$type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left));
$leftTypes = $this->create($expr->left, $type, $context);
$rightTypes = $this->create($expr->right, $type, $context);
return $leftTypes->unionWith($rightTypes);
} elseif ($context->false()) {
$identicalType = $scope->getType($expr);
if ($identicalType instanceof ConstantBooleanType) {
$never = new NeverType();
$contextForTypes = $identicalType->getValue() ? $context->negate() : $context;
$leftTypes = $this->create($expr->left, $never, $contextForTypes);
$rightTypes = $this->create($expr->right, $never, $contextForTypes);
return $leftTypes->unionWith($rightTypes);
}
if (
(
$expr->left instanceof Node\Scalar
|| $expr->left instanceof Expr\Array_
)
&& !$expr->right instanceof Node\Scalar
) {
return $this->create(
$expr->right,
$scope->getType($expr->left),
$context
);
}
if (
(
$expr->right instanceof Node\Scalar
|| $expr->right instanceof Expr\Array_
)
&& !$expr->left instanceof Node\Scalar
) {
return $this->create(
$expr->left,
$scope->getType($expr->right),
$context
);
}
}
} elseif ($expr instanceof Node\Expr\BinaryOp\NotIdentical) {
return $this->specifyTypesInCondition(
$scope,
new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Identical($expr->left, $expr->right)),
$context
);
} elseif ($expr instanceof Node\Expr\BinaryOp\Equal) {
$expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr);
if ($expressions !== null) {
/** @var Expr $exprNode */
$exprNode = $expressions[0];
/** @var \PHPStan\Type\ConstantScalarType $constantType */
$constantType = $expressions[1];
if ($constantType->getValue() === false || $constantType->getValue() === null) {
return $this->specifyTypesInCondition(
$scope,
$exprNode,
$context->true() ? TypeSpecifierContext::createFalsey() : TypeSpecifierContext::createFalsey()->negate()
);
}
if ($constantType->getValue() === true) {
return $this->specifyTypesInCondition(
$scope,
$exprNode,
$context->true() ? TypeSpecifierContext::createTruthy() : TypeSpecifierContext::createTruthy()->negate()
);
}
}
} elseif ($expr instanceof Node\Expr\BinaryOp\NotEqual) {
return $this->specifyTypesInCondition(
$scope,
new Node\Expr\BooleanNot(new Node\Expr\BinaryOp\Equal($expr->left, $expr->right)),
$context
);
} elseif ($expr instanceof FuncCall && $expr->name instanceof Name) {
if ($this->broker->hasFunction($expr->name, $scope)) {
$functionReflection = $this->broker->getFunction($expr->name, $scope);
foreach ($this->getFunctionTypeSpecifyingExtensions() as $extension) {
if (!$extension->isFunctionSupported($functionReflection, $expr, $context)) {
continue;
}
return $extension->specifyTypes($functionReflection, $expr, $scope, $context);
}
}
if ($defaultHandleFunctions) {
return $this->handleDefaultTruthyOrFalseyContext($context, $expr);
}
} elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) {
$methodCalledOnType = $scope->getType($expr->var);
$referencedClasses = TypeUtils::getDirectClassNames($methodCalledOnType);
if (
count($referencedClasses) === 1
&& $this->broker->hasClass($referencedClasses[0])
) {
$methodClassReflection = $this->broker->getClass($referencedClasses[0]);
if ($methodClassReflection->hasMethod($expr->name->name)) {
$methodReflection = $methodClassReflection->getMethod($expr->name->name, $scope);
foreach ($this->getMethodTypeSpecifyingExtensionsForClass($methodClassReflection->getName()) as $extension) {
if (!$extension->isMethodSupported($methodReflection, $expr, $context)) {
continue;
}
return $extension->specifyTypes($methodReflection, $expr, $scope, $context);
}
}
}
if ($defaultHandleFunctions) {
return $this->handleDefaultTruthyOrFalseyContext($context, $expr);
}
} elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) {
if ($expr->class instanceof Name) {
$calleeType = new ObjectType($scope->resolveName($expr->class));
} else {
$calleeType = $scope->getType($expr->class);
}
if ($calleeType->hasMethod($expr->name->name)->yes()) {
$staticMethodReflection = $calleeType->getMethod($expr->name->name, $scope);
$referencedClasses = TypeUtils::getDirectClassNames($calleeType);
if (
count($referencedClasses) === 1
&& $this->broker->hasClass($referencedClasses[0])
) {
$staticMethodClassReflection = $this->broker->getClass($referencedClasses[0]);
foreach ($this->getStaticMethodTypeSpecifyingExtensionsForClass($staticMethodClassReflection->getName()) as $extension) {
if (!$extension->isStaticMethodSupported($staticMethodReflection, $expr, $context)) {
continue;
}
return $extension->specifyTypes($staticMethodReflection, $expr, $scope, $context);
}
}
}
if ($defaultHandleFunctions) {
return $this->handleDefaultTruthyOrFalseyContext($context, $expr);
}
} elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) {
$leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context);
$rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context);
return $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->intersectWith($rightTypes);
} elseif ($expr instanceof BooleanOr || $expr instanceof LogicalOr) {
$leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context);
$rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context);
return $context->true() ? $leftTypes->intersectWith($rightTypes) : $leftTypes->unionWith($rightTypes);
} elseif ($expr instanceof Node\Expr\BooleanNot && !$context->null()) {
return $this->specifyTypesInCondition($scope, $expr->expr, $context->negate());
} elseif ($expr instanceof Node\Expr\Assign) {
if ($context->null()) {
return $this->specifyTypesInCondition($scope, $expr->expr, $context);
}
return $this->specifyTypesInCondition($scope, $expr->var, $context);
} elseif (
(
$expr instanceof Expr\Isset_
&& count($expr->vars) > 0
&& $context->truthy()
)
|| ($expr instanceof Expr\Empty_ && $context->falsey())
) {
$vars = [];
if ($expr instanceof Expr\Isset_) {
$varsToIterate = $expr->vars;
} else {
$varsToIterate = [$expr->expr];
}
foreach ($varsToIterate as $var) {
$vars[] = $var;
while (
$var instanceof ArrayDimFetch
|| $var instanceof PropertyFetch
|| (
$var instanceof StaticPropertyFetch
&& $var->class instanceof Expr
)
) {
if ($var instanceof StaticPropertyFetch) {
/** @var Expr $var */
$var = $var->class;
} else {
$var = $var->var;
}
$vars[] = $var;
}
}
$types = null;
foreach ($vars as $var) {
if ($expr instanceof Expr\Isset_) {
if (
$var instanceof ArrayDimFetch
&& $var->dim !== null
&& !$scope->getType($var->var) instanceof MixedType
) {
$type = $this->create(
$var->var,
new HasOffsetType($scope->getType($var->dim)),
$context
)->unionWith(
$this->create($var, new NullType(), TypeSpecifierContext::createFalse())
);
} else {
$type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse());
}
} else {
$type = $this->create(
$var,
new UnionType([
new NullType(),
new ConstantBooleanType(false),
]),
TypeSpecifierContext::createFalse()
);
}
if ($types === null) {
$types = $type;
} else {
$types = $types->unionWith($type);
}
}
/** @var SpecifiedTypes $types */
$types = $types;
if (
$expr instanceof Expr\Empty_
&& (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes()) {
$types = $types->unionWith(
$this->create($expr->expr, new NonEmptyArrayType(), $context->negate())
);
}
return $types;
} elseif (
$expr instanceof Expr\Empty_ && $context->truthy()
&& (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes()
) {
return $this->create($expr->expr, new NonEmptyArrayType(), $context->negate());
} elseif (!$context->null()) {
return $this->handleDefaultTruthyOrFalseyContext($context, $expr);
}
return new SpecifiedTypes();
}
private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, Expr $expr): SpecifiedTypes
{
if (!$context->truthy()) {
$type = new UnionType([new ObjectWithoutClassType(), new NonEmptyArrayType()]);
return $this->create($expr, $type, TypeSpecifierContext::createFalse());
} elseif (!$context->falsey()) {
$type = new UnionType([
new NullType(),
new ConstantBooleanType(false),
new ConstantIntegerType(0),
new ConstantFloatType(0.0),
new ConstantStringType(''),
new ConstantArrayType([], []),
]);
return $this->create($expr, $type, TypeSpecifierContext::createFalse());
}
return new SpecifiedTypes();
}
/**
* @param \PHPStan\Analyser\Scope $scope
* @param \PhpParser\Node\Expr\BinaryOp $binaryOperation
* @return (Expr|\PHPStan\Type\ConstantScalarType)[]|null
*/
private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array
{
$leftType = $scope->getType($binaryOperation->left);
$rightType = $scope->getType($binaryOperation->right);
if (
$leftType instanceof \PHPStan\Type\ConstantScalarType
&& !$binaryOperation->right instanceof ConstFetch
&& !$binaryOperation->right instanceof Expr\ClassConstFetch
) {
return [$binaryOperation->right, $leftType];
} elseif (
$rightType instanceof \PHPStan\Type\ConstantScalarType
&& !$binaryOperation->left instanceof ConstFetch
&& !$binaryOperation->left instanceof Expr\ClassConstFetch
) {
return [$binaryOperation->left, $rightType];
}
return null;
}
public function create(Expr $expr, Type $type, TypeSpecifierContext $context): SpecifiedTypes
{
if ($expr instanceof New_) {
return new SpecifiedTypes();
}
$sureTypes = [];
$sureNotTypes = [];
$exprString = $this->printer->prettyPrintExpr($expr);
if ($context->false()) {
$sureNotTypes[$exprString] = [$expr, $type];
} elseif ($context->true()) {
$sureTypes[$exprString] = [$expr, $type];
}
return new SpecifiedTypes($sureTypes, $sureNotTypes);
}
/**
* @return \PHPStan\Type\FunctionTypeSpecifyingExtension[]
*/
public function getFunctionTypeSpecifyingExtensions(): array
{
return $this->functionTypeSpecifyingExtensions;
}
/**
* @param string $className
* @return \PHPStan\Type\MethodTypeSpecifyingExtension[]
*/
public function getMethodTypeSpecifyingExtensionsForClass(string $className): array
{
if ($this->methodTypeSpecifyingExtensionsByClass === null) {
$byClass = [];
foreach ($this->methodTypeSpecifyingExtensions as $extension) {
$byClass[$extension->getClass()][] = $extension;
}
$this->methodTypeSpecifyingExtensionsByClass = $byClass;
}
return $this->getTypeSpecifyingExtensionsForType($this->methodTypeSpecifyingExtensionsByClass, $className);
}
/**
* @param string $className
* @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[]
*/
public function getStaticMethodTypeSpecifyingExtensionsForClass(string $className): array
{
if ($this->staticMethodTypeSpecifyingExtensionsByClass === null) {
$byClass = [];
foreach ($this->staticMethodTypeSpecifyingExtensions as $extension) {
$byClass[$extension->getClass()][] = $extension;
}
$this->staticMethodTypeSpecifyingExtensionsByClass = $byClass;
}
return $this->getTypeSpecifyingExtensionsForType($this->staticMethodTypeSpecifyingExtensionsByClass, $className);
}
/**
* @param \PHPStan\Type\MethodTypeSpecifyingExtension[][]|\PHPStan\Type\StaticMethodTypeSpecifyingExtension[][] $extensions
* @param string $className
* @return mixed[]
*/
private function getTypeSpecifyingExtensionsForType(array $extensions, string $className): array
{
$extensionsForClass = [];
$class = $this->broker->getClass($className);
foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) {
if (!isset($extensions[$extensionClassName])) {
continue;
}
$extensionsForClass = array_merge($extensionsForClass, $extensions[$extensionClassName]);
}
return $extensionsForClass;
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
interface TypeSpecifierAwareExtension
{
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void;
}

View file

@ -0,0 +1,90 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
class TypeSpecifierContext
{
public const CONTEXT_TRUE = 0b0001;
public const CONTEXT_TRUTHY_BUT_NOT_TRUE = 0b0010;
public const CONTEXT_TRUTHY = self::CONTEXT_TRUE | self::CONTEXT_TRUTHY_BUT_NOT_TRUE;
public const CONTEXT_FALSE = 0b0100;
public const CONTEXT_FALSEY_BUT_NOT_FALSE = 0b1000;
public const CONTEXT_FALSEY = self::CONTEXT_FALSE | self::CONTEXT_FALSEY_BUT_NOT_FALSE;
/** @var int|null */
private $value;
/** @var self[] */
private static $registry;
private function __construct(?int $value)
{
$this->value = $value;
}
private static function create(?int $value): self
{
self::$registry[$value] = self::$registry[$value] ?? new self($value);
return self::$registry[$value];
}
public static function createTrue(): self
{
return self::create(self::CONTEXT_TRUE);
}
public static function createTruthy(): self
{
return self::create(self::CONTEXT_TRUTHY);
}
public static function createFalse(): self
{
return self::create(self::CONTEXT_FALSE);
}
public static function createFalsey(): self
{
return self::create(self::CONTEXT_FALSEY);
}
public static function createNull(): self
{
return self::create(null);
}
public function negate(): self
{
if ($this->value === null) {
throw new \PHPStan\ShouldNotHappenException();
}
return self::create(~$this->value);
}
public function true(): bool
{
return $this->value !== null && (bool) ($this->value & self::CONTEXT_TRUE);
}
public function truthy(): bool
{
return $this->value !== null && (bool) ($this->value & self::CONTEXT_TRUTHY);
}
public function false(): bool
{
return $this->value !== null && (bool) ($this->value & self::CONTEXT_FALSE);
}
public function falsey(): bool
{
return $this->value !== null && (bool) ($this->value & self::CONTEXT_FALSEY);
}
public function null(): bool
{
return $this->value === null;
}
}

View file

@ -0,0 +1,58 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use Nette\DI\Container;
use PhpParser\PrettyPrinter\Standard;
use PHPStan\Broker\Broker;
use PHPStan\Broker\BrokerFactory;
class TypeSpecifierFactory
{
public const FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.functionTypeSpecifyingExtension';
public const METHOD_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.methodTypeSpecifyingExtension';
public const STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG = 'phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension';
/** @var \Nette\DI\Container */
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function create(): TypeSpecifier
{
$tagToService = function (array $tags) {
return array_map(function (string $serviceName) {
return $this->container->getService($serviceName);
}, array_keys($tags));
};
$typeSpecifier = new TypeSpecifier(
$this->container->getByType(Standard::class),
$this->container->getByType(Broker::class),
$tagToService($this->container->findByTag(self::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG)),
$tagToService($this->container->findByTag(self::METHOD_TYPE_SPECIFYING_EXTENSION_TAG)),
$tagToService($this->container->findByTag(self::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG))
);
foreach (array_merge(
$tagToService($this->container->findByTag(BrokerFactory::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG)),
$tagToService($this->container->findByTag(BrokerFactory::METHODS_CLASS_REFLECTION_EXTENSION_TAG)),
$tagToService($this->container->findByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG)),
$tagToService($this->container->findByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG)),
$tagToService($this->container->findByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG))
) as $extension) {
if (!($extension instanceof TypeSpecifierAwareExtension)) {
continue;
}
$extension->setTypeSpecifier($typeSpecifier);
}
return $typeSpecifier;
}
}

View file

@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
class UndefinedVariableException extends \PHPStan\AnalysedCodeException
{
/** @var \PHPStan\Analyser\Scope */
private $scope;
/** @var string */
private $variableName;
public function __construct(Scope $scope, string $variableName)
{
parent::__construct(sprintf('Undefined variable: $%s', $variableName));
$this->scope = $scope;
$this->variableName = $variableName;
}
public function getScope(): Scope
{
return $this->scope;
}
public function getVariableName(): string
{
return $this->variableName;
}
}

View file

@ -0,0 +1,55 @@
<?php declare(strict_types = 1);
namespace PHPStan\Analyser;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
class VariableTypeHolder
{
/** @var \PHPStan\Type\Type */
private $type;
/** @var \PHPStan\TrinaryLogic */
private $certainty;
public function __construct(Type $type, TrinaryLogic $certainty)
{
if ($certainty->no()) {
throw new \PHPStan\ShouldNotHappenException();
}
$this->type = $type;
$this->certainty = $certainty;
}
public static function createYes(Type $type): self
{
return new self($type, TrinaryLogic::createYes());
}
public static function createMaybe(Type $type): self
{
return new self($type, TrinaryLogic::createMaybe());
}
public function and(self $other): self
{
return new self(
TypeCombinator::union($this->getType(), $other->getType()),
$this->getCertainty()->and($other->getCertainty())
);
}
public function getType(): Type
{
return $this->type;
}
public function getCertainty(): TrinaryLogic
{
return $this->certainty;
}
}

View file

@ -0,0 +1,45 @@
<?php declare(strict_types = 1);
namespace PHPStan\Broker;
use PHPStan\File\FileHelper;
use PHPStan\File\RelativePathHelper;
class AnonymousClassNameHelper
{
/** @var FileHelper */
private $fileHelper;
/** @var RelativePathHelper */
private $relativePathHelper;
public function __construct(
FileHelper $fileHelper,
RelativePathHelper $relativePathHelper
)
{
$this->fileHelper = $fileHelper;
$this->relativePathHelper = $relativePathHelper;
}
public function getAnonymousClassName(
\PhpParser\Node\Expr\New_ $node,
string $filename
): string
{
if (!$node->class instanceof \PhpParser\Node\Stmt\Class_) {
throw new \PHPStan\ShouldNotHappenException();
}
$filename = $this->relativePathHelper->getRelativePath(
$this->fileHelper->normalizePath($filename)
);
return sprintf(
'AnonymousClass%s',
md5(sprintf('%s:%s', $filename, $node->class->getLine()))
);
}
}

View file

@ -0,0 +1,592 @@
<?php declare(strict_types = 1);
namespace PHPStan\Broker;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\File\RelativePathHelper;
use PHPStan\Parser\Parser;
use PHPStan\PhpDoc\Tag\ParamTag;
use PHPStan\Reflection\BrokerAwareExtension;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionReflectionFactory;
use PHPStan\Reflection\FunctionVariant;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\SignatureMap\ParameterSignature;
use PHPStan\Reflection\SignatureMap\SignatureMapProvider;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\NullType;
use PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use ReflectionClass;
class Broker
{
/** @var \PHPStan\Reflection\PropertiesClassReflectionExtension[] */
private $propertiesClassReflectionExtensions;
/** @var \PHPStan\Reflection\MethodsClassReflectionExtension[] */
private $methodsClassReflectionExtensions;
/** @var \PHPStan\Type\DynamicMethodReturnTypeExtension[] */
private $dynamicMethodReturnTypeExtensions = [];
/** @var \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] */
private $dynamicStaticMethodReturnTypeExtensions = [];
/** @var \PHPStan\Type\DynamicMethodReturnTypeExtension[][]|null */
private $dynamicMethodReturnTypeExtensionsByClass;
/** @var \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[][]|null */
private $dynamicStaticMethodReturnTypeExtensionsByClass;
/** @var \PHPStan\Type\DynamicFunctionReturnTypeExtension[] */
private $dynamicFunctionReturnTypeExtensions = [];
/** @var \PHPStan\Reflection\ClassReflection[] */
private $classReflections = [];
/** @var \PHPStan\Reflection\FunctionReflectionFactory */
private $functionReflectionFactory;
/** @var \PHPStan\Type\FileTypeMapper */
private $fileTypeMapper;
/** @var \PHPStan\Reflection\SignatureMap\SignatureMapProvider */
private $signatureMapProvider;
/** @var \PhpParser\PrettyPrinter\Standard */
private $printer;
/** @var AnonymousClassNameHelper */
private $anonymousClassNameHelper;
/** @var Parser */
private $parser;
/** @var RelativePathHelper */
private $relativePathHelper;
/** @var string[] */
private $universalObjectCratesClasses;
/** @var \PHPStan\Reflection\FunctionReflection[] */
private $functionReflections = [];
/** @var \PHPStan\Reflection\Php\PhpFunctionReflection[] */
private $customFunctionReflections = [];
/** @var self|null */
private static $instance;
/** @var bool[] */
private $hasClassCache;
/** @var NativeFunctionReflection[] */
private static $functionMap = [];
/** @var \PHPStan\Reflection\ClassReflection[] */
private static $anonymousClasses = [];
/**
* @param \PHPStan\Reflection\PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions
* @param \PHPStan\Reflection\MethodsClassReflectionExtension[] $methodsClassReflectionExtensions
* @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions
* @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions
* @param \PHPStan\Type\DynamicFunctionReturnTypeExtension[] $dynamicFunctionReturnTypeExtensions
* @param \PHPStan\Reflection\FunctionReflectionFactory $functionReflectionFactory
* @param \PHPStan\Type\FileTypeMapper $fileTypeMapper
* @param \PHPStan\Reflection\SignatureMap\SignatureMapProvider $signatureMapProvider
* @param \PhpParser\PrettyPrinter\Standard $printer
* @param AnonymousClassNameHelper $anonymousClassNameHelper
* @param Parser $parser
* @param RelativePathHelper $relativePathHelper
* @param string[] $universalObjectCratesClasses
*/
public function __construct(
array $propertiesClassReflectionExtensions,
array $methodsClassReflectionExtensions,
array $dynamicMethodReturnTypeExtensions,
array $dynamicStaticMethodReturnTypeExtensions,
array $dynamicFunctionReturnTypeExtensions,
FunctionReflectionFactory $functionReflectionFactory,
FileTypeMapper $fileTypeMapper,
SignatureMapProvider $signatureMapProvider,
\PhpParser\PrettyPrinter\Standard $printer,
AnonymousClassNameHelper $anonymousClassNameHelper,
Parser $parser,
RelativePathHelper $relativePathHelper,
array $universalObjectCratesClasses
)
{
$this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions;
$this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions;
foreach (array_merge($propertiesClassReflectionExtensions, $methodsClassReflectionExtensions, $dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions, $dynamicFunctionReturnTypeExtensions) as $extension) {
if (!($extension instanceof BrokerAwareExtension)) {
continue;
}
$extension->setBroker($this);
}
$this->dynamicMethodReturnTypeExtensions = $dynamicMethodReturnTypeExtensions;
$this->dynamicStaticMethodReturnTypeExtensions = $dynamicStaticMethodReturnTypeExtensions;
foreach ($dynamicFunctionReturnTypeExtensions as $functionReturnTypeExtension) {
$this->dynamicFunctionReturnTypeExtensions[] = $functionReturnTypeExtension;
}
$this->functionReflectionFactory = $functionReflectionFactory;
$this->fileTypeMapper = $fileTypeMapper;
$this->signatureMapProvider = $signatureMapProvider;
$this->printer = $printer;
$this->anonymousClassNameHelper = $anonymousClassNameHelper;
$this->parser = $parser;
$this->relativePathHelper = $relativePathHelper;
$this->universalObjectCratesClasses = $universalObjectCratesClasses;
}
public static function registerInstance(Broker $broker): void
{
self::$instance = $broker;
}
public static function getInstance(): self
{
if (self::$instance === null) {
throw new \PHPStan\ShouldNotHappenException();
}
return self::$instance;
}
/**
* @return string[]
*/
public function getUniversalObjectCratesClasses(): array
{
return $this->universalObjectCratesClasses;
}
/**
* @param string $className
* @return \PHPStan\Type\DynamicMethodReturnTypeExtension[]
*/
public function getDynamicMethodReturnTypeExtensionsForClass(string $className): array
{
if ($this->dynamicMethodReturnTypeExtensionsByClass === null) {
$byClass = [];
foreach ($this->dynamicMethodReturnTypeExtensions as $extension) {
$byClass[$extension->getClass()][] = $extension;
}
$this->dynamicMethodReturnTypeExtensionsByClass = $byClass;
}
return $this->getDynamicExtensionsForType($this->dynamicMethodReturnTypeExtensionsByClass, $className);
}
/**
* @param string $className
* @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[]
*/
public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $className): array
{
if ($this->dynamicStaticMethodReturnTypeExtensionsByClass === null) {
$byClass = [];
foreach ($this->dynamicStaticMethodReturnTypeExtensions as $extension) {
$byClass[$extension->getClass()][] = $extension;
}
$this->dynamicStaticMethodReturnTypeExtensionsByClass = $byClass;
}
return $this->getDynamicExtensionsForType($this->dynamicStaticMethodReturnTypeExtensionsByClass, $className);
}
/**
* @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[]
*/
public function getDynamicFunctionReturnTypeExtensions(): array
{
return $this->dynamicFunctionReturnTypeExtensions;
}
/**
* @param \PHPStan\Type\DynamicMethodReturnTypeExtension[][]|\PHPStan\Type\DynamicStaticMethodReturnTypeExtension[][] $extensions
* @param string $className
* @return mixed[]
*/
private function getDynamicExtensionsForType(array $extensions, string $className): array
{
$extensionsForClass = [];
$class = $this->getClass($className);
foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) {
if (!isset($extensions[$extensionClassName])) {
continue;
}
$extensionsForClass = array_merge($extensionsForClass, $extensions[$extensionClassName]);
}
return $extensionsForClass;
}
public function getClass(string $className): \PHPStan\Reflection\ClassReflection
{
if (!$this->hasClass($className)) {
throw new \PHPStan\Broker\ClassNotFoundException($className);
}
if (isset(self::$anonymousClasses[$className])) {
return self::$anonymousClasses[$className];
}
if (!isset($this->classReflections[$className])) {
$reflectionClass = new ReflectionClass($className);
$filename = null;
if ($reflectionClass->getFileName() !== false) {
$filename = $reflectionClass->getFileName();
}
$classReflection = $this->getClassFromReflection(
$reflectionClass,
$reflectionClass->getName(),
$reflectionClass->isAnonymous() ? $filename : null
);
$this->classReflections[$className] = $classReflection;
if ($className !== $reflectionClass->getName()) {
// class alias optimization
$this->classReflections[$reflectionClass->getName()] = $classReflection;
}
}
return $this->classReflections[$className];
}
public function getAnonymousClassReflection(
\PhpParser\Node\Expr\New_ $node,
Scope $scope
): ClassReflection
{
if (!$node->class instanceof \PhpParser\Node\Stmt\Class_) {
throw new \PHPStan\ShouldNotHappenException();
}
if (!$scope->isInTrait()) {
$scopeFile = $scope->getFile();
} else {
$scopeFile = $scope->getTraitReflection()->getFileName();
if ($scopeFile === false) {
$scopeFile = $scope->getFile();
}
}
$filename = $this->relativePathHelper->getRelativePath($scopeFile);
$className = $this->anonymousClassNameHelper->getAnonymousClassName(
$node,
$filename
);
if (isset(self::$anonymousClasses[$className])) {
return self::$anonymousClasses[$className];
}
$classNode = $node->class;
$classNode->name = new \PhpParser\Node\Identifier($className);
eval($this->printer->prettyPrint([$classNode]));
unset($classNode);
self::$anonymousClasses[$className] = $this->getClassFromReflection(
new \ReflectionClass('\\' . $className),
sprintf('class@anonymous/%s:%s', $filename, $node->getLine()),
$scopeFile
);
$this->classReflections[$className] = self::$anonymousClasses[$className];
return self::$anonymousClasses[$className];
}
public function getClassFromReflection(\ReflectionClass $reflectionClass, string $displayName, ?string $anonymousFilename): ClassReflection
{
$className = $reflectionClass->getName();
if (!isset($this->classReflections[$className])) {
$classReflection = new ClassReflection(
$this,
$this->fileTypeMapper,
$this->propertiesClassReflectionExtensions,
$this->methodsClassReflectionExtensions,
$displayName,
$reflectionClass,
$anonymousFilename
);
$this->classReflections[$className] = $classReflection;
}
return $this->classReflections[$className];
}
public function hasClass(string $className): bool
{
if (isset($this->hasClassCache[$className])) {
return $this->hasClassCache[$className];
}
spl_autoload_register($autoloader = function (string $autoloadedClassName) use ($className): void {
if ($autoloadedClassName !== $className && !$this->isExistsCheckCall()) {
throw new \PHPStan\Broker\ClassAutoloadingException($autoloadedClassName);
}
});
try {
return $this->hasClassCache[$className] = class_exists($className) || interface_exists($className) || trait_exists($className);
} catch (\PHPStan\Broker\ClassAutoloadingException $e) {
throw $e;
} catch (\Throwable $t) {
throw new \PHPStan\Broker\ClassAutoloadingException(
$className,
$t
);
} finally {
spl_autoload_unregister($autoloader);
}
}
public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): \PHPStan\Reflection\FunctionReflection
{
$functionName = $this->resolveFunctionName($nameNode, $scope);
if ($functionName === null) {
throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode);
}
$lowerCasedFunctionName = strtolower($functionName);
if (!isset($this->functionReflections[$lowerCasedFunctionName])) {
if (isset(self::$functionMap[$lowerCasedFunctionName])) {
return $this->functionReflections[$lowerCasedFunctionName] = self::$functionMap[$lowerCasedFunctionName];
}
if ($this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName)) {
$variantName = $lowerCasedFunctionName;
$variants = [];
$i = 0;
while ($this->signatureMapProvider->hasFunctionSignature($variantName)) {
$functionSignature = $this->signatureMapProvider->getFunctionSignature($variantName, null);
$returnType = $functionSignature->getReturnType();
if ($lowerCasedFunctionName === 'pow') {
$returnType = TypeUtils::toBenevolentUnion($returnType);
}
$variants[] = new FunctionVariant(
array_map(static function (ParameterSignature $parameterSignature) use ($lowerCasedFunctionName): NativeParameterReflection {
$type = $parameterSignature->getType();
if (
$parameterSignature->getName() === 'args'
&& (
$lowerCasedFunctionName === 'printf'
|| $lowerCasedFunctionName === 'sprintf'
)
) {
$type = new UnionType([
new StringAlwaysAcceptingObjectWithToStringType(),
new IntegerType(),
new FloatType(),
new NullType(),
new BooleanType(),
]);
}
return new NativeParameterReflection(
$parameterSignature->getName(),
$parameterSignature->isOptional(),
$type,
$parameterSignature->passedByReference(),
$parameterSignature->isVariadic()
);
}, $functionSignature->getParameters()),
$functionSignature->isVariadic(),
$returnType
);
$i++;
$variantName = sprintf($lowerCasedFunctionName . '\'' . $i);
}
$functionReflection = new NativeFunctionReflection(
$lowerCasedFunctionName,
$variants,
null
);
self::$functionMap[$lowerCasedFunctionName] = $functionReflection;
$this->functionReflections[$lowerCasedFunctionName] = $functionReflection;
} else {
$this->functionReflections[$lowerCasedFunctionName] = $this->getCustomFunction($nameNode, $scope);
}
}
return $this->functionReflections[$lowerCasedFunctionName];
}
public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool
{
return $this->resolveFunctionName($nameNode, $scope) !== null;
}
public function hasCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool
{
$functionName = $this->resolveFunctionName($nameNode, $scope);
if ($functionName === null) {
return false;
}
$lowerCasedFunctionName = strtolower($functionName);
return !$this->signatureMapProvider->hasFunctionSignature($lowerCasedFunctionName);
}
public function getCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): \PHPStan\Reflection\Php\PhpFunctionReflection
{
if (!$this->hasCustomFunction($nameNode, $scope)) {
throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode);
}
/** @var string $functionName */
$functionName = $this->resolveFunctionName($nameNode, $scope);
if (!function_exists($functionName)) {
throw new \PHPStan\Broker\FunctionNotFoundException($functionName);
}
$lowerCasedFunctionName = strtolower($functionName);
if (isset($this->customFunctionReflections[$lowerCasedFunctionName])) {
return $this->customFunctionReflections[$lowerCasedFunctionName];
}
$reflectionFunction = new \ReflectionFunction($functionName);
$phpDocParameterTags = [];
$phpDocReturnTag = null;
$phpDocThrowsTag = null;
$isDeprecated = false;
$isInternal = false;
$isFinal = false;
if ($reflectionFunction->getFileName() !== false && $reflectionFunction->getDocComment() !== false) {
$fileName = $reflectionFunction->getFileName();
$docComment = $reflectionFunction->getDocComment();
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $docComment);
$phpDocParameterTags = $resolvedPhpDoc->getParamTags();
$phpDocReturnTag = $resolvedPhpDoc->getReturnTag();
$phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag();
$isDeprecated = $resolvedPhpDoc->isDeprecated();
$isInternal = $resolvedPhpDoc->isInternal();
$isFinal = $resolvedPhpDoc->isFinal();
}
$functionReflection = $this->functionReflectionFactory->create(
$reflectionFunction,
array_map(static function (ParamTag $paramTag): Type {
return $paramTag->getType();
}, $phpDocParameterTags),
$phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null,
$phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null,
$isDeprecated,
$isInternal,
$isFinal,
$reflectionFunction->getFileName()
);
$this->customFunctionReflections[$lowerCasedFunctionName] = $functionReflection;
return $functionReflection;
}
public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string
{
return $this->resolveName($nameNode, function (string $name): bool {
$exists = function_exists($name);
if ($exists) {
return true;
}
$lowercased = strtolower($name);
return $this->signatureMapProvider->hasFunctionSignature($lowercased);
}, $scope);
}
public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool
{
return $this->resolveConstantName($nameNode, $scope) !== null;
}
public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string
{
return $this->resolveName($nameNode, function (string $name) use ($scope): bool {
$isCompilerHaltOffset = $name === '__COMPILER_HALT_OFFSET__';
if ($isCompilerHaltOffset && $scope !== null && $this->fileHasCompilerHaltStatementCalls($scope->getFile())) {
return true;
}
return defined($name);
}, $scope);
}
private function fileHasCompilerHaltStatementCalls(string $pathToFile): bool
{
$nodes = $this->parser->parseFile($pathToFile);
foreach ($nodes as $node) {
if ($node instanceof Node\Stmt\HaltCompiler) {
return true;
}
}
return false;
}
/**
* @param Node\Name $nameNode
* @param \Closure(string $name): bool $existsCallback
* @param Scope|null $scope
* @return string|null
*/
private function resolveName(
\PhpParser\Node\Name $nameNode,
\Closure $existsCallback,
?Scope $scope
): ?string
{
$name = (string) $nameNode;
if ($scope !== null && $scope->getNamespace() !== null && !$nameNode->isFullyQualified()) {
$namespacedName = sprintf('%s\\%s', $scope->getNamespace(), $name);
if ($existsCallback($namespacedName)) {
return $namespacedName;
}
}
if ($existsCallback($name)) {
return $name;
}
return null;
}
private function isExistsCheckCall(): bool
{
$debugBacktrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$existsCallTypes = [
'class_exists' => true,
'interface_exists' => true,
'trait_exists' => true,
];
foreach ($debugBacktrace as $traceStep) {
if (
isset($traceStep['function'])
&& isset($existsCallTypes[$traceStep['function']])
// We must ignore the self::hasClass calls
&& (!isset($traceStep['file']) || $traceStep['file'] !== __FILE__)
) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,65 @@
<?php declare(strict_types = 1);
namespace PHPStan\Broker;
use PHPStan\File\RelativePathHelper;
use PHPStan\Parser\Parser;
use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension;
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
use PHPStan\Reflection\FunctionReflectionFactory;
use PHPStan\Reflection\Php\PhpClassReflectionExtension;
use PHPStan\Reflection\PhpDefect\PhpDefectClassReflectionExtension;
use PHPStan\Reflection\SignatureMap\SignatureMapProvider;
use PHPStan\Type\FileTypeMapper;
class BrokerFactory
{
public const PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.propertiesClassReflectionExtension';
public const METHODS_CLASS_REFLECTION_EXTENSION_TAG = 'phpstan.broker.methodsClassReflectionExtension';
public const DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicMethodReturnTypeExtension';
public const DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicStaticMethodReturnTypeExtension';
public const DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG = 'phpstan.broker.dynamicFunctionReturnTypeExtension';
/** @var \Nette\DI\Container */
private $container;
public function __construct(\Nette\DI\Container $container)
{
$this->container = $container;
}
public function create(): Broker
{
$tagToService = function (array $tags) {
return array_map(function (string $serviceName) {
return $this->container->getService($serviceName);
}, array_keys($tags));
};
$phpClassReflectionExtension = $this->container->getByType(PhpClassReflectionExtension::class);
$annotationsMethodsClassReflectionExtension = $this->container->getByType(AnnotationsMethodsClassReflectionExtension::class);
$annotationsPropertiesClassReflectionExtension = $this->container->getByType(AnnotationsPropertiesClassReflectionExtension::class);
$phpDefectClassReflectionExtension = $this->container->getByType(PhpDefectClassReflectionExtension::class);
/** @var RelativePathHelper $relativePathHelper */
$relativePathHelper = $this->container->getService('relativePathHelper');
return new Broker(
array_merge([$phpClassReflectionExtension, $phpDefectClassReflectionExtension], $tagToService($this->container->findByTag(self::PROPERTIES_CLASS_REFLECTION_EXTENSION_TAG)), [$annotationsPropertiesClassReflectionExtension]),
array_merge([$phpClassReflectionExtension], $tagToService($this->container->findByTag(self::METHODS_CLASS_REFLECTION_EXTENSION_TAG)), [$annotationsMethodsClassReflectionExtension]),
$tagToService($this->container->findByTag(self::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG)),
$tagToService($this->container->findByTag(self::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG)),
$tagToService($this->container->findByTag(self::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG)),
$this->container->getByType(FunctionReflectionFactory::class),
$this->container->getByType(FileTypeMapper::class),
$this->container->getByType(SignatureMapProvider::class),
$this->container->getByType(\PhpParser\PrettyPrinter\Standard::class),
$this->container->getByType(AnonymousClassNameHelper::class),
$this->container->getByType(Parser::class),
$relativePathHelper,
$this->container->parameters['universalObjectCratesClasses']
);
}
}

View file

@ -0,0 +1,38 @@
<?php declare(strict_types = 1);
namespace PHPStan\Broker;
class ClassAutoloadingException extends \PHPStan\AnalysedCodeException
{
/** @var string */
private $className;
public function __construct(
string $functionName,
?\Throwable $previous = null
)
{
if ($previous !== null) {
parent::__construct(sprintf(
'%s (%s) thrown while autoloading class %s.',
get_class($previous),
$previous->getMessage(),
$functionName
), 0, $previous);
} else {
parent::__construct(sprintf(
'Class %s not found and could not be autoloaded.',
$functionName
), 0);
}
$this->className = $functionName;
}
public function getClassName(): string
{
return $this->className;
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\Broker;
class ClassNotFoundException extends \PHPStan\AnalysedCodeException
{
/** @var string */
private $className;
public function __construct(string $functionName)
{
parent::__construct(sprintf('Class %s was not found while trying to analyse it - autoloading is probably not configured properly.', $functionName));
$this->className = $functionName;
}
public function getClassName(): string
{
return $this->className;
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types = 1);
namespace PHPStan\Broker;
class FunctionNotFoundException extends \PHPStan\AnalysedCodeException
{
/** @var string */
private $functionName;
public function __construct(string $functionName)
{
parent::__construct(sprintf('Function %s not found while trying to analyse it - autoloading is probably not configured properly.', $functionName));
$this->functionName = $functionName;
}
public function getFunctionName(): string
{
return $this->functionName;
}
}

View file

@ -0,0 +1,35 @@
<?php declare(strict_types = 1);
namespace PHPStan\Cache;
class Cache
{
/** @var \PHPStan\Cache\CacheStorage */
private $storage;
public function __construct(CacheStorage $storage)
{
$this->storage = $storage;
}
/**
* @param string $key
* @return mixed|null
*/
public function load(string $key)
{
return $this->storage->load($key);
}
/**
* @param string $key
* @param mixed $data
* @return bool
*/
public function save(string $key, $data): bool
{
return $this->storage->save($key, $data);
}
}

View file

@ -0,0 +1,21 @@
<?php declare(strict_types = 1);
namespace PHPStan\Cache;
interface CacheStorage
{
/**
* @param string $key
* @return mixed|null
*/
public function load(string $key);
/**
* @param string $key
* @param mixed $data
* @return bool
*/
public function save(string $key, $data): bool;
}

View file

@ -0,0 +1,51 @@
<?php declare(strict_types = 1);
namespace PHPStan\Cache;
class FileCacheStorage implements CacheStorage
{
/** @var string */
private $directory;
public function __construct(string $directory)
{
$this->directory = $directory;
if (@mkdir($this->directory) && !is_dir($this->directory)) {
throw new \InvalidArgumentException(sprintf('Directory "%s" doesn\'t exist.', $this->directory));
}
}
/**
* @param string $key
* @return mixed|null
*/
public function load(string $key)
{
return (function (string $key) {
$filePath = $this->getFilePath($key);
return is_file($filePath) ? require $this->getFilePath($key) : null;
})($key);
}
/**
* @param string $key
* @param mixed $data
* @return bool
*/
public function save(string $key, $data): bool
{
$writtenBytes = @file_put_contents(
$this->getFilePath($key),
sprintf("<?php declare(strict_types = 1);\n\nreturn %s;", var_export($data, true))
);
return $writtenBytes !== false;
}
private function getFilePath(string $key): string
{
return sprintf('%s/%s.php', $this->directory, preg_replace('~[^-\\w]~', '_', $key));
}
}

View file

@ -0,0 +1,31 @@
<?php declare(strict_types = 1);
namespace PHPStan\Cache;
class MemoryCacheStorage implements CacheStorage
{
/** @var mixed[] */
private $storage = [];
/**
* @param string $key
* @return mixed|null
*/
public function load(string $key)
{
return $this->storage[$key] ?? null;
}
/**
* @param string $key
* @param mixed $data
* @return bool
*/
public function save(string $key, $data): bool
{
$this->storage[$key] = $data;
return true;
}
}

View file

@ -0,0 +1,127 @@
<?php declare(strict_types = 1);
namespace PHPStan\Command;
use PHPStan\Analyser\Analyser;
use PHPStan\Command\ErrorFormatter\ErrorFormatter;
use PHPStan\File\FileHelper;
use Symfony\Component\Console\Style\OutputStyle;
class AnalyseApplication
{
/** @var \PHPStan\Analyser\Analyser */
private $analyser;
/** @var string */
private $memoryLimitFile;
/** @var \PHPStan\File\FileHelper */
private $fileHelper;
/** @var string */
private $currentWorkingDirectory;
public function __construct(
Analyser $analyser,
string $memoryLimitFile,
FileHelper $fileHelper,
string $currentWorkingDirectory
)
{
$this->analyser = $analyser;
$this->memoryLimitFile = $memoryLimitFile;
$this->fileHelper = $fileHelper;
$this->currentWorkingDirectory = $currentWorkingDirectory;
}
/**
* @param string[] $files
* @param bool $onlyFiles
* @param \Symfony\Component\Console\Style\OutputStyle $style
* @param \PHPStan\Command\ErrorFormatter\ErrorFormatter $errorFormatter
* @param bool $defaultLevelUsed
* @param bool $debug
* @return int Error code.
*/
public function analyse(
array $files,
bool $onlyFiles,
OutputStyle $style,
ErrorFormatter $errorFormatter,
bool $defaultLevelUsed,
bool $debug
): int
{
$this->updateMemoryLimitFile();
$errors = [];
if (!$debug) {
$progressStarted = false;
$fileOrder = 0;
$preFileCallback = null;
$postFileCallback = function () use ($style, &$progressStarted, $files, &$fileOrder): void {
if (!$progressStarted) {
$style->progressStart(count($files));
$progressStarted = true;
}
$style->progressAdvance();
if ($fileOrder % 100 === 0) {
$this->updateMemoryLimitFile();
}
$fileOrder++;
};
} else {
$preFileCallback = static function (string $file) use ($style): void {
$style->writeln($file);
};
$postFileCallback = null;
}
$errors = array_merge($errors, $this->analyser->analyse(
$files,
$onlyFiles,
$preFileCallback,
$postFileCallback,
$debug
));
if (isset($progressStarted) && $progressStarted) {
$style->progressFinish();
}
$fileSpecificErrors = [];
$notFileSpecificErrors = [];
foreach ($errors as $error) {
if (is_string($error)) {
$notFileSpecificErrors[] = $error;
} else {
$fileSpecificErrors[] = $error;
}
}
return $errorFormatter->formatErrors(
new AnalysisResult(
$fileSpecificErrors,
$notFileSpecificErrors,
$defaultLevelUsed,
$this->fileHelper->normalizePath($this->currentWorkingDirectory)
),
$style
);
}
private function updateMemoryLimitFile(): void
{
$bytes = memory_get_peak_usage(true);
$megabytes = ceil($bytes / 1024 / 1024);
file_put_contents($this->memoryLimitFile, sprintf('%d MB', $megabytes));
if (!function_exists('pcntl_signal_dispatch')) {
return;
}
pcntl_signal_dispatch();
}
}

View file

@ -0,0 +1,131 @@
<?php declare(strict_types = 1);
namespace PHPStan\Command;
use PHPStan\Command\ErrorFormatter\ErrorFormatter;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class AnalyseCommand extends \Symfony\Component\Console\Command\Command
{
private const NAME = 'analyse';
public const OPTION_LEVEL = 'level';
public const DEFAULT_LEVEL = CommandHelper::DEFAULT_LEVEL;
protected function configure(): void
{
$this->setName(self::NAME)
->setDescription('Analyses source code')
->setDefinition([
new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'),
new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'),
new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'),
new InputOption(self::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'),
new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'),
new InputOption('debug', null, InputOption::VALUE_NONE, 'Show debug information - which file is analysed, do not catch internal errors'),
new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'),
new InputOption('error-format', null, InputOption::VALUE_REQUIRED, 'Format in which to print the result of the analysis', 'table'),
new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'),
]);
}
/**
* @return string[]
*/
public function getAliases(): array
{
return ['analyze'];
}
protected function initialize(InputInterface $input, OutputInterface $output): void
{
if ((bool) $input->getOption('debug')) {
$this->getApplication()->setCatchExceptions(false);
return;
}
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$paths = $input->getArgument('paths');
$memoryLimit = $input->getOption('memory-limit');
$autoloadFile = $input->getOption('autoload-file');
$configuration = $input->getOption('configuration');
$level = $input->getOption(self::OPTION_LEVEL);
$pathsFile = $input->getOption('paths-file');
if (
!is_array($paths)
|| (!is_string($memoryLimit) && $memoryLimit !== null)
|| (!is_string($autoloadFile) && $autoloadFile !== null)
|| (!is_string($configuration) && $configuration !== null)
|| (!is_string($level) && $level !== null)
|| (!is_string($pathsFile) && $pathsFile !== null)
) {
throw new \PHPStan\ShouldNotHappenException();
}
try {
$inceptionResult = CommandHelper::begin(
$input,
$output,
$paths,
$pathsFile,
$memoryLimit,
$autoloadFile,
$configuration,
$level
);
} catch (\PHPStan\Command\InceptionNotSuccessfulException $e) {
return 1;
}
$errorOutput = $inceptionResult->getErrorOutput();
$errorFormat = $input->getOption('error-format');
if (!is_string($errorFormat) && $errorFormat !== null) {
throw new \PHPStan\ShouldNotHappenException();
}
$container = $inceptionResult->getContainer();
$errorFormatterServiceName = sprintf('errorFormatter.%s', $errorFormat);
if (!$container->hasService($errorFormatterServiceName)) {
$errorOutput->writeln(sprintf(
'Error formatter "%s" not found. Available error formatters are: %s',
$errorFormat,
implode(', ', array_map(static function (string $name) {
return substr($name, strlen('errorFormatter.'));
}, $container->findByType(ErrorFormatter::class)))
));
return 1;
}
/** @var ErrorFormatter $errorFormatter */
$errorFormatter = $container->getService($errorFormatterServiceName);
/** @var AnalyseApplication $application */
$application = $container->getByType(AnalyseApplication::class);
$debug = $input->getOption('debug');
if (!is_bool($debug)) {
throw new \PHPStan\ShouldNotHappenException();
}
return $inceptionResult->handleReturn(
$application->analyse(
$inceptionResult->getFiles(),
$inceptionResult->isOnlyFiles(),
$inceptionResult->getConsoleStyle(),
$errorFormatter,
$inceptionResult->isDefaultLevelUsed(),
$debug
)
);
}
}

Some files were not shown because too many files have changed in this diff Show more