diff --git a/.travis.yml b/.travis.yml index fb436132..805aae5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,6 @@ cache: - validation/frameworks before_script: - - if [[ $STATIC_ANALYSIS = true ]]; then composer require phpstan/phpstan --no-update; fi - composer install - set -e # Stop on first error. - phpenv config-rm xdebug.ini || true diff --git a/composer.json b/composer.json index 91df816a..1583949f 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "php": ">=7.2" }, "require-dev": { - "phpunit/phpunit": "^8.5.15" + "phpunit/phpunit": "^8.5.15", + "phpstan/phpstan": "^1.8" }, "license": "MIT", "authors": [ diff --git a/phpstan.neon b/phpstan.neon index 3d24b87a..ee6614c4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 2 + level: 3 paths: - src/ ignoreErrors: diff --git a/src/Node.php b/src/Node.php index bcc88731..f2356361 100644 --- a/src/Node.php +++ b/src/Node.php @@ -140,6 +140,8 @@ public function getRoot() : Node { while ($node->parent !== null) { $node = $node->parent; } + + /** @var SourceFileNode $node */ return $node; } @@ -613,6 +615,7 @@ public function getNamespaceDefinition() { $namespaceDefinition = null; } + /** @var NamespaceDefinition|null $namespaceDefinition */ return $namespaceDefinition; } diff --git a/src/Node/EnumCaseDeclaration.php b/src/Node/EnumCaseDeclaration.php index 64f3f91e..0aec5a2b 100644 --- a/src/Node/EnumCaseDeclaration.php +++ b/src/Node/EnumCaseDeclaration.php @@ -16,7 +16,7 @@ class EnumCaseDeclaration extends Node { /** @var Token */ public $caseKeyword; - /** @var QualifiedName */ + /** @var Token */ public $name; /** @var Token|null */ diff --git a/src/Node/Expression/AssignmentExpression.php b/src/Node/Expression/AssignmentExpression.php index dc1683bc..9790ad9b 100644 --- a/src/Node/Expression/AssignmentExpression.php +++ b/src/Node/Expression/AssignmentExpression.php @@ -11,7 +11,7 @@ class AssignmentExpression extends BinaryExpression { - /** @var Expression */ + /** @var Expression|Token */ public $leftOperand; /** @var Token */ diff --git a/src/Node/Expression/BinaryExpression.php b/src/Node/Expression/BinaryExpression.php index 61ec6ff7..2b66bfb9 100644 --- a/src/Node/Expression/BinaryExpression.php +++ b/src/Node/Expression/BinaryExpression.php @@ -11,13 +11,13 @@ class BinaryExpression extends Expression { - /** @var Expression */ + /** @var Expression|Token */ public $leftOperand; /** @var Token */ public $operator; - /** @var Expression */ + /** @var Expression|Token */ public $rightOperand; const CHILD_NAMES = [ diff --git a/src/Node/Expression/PrefixUpdateExpression.php b/src/Node/Expression/PrefixUpdateExpression.php index 2ca61ffd..8e76d5d0 100644 --- a/src/Node/Expression/PrefixUpdateExpression.php +++ b/src/Node/Expression/PrefixUpdateExpression.php @@ -13,9 +13,6 @@ class PrefixUpdateExpression extends UnaryExpression { /** @var Token */ public $incrementOrDecrementOperator; - /** @var Variable */ - public $operand; - const CHILD_NAMES = [ 'incrementOrDecrementOperator', 'operand' diff --git a/src/Node/Expression/SubscriptExpression.php b/src/Node/Expression/SubscriptExpression.php index 4756f686..7ac986ef 100644 --- a/src/Node/Expression/SubscriptExpression.php +++ b/src/Node/Expression/SubscriptExpression.php @@ -6,6 +6,7 @@ namespace Microsoft\PhpParser\Node\Expression; +use Microsoft\PhpParser\MissingToken; use Microsoft\PhpParser\Node\Expression; use Microsoft\PhpParser\Token; @@ -17,7 +18,7 @@ class SubscriptExpression extends Expression { /** @var Token */ public $openBracketOrBrace; - /** @var Expression */ + /** @var Expression|MissingToken */ public $accessExpression; /** @var Token */ diff --git a/src/Node/Expression/UnaryExpression.php b/src/Node/Expression/UnaryExpression.php index 6da38cdd..ee6591a0 100644 --- a/src/Node/Expression/UnaryExpression.php +++ b/src/Node/Expression/UnaryExpression.php @@ -7,9 +7,10 @@ namespace Microsoft\PhpParser\Node\Expression; use Microsoft\PhpParser\Node\Expression; +use Microsoft\PhpParser\Token; class UnaryExpression extends Expression { - /** @var UnaryExpression|Variable */ + /** @var Expression|Variable|Token */ public $operand; const CHILD_NAMES = [ diff --git a/src/Node/Expression/UnaryOpExpression.php b/src/Node/Expression/UnaryOpExpression.php index 0acc0444..962f9bd9 100644 --- a/src/Node/Expression/UnaryOpExpression.php +++ b/src/Node/Expression/UnaryOpExpression.php @@ -13,9 +13,6 @@ class UnaryOpExpression extends UnaryExpression { /** @var Token */ public $operator; - /** @var UnaryExpression */ - public $operand; - const CHILD_NAMES = [ 'operator', 'operand' diff --git a/src/Node/FunctionReturnType.php b/src/Node/FunctionReturnType.php index 1429234e..d93356f2 100644 --- a/src/Node/FunctionReturnType.php +++ b/src/Node/FunctionReturnType.php @@ -6,6 +6,7 @@ namespace Microsoft\PhpParser\Node; +use Microsoft\PhpParser\MissingToken; use Microsoft\PhpParser\Token; trait FunctionReturnType { @@ -14,6 +15,6 @@ trait FunctionReturnType { // TODO: This may be the wrong choice if ?type can ever be mixed with other types in union types /** @var Token|null */ public $questionToken; - /** @var DelimitedList\QualifiedNameList|null */ + /** @var DelimitedList\QualifiedNameList|null|MissingToken */ public $returnTypeList; } diff --git a/src/Node/MissingMemberDeclaration.php b/src/Node/MissingMemberDeclaration.php index c3043ca5..280d0a03 100644 --- a/src/Node/MissingMemberDeclaration.php +++ b/src/Node/MissingMemberDeclaration.php @@ -6,6 +6,7 @@ namespace Microsoft\PhpParser\Node; +use Microsoft\PhpParser\MissingToken; use Microsoft\PhpParser\ModifiedTypeInterface; use Microsoft\PhpParser\ModifiedTypeTrait; use Microsoft\PhpParser\Node; @@ -20,7 +21,7 @@ class MissingMemberDeclaration extends Node implements ModifiedTypeInterface { /** @var Token|null needed along with typeDeclaration for what looked like typed property declarations but was missing VariableName */ public $questionToken; - /** @var DelimitedList\QualifiedNameList|null */ + /** @var DelimitedList\QualifiedNameList|null|MissingToken */ public $typeDeclarationList; const CHILD_NAMES = [ diff --git a/src/Node/NamespaceUseClause.php b/src/Node/NamespaceUseClause.php index ef596280..d433fde3 100644 --- a/src/Node/NamespaceUseClause.php +++ b/src/Node/NamespaceUseClause.php @@ -6,12 +6,13 @@ namespace Microsoft\PhpParser\Node; +use Microsoft\PhpParser\MissingToken; use Microsoft\PhpParser\Node; use Microsoft\PhpParser\Node\DelimitedList; use Microsoft\PhpParser\Token; class NamespaceUseClause extends Node { - /** @var QualifiedName */ + /** @var QualifiedName|MissingToken */ public $namespaceName; /** @var NamespaceAliasingClause */ public $namespaceAliasingClause; diff --git a/src/Node/Statement/InlineHtml.php b/src/Node/Statement/InlineHtml.php index 8e158ca3..9b89f33c 100644 --- a/src/Node/Statement/InlineHtml.php +++ b/src/Node/Statement/InlineHtml.php @@ -20,7 +20,7 @@ class InlineHtml extends StatementNode { public $scriptSectionStartTag; /** - * @var ExpressionStatement|null used to represent the expression echoed by `openBrace = $this->eat1(TokenKind::OpenBraceToken); $classMembers->classMemberDeclarations = $this->parseList($classMembers, ParseContext::ClassMembers); @@ -804,7 +804,7 @@ private function parseAttributeGroups($parentNode): array } /** - * @return DelimitedList\AttributeElementList + * @return DelimitedList\AttributeElementList|null */ private function parseAttributeElementList(AttributeGroup $parentNode) { return $this->parseDelimitedList( @@ -1638,13 +1638,14 @@ private function isParameterStartFn() { } /** - * @param string $className (name of subclass of DelimitedList) + * @template TDelimitedList of DelimitedList + * @param class-string $className (name of subclass of DelimitedList) * @param int|int[] $delimiter * @param callable $isElementStartFn * @param callable $parseElementFn * @param Node $parentNode * @param bool $allowEmptyElements - * @return DelimitedList|null instance of $className + * @return TDelimitedList|null instance of $className */ private function parseDelimitedList($className, $delimiter, $isElementStartFn, $parseElementFn, $parentNode, $allowEmptyElements = false) { // TODO consider allowing empty delimiter to be more tolerant @@ -1994,7 +1995,7 @@ private function parseWhileStatement($parentNode) { /** * @param Node $parentNode * @param bool $force - * @return Node|MissingToken|array - The expression, or a missing token, or (if $force) an array containing a missed and skipped token + * @return Expression|MissingToken|array - The expression, or a missing token, or (if $force) an array containing a missed and skipped token */ private function parseExpression($parentNode, $force = false) { $token = $this->getCurrentToken(); @@ -2020,7 +2021,7 @@ private function parseExpressionFn() { /** * @param Node $parentNode - * @return Expression + * @return UnaryExpression|MissingToken|Variable|ThrowExpression */ private function parseUnaryExpressionOrHigher($parentNode) { $token = $this->getCurrentToken(); @@ -3212,9 +3213,16 @@ private function parseMemberAccessExpression($expression):MemberAccessExpression private function parseScopedPropertyAccessExpression($expression, $fallbackParentNode): ScopedPropertyAccessExpression { $scopedPropertyAccessExpression = new ScopedPropertyAccessExpression(); $scopedPropertyAccessExpression->parent = $expression->parent ?? $fallbackParentNode; + if ($expression instanceof Node) { $expression->parent = $scopedPropertyAccessExpression; - $scopedPropertyAccessExpression->scopeResolutionQualifier = $expression; // TODO ensure always a Node + + // scopeResolutionQualifier does not accept `Node` but + // `Expression|QualifiedName|Token`. I'm not sure if we can depend + // on that being the case. + // + // @phpstan-ignore-next-line + $scopedPropertyAccessExpression->scopeResolutionQualifier = $expression; } $scopedPropertyAccessExpression->doubleColon = $this->eat1(TokenKind::ColonColonToken); @@ -3362,18 +3370,18 @@ private function parseClassConstDeclaration($parentNode, $modifiers) { } private function parseEnumCaseDeclaration($parentNode) { - $classConstDeclaration = new EnumCaseDeclaration(); - $classConstDeclaration->parent = $parentNode; - $classConstDeclaration->caseKeyword = $this->eat1(TokenKind::CaseKeyword); - $classConstDeclaration->name = $this->eat($this->nameOrKeywordOrReservedWordTokens); - $classConstDeclaration->equalsToken = $this->eatOptional1(TokenKind::EqualsToken); - if ($classConstDeclaration->equalsToken !== null) { + $enumCaseDeclaration = new EnumCaseDeclaration(); + $enumCaseDeclaration->parent = $parentNode; + $enumCaseDeclaration->caseKeyword = $this->eat1(TokenKind::CaseKeyword); + $enumCaseDeclaration->name = $this->eat($this->nameOrKeywordOrReservedWordTokens); + $enumCaseDeclaration->equalsToken = $this->eatOptional1(TokenKind::EqualsToken); + if ($enumCaseDeclaration->equalsToken !== null) { // TODO add post-parse rule that checks for invalid assignments - $classConstDeclaration->assignment = $this->parseExpression($classConstDeclaration); + $enumCaseDeclaration->assignment = $this->parseExpression($enumCaseDeclaration); } - $classConstDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); + $enumCaseDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken); - return $classConstDeclaration; + return $enumCaseDeclaration; } /** @@ -3446,7 +3454,7 @@ private function parseQualifiedNameCatchList($parentNode) { return $result; } - private function parseInterfaceDeclaration($parentNode) { + private function parseInterfaceDeclaration($parentNode): InterfaceDeclaration { $interfaceDeclaration = new InterfaceDeclaration(); // TODO verify not nested $interfaceDeclaration->parent = $parentNode; $interfaceDeclaration->interfaceKeyword = $this->eat1(TokenKind::InterfaceKeyword); @@ -3456,7 +3464,7 @@ private function parseInterfaceDeclaration($parentNode) { return $interfaceDeclaration; } - private function parseInterfaceMembers($parentNode) : Node { + private function parseInterfaceMembers($parentNode) : InterfaceMembers { $interfaceMembers = new InterfaceMembers(); $interfaceMembers->openBrace = $this->eat1(TokenKind::OpenBraceToken); $interfaceMembers->interfaceMemberDeclarations = $this->parseList($interfaceMembers, ParseContext::InterfaceMembers);