From 937564f03f1c76a7eddcd62388fe45a213155c98 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Thu, 22 Sep 2022 18:17:00 +0200 Subject: [PATCH] Update code to PHPStan Level 3 Added ReturnTypeWillChange Fix return type for Expression Add type hints for returned variables Add generic parameter for delimted list Fixing type hints Ignore co-variance error Remove TODO - it already must always be a Node Ignore error Add missing types Update phpstan to level 3 Add phpstan to dev reqs No need to install phpstan independently Do not override unary expression operand It seems to me that the operand can be any expression Added token to operand types -- should this be MissingToken? Include tokens in return types ExpressionStatement => EchoStatement It seems this is always an EchoStatement not an ExpressionStatement unaryExpressionOrHigher can return a ThrowExpression Remove overridden property Add Token to union Remove QualifiedName and rename local variable Remove unused import Add return types Remove trailing whitespace Add MissingToken type --- .travis.yml | 1 - composer.json | 3 +- phpstan.neon | 2 +- src/Node.php | 3 ++ src/Node/EnumCaseDeclaration.php | 2 +- src/Node/Expression/AssignmentExpression.php | 2 +- src/Node/Expression/BinaryExpression.php | 4 +- .../Expression/PrefixUpdateExpression.php | 3 -- src/Node/Expression/SubscriptExpression.php | 3 +- src/Node/Expression/UnaryExpression.php | 3 +- src/Node/Expression/UnaryOpExpression.php | 3 -- src/Node/FunctionReturnType.php | 3 +- src/Node/MissingMemberDeclaration.php | 3 +- src/Node/NamespaceUseClause.php | 3 +- src/Node/Statement/InlineHtml.php | 2 +- src/Node/Statement/NamespaceDefinition.php | 3 +- src/Parser.php | 44 +++++++++++-------- 17 files changed, 49 insertions(+), 38 deletions(-) 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);